You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WinUI-3 C#的Shimmer控件中实现波浪动画?

WinUI 3 Shimmer 控件波浪动画实现方案

针对已搭建的Shimmer控件结构,以下是实现波浪动画的具体方案,包含XAML模板修改、动画逻辑整合以及自定义属性的绑定处理:

一、修改ControlTemplate 添加动画层

将原静态填充的椭圆和矩形替换为带渐变刷的动态元素,并添加VisualState管理动画状态:

<Style x:Key="ShimmerStyle" TargetType="local:Shimmer">
    <Setter Property="HorizontalAlignment" Value="Center"/>
    <Setter Property="VerticalAlignment" Value="Center"/>
    <Setter Property="Fill" Value="{ThemeResource ShimmerFillColor}"/>
    <Setter Property="WaveColor" Value="#FFFFFF"/>
    <Setter Property="AnimationDuration" Value="0:0:1.5"/>
    <Setter Property="WaveDirection" Value="Horizontal"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:Shimmer">
                <StackPanel>
                    <Grid x:Name="Grid1">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="ActiveStates">
                                <VisualState x:Name="Active">
                                    <Storyboard RepeatBehavior="Forever">
                                        <DoubleAnimation
                                            Storyboard.TargetName="EliGradientTransform"
                                            Storyboard.TargetProperty="X"
                                            From="-1" To="1"
                                            Duration="{TemplateBinding AnimationDuration}"/>
                                        <DoubleAnimation
                                            Storyboard.TargetName="RectGradientTransform"
                                            Storyboard.TargetProperty="X"
                                            From="-1" To="1"
                                            Duration="{TemplateBinding AnimationDuration}"/>
                                        <DoubleAnimation
                                            Storyboard.TargetName="Rect1GradientTransform"
                                            Storyboard.TargetProperty="X"
                                            From="-1" To="1"
                                            Duration="{TemplateBinding AnimationDuration}"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Inactive"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>

                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>

                        <Ellipse x:Name="Eli" Grid.Row="0" Grid.Column="0" Height="200" Width="200" HorizontalAlignment="Left">
                            <Ellipse.Fill>
                                <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="0"/>
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="0.4"/>
                                    <GradientStop Color="{TemplateBinding WaveColor}" Offset="0.5"/>
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="0.6"/>
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="1"/>
                                    <LinearGradientBrush.Transform>
                                        <TranslateTransform x:Name="EliGradientTransform" X="-1"/>
                                    </LinearGradientBrush.Transform>
                                </LinearGradientBrush>
                            </Ellipse.Fill>
                        </Ellipse>

                        <Rectangle x:Name="Rect" Margin="40,30,50,100" Grid.Row="0" Grid.Column="1" Height="60" Width="800" Opacity="0.8">
                            <Rectangle.Fill>
                                <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="0"/>
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="0.4"/>
                                    <GradientStop Color="{TemplateBinding WaveColor}" Offset="0.5"/>
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="0.6"/>
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="1"/>
                                    <LinearGradientBrush.Transform>
                                        <TranslateTransform x:Name="RectGradientTransform" X="-1"/>
                                    </LinearGradientBrush.Transform>
                                </LinearGradientBrush>
                            </Rectangle.Fill>
                        </Rectangle>

                        <Rectangle x:Name="Rect1" Grid.Row="0" Grid.Column="1" Margin="40,90,0,0" Height="60" Width="300" HorizontalAlignment="Left">
                            <Rectangle.Fill>
                                <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="0"/>
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="0.4"/>
                                    <GradientStop Color="{TemplateBinding WaveColor}" Offset="0.5"/>
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="0.6"/>
                                    <GradientStop Color="{TemplateBinding Fill}" Offset="1"/>
                                    <LinearGradientBrush.Transform>
                                        <TranslateTransform x:Name="Rect1GradientTransform" X="-1"/>
                                    </LinearGradientBrush.Transform>
                                </LinearGradientBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                    </Grid>

                    <Grid x:Name="Grid2">
                        <ContentPresenter Content="{TemplateBinding Content}"/>
                    </Grid>
                </StackPanel>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsActive" Value="True">
                        <Setter TargetName="Grid1" Property="Visibility" Value="Visible"/>
                        <Setter TargetName="Grid2" Property="Visibility" Value="Collapsed"/>
                        <Setter Property="VisualStateManager.GoToState" TargetName="Grid1" Value="Active"/>
                    </Trigger>
                    <Trigger Property="IsActive" Value="False">
                        <Setter TargetName="Grid1" Property="Visibility" Value="Collapsed"/>
                        <Setter TargetName="Grid2" Property="Visibility" Value="Visible"/>
                        <Setter Property="VisualStateManager.GoToState" TargetName="Grid1" Value="Inactive"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style BasedOn="{StaticResource ShimmerStyle}" TargetType="local:Shimmer"/>

二、核心逻辑说明

  1. 渐变刷与平移动画:每个元素使用包含基础填充色和波浪色的线性渐变刷,通过平移变换(TranslateTransform)的X轴偏移动画,实现亮条从左到右扫过的效果。
  2. 状态切换:通过IsActive属性触发ControlTemplate触发器,切换Shimmer骨架和内容区域的可见性,并启动/停止动画。
  3. 自定义属性绑定:WaveColor绑定到渐变的亮条颜色,AnimationDuration直接控制动画时长,确保控件的可配置性。

三、扩展WaveDirection属性支持

若要实现垂直方向的波浪动画,可在后台类中添加属性变更回调,动态调整渐变刷方向和动画目标:

public enum ShimmerDirection
{
    Horizontal,
    Vertical
}

public static readonly DependencyProperty WaveDirectionProperty = DependencyProperty.Register(
    nameof(WaveDirection), typeof(ShimmerDirection), typeof(Shimmer), new PropertyMetadata(ShimmerDirection.Horizontal, OnWaveDirectionChanged));

public ShimmerDirection WaveDirection
{
    get => (ShimmerDirection)GetValue(WaveDirectionProperty);
    set => SetValue(WaveDirectionProperty, value);
}

private static void OnWaveDirectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is Shimmer shimmer && shimmer.GetTemplateChild("Grid1") is Grid grid1)
    {
        var targetDirection = (ShimmerDirection)e.NewValue;
        UpdateElementGradientDirection(grid1.FindName("Eli") as Ellipse, targetDirection);
        UpdateElementGradientDirection(grid1.FindName("Rect") as Rectangle, targetDirection);
        UpdateElementGradientDirection(grid1.FindName("Rect1") as Rectangle, targetDirection);
        UpdateAnimationTargetProperty(shimmer, targetDirection);
    }
}

private static void UpdateElementGradientDirection(Shape shape, ShimmerDirection direction)
{
    if (shape?.Fill is LinearGradientBrush brush)
    {
        if (direction == ShimmerDirection.Vertical)
        {
            brush.StartPoint = new Point(0, 0);
            brush.EndPoint = new Point(0, 1);
        }
        else
        {
            brush.StartPoint = new Point(0, 0);
            brush.EndPoint = new Point(1, 0);
        }
    }
}

private static void UpdateAnimationTargetProperty(Shimmer shimmer, ShimmerDirection direction)
{
    if (shimmer.GetTemplateChild("ActiveStates") is VisualStateGroup stateGroup)
    {
        var activeState = stateGroup.States.FirstOrDefault(s => s.Name == "Active");
        if (activeState?.Storyboard is Storyboard storyboard)
        {
            foreach (var animation in storyboard.Children.OfType<DoubleAnimation>())
            {
                animation.TargetProperty = direction == ShimmerDirection.Vertical ? "Y" : "X";
                animation.From = direction == ShimmerDirection.Vertical ? -1 : -1;
                animation.To = direction == ShimmerDirection.Vertical ? 1 : 1;
            }
        }
    }
}

四、注意事项

  • 确保项目中已引用Microsoft.UI.Xaml.Controls,并定义BooleanToVisibilityConverter(或使用内置转换器)。
  • 若波浪效果不明显,可调整渐变停止点的Offset值(如将0.4/0.6改为0.3/0.7),或修改WaveColor的透明度增强对比度。
  • 动画启动后会循环播放,直到IsActive设为False,此时动画自动停止并切换到内容显示。

内容的提问来源于stack exchange,提问作者Budding developer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 11:19:54