16 KiB
AtomUI 媒体查询系统 — 开发者指南
本文档面向 AtomUI 控件使用者和控件开发者,介绍如何在 AXAML 和 C# 中使用媒体查询实现响应式布局。
1. 概念:断点体系
AtomUI 遵循 Ant Design 5.0 的六级响应式断点。当窗口内容区域宽度达到某个阈值时,自动切换到对应断点:
| 缩写 | 断点 | 最小宽度 | 说明 |
|---|---|---|---|
xs |
ExtraSmall | < 576px | 手机竖屏 |
sm |
Small | ≥ 576px | 手机横屏 |
md |
Medium | ≥ 768px | 平板竖屏 |
lg |
Large | ≥ 992px | 小桌面(默认) |
xl |
ExtraLarge | ≥ 1200px | 标准桌面 |
xxl |
ExtraExtraLarge | ≥ 1600px | 大屏桌面 |
前提:应用的
Window必须使用 AtomUI 提供的atom:Window(而非 Avalonia 原生Window),因为断点检测逻辑内置在 AtomUI Window 的主题模板中。
2. Grid 栅格系统(Row / Col)
AtomUI 的 Row + Col 组合实现了 Ant Design 的 24 栏栅格系统。Row 是容器,Col 是列。
2.1 基础用法
<atom:Row Gutter="16,16">
<atom:Col Span="8">
<Border Background="#1677FF" Height="48" />
</atom:Col>
<atom:Col Span="8">
<Border Background="#4096FF" Height="48" />
</atom:Col>
<atom:Col Span="8">
<Border Background="#1677FF" Height="48" />
</atom:Col>
</atom:Row>
Span指定列跨度(1~24)。Gutter指定水平和垂直间距("水平,垂直"格式)。
2.2 响应式列跨度
通过 Col 的 Xs、Sm、Md、Lg、Xl、Xxl 属性,为不同断点设置不同的布局配置:
<atom:Row Gutter="16,16">
<!-- 在 xs 断点全宽,sm 断点半宽,lg 断点 1/4 宽 -->
<atom:Col Xs="24" Sm="12" Md="16" Lg="6" Xl="6">
<Border Background="#1677FF" Height="48" />
</atom:Col>
<atom:Col Xs="24" Sm="12" Md="16" Lg="6" Xl="6">
<Border Background="#4096FF" Height="48" />
</atom:Col>
<atom:Col Xs="24" Sm="12" Md="16" Lg="6" Xl="6">
<Border Background="#1677FF" Height="48" />
</atom:Col>
<atom:Col Xs="24" Sm="12" Md="16" Lg="6" Xl="6">
<Border Background="#4096FF" Height="48" />
</atom:Col>
</atom:Row>
当窗口宽度 < 576px 时(xs),每列占满整行(24/24);宽度 ≥ 576px 时(sm),每行两列(12/24);宽度 ≥ 992px 时(lg),每行四列(6/24)。
字符串格式:断点属性的值可以是:
- 纯数字:
"12"→ 仅设置 Span - 键值对:
"span:12,offset:2,order:1"→ 设置多个布局属性
2.3 使用 ColInfo 进行高级响应式覆盖
ColInfo 是 GridColSize 的别名,支持在特定断点下完整覆盖 Span、Offset、Order、Push、Pull:
方式一:属性元素语法
<atom:Col Span="24">
<atom:Col.Md>
<atom:ColInfo Span="12" Order="2" />
</atom:Col.Md>
<atom:Col.Lg>
<atom:ColInfo Span="6" Order="1" />
</atom:Col.Lg>
<Border Background="#1677FF" Height="48" />
</atom:Col>
方式二:标记扩展语法
<atom:Col Span="6" Order="1" Sm="{atom:ColInfo Order=2}">
<Border Background="#1677FF" Height="48" />
</atom:Col>
方式三:StaticResource 共享
<StackPanel.Resources>
<atom:ColInfo x:Key="MdCol1" Span="12" Order="2" />
<atom:ColInfo x:Key="LgCol1" Span="6" Order="1" />
</StackPanel.Resources>
<atom:Col Span="24" Md="{StaticResource MdCol1}" Lg="{StaticResource LgCol1}">
<Border Background="#1677FF" Height="48" />
</atom:Col>
2.4 响应式 Span
Col.Span 属性也支持按断点指定不同的值:
<!-- 在 AXAML 中使用字符串格式 -->
<atom:Col Span="xs:24,sm:12,lg:8">
<Border Background="#1677FF" Height="48" />
</atom:Col>
2.5 响应式 Gutter
Row.Gutter 支持按断点设置不同的间距:
<!-- 固定间距 -->
<atom:Row Gutter="16">...</atom:Row>
<!-- 水平 + 垂直固定间距 -->
<atom:Row Gutter="16,24">...</atom:Row>
<!-- 水平间距按断点变化 -->
<atom:Row Gutter="xs:8,sm:16,lg:24">...</atom:Row>
<!-- 水平和垂直都按断点变化(用分号分隔) -->
<atom:Row Gutter="xs:8,lg:16;xs:4,lg:12">...</atom:Row>
2.6 Offset / Push / Pull / Order
<!-- 偏移 -->
<atom:Col Span="8" Offset="8">...</atom:Col>
<!-- 推拉(视觉位置互换) -->
<atom:Col Span="18" Push="6">...</atom:Col>
<atom:Col Span="6" Pull="18">...</atom:Col>
<!-- 排序(逻辑顺序不变,视觉顺序改变) -->
<atom:Col Span="6" Order="4">...</atom:Col>
<atom:Col Span="6" Order="3">...</atom:Col>
<atom:Col Span="6" Order="2">...</atom:Col>
<atom:Col Span="6" Order="1">...</atom:Col>
2.7 Justify 和 Align
<!-- 主轴对齐(水平方向) -->
<atom:Row Justify="center">...</atom:Row>
<atom:Row Justify="SpaceBetween">...</atom:Row>
<atom:Row Justify="SpaceAround">...</atom:Row>
<atom:Row Justify="SpaceEvenly">...</atom:Row>
<!-- 交叉轴对齐(垂直方向) -->
<atom:Row Align="top">...</atom:Row>
<atom:Row Align="middle">...</atom:Row>
<atom:Row Align="bottom">...</atom:Row>
<atom:Row Align="stretch">...</atom:Row> <!-- 默认 -->
3. Form 表单响应式布局
Form 和 FormItem 的标签/内容列宽可以通过 MediaBreakGridLength 实现响应式:
3.1 基本用法
<atom:Form LabelColInfo="xs:1*,lg:120"
WrapperColInfo="xs:3*,lg:*">
<atom:FormItem LabelText="Username">
<atom:TextBox />
</atom:FormItem>
<atom:FormItem LabelText="Password">
<atom:TextBox />
</atom:FormItem>
</atom:Form>
LabelColInfo:标签列的 GridLength,按断点配置。WrapperColInfo:内容列的 GridLength,按断点配置。- 值格式支持标准 GridLength 语法:
"120"(像素)、"*"(比例)、"Auto"、"2*"(2 倍比例)。
3.2 字符串格式
"xs:1*,lg:120"
解析规则:
- 逗号分隔多个断点设置
- 每个断点格式为
缩写:GridLength值 - 支持的缩写:
xs、sm、md、lg、xl、xxl - 单一值(无冒号)应用于所有断点:
"120"→ 所有断点均 120px
3.3 FormItem 级别覆盖
FormItem 也有自己的 LabelColInfo 和 WrapperColInfo 属性,可以覆盖 Form 级别的设置:
<atom:Form LabelColInfo="120" WrapperColInfo="*">
<!-- 大多数表单项使用 Form 级别的设置 -->
<atom:FormItem LabelText="Name">
<atom:TextBox />
</atom:FormItem>
<!-- 这个表单项使用自定义的响应式列宽 -->
<atom:FormItem LabelText="Address"
LabelColInfo="xs:1*,lg:200"
WrapperColInfo="xs:3*,lg:*">
<atom:TextBox />
</atom:FormItem>
</atom:Form>
4. Descriptions 描述列表
Descriptions 控件的列数支持响应式配置:
4.1 基本用法
<atom:Descriptions Header="User Info" ColumnInfo="3">
<atom:DescriptionItem Label="UserName" Content="Zhou Maomao" />
<atom:DescriptionItem Label="Telephone" Content="1810000000" />
<atom:DescriptionItem Label="Live" Content="Hangzhou, Zhejiang" />
</atom:Descriptions>
4.2 响应式列数
<!-- xs 断点 1 列,sm 断点 2 列,lg 断点 3 列 -->
<atom:Descriptions Header="User Info" ColumnInfo="xs:1,sm:2,lg:3">
<atom:DescriptionItem Label="UserName" Content="Zhou Maomao" />
<atom:DescriptionItem Label="Telephone" Content="1810000000" />
<atom:DescriptionItem Label="Live" Content="Hangzhou, Zhejiang" />
</atom:Descriptions>
4.3 响应式项目跨度
DescriptionItem.Span 属性也支持按断点配置:
<atom:DescriptionItem Label="Address"
Content="No. 18, Wantang Road"
Span="xs:1,lg:2" />
5. 控件开发者:如何创建响应式控件
如果你正在为 AtomUI 开发新控件并需要响应断点变化,请遵循以下模式。
5.1 订阅断点变更
using AtomUI.Controls;
using Avalonia;
using Avalonia.Controls;
namespace AtomUI.Desktop.Controls;
public class MyResponsiveControl : Control
{
private MediaBreakPoint? _breakPoint;
private IMediaBreakAwareControl? _mediaOwner;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
// 通过接口获取(推荐,更通用)
if (TopLevel.GetTopLevel(this) is IMediaBreakAwareControl mediaOwner)
{
_mediaOwner = mediaOwner;
_breakPoint = mediaOwner.MediaBreakPoint;
mediaOwner.MediaBreakPointChanged += HandleMediaBreakChanged;
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (_mediaOwner != null)
{
_mediaOwner.MediaBreakPointChanged -= HandleMediaBreakChanged;
_mediaOwner = null;
}
}
private void HandleMediaBreakChanged(object? sender, MediaBreakPointChangedEventArgs args)
{
_breakPoint = args.MediaBreakPoint;
// 触发重新布局或刷新状态
InvalidateMeasure();
InvalidateArrange();
}
private MediaBreakPoint GetBreakPoint()
{
if (_breakPoint.HasValue)
{
return _breakPoint.Value;
}
// 降级:尝试即时获取
if (TopLevel.GetTopLevel(this) is IMediaBreakAwareControl mediaOwner)
{
_breakPoint = mediaOwner.MediaBreakPoint;
return _breakPoint.Value;
}
// 最终降级:使用默认值
return MediaBreakPoint.Large;
}
}
5.2 定义响应式断点信息类型
如果你的控件需要一个新的响应式属性(类似 GridGutterInfo),遵循已有的 record 模式:
public record MyMediaBreakInfo
{
public double ExtraSmall { get; init; }
public double Small { get; init; }
public double Medium { get; init; }
public double Large { get; init; }
public double ExtraLarge { get; init; }
public double ExtraExtraLarge { get; init; }
// 统一值构造
public MyMediaBreakInfo(double value)
{
ExtraSmall = Small = Medium = Large = ExtraLarge = ExtraExtraLarge = value;
}
// 按断点获取值
public double GetValue(MediaBreakPoint breakPoint)
{
return breakPoint switch
{
MediaBreakPoint.ExtraSmall => ExtraSmall,
MediaBreakPoint.Small => Small,
MediaBreakPoint.Medium => Medium,
MediaBreakPoint.Large => Large,
MediaBreakPoint.ExtraLarge => ExtraLarge,
_ => ExtraExtraLarge
};
}
// 支持 "xs:10,lg:20" 格式的字符串解析
public static MyMediaBreakInfo Parse(string input) { ... }
}
关键约定:
- 使用
record类型,支持with表达式。 - 提供统一值构造函数(所有断点相同)。
- 提供
Parse(string)方法,支持缩写:值的逗号分隔格式。 - 断点缩写统一为小写:
xs、sm、md、lg、xl、xxl(解析时忽略大小写)。 - 如果需要 AXAML 字符串转换,提供
TypeConverter。
5.3 字符串格式规范
所有响应式属性的字符串解析遵循统一规范:
单一值:
"16" → 所有断点使用 16
分断点(逗号分隔):
"xs:8,sm:16,lg:24" → xs=8, sm=16, lg=24, 未指定的保持默认值
键值对分隔符:
冒号 (:) → 断点名与值之间
逗号 (,) → 多个断点之间
分号 (;) → 仅 GridGutter: 水平与垂直之间
6. 底层原理:Avalonia Container Query
AtomUI 的媒体查询构建在 Avalonia 的 Container Query 功能之上。理解底层机制有助于排查问题。
6.1 核心概念
- Container(容器):通过
Container.Name和Container.Sizing附加属性声明。容器会跟踪自身尺寸。 - ContainerQuery:声明在
ControlTheme或Styles中的查询规则。当关联容器的尺寸满足条件时,激活内部的 Style。 - 查询语法:
min-width:576→ 宽度 ≥ 576px 时激活max-width:575→ 宽度 ≤ 575px 时激活min-width:400 and max-width:800→ 宽度在 400~800px 之间max-width:300,min-height:600→ 宽度 ≤ 300px 或 高度 ≥ 600px
6.2 AtomUI 的封装
AtomUI 没有直接让每个控件自行声明 ContainerQuery。而是:
- Window 的主题模板中声明了一个全局容器
PART_GlobalQueryContainer。 - 6 组
ContainerQuery监听此容器的宽度变化。 - 匹配时设置一个不可见的
WindowMediaQueryIndicator控件的MediaBreakPoint属性。 WindowMediaQueryIndicator将变更通知给Window。Window通过MediaBreakPointChanged事件广播给所有订阅者。
这种集中式设计的优势:
- 单一检测点:只有一个容器查询管道,避免重复计算。
- 统一断点:所有控件始终看到相同的断点值。
- 可预测性:断点变更的时序和顺序是确定的。
6.3 限制与注意事项
- 必须使用
atom:Window:标准 AvaloniaWindow不包含 ContainerQuery 声明,断点不会生效。 - ContainerQuery 不能影响容器本身:这是 Avalonia 的设计约束,因此需要
WindowMediaQueryIndicator作为中继。 - 断点基于内容区域宽度:不是整个窗口宽度,而是
ContentFrame(扣除标题栏和 Padding 后的区域)的宽度。 - 默认断点是
Large:如果控件在附加到视觉树之前需要断点信息,应使用MediaBreakPoint.Large作为回退值。
7. 完整示例
7.1 响应式三栏布局
<atom:Row Gutter="16,16" Wrap="True">
<!-- 大屏三栏,中屏两栏,小屏单栏 -->
<atom:Col Xs="24" Md="12" Lg="8">
<Border Background="#1677FF" Height="100" CornerRadius="4">
<TextBlock Text="Panel 1" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</atom:Col>
<atom:Col Xs="24" Md="12" Lg="8">
<Border Background="#4096FF" Height="100" CornerRadius="4">
<TextBlock Text="Panel 2" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</atom:Col>
<atom:Col Xs="24" Md="24" Lg="8">
<Border Background="#1677FF" Height="100" CornerRadius="4">
<TextBlock Text="Panel 3" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</atom:Col>
</atom:Row>
7.2 响应式表单
<atom:Form LabelColInfo="xs:*,lg:120"
WrapperColInfo="xs:3*,lg:*">
<atom:FormItem LabelText="Username">
<atom:TextBox Watermark="Please input username" />
</atom:FormItem>
<atom:FormItem LabelText="Email">
<atom:TextBox Watermark="Please input email" />
</atom:FormItem>
<atom:FormItem LabelText="Address"
LabelColInfo="xs:*,lg:200"
WrapperColInfo="xs:2*,lg:*">
<atom:TextBox Watermark="Detailed address" />
</atom:FormItem>
</atom:Form>
7.3 响应式描述列表
<atom:Descriptions Header="Order Info"
ColumnInfo="xs:1,sm:2,lg:3"
IsBordered="True">
<atom:DescriptionItem Label="Product" Content="Cloud Database" />
<atom:DescriptionItem Label="Billing" Content="Prepaid" />
<atom:DescriptionItem Label="Renewal" Content="YES" />
<atom:DescriptionItem Label="Order Time" Content="2024-01-15 10:00:00" />
<atom:DescriptionItem Label="Usage Time" Content="2025-01-15 10:00:00" Span="2" />
<atom:DescriptionItem Label="Status" Content="Running" Span="xs:1,lg:3" />
</atom:Descriptions>
7.4 响应式列序变换
<atom:Row Gutter="16,16">
<!-- 大屏:按 1-2-3-4 排列;小屏(sm):按 2-1-4-3 排列 -->
<atom:Col Span="6" Order="1" Sm="{atom:ColInfo Order=2}">
<Border Background="#1677FF" Height="48" />
</atom:Col>
<atom:Col Span="6" Order="2" Sm="{atom:ColInfo Order=1}">
<Border Background="#4096FF" Height="48" />
</atom:Col>
<atom:Col Span="6" Order="3" Sm="{atom:ColInfo Order=4}">
<Border Background="#1677FF" Height="48" />
</atom:Col>
<atom:Col Span="6" Order="4" Sm="{atom:ColInfo Order=3}">
<Border Background="#4096FF" Height="48" />
</atom:Col>
</atom:Row>