Loading...
正在加载...
请稍候

第九章:图形与动画:WinUI 的表现力

✨步子哥 (steper) 2026年02月17日 05:28
# 第九章:图形与动画:WinUI 的表现力 > **本章导读**:如果说布局和控件是应用的骨架,那么图形和动画就是它的皮肤和表情。一个静态的界面就像一张照片——准确但缺乏生气;而一个有动画的界面就像一段视频——生动、流畅、有情感。本章将带你进入 XAML 图形与动画的奇妙世界,让你的应用从"能用"进化到"令人愉悦"。 --- ## 🔷 9.1 矢量图形:构建界面的几何原子 在传统的 Web 开发中,我们习惯使用位图图片(PNG、JPG)来展示图形。但在现代 UI 框架中,**矢量图形**才是主角。矢量图形用数学公式描述形状,无论放大多少倍都保持清晰,而且文件体积通常更小。 ### 📐 9.1.1 基础形状控件 Uno Platform 完整实现了 WinUI 的 `Shapes` 命名空间,提供了多种基础形状控件。 **Rectangle(矩形)** 是最常用的形状,常用于创建带圆角的背景: ```xml <Rectangle Fill="LightBlue" Stroke="Blue" StrokeThickness="2" RadiusX="8" RadiusY="8" Width="200" Height="100" /> ``` **Ellipse(椭圆)** 可以绘制圆形或椭圆形: ```xml <!-- 圆形状态指示器 --> <Ellipse Fill="Green" Width="12" Height="12" /> <!-- 椭圆形头像框 --> <Ellipse Width="80" Height="80"> <Ellipse.Fill> <ImageBrush ImageSource="avatar.jpg" Stretch="UniformToFill" /> </Ellipse.Fill> </Ellipse> ``` **Line(直线)** 用于绘制线段: ```xml <Line X1="0" Y1="0" X2="100" Y2="100" Stroke="Black" StrokeThickness="2" /> ``` **Polygon(多边形)** 用于绘制闭合的多边形: ```xml <!-- 六边形 --> <Polygon Points="50,0 100,25 100,75 50,100 0,75 0,25" Fill="Purple" Stroke="DarkPurple" StrokeThickness="2" /> ``` **Polyline(折线)** 用于绘制连续但不闭合的线段: ```xml <!-- 简单的折线图 --> <Polyline Points="0,100 30,80 60,90 90,40 120,50 150,20" Stroke="Blue" StrokeThickness="3" /> ``` > **第一性原理**:为什么 Shape 是控件? > > 在 XAML 中,Shape 继承自 `FrameworkElement`,这意味着它是一个完整的控件,可以参与布局系统、响应事件、应用变换。这与其他框架将 Shape 视为简单的绘图原语不同。这种设计带来了极大的灵活性——你可以将 Shape 放在任何容器中,绑定它的属性,甚至在 Shape 上处理鼠标事件。 ### 🛤️ 9.1.2 Path:终极绘图工具 `Path` 是所有形状中最强大的,它可以绘制任意复杂的几何图形。`Data` 属性接受一个"路径标记语法"字符串,这种语法源自 SVG 标准。 ```xml <!-- 简单的三角形 --> <Path Data="M 0,0 L 100,0 L 50,100 Z" Fill="Red" /> <!-- 心形 --> <Path Data="M 50,10 C 20,-10 0,20 50,60 C 100,20 80,-10 50,10" Fill="Pink" Stroke="Red" StrokeThickness="2" /> <!-- 复杂的图标路径 --> <Path Data="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" Fill="Blue" /> ``` **路径标记语法速览**: | 命令 | 含义 | 示例 | |------|------|------| | M | 移动到起点 | `M 10,10` | | L | 直线到终点 | `L 100,100` | | C | 三次贝塞尔曲线 | `C 20,0 80,100 100,50` | | Q | 二次贝塞尔曲线 | `Q 50,0 100,100` | | A | 弧线 | `A 50,50 0 0 1 100,100` | | Z | 闭合路径 | `Z` | > **费曼技巧提问**:什么时候用 Path 而不是其他 Shape? > > 简单的形状(矩形、圆形)用对应的 Shape 控件更直观。但当形状不符合这些基本类型时,Path 就是你的选择。大多数图标库(如 Material Icons、Font Awesome)都提供 SVG 路径数据,你可以直接复制到 Path 中使用。 ### 🧩 9.1.3 Geometry:可复用的几何定义 如果你需要在多个地方使用相同的几何形状,可以将它定义为资源: ```xml <Page.Resources> <!-- 定义可复用的几何图形 --> <PathGeometry x:Key="StarGeometry"> <PathGeometry.Figures> <PathFigure StartPoint="50,0"> <PolyLineSegment Points="61,35 98,35 68,57 79,91 50,70 21,91 32,57 2,35 39,35" /> </PathFigure> </PathGeometry.Figures> </PathGeometry> </Page.Resources> <!-- 在多个 Path 中复用 --> <Path Data="{StaticResource StarGeometry}" Fill="Gold" /> <Path Data="{StaticResource StarGeometry}" Fill="Gray" Stretch="Uniform" Width="24" Height="24" /> ``` --- ## 🎨 9.2 画刷系统:色彩的无限可能 画刷(Brush)决定了图形和控件如何被"填充"。XAML 提供了多种画刷类型,从简单的单色到复杂的渐变和图像。 ### 🎯 9.2.1 SolidColorBrush:纯色填充 最基础的画刷,填充单一颜色: ```xml <!-- 使用颜色名称 --> <Button Background="Red" /> <!-- 使用十六进制颜色值 --> <Button Background="#FF0000" /> <!-- 使用 ARGB 格式(带透明度) --> <Button Background="#80FF0000" /> <!-- 使用画刷对象 --> <Button> <Button.Background> <SolidColorBrush Color="Red" Opacity="0.5" /> </Button.Background> </Button> ``` ### 🌈 9.2.2 LinearGradientBrush:线性渐变 线性渐变沿一条直线方向过渡颜色: ```xml <Border Width="200" Height="100" CornerRadius="8"> <Border.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="#667eea" Offset="0" /> <GradientStop Color="#764ba2" Offset="1" /> </LinearGradientBrush> </Border.Background> </Border> ``` `StartPoint` 和 `EndPoint` 使用相对坐标(0-1),表示渐变的方向。`(0,0)` 是左上角,`(1,1)` 是右下角。 多色渐变可以添加多个 `GradientStop`: ```xml <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> <GradientStop Color="Red" Offset="0" /> <GradientStop Color="Yellow" Offset="0.33" /> <GradientStop Color="Green" Offset="0.66" /> <GradientStop Color="Blue" Offset="1" /> </LinearGradientBrush> ``` ### 🌀 9.2.3 RadialGradientBrush:径向渐变 径向渐变从一个中心点向外辐射: ```xml <Ellipse Width="200" Height="200"> <Ellipse.Fill> <RadialGradientBrush Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5"> <GradientStop Color="White" Offset="0" /> <GradientStop Color="Black" Offset="1" /> </RadialGradientBrush> </Ellipse.Fill> </Ellipse> ``` ### 🖼️ 9.2.4 ImageBrush:图像填充 用图像作为填充内容: ```xml <Border Width="300" Height="200" CornerRadius="12"> <Border.Background> <ImageBrush ImageSource="background.jpg" Stretch="UniformToFill" AlignmentX="Center" AlignmentY="Center" /> </Border.Background> </Border> <!-- 文字填充图像 --> <TextBlock Text="Hello" FontSize="72" FontWeight="Bold"> <TextBlock.Foreground> <ImageBrush ImageSource="texture.jpg" /> </TextBlock.Foreground> </TextBlock> ``` ### 🎭 9.2.5 AcrylicBrush:毛玻璃效果 WinUI 3 提供了现代的毛玻璃效果画刷: ```xml <Border Width="400" Height="300"> <Border.Background> <AcrylicBrush TintColor="LightGray" TintOpacity="0.5" FallbackColor="Gray" /> </Border.Background> </Border> ``` > **技术细节**:AcrylicBrush 的性能考量 > > 毛玻璃效果需要实时采样背景内容,在低端设备上可能影响性能。`TintColor` 设置基底颜色,`TintOpacity` 控制透明度,`FallbackColor` 是在不支持毛玻璃的设备上使用的备用颜色。 --- ## 🔄 9.3 变换:打破静态布局 变换(Transform)允许你在不影响布局的情况下改变元素的视觉呈现。这对于动画和特效至关重要。 ### 🔄 9.3.1 常用变换类型 **RotateTransform**:绕点旋转: ```xml <Image Source="arrow.png" Width="50"> <Image.RenderTransform> <RotateTransform Angle="45" CenterX="25" CenterY="25" /> </Image.RenderTransform> </Image> ``` **ScaleTransform**:缩放: ```xml <Button Content="放大" Width="100" Height="40"> <Button.RenderTransform> <ScaleTransform ScaleX="1.2" ScaleY="1.2" /> </Button.RenderTransform> </Button> ``` **TranslateTransform**:位移: ```xml <Border Width="100" Height="100" Background="Blue"> <Border.RenderTransform> <TranslateTransform X="50" Y="20" /> </Border.RenderTransform> </Border> ``` **SkewTransform**:倾斜: ```xml <TextBlock Text="斜体效果" FontSize="24"> <TextBlock.RenderTransform> <SkewTransform AngleX="-15" /> </TextBlock.RenderTransform> </TextBlock> ``` ### 🔀 9.3.2 组合变换 `TransformGroup` 允许组合多个变换: ```xml <Button Content="组合变换"> <Button.RenderTransform> <TransformGroup> <ScaleTransform ScaleX="1.5" ScaleY="1.5" /> <RotateTransform Angle="30" /> </TransformGroup> </Button.RenderTransform> </Button> ``` > **重要区别**:RenderTransform vs LayoutTransform > > XAML 中有两种变换:`RenderTransform` 和 `LayoutTransform`。 > - `RenderTransform` 只影响视觉呈现,不影响布局测量。元素在布局中仍占据原来的位置和空间。 > - `LayoutTransform` 会影响布局,其他元素会为变换后的元素让出空间。 > > 大多数情况下使用 `RenderTransform`,因为它性能更好,不会触发布局重计算。 --- ## 🎬 9.4 传统动画:Storyboard 与时间轴 动画是让界面"活起来"的关键。XAML 提供了强大的声明式动画系统。 ### 🎞️ 9.4.1 Storyboard 基础 `Storyboard` 是动画的容器,它管理一个或多个动画的时间线: ```xml <Page.Resources> <!-- 淡入动画 --> <Storyboard x:Name="FadeInStoryboard"> <DoubleAnimation Storyboard.TargetName="MyImage" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.5" /> </Storyboard> <!-- 淡出动画 --> <Storyboard x:Name="FadeOutStoryboard"> <DoubleAnimation Storyboard.TargetName="MyImage" Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:0.3" /> </Storyboard> </Page.Resources> <Image x:Name="MyImage" Source="photo.jpg" Opacity="0" /> ``` 在代码中启动动画: ```csharp private void ShowImage() { FadeInStoryboard.Begin(); } private void HideImage() { FadeOutStoryboard.Begin(); } ``` ### 📊 9.4.2 动画类型 **DoubleAnimation**:动画化 double 类型属性(Opacity、Width、Angle 等): ```xml <DoubleAnimation Storyboard.TargetName="MyElement" Storyboard.TargetProperty="Width" From="100" To="200" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever" /> ``` **ColorAnimation**:动画化颜色: ```xml <ColorAnimation Storyboard.TargetName="MyBrush" Storyboard.TargetProperty="Color" From="Red" To="Blue" Duration="0:0:2" /> ``` **PointAnimation**:动画化点坐标: ```xml <PointAnimation Storyboard.TargetName="MyEllipse" Storyboard.TargetProperty="Center" From="0,0" To="100,100" Duration="0:0:1" /> ``` ### ⏱️ 9.4.3 缓动函数(Easing Functions) 缓动函数让动画更自然,模拟现实世界的物理特性: ```xml <DoubleAnimation From="0" To="300" Duration="0:0:2"> <DoubleAnimation.EasingFunction> <BounceEase Bounces="3" Bounciness="2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> ``` 常用缓动函数: | 缓动函数 | 效果 | 适用场景 | |----------|------|----------| | `LinearEase` | 匀速 | 进度条 | | `QuadraticEase` | 加速/减速 | 一般过渡 | | `CubicEase` | 更明显的加速/减速 | 强调动作 | | `BounceEase` | 弹跳 | 活泼的效果 | | `ElasticEase` | 弹簧 | 自然感觉 | | `BackEase` | 回退 | 超出再回来 | ### 🔄 9.4.4 关键帧动画 关键帧动画允许在时间线上定义多个状态: ```xml <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyElement" Storyboard.TargetProperty="Opacity"> <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" /> <LinearDoubleKeyFrame Value="1" KeyTime="0:0:0.5" /> <LinearDoubleKeyFrame Value="0.5" KeyTime="0:0:1" /> <LinearDoubleKeyFrame Value="1" KeyTime="0:0:1.5" /> </DoubleAnimationUsingKeyFrames> ``` 不同类型的关键帧提供不同的插值方式: ```xml <DoubleAnimationUsingKeyFrames> <!-- 线性插值 --> <LinearDoubleKeyFrame Value="100" KeyTime="0:0:0.5" /> <!-- 样条曲线(贝塞尔) --> <SplineDoubleKeyFrame Value="200" KeyTime="0:0:1" KeySpline="0.5,0 0.5,1" /> <!-- 离散(瞬间跳变) --> <DiscreteDoubleKeyFrame Value="300" KeyTime="0:0:1.5" /> <!-- 带缓动函数 --> <EasingDoubleKeyFrame Value="400" KeyTime="0:0:2"> <EasingDoubleKeyFrame.EasingFunction> <BounceEase /> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> ``` --- ## ⚡ 9.5 Composition API:高性能视觉层 对于需要 60fps 流畅度的复杂动画,传统 Storyboard 可能力不从心。WinUI 提供了 **Composition API**,它在独立的合成线程上运行,直接操作 GPU。 ### 🎯 9.5.1 Composition 基础 ```csharp // 获取元素的 Visual var visual = ElementCompositionPreview.GetElementVisual(MyElement); // 创建 Compositor var compositor = visual.Compositor; // 创建动画 var animation = compositor.CreateScalarKeyFrameAnimation(); animation.InsertKeyFrame(0, 0); animation.InsertKeyFrame(1, 360); animation.Duration = TimeSpan.FromSeconds(2); // 应用动画到 RotationAngle 属性 visual.StartAnimation("RotationAngle", animation); ``` ### 🌊 9.5.2 表达式动画 Composition API 支持基于表达式的动画,可以让一个属性根据其他属性动态计算: ```csharp var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor; // 创建一个"跟随鼠标"的表达式动画 var pointerPosition = compositor.CreatePointerPositionPropertySet(); var expression = compositor.CreateExpressionAnimation( "Vector3(target.Position.X - 50, target.Position.Y - 50, 0)" ); expression.SetReferenceParameter("target", pointerPosition); var myVisual = ElementCompositionPreview.GetElementVisual(MyElement); myVisual.StartAnimation("Offset", expression); ``` ### 🔧 9.5.3 自然动画 Composition API 提供了基于物理的自然动画: ```csharp // 弹簧动画 var springAnimation = compositor.CreateSpringScalarAnimation(); springAnimation.Period = TimeSpan.FromMilliseconds(50); springAnimation.DampingRatio = 0.5f; springAnimation.FinalValue = 100f; visual.StartAnimation("Offset.X", springAnimation); ``` > **何时使用 Composition API**: > > - 需要高性能、60fps 的动画 > - 需要基于物理的自然动画 > - 需要动画响应实时输入(如鼠标位置) > - 需要同时动画大量元素 > > 对于简单的 UI 状态过渡,传统 Storyboard 仍然是更简单的选择。 --- ## 🎭 9.6 Lottie:矢量动画的魔法 **Lottie** 是 Airbnb 开发的一种基于 JSON 的矢量动画格式,设计师可以在 After Effects 中制作动画,导出为 Lottie 格式,然后在应用中播放。 ### 📦 9.6.1 安装和配置 首先安装 `Uno.WinUI.Lottie` NuGet 包。 ### ▶️ 9.6.2 播放 Lottie 动画 ```xml xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals" xmlns:lottie="using:Uno.UI.Lottie" <AnimatedVisualPlayer x:Name="LottiePlayer" AutoPlay="True" Speed="1.0"> <lottie:LottieVisualSource UriSource="ms-appx:///Assets/success.json" /> </AnimatedVisualPlayer> ``` ### 🎮 9.6.3 控制播放 ```csharp // 播放 await LottiePlayer.PlayAsync(0, 1, looped: false); // 暂停 LottiePlayer.Pause(); // 停止 LottiePlayer.Stop(); // 设置播放速度(负数反向播放) LottiePlayer.Speed = 0.5; // 设置进度(0-1) LottiePlayer.SetProgress(0.5); ``` ### 🔗 9.6.4 与数据绑定结合 ```xml <AnimatedVisualPlayer x:Name="LoadingPlayer"> <lottie:LottieVisualSource UriSource="ms-appx:///Assets/loading.json" /> </AnimatedVisualPlayer> <Button Content="{x:Bind ViewModel.IsLoading, Converter={StaticResource BoolToVisibility}}" /> ``` ```csharp // 当加载状态改变时控制动画 public bool IsLoading { get => _isLoading; set { if (SetProperty(ref _isLoading, value)) { if (value) { LoadingPlayer.PlayAsync(0, 1, looped: true); } else { LoadingPlayer.Stop(); } } } } ``` --- ## 📝 本章小结 图形和动画是让应用从"能用"变成"令人愉悦"的关键因素。本章我们学习了 XAML 的矢量图形系统——从基础的矩形和椭圆到强大的 Path 控件,你可以创建任意复杂的形状。 画刷系统提供了丰富的填充方式:纯色、渐变、图像,以及现代的毛玻璃效果。变换让你能够旋转、缩放、位移元素,为动画奠定基础。 动画系统分为两个层次:传统 Storyboard 适合大多数 UI 状态过渡,Composition API 则为高性能场景提供了更强大的能力。Lottie 的集成让设计师制作的复杂动画可以轻松集成到应用中。 在下一章中,我们将从视觉层转向硬件层——学习如何访问**设备硬件与原生 API**,包括相机、传感器、地理位置等跨平台能力。 --- > **动手实验**: > 1. 使用 Path 绘制一个自定义图标(如五角星或心形),并应用渐变填充。 > 2. 创建一个按钮悬停动画:鼠标悬停时按钮放大 1.1 倍并微微旋转,离开时恢复。 > 3. 实现一个加载动画:三个圆点依次上下跳动,使用 Storyboard 和关键帧动画。 > 4. (进阶)使用 Composition API 创建一个跟随手指/鼠标移动的元素,带有自然的弹性效果。

讨论回复

0 条回复

还没有人回复,快来发表你的看法吧!