您正在查看静态缓存页面 · 查看完整动态版本 · 登录 参与讨论

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

✨步子哥 (steper) 2026年02月17日 05:28 0 次浏览

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

本章导读:如果说布局和控件是应用的骨架,那么图形和动画就是它的皮肤和表情。一个静态的界面就像一张照片——准确但缺乏生气;而一个有动画的界面就像一段视频——生动、流畅、有情感。本章将带你进入 XAML 图形与动画的奇妙世界,让你的应用从"能用"进化到"令人愉悦"。

🔷 9.1 矢量图形:构建界面的几何原子

在传统的 Web 开发中,我们习惯使用位图图片(PNG、JPG)来展示图形。但在现代 UI 框架中,矢量图形才是主角。矢量图形用数学公式描述形状,无论放大多少倍都保持清晰,而且文件体积通常更小。

📐 9.1.1 基础形状控件

Uno Platform 完整实现了 WinUI 的 Shapes 命名空间,提供了多种基础形状控件。

Rectangle(矩形) 是最常用的形状,常用于创建带圆角的背景:

<Rectangle Fill="LightBlue" 
           Stroke="Blue" 
           StrokeThickness="2"
           RadiusX="8" RadiusY="8"
           Width="200" Height="100" />

Ellipse(椭圆) 可以绘制圆形或椭圆形:

<!-- 圆形状态指示器 -->
<Ellipse Fill="Green" Width="12" Height="12" />

<!-- 椭圆形头像框 -->
<Ellipse Width="80" Height="80">
    <Ellipse.Fill>
        <ImageBrush ImageSource="avatar.jpg" Stretch="UniformToFill" />
    </Ellipse.Fill>
</Ellipse>

Line(直线) 用于绘制线段:

<Line X1="0" Y1="0" X2="100" Y2="100" 
      Stroke="Black" StrokeThickness="2" />

Polygon(多边形) 用于绘制闭合的多边形:

<!-- 六边形 -->
<Polygon Points="50,0 100,25 100,75 50,100 0,75 0,25"
         Fill="Purple" Stroke="DarkPurple" StrokeThickness="2" />

Polyline(折线) 用于绘制连续但不闭合的线段:

<!-- 简单的折线图 -->
<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 标准。

<!-- 简单的三角形 -->
<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:可复用的几何定义

如果你需要在多个地方使用相同的几何形状,可以将它定义为资源:

<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:纯色填充

最基础的画刷,填充单一颜色:

<!-- 使用颜色名称 -->
<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:线性渐变

线性渐变沿一条直线方向过渡颜色:

<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>

StartPointEndPoint 使用相对坐标(0-1),表示渐变的方向。(0,0) 是左上角,(1,1) 是右下角。

多色渐变可以添加多个 GradientStop

<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:径向渐变

径向渐变从一个中心点向外辐射:

<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:图像填充

用图像作为填充内容:

<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 提供了现代的毛玻璃效果画刷:

<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:绕点旋转:

<Image Source="arrow.png" Width="50">
    <Image.RenderTransform>
        <RotateTransform Angle="45" CenterX="25" CenterY="25" />
    </Image.RenderTransform>
</Image>

ScaleTransform:缩放:

<Button Content="放大" Width="100" Height="40">
    <Button.RenderTransform>
        <ScaleTransform ScaleX="1.2" ScaleY="1.2" />
    </Button.RenderTransform>
</Button>

TranslateTransform:位移:

<Border Width="100" Height="100" Background="Blue">
    <Border.RenderTransform>
        <TranslateTransform X="50" Y="20" />
    </Border.RenderTransform>
</Border>

SkewTransform:倾斜:

<TextBlock Text="斜体效果" FontSize="24">
    <TextBlock.RenderTransform>
        <SkewTransform AngleX="-15" />
    </TextBlock.RenderTransform>
</TextBlock>

🔀 9.3.2 组合变换

TransformGroup 允许组合多个变换:

<Button Content="组合变换">
    <Button.RenderTransform>
        <TransformGroup>
            <ScaleTransform ScaleX="1.5" ScaleY="1.5" />
            <RotateTransform Angle="30" />
        </TransformGroup>
    </Button.RenderTransform>
</Button>
重要区别:RenderTransform vs LayoutTransform XAML 中有两种变换:RenderTransformLayoutTransform
  • RenderTransform 只影响视觉呈现,不影响布局测量。元素在布局中仍占据原来的位置和空间。
  • LayoutTransform 会影响布局,其他元素会为变换后的元素让出空间。
大多数情况下使用 RenderTransform,因为它性能更好,不会触发布局重计算。

🎬 9.4 传统动画:Storyboard 与时间轴

动画是让界面"活起来"的关键。XAML 提供了强大的声明式动画系统。

🎞️ 9.4.1 Storyboard 基础

Storyboard 是动画的容器,它管理一个或多个动画的时间线:

<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" />

在代码中启动动画:

private void ShowImage()
{
    FadeInStoryboard.Begin();
}

private void HideImage()
{
    FadeOutStoryboard.Begin();
}

📊 9.4.2 动画类型

DoubleAnimation:动画化 double 类型属性(Opacity、Width、Angle 等):

<DoubleAnimation
    Storyboard.TargetName="MyElement"
    Storyboard.TargetProperty="Width"
    From="100" To="200"
    Duration="0:0:1"
    AutoReverse="True"
    RepeatBehavior="Forever" />

ColorAnimation:动画化颜色:

<ColorAnimation
    Storyboard.TargetName="MyBrush"
    Storyboard.TargetProperty="Color"
    From="Red" To="Blue"
    Duration="0:0:2" />

PointAnimation:动画化点坐标:

<PointAnimation
    Storyboard.TargetName="MyEllipse"
    Storyboard.TargetProperty="Center"
    From="0,0" To="100,100"
    Duration="0:0:1" />

⏱️ 9.4.3 缓动函数(Easing Functions)

缓动函数让动画更自然,模拟现实世界的物理特性:

<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 关键帧动画

关键帧动画允许在时间线上定义多个状态:

<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>

不同类型的关键帧提供不同的插值方式:

<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 基础

// 获取元素的 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 支持基于表达式的动画,可以让一个属性根据其他属性动态计算:

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 提供了基于物理的自然动画:

// 弹簧动画
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 动画

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 控制播放

// 播放
await LottiePlayer.PlayAsync(0, 1, looped: false);

// 暂停
LottiePlayer.Pause();

// 停止
LottiePlayer.Stop();

// 设置播放速度(负数反向播放)
LottiePlayer.Speed = 0.5;

// 设置进度(0-1)
LottiePlayer.SetProgress(0.5);

🔗 9.6.4 与数据绑定结合

<AnimatedVisualPlayer x:Name="LoadingPlayer">
    <lottie:LottieVisualSource UriSource="ms-appx:///Assets/loading.json" />
</AnimatedVisualPlayer>

<Button Content="{x:Bind ViewModel.IsLoading, Converter={StaticResource BoolToVisibility}}" />
// 当加载状态改变时控制动画
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 条回复

还没有人回复