Avalonia 学习笔记02. Fonts and Animations(字体和动画) (转载)
原文链接:Avalonia 学习笔记02. Fonts and Animations(字体和动画) - simonoct - 博客园
2.1 App.axaml
引入自定义字体,这些字体如Phosphor还包含emoji,直接通过unicode调用而避免多个svg下载调用。
<Application xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="AvaloniaApplication2.App" RequestedThemeVariant="Default"> <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --> <Application.Styles> <FluentTheme /> <StyleInclude Source="Styles/AppDefaultStyles.axaml"></StyleInclude> </Application.Styles> <Application.Resources> <SolidColorBrush x:Key="PrimaryForeground">#CFCFCF</SolidColorBrush> <SolidColorBrush x:Key="PrimaryBackground">#14172D</SolidColorBrush> <LinearGradientBrush x:Key="PrimaryBackgroundGradient" StartPoint="0%, 0%" EndPoint="100%, 0%"> <GradientStop Offset="0" Color="#111214"></GradientStop> <GradientStop Offset="1" Color="#151E3E"></GradientStop> </LinearGradientBrush> <SolidColorBrush x:Key="PrimaryHoverBackground">#333B5A</SolidColorBrush> <SolidColorBrush x:Key="PrimaryHoverForeground">White</SolidColorBrush> <FontFamily x:Key="AkkoPro">/Assets/Fonts/AkkoPro-Regular.ttf#Akko Pro</FontFamily> <FontFamily x:Key="AkkoProBold">/Assets/Fonts/AkkoPro-Bold.ttf#Akko Pro</FontFamily> <FontFamily x:Key="Phosphor">/Assets/Fonts/Phosphor.ttf#Phosphor</FontFamily> <FontFamily x:Key="Phosphor-Fill">/Assets/Fonts/Phosphor-Fill.ttf#Phosphor</FontFamily> </Application.Resources> </Application>
- 新增
<FontFamily ...>(字体家族): 让程序展示时可以选择某个特定的字体风格,但需要先下载、存放,然后将Bulid Action修改为:AvaloniaResource。 x:Key="AkkoPro": 我们给这个字体起了个昵称(Key),叫做 AkkoPro。之后在应用的其他地方,我们就可以通过这个昵称来使用它。- 路径语法 (最关键的部分!):
/Assets/Fonts/AkkoPro-Regular.ttf#Akko Pro - 这串字符分为两部分,由一个 # 分隔,两部分都非常重要。
- # 前面的部分:
/Assets/Fonts/AkkoPro-Regular.ttf是字体文件在项目中的物理路径。它告诉 Avalonia 去哪里找这个 .ttf 文件。 - # 后面的部分:
Akko Pro是这个字体文件内部定义的真正的“字体家族名称”。你可以在 Windows 或 macOS 中双击字体文件,在预览窗口看到的那个名字。 - 为什么要这样写? 因为一个字体家族(比如 “微软雅黑”)可能包含多个文件(常规体、粗体、细体)。通过指定相同的 #字体家族名称,Avalonia 才能把这些不同的文件识别为“一家人”,从而正确地应用粗体、斜体等样式。必须严格匹配。
2.2 Styles/AppDefaultStyles.axaml
定义按钮的样式,还要为图标、带图标的按钮、动画效果等制定规则。
这里的代码顺序和视频的不完全一样,方便记录笔记,能正常用。教程展示时用了不少margin修复字体不在button中心线上的BUG,但是新版本Avalonia(11.3.2)已经修复,所以相关的修复样式不包含在下方。
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Design.PreviewWith> <Border Padding="20" Background="{DynamicResource PrimaryBackgroundGradient}" Width="200"> <!-- Add Controls for Previewer Here --> <!-- 这是一个预览区,包含了我们所有类型的按钮,让我们在编写样式时能立刻看到所有变化 --> <StackPanel Spacing="12"> <Image Source="{SvgImage /Assets/Images/logo.svg}" Width="200"></Image> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Home"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Process"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Actions"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Macros"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Reporter"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="History"></Label> </StackPanel> </Button> <Button> <Label Classes="icon-only" Content=""></Label> </Button> <Button Classes="transparent" Grid.Row="1"> <Label Classes="icon-only" Content=""></Label> </Button> </StackPanel> </Border> </Design.PreviewWith> <!-- ======== 核心样式规则 ======== --> <!-- Add Styles Here --> <!-- 1. 全局字体设定 (可选) --> <Style Selector="Window"> <!-- <Setter Property="FontFamily" Value="{DynamicResource AkkoPro}"></Setter> --> <!-- 这行被注释掉了,但它的作用是让整个应用的所有文字默认都使用 AkkoPro 字体。 这是一种高效的全局样式设置方法,利用了“属性继承”的特性。 --> </Style> <!-- 2. 图标的基础样式 --> <Style Selector="Label.icon, Label.icon-only"> <Setter Property="FontFamily" Value="{DynamicResource Phosphor-Fill}"></Setter> <Setter Property="Margin" Value="0 2 5 0"></Setter> <Setter Property="FontSize" Value="19"></Setter> </Style> <!-- 3. “仅图标”样式的覆盖规则 --> <Style Selector="Label.icon-only"> <Setter Property="Margin" Value="0"></Setter> </Style> <!-- 4. 文本部分的字体样式 --> <Style Selector="Button, Label.akko"> <Setter Property="FontFamily" Value="{DynamicResource AkkoPro}"></Setter> </Style> <!-- 5. 所有按钮的基础外观 --> <Style Selector="Button"> <Setter Property="FontSize" Value="20"></Setter> <Setter Property="CornerRadius" Value="10"></Setter> <Setter Property="Foreground" Value="{DynamicResource PrimaryForeground}"></Setter> <Setter Property="Background" Value="{DynamicResource PrimaryBackground}"></Setter> </Style> <!-- 6. 动画的核心:为所有按钮定义“过渡”效果 --> <Style Selector="Button /template/ ContentPresenter"> <Setter Property="RenderTransform" Value="scale(1)"></Setter> <Setter Property="Transitions"> <Transitions> <BrushTransition Property="Foreground" Duration="0:0:0.1"></BrushTransition> <BrushTransition Property="Background" Duration="0:0:0.1"></BrushTransition> <TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.1"></TransformOperationsTransition> </Transitions> </Setter> </Style> <!-- 7. 标准按钮的“鼠标悬停”效果 --> <Style Selector="Button:pointerover /template/ ContentPresenter"> <Setter Property="Foreground" Value="{DynamicResource PrimaryHoverForeground}"></Setter> <Setter Property="Background" Value="{DynamicResource PrimaryHoverBackground}"></Setter> </Style> <!-- ======== 特殊按钮:“透明按钮”的系列规则 ======== --> <!-- 8. 透明按钮的基础样式 --> <Style Selector="Button.transparent"> <Setter Property="Background" Value="Transparent"></Setter> </Style> <!-- 9. 透明按钮的图标样式(从“填充”变为“轮廓”) --> <Style Selector="Button.transparent Label.icon-only"> <Setter Property="FontFamily" Value="{DynamicResource Phosphor}"></Setter> </Style> <!-- 10. 透明按钮的悬停效果:图标放大 --> <Style Selector="Button.transparent:pointerover Label"> <Setter Property="RenderTransform" Value="scale(1.2)"></Setter> </Style> <!-- 11. 透明按钮的悬停效果:保持背景透明 --> <Style Selector="Button.transparent:pointerover /template/ ContentPresenter"> <Setter Property="Background" Value="Transparent"></Setter> </Style> </Styles>
- 全局字体设定 (
Style Selector="Window")- 这行被注释掉了,但它的作用是让整个应用的所有文字默认都使用 AkkoPro 字体。这是一种高效的全局样式设置方法,利用了“属性继承”的特性。
- 图标的基础样式 (
Label.icon, Label.icon-only)- 目标: 定义所有图标的通用外观。
- 选择器: Label.icon, Label.icon-only 同时选中了带 icon 类的和带 icon-only 类的 Label。
- 作用:
FontFamily: 将字体设置为 Phosphor-Fill(填充样式的图标字体)。Margin="0 2 5 0": 这是一个微调。上边距为2可以修正垂直对齐,右边距为5是为了在图标和文字之间创造一个舒适的间距。FontSize: 设置图标的大小。
- “仅图标”样式的覆盖规则 (
Label.icon-only)- 目标: 修正只有一个图标时的边距。
- 选择器:
Label.icon-only比上面的选择器更具体且在下方(一样具体的规则,下方规则会覆盖上方规则)。 - 核心概念:样式覆盖 (Override)。当一个控件同时匹配多个样式规则时,更具体的规则会覆盖掉宽泛的规则。
- 作用:
Margin="0"。因为这个 Label 是单独存在的,旁边没有文字,所以我们不再需要右边的 5 个间距。这条规则会覆盖掉上一条规则中的 Margin 设置,将所有外边距重置为0,让这个单独的图标能在按钮里完美居中。
- 文本部分的字体样式 (
Button, Label.akko)- 为所有按钮和带 akko 类的 Label 设置了文字字体。
- 所有按钮的基础外观 (
Button)- 为所有按钮设置了统一的字体大小、圆角和默认的前景色/背景色。
- 动画的核心:过渡效果 (
Button /template/ ContentPresenter)- 目标: 让所有按钮的颜色和大小变化都变得平滑。
RenderTransform="scale(1)": 设置一个“动画前”的初始状态,即原始大小。Transitions: 这是实现动画的核心。Avalonia接收这个参数后:“对于任何按钮,当它的背景、前景或大小(Transform)要改变时,花 0.1 秒来平滑过渡。” 这是一个一次性声明,之后的所有相关变化都会自动应用此动画。
- 标准按钮的“鼠标悬停”效果 (
Button:pointerover ...)- 目标: 定义鼠标放在普通按钮上时,它应该变成什么样子。
- 作用: 改变
Foreground和Background的值为我们预设的悬停颜色。 - 它如何与动画配合? 当鼠标移上去时,这条规则被激活,Avalonia 准备将背景色从
PrimaryBackground改为PrimaryHoverBackground。这时,第6条规则里的Transitions就会介入:“不立刻改,花0.1秒慢慢变过去”。 于是,实现了平滑的渐变效果。
- 透明按钮的基础样式 (
Button.transparent)- 目标: 创建一种新的按钮变体——透明按钮。
- 选择器:
Button.transparent只会选中那些在 XAML 中被我们写上Classes="transparent"的按钮。 - 作用: 简单地将背景设置为透明。
- 透明按钮的图标样式 (
Button.transparent Label.icon-only)- 目标: 让透明按钮里的图标看起来不一样(例如,更精致)。
- 选择器: 这个选择器非常具体:“找到一个带
transparent类的按钮,然后再找到它里面的那个带icon-only类的Label”。 - 作用:
FontFamily="{DynamicResource Phosphor}"。它将这个特定图标的字体从默认的Phosphor-Fill (填充)覆盖为了 Phosphor (轮廓)。
- 透明按钮的悬停效果:图标放大 (
Button.transparent:pointerover Label)- 目标: 给透明按钮一个独特的悬停反馈。
- 选择器: “当鼠标悬停在一个
transparent按钮上时,找到它里面的任何Label”。 - 作用:
RenderTransform="scale(1.2)"。让图标放大到1.2倍。同样,这个变化也会被第6条规则里的Transitions捕捉到,从而产生平滑的放大动画。
- 透明按钮的悬停效果:保持背景透明 (
Button.transparent:pointerover ...) - 目标: 阻止透明按钮在悬停时出现标准按钮的背景色。
- 核心概念:再次覆盖! 如果没有这条规则,当鼠标悬停在透明按钮上时,它会同时匹配第7条规则(
Button:pointerover)和第8条规则(Button.transparent)。通常颜色会覆盖透明,导致出现不想要的背景。 - 作用:
Background="Transparent"。这条规则更具体,它强行规定:“即使是在悬停状态,透明按钮的背景也必须是透明的”,从而覆盖了第7条规则中设置悬停背景色的行为。
2.3 MainWindow.axaml
以前的按钮里只有一个简单的文本,现在我们要把“图标”和“文字”两个东西都放进去,图标使用unicode指定Phosphor内的emoji。&#x是unicode开头,以;结尾,中间的就是Unicode值。
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="1024" d:DesignHeight="600" Width="1024" Height="600" x:Class="AvaloniaApplication2.MainWindow" Title="AvaloniaApplication2"> <Grid Background="{DynamicResource PrimaryBackground}" ColumnDefinitions="Auto, *"> <Border Padding="20" Background="{DynamicResource PrimaryBackgroundGradient}"> <Grid RowDefinitions="*, Auto"> <StackPanel Spacing="12"> <Image Source="{SvgImage /Assets/Images/logo.svg}" Width="200"></Image> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Home"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Process"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Actions"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Macros"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="Reporter"></Label> </StackPanel> </Button> <Button HorizontalAlignment="Stretch"> <StackPanel Orientation="Horizontal"> <Label Classes="icon" Content=""></Label> <Label Classes="akko" Content="History"></Label> </StackPanel> </Button> </StackPanel> <Button Classes="transparent" Grid.Row="1"> <Label Classes="icon-only" Content=""></Label> </Button> </Grid> </Border> </Grid> </Window>
- 按钮内部的
StackPanel:- 一个
<Button>的Content(内容) 属性很灵活,它不只能放文字,可以放任何单个控件。 - 为了在按钮里并排显示图标和文字,我们先放一个
<StackPanel>作为唯一的“容器”。 Orientation="Horizontal": 让这个StackPanel里的东西从左到右排列。- 然后,我们在这个水平的
StackPanel里放入一个用于显示图标的Label和一个用于显示文字的Label。
- 一个
Classes="icon"(贴上标签/类):- 这就是我们之前在样式文件中定义的“类选择器”的应用之处。
<Label Classes="icon" ...>: 我们给这个Label贴上了icon的标签。于是,AppDefaultStyles.axaml 中那条Selector="Label.icon"的规则就会自动找到它,并把它变成Phosphor图标字体、调整它的边距。<Label Classes="akko" ...>: 同理,这个Label被应用了akko字体样式。- 这就是分离的威力:MainWindow.axaml 只负责结构和内容(一个图标挨着一个文字),而 AppDefaultStyles.axaml 负责外观(它应该长什么样)。
Content="\&\#xE03A;"(Unicode 图标,因为没有字体库通常是无法正常显示):- 图标字体的原理是,它把一些特殊的 Unicode 编码“画”成了图标。
&#x...;是在 XAML 中输入一个十六进制 Unicode 码的标准写法。- E2C2 就是我们在 Phosphor Icons 网站上查到的“房子”图标对应的编码。
- 你不需要记这些编码,只需要从网站上复制,然后粘贴到 Content 属性里,并用 &#x 和 ; 包起来即可。
Button Classes="transparent":- 对于底部的设置按钮,我们给它贴上了
transparent的标签。 - 这样,我们为
.transparent类定义的所有样式(背景透明、悬停时图标放大等)就都会自动应用到这个按钮上。
2.4 样式优先级
AppDefaultStyles.axaml内的样式代码是有顺序的,如果随意调整效果可能不尽如人意,因为样式是由优先级之分!
后面有一集教程专门讲样式优先级,这里就简单复制一些网上内容。
最简单来说:样式选择器越具体,其优先级越高!
2.4.1 定义 (Definition)
样式优先级是 Avalonia 用来解决样式冲突的一套确定性规则。当一个控件的同一个属性(如 Background)被多个不同的样式规则同时设置时,Avalonia 会根据这套优先级规则,决定最终应用哪一个样式的值。
理解这套规则对于调试样式问题和编写可维护的、可预测的 UI至关重要。
2.4.2 优先级判定规则 (The Rules of Priority Determination)
Avalonia 判定优先级遵循一个严格的顺序。以下规则按从高到低的优先级排列:
直接在控件的 XAML 标签上设置的属性值。这是最高优先级,它会覆盖任何来自样式文件的规则。
- 用法:
<Button Background="Red" Content="This will be red"/>
- 注意事项:
- 优先级最高,难以被外部样式覆盖。
- 不利于样式复用和统一维护,应尽量避免在大型项目中使用,主要用于快速原型或特定的一次性覆盖。
这是最核心的规则。Avalonia 会评估一个选择器的“具体程度”。选择器的构成越复杂、限制条件越精确,其优先级就越高。
-
定义: 一个选择器的具体性由其构成的组件(如类型、类、伪类)决定。
-
用法: 通过组合不同的选择器来精确地定位目标控件。例如,Button.transparent:pointerover 比 Button:pointerover 更具体。
-
详细说明: 见第3节。
当两个或多个样式规则的具体性完全相同时,在 .axaml 文件中定义在后面的规则会覆盖定义在前面的规则。
- 用法:
<!-- 规则 A --> <Style Selector="Button"> <Setter Property="Background" Value="Blue"/> </Style> <!-- 规则 B,具体性与A相同,但定义在后 --> <Style Selector="Button"> <Setter Property="Background" Value="Green"/> </Style> <!-- 结果: 按钮背景为绿色 -->
-
注意事项:
- 这是组织样式文件结构的重要依据。通常将通用的、基础的样式放在文件前面,将特殊的、覆盖性的样式放在后面。
这是优先级最低的样式,由引入的主题(如 )提供。我们编写的任何自定义样式,只要能匹配到控件,其优先级都高于主题默认样式。
2.4.3 选择器具体性 (Selector Specificity) 详解
一个选择器的具体性由构成它的所有组件共同决定,可以理解为一个加权计分系统。分数越高的选择器,优先级越高。
以下是各组件的大致权重参考,注意这里计分系统是教学模型,帮助理解,官方文档没有这种具体数值:
| 组件类型 | 示例 | 权重 (参考值) |
|---|---|---|
| 名称选择器 (Name) | #MyUniqueButton | 100 |
| 类选择器 (Class) | .transparent | 10 |
| 伪类 (PseudoClass) | :pointerover, :disabled | 10 |
| 类型选择器 (Type) | Button, Label | 1 |
让我们用这个权重系统来分析您笔记中的代码:
场景A: Label.icon-only 的 Margin 覆盖问题
- 规则 1: Style Selector="Label.icon, Label.icon-only"
- 对于 Label.icon-only 部分,其具体性 = 1 (Label) + 10 (.icon-only) = 11。
- 规则 2: Style Selector="Label.icon-only"
- 具体性 = 1 (Label) + 10 (.icon-only) = 11。
两个规则的具体性分数完全相同。根据 规则 2.3 (定义顺序),写在后面的规则 2 会覆盖规则 1。因此,Margin="0" 生效。
场景B: Button.transparent:pointerover 的 Background 覆盖问题
- 规则 1 (标准悬停): Style Selector="Button:pointerover ..."
- 具体性 = 1 (Button) + 10 (:pointerover) = 11。
- 规则 2 (透明悬停): Style Selector="Button.transparent:pointerover ..."
- 具体性 = 1 (Button) + 10 (.transparent) + 10 (:pointerover) = 21。
规则 2 的具体性分数 (21) 高于规则 1 (11)。因此,无论它们的定义顺序如何,Avalonia 都会应用规则 2 的值,Background 被设置为 Transparent。
2.4.4 特殊修饰符:!important
这是一个可以打破正常优先级规则的特殊工具。
在 Setter 的Value 字符串末尾添加!important。
<Setter Property="Background" Value="Red !important" />
- 极高优先级: !important 会使该条设置的优先级高于几乎所有其他样式,包括内联样式。
- 破坏可维护性: 滥用 !important 会使样式层级变得混乱,后续调试和覆盖会变得极其困难。
- 使用时机: 仅在万不得已的情况下使用,例如需要覆盖一个第三方控件库中你不便修改的、且优先级很高的样式时。在自己的项目中应极力避免。
2.4.5 最佳实践与使用建议 (Best Practices)
- 优先使用类选择器: 通过为控件添加 Classes 来定义不同的变体,这是管理和覆盖样式的首选方式,清晰且可控。
- 合理组织文件: 将通用样式放在样式文件的顶部,将更具体的或派生的样式放在下面,利用定义顺序规则。
- 避免内联样式: 将样式集中在 .axaml 样式文件中,以实现关注点分离和便于维护。
- 谨慎使用 !important: 将其视为解决棘手问题的最后手段,而不是常规工具。

浙公网安备 33010602011771号