✦ 布局系统概述

Avalonia 使用两阶段布局系统,这是理解整个布局机制的核心起点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────────────┐
│ 第一阶段:[[Measure]](测量) │
│ ───────────────────────────────────────────────────────────── │
│ • 从根节点开始,递归向下测量 │
│ • 每个元素报告"我需要多大空间" │
│ • 结果保存在 [[DesiredSize]] 属性中 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 第二阶段:[[Arrange]](排列) │
│ ───────────────────────────────────────────────────────────── │
│ • 从根节点开始,递归向下排列 │
│ • 父容器根据子元素的 [[DesiredSize]] 和对齐方式分配实际空间 │
│ • 确定每个元素的最终位置和大小 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 第三阶段:Render(渲染) │
│ ───────────────────────────────────────────────────────────── │
│ • 根据 [[Arrange]] 的结果绘制元素 │
└─────────────────────────────────────────────────────────────────┘

这个流程确保了每个元素在被绘制之前,都已经明确知道自己需要多少空间、实际获得了多少空间、以及最终定位在何处。


✦ 尺寸属性体系

✦ 属性层次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─────────────────────────────────────────────────────────────────┐
│ 尺寸属性体系 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 约束属性(决定布局行为) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ MinWidth │ │ Width │ │ MaxWidth │ │
│ │ MinHeight │ │ Height │ │ MaxHeight │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ↑ ↑ ↑ │
│ └────────────────┼────────────────┘ │
│ ↓ │
│ 最终宽度 = Math.Max(MinWidth, Math.Min(Width, MaxWidth)) │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 只读属性(布局结果) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [[DesiredSize]] - 测量后元素想要的大小 │ │
│ │ ActualWidth - 排列后元素的实际宽度 │ │
│ │ ActualHeight - 排列后元素的实际高度 │ │
│ │ Bounds - 元素的边界矩形(含位置) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

✦ 尺寸属性详解

属性 类型 说明
Width double 期望宽度,NaN 表示自适应
Height double 期望高度,NaN 表示自适应
MinWidth double 最小宽度,默认 0
MinHeight double 最小高度,默认 0
MaxWidth double 最大宽度,默认
MaxHeight double 最大高度,默认
DesiredSize Size 测量后元素想要的大小(只读)
ActualWidth double 排列后的实际宽度(只读)
ActualHeight double 排列后的实际高度(只读)

✦ 尺寸计算流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
┌─────────────────────────────────────────────────────────────────┐
│ 尺寸计算流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 父容器可用空间 │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. 应用 MaxWidth/MaxHeight 限制 │ │
│ │ available = min(available, maxSize) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 2. 测量子元素,获取 [[DesiredSize]] │ │
│ │ child.Measure(available) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 3. 确定实际大小 │ │
│ │ if (Width != NaN) │ │
│ │ actualWidth = Width │ │
│ │ else │ │
│ │ actualWidth = [[DesiredSize]].Width │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 4. 应用 MinWidth/MinHeight 限制 │ │
│ │ actualWidth = max(actualWidth, minWidth) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

✦ 对齐属性(关键!)

对齐属性是布局系统中最容易被误解的部分。它们同时决定了元素的尺寸行为位置行为

✦ HorizontalAlignment(水平对齐)

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────────┐
│ HorizontalAlignment 详解 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 值 │ 大小行为 │ 位置行为 │
│ ──────────┼────────────────────────┼────────────────────────── │
│ Stretch │ 拉伸填满父容器宽度 │ 从左边开始 │
│ Left │ 只占自身内容宽度 │ 靠左 │
│ Center │ 只占自身内容宽度 │ 水平居中 │
│ Right │ 只占自身内容宽度 │ 靠右 │
│ │
└─────────────────────────────────────────────────────────────────┘

图示说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
父容器宽度 = 300px
按钮内容需要宽度 = 100px

Stretch:
┌─────────────────────────────────────────────────────────────────┐
│ [██████████████████████████████████████████████████████████████] │
│ 按钮宽度 = 300px │
└─────────────────────────────────────────────────────────────────┘

Left:
┌─────────────────────────────────────────────────────────────────┐
│ [████████] │
│ 按钮宽度 = 100px │
└─────────────────────────────────────────────────────────────────┘

Center:
┌─────────────────────────────────────────────────────────────────┐
│ [████████] │
│ 按钮宽度 = 100px │
└─────────────────────────────────────────────────────────────────┘

Right:
┌─────────────────────────────────────────────────────────────────┐
│ [████████] │
│ 按钮宽度 = 100px │
└─────────────────────────────────────────────────────────────────┘

✦ VerticalAlignment(垂直对齐)

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────────┐
│ VerticalAlignment 详解 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 值 │ 大小行为 │ 位置行为 │
│ ──────────┼────────────────────────┼────────────────────────── │
│ Stretch │ 拉伸填满父容器高度 │ 从顶部开始 │
│ Top │ 只占自身内容高度 │ 面顶 │
│ Center │ 只占自身内容高度 │ 垂直居中 │
│ Bottom │ 只占自身内容高度 │ 面底 │
│ │
└─────────────────────────────────────────────────────────────────┘

图示说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
父容器高度 = 200px
按钮内容需要高度 = 40px

Stretch:
┌──────────────────────────────────────┐
│ [████████████████████████████████] │
│ [████████████████████████████████] │
│ [████████████████████████████████] │
│ [████████████████████████████████] │
│ [████████████████████████████████] │
└──────────────────────────────────────┘
按钮高度 = 200px

Top:
┌──────────────────────────────────────┐
│ [████████████████████████████████] │
│ │
│ │
│ │
│ │
└──────────────────────────────────────┘
按钮高度 = 40px

Center:
┌──────────────────────────────────────┐
│ │
│ │
│ [████████████████████████████████] │
│ │
│ │
└──────────────────────────────────────┘
按钮高度 = 40px

Bottom:
┌──────────────────────────────────────┐
│ │
│ │
│ │
│ │
│ [████████████████████████████████] │
└──────────────────────────────────────┘
按钮高度 = 40px

✦ 对齐属性组合

1
2
3
4
<!-- 常见组合 -->
<Button HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> <!-- 填满 -->
<Button HorizontalAlignment="Center" VerticalAlignment="Center"/> <!-- 居中 -->
<Button HorizontalAlignment="Right" VerticalAlignment="Bottom"/> <!-- 右下角 -->

✦ 边距属性

✦ Margin 与 Padding 区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────────────┐
│ Margin vs Padding │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Margin(外边距)- 元素外部的空间 │
│ Padding(内边距)- 元素内部的空间 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Margin (20) │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ Border │ │ │
│ │ │ ┌─────────────────────────────────────────────┐ │ │ │
│ │ │ │ Padding (10) │ │ │ │
│ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ Content │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ └───────────────────────────────────────┘ │ │ │ │
│ │ │ └─────────────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

✦ 语法

1
2
3
4
5
6
7
8
<!-- 四边相同 -->
<Button Margin="10"/>

<!-- 上下 左右 -->
<Button Margin="10 20"/>

<!--上 右 下 左(顺时针)-->
<Button Margin="10 20 30 40"/>

✦ 布局面板

✦ Grid(最灵活)

[[Grid]] 是 Avalonia 中最强大的布局面板,通过行列定义实现复杂布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- 自适应 -->
<RowDefinition Height="*"/> <!-- 占剩余空间 -->
<RowDefinition Height="2*"/> <!-- 占剩余空间的2倍 -->
<RowDefinition Height="100"/> <!-- 固定高度 -->
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>

<TextBlock Text="R0C0" Grid.Row="0" Grid.Column="0"/>
<TextBlock Text="R1C1 跨2列" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"/>
</Grid>

高度/宽度模式:

模式 说明
Auto 根据内容自适应
* 占剩余空间
n* 占剩余空间的 n 倍
100 固定像素值

✦ StackPanel

StackPanel 按顺序线性排列子元素。

1
2
3
4
5
6
7
8
9
10
11
<!-- 垂直堆叠 -->
<StackPanel Spacing="10">
<Button Content="1"/>
<Button Content="2"/>
</StackPanel>

<!-- 水平堆叠 -->
<StackPanel Orientation="Horizontal" Spacing="10">
<Button Content="1"/>
<Button Content="2"/>
</StackPanel>

✦ DockPanel

DockPanel 让子元素停靠到边缘。

1
2
3
4
5
6
<DockPanel>
<Border DockPanel.Dock="Top" Height="50" Background="Red"/>
<Border DockPanel.Dock="Bottom" Height="50" Background="Blue"/>
<Border DockPanel.Dock="Left" Width="100" Background="Green"/>
<Border Background="Yellow"/> <!-- 最后一个填充剩余空间 -->
</DockPanel>

布局示意:

1
2
3
4
5
6
7
8
9
10
┌──────────────────────────────┐
│ Top │
├────┬────────────────────┬────┤
│ L │ │ │
│ e │ 剩余空间 │ │
│ f │ │ │
│ t │ │ │
├────┴────────────────────┴────┤
│ Bottom │
└──────────────────────────────┘

✦ WrapPanel

WrapPanel 按顺序排列元素,空间不足时自动换行。

1
2
3
4
5
6
<WrapPanel>
<Button Content="1" Width="100"/>
<Button Content="2" Width="100"/>
<Button Content="3" Width="100"/>
<!-- 空间不够时自动换行 -->
</WrapPanel>

✦ Canvas(绝对定位)

Canvas 通过坐标精确控制元素位置。

1
2
3
<Canvas>
<Rectangle Canvas.Left="10" Canvas.Top="20" Width="50" Height="50"/>
</Canvas>

✦ 面板对比

面板 用途 特点
Grid 复杂布局 行列定义,最灵活
StackPanel 线性排列 简单垂直/水平
DockPanel 边缘停靠 适合工具栏/状态栏
WrapPanel 自动换行 适合标签云
Canvas 绝对定位 精确控制位置
UniformGrid 均匀网格 所有格子大小相同

✦ 布局流程图解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
┌─────────────────────────────────────────────────────────────────┐
│ 完整布局流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. [[Measure]] 递归 │ │
│ │ │ │
│ │ Root.Measure(availableSize) │ │
│ │ ↓ │ │
│ │ Child1.Measure(约束后空间) │ │
│ │ ↓ │ │
│ │ Child2.Measure(约束后空间) │ │
│ │ ↓ │ │
│ │ ... │ │
│ │ ↓ │ │
│ │ 叶子节点测量完成,返回 [[DesiredSize]] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2. [[Arrange]] 递归 │ │
│ │ │ │
│ │ Root.Arrange(finalRect) │ │
│ │ ↓ │ │
│ │ 根据 HorizontalAlignment 决定子元素宽度 │ │
│ │ 根据 VerticalAlignment 决定子元素高度 │ │
│ │ ↓ │ │
│ │ Child1.Arrange(分配的矩形) │ │
│ │ ↓ │ │
│ │ Child2.Arrange(分配的矩形) │ │
│ │ ↓ │ │
│ │ ... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 3. Render │ │
│ │ │ │
│ │ 根据 [[Arrange]] 结果绘制所有元素 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

✦ 常见布局场景

✦ 场景一:全屏居中对话框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Grid>
<Border Background="#80000000"/>
<Border Background="White"
CornerRadius="10"
Padding="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MinWidth="300">
<StackPanel>
<TextBlock Text="标题" FontSize="20"/>
<Button Content="确定" HorizontalAlignment="Right"/>
</StackPanel>
</Border>
</Grid>
1
2
3
4
5
6
7
8
9
10
11
<Grid RowDefinitions="Auto,*,Auto">
<Border Grid.Row="0" Background="#2196F3" Padding="15">
<TextBlock Text="标题" Foreground="White"/>
</Border>
<ScrollViewer Grid.Row="1">
<TextBlock Text="内容..." TextWrapping="Wrap"/>
</ScrollViewer>
<Border Grid.Row="2" Background="#F5F5F5" Padding="10">
<Button Content="确定" HorizontalAlignment="Right"/>
</Border>
</Grid>

✦ 场景三:等宽三列

1
2
3
4
5
6
7
8
9
10
11
<Grid ColumnDefinitions="*,*,*">
<Border Grid.Column="0" Background="Red" Margin="5" Padding="10">
<TextBlock Text="1"/>
</Border>
<Border Grid.Column="1" Background="Green" Margin="5" Padding="10">
<TextBlock Text="2"/>
</Border>
<Border Grid.Column="2" Background="Blue" Margin="5" Padding="10">
<TextBlock Text="3"/>
</Border>
</Grid>

✦ 速查表

属性 说明
HorizontalAlignment Stretch 拉伸填满宽度
Left 面左,宽度=内容
Center 居中,宽度=内容
Right 面右,宽度=内容
VerticalAlignment Stretch 拉伸填满高度
Top 面顶,高度=内容
Center 居中,高度=内容
Bottom 面底,高度=内容
Grid.RowDefinitions Auto 自适应
* 占剩余空间
n* n倍剩余空间
100 固定值

✦ 核心要点总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────────┐
│ 核心要点 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 对齐属性 = 大小 + 位置 │
│ • Stretch → 填满父容器 │
│ • 其他 → 只占自身内容大小 │
│ │
│ 2. 两阶段布局 │
│ • [[Measure]]: 确定每个元素需要多大 │
│ • [[Arrange]]: 确定每个元素的最终位置和大小 │
│ │
│ 3. 尺寸优先级 │
│ • Width > [[DesiredSize]] │
│ • MinWidth ≤ 实际宽度 ≤ MaxWidth │
│ │
│ 4. 面板选择 │
│ • 复杂布局 → [[Grid]] │
│ • 线性排列 → StackPanel │
│ • 边缘停靠 → DockPanel │
│ │
└─────────────────────────────────────────────────────────────────┘