✦ 布局系统概述
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*"/> <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 │ │ │ └─────────────────────────────────────────────────────────────────┘
|
作者:Arturia
声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!