zoukankan      html  css  js  c++  java
  • [UWP]理解ControlTemplate中的VisualTransition

    1. 前言

    VisualTransition是控件模板中的重要组成部分,无论是自定义控件或者修改控件样式都会接触到VisualTransition。明明这么重要,博客园上好像都没多少关于VisualTransition的主题。

    2. 什么是VisualTransition

    VisualTransition动画定义VisualState之前切换时的过渡行为,包括过渡时间和过渡动画。

    VisualTransition的类定义如下:

    [ContentProperty(Name = "Storyboard")]
    public class VisualTransition : DependencyObject, IVisualTransition
    {
        public VisualTransition();
    
        // 摘要:
        //     获取或设置要转换为的 Windows.UI.Xaml.VisualState 的名称。
        public string To { get; set; }
    
        //
        // 摘要:
        //     获取或设置在发生转换时运行的 Windows.UI.Xaml.Media.Animation.Storyboard。
        public Storyboard Storyboard { get; set; }
    
        //
        // 摘要:
        //     获取或设置应用于生成的动画的缓动函数。
        public EasingFunctionBase GeneratedEasingFunction { get; set; }
    
        //
        // 摘要:
        //     获取或设置从一种状态转换到另一种状态所花的时间,以及任何隐式过渡动画应作为过渡行为的一部分运行的时间
        public Duration GeneratedDuration { get; set; }
    
        //
        // 摘要:
        //     获取或设置要转换的 Windows.UI.Xaml.VisualState 的名称。
        public string From { get; set; }
    }
    
    

    3.为什么使用VisualTransition

    虽然自WPF4以来VisualTransition一直都存在,但很多人还是习惯这样写VisualState:

    <VisualStateGroup x:Name="CommonStates">
        <VisualState x:Name="Normal" />
        <VisualState x:Name="PointerOver">
            <Storyboard>
                <DoubleAnimation  Storyboard.TargetProperty="Opacity"
                                  Storyboard.TargetName="PointOverElement"
                                  Duration="0"
                                  To="1" />
            </Storyboard>
        </VisualState>
        <VisualState x:Name="Pressed">
            <Storyboard>
                <DoubleAnimation  Storyboard.TargetProperty="Opacity"
                                  Storyboard.TargetName="PressElement"
                                  Duration="0"
                                  To="1" />
            </Storyboard>
        </VisualState>
        <VisualState x:Name="Disabled" />
    </VisualStateGroup>
    
    

    正确的做法应该是这样:

    <VisualStateGroup x:Name="CommonStates">
        <VisualStateGroup.Transitions>
            <VisualTransition To="PointerOver">
                <Storyboard>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
                                                   Storyboard.TargetName="PointOverElement">
                        <EasingDoubleKeyFrame KeyTime="0"
                                              Value="0" />
                        <EasingDoubleKeyFrame KeyTime="0:0:2"
                                              Value="1">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <CubicEase EasingMode="EaseOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </VisualTransition>
            <VisualTransition To="Pressed">
                <Storyboard>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
                                                   Storyboard.TargetName="PressElement">
                        <EasingDoubleKeyFrame KeyTime="0"
                                              Value="0" />
                        <EasingDoubleKeyFrame KeyTime="0:0:2"
                                              Value="1">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <CubicEase EasingMode="EaseOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </VisualTransition>
            <VisualTransition To="Disabled">
                <Storyboard Completed="Storyboard_Completed"></Storyboard>
            </VisualTransition>
        </VisualStateGroup.Transitions>
        <VisualState x:Name="Normal" />
        <VisualState x:Name="PointerOver">
            <Storyboard>
                <DoubleAnimation  Storyboard.TargetProperty="Opacity"
                                  Storyboard.TargetName="PointOverElement"
                                  Duration="0"
                                  To="1" />
            </Storyboard>
        </VisualState>
        <VisualState x:Name="Pressed">
            <Storyboard>
                <DoubleAnimation  Storyboard.TargetProperty="Opacity"
                                  Storyboard.TargetName="PressElement"
                                  Duration="0"
                                  To="1" />
            </Storyboard>
        </VisualState>
        <VisualState x:Name="Disabled" />
    </VisualStateGroup>
    
    

    可以看到VisualState中的Storyboard只用于定义VisualState的最终可视状态,而在VIsualState间转换时用户看到的是VisualTransition 中定义的Storyboard。但这样的话两处的Storyboard不就重复了?带着这个疑问很多年,微软终于给出了另一种方案VisualState.Setters:

    <VisualStateGroup x:Name="CommonStates">
        <VisualStateGroup.Transitions>
            ...
        </VisualStateGroup.Transitions>
        <VisualState x:Name="Normal" />
        <VisualState x:Name="PointerOver">
            <VisualState.Setters>
                <Setter Target="PointOverElement.(UIElement.Opacity)"
                        Value="1" />
            </VisualState.Setters>
        </VisualState>
        <VisualState x:Name="Pressed">
            <VisualState.Setters>
                <Setter Target="PressElement.(UIElement.Opacity)"
                        Value="1" />
            </VisualState.Setters>
        </VisualState>
        <VisualState x:Name="Disabled" />
    </VisualStateGroup>
    
    

    这样VisualState的做法就十分清晰明了:

    • 代码使用VisualStateManager控制控件当前的VisualState;
    • VisualState.Setters定义这个VisualState最终在UI上如何呈现;
    • VisualState间的过渡动画由VisualTransition定义;

    4. 怎么使用VisualTransition

    4.1 隐式转换

    不使用Storyboard的VisualTransition称为隐式转换:

    <VisualStateGroup.Transitions >
        <VisualTransition GeneratedDuration="0:0:3"/>
    </VisualStateGroup.Transitions>
    
    

    如上面这段XAML中的VisualTransition ,它指定VisualStateGroup中所有VisualState之间的过渡时间都是3秒,在这3秒中VisualState中的Double、Point和Color使用默认的线性插值方式进行动画转换。而其它值,如Visibility,则不可以使用隐式转换。

    这段XAML在Blend中对应“状态”面板里VisualStateGroup的“默认过渡”。

    隐式转换可以进一步设置其它属性,如以下XAML:

    <VisualStateGroup.Transitions>
        <VisualTransition To="PointerOver"
                          GeneratedDuration="0:0:3">
            <VisualTransition.GeneratedEasingFunction>
                <ExponentialEase EasingMode="EaseOut" />
            </VisualTransition.GeneratedEasingFunction>
        </VisualTransition>
        <VisualTransition From="PointerOver"
                          To="Pressed"
                          GeneratedDuration="0:0:3">
            <VisualTransition.GeneratedEasingFunction>
                <ExponentialEase EasingMode="EaseOut" />
            </VisualTransition.GeneratedEasingFunction>
        </VisualTransition>
    </VisualStateGroup.Transitions>
    
    

    这段XAML中VisualTransition指定了以下三种属性:

    • From和To,转换的旧状态和新状态,可以单独指定。

    • 动画的缓动函数。

    4.2 使用Storyboard

    当隐式转换不能满足需求,可以使用Storyboard指定转换的动画。这时Storyboard不需要设置FillBehavior="HoldEnd",因为Storyboard结束后将保持VisualState设置的最终状态。

    <VisualTransition To="PointerOver">
        <Storyboard>
            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                           Storyboard.TargetName="PointOverElement">
                <DiscreteObjectKeyFrame KeyTime="0">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Visible</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
                                           Storyboard.TargetName="PointOverElement">
                <EasingDoubleKeyFrame KeyTime="0"
                                      Value="0" />
                <EasingDoubleKeyFrame KeyTime="0:0:2"
                                      Value="1">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <CubicEase EasingMode="EaseOut" />
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </VisualTransition>
    <VisualTransition To="Pressed">
        <Storyboard>
            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                           Storyboard.TargetName="PressElement">
                <DiscreteObjectKeyFrame KeyTime="0">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Visible</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
                                           Storyboard.TargetName="PressElement">
                <EasingDoubleKeyFrame KeyTime="0"
                                      Value="0" />
                <EasingDoubleKeyFrame KeyTime="0:0:2"
                                      Value="1">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <CubicEase EasingMode="EaseOut" />
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </VisualTransition>
    
    

    5. 为什么有时候VisualTransition没有生效

    ControlTemplate在VisualState之间切换是靠下面这个函数控制的:

    //
    // 摘要:
    //     通过按名称请求新的 Windows.UI.Xaml.VisualState 来在两个状态之间转换控件。
    //
    // 参数:
    //   control:
    //     要进行状态过渡的控件。
    //
    //   stateName:
    //     要过渡到的状态。
    //
    //   useTransitions:
    //     如果使用 Windows.UI.Xaml.VisualTransition 在各状态之间转换,则为 **true**。 如果跳过使用转换并直接转到请求的状态,则为
    //     **false**。 默认值为 **false**。
    //
    // 返回结果:
    //     如果控件成功转换到新状态或者已经在使用该状态,则为 **true**;否则为 **false**。
    public static bool GoToState(Control control, string stateName, bool useTransitions);
    
    

    如果useTransitions这个参数为false,则VisualState之间切换时不会使用VisualTransition。在控件加载模板时(即调用OnApplyTemplate()函数时)通常会这样做,因为控件在呈现时通常都不需要做动画。

    另外,VisualStateManager.GoToState不会使控件重复进入某个状态,即如果控件已处于PointerOver的VisualState,再次调用VisualStateManager.GoToState(this, PointerOverState, useTransitions)不会触发任何操作,也不会重复触发动画。

    6. 结语

    除了VisualState.Setters,这篇文章的内容基本和WPF通用。

    上次被批评写得太复杂了,这次本来写了很多,为了文章简单易懂删了一半,希望对理解VisualTransition有帮助。

    7. 参考

    VisualTransition Class (Windows)
    VisualTransition Class (Windows.UI.Xaml) - UWP app developer Microsoft Docs

    8. 源码

    AnimationTest

  • 相关阅读:
    (简单) POJ 1860 Currency Exchange,SPFA判圈。
    (简单) POJ 3259 Wormholes,SPFA判断负环。
    (简单) POJ 1502 MPI Maelstrom,Dijkstra。
    (中等) POJ 3660 Cow Contest,Floyd。
    (简单) POJ 2240 Arbitrage,SPFA。
    3.Git基础-查看当前文件状态、跟踪新文件、暂存文件、忽略文件、提交更新、移除文件、移动文件
    2.Git基础-仓库的获取方式与Git文件的状态变化周期(生命周期)
    1.Git起步-Git的三种状态以及三种工作区域、CVCS与DVCS的区别、Git基本工作流程
    获取URL参数
    进度条
  • 原文地址:https://www.cnblogs.com/dino623/p/VisualTransition.html
Copyright © 2011-2022 走看看