[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. 源码
[UWP]理解ControlTemplate中的VisualTransition的更多相关文章
- [UWP]理解及扩展Expander
		
##1. 前言 最近在自定义Expander的样式,顺便看了看它的源码. Expander控件是一个ContentControl,它通过IsExpanded属性或者通过点击Header中的Toggle ...
 - 如何理解javaSript中函数的参数是按值传递
		
本文是我基于红宝书<Javascript高级程序设计>中的第四章,4.1.3传递参数小节P70,进一步理解javaSript中函数的参数,当传递的参数是对象时的传递方式. (结合资料的个人 ...
 - 怎么理解js中的事件委托
		
怎么理解js中的事件委托 时间 2015-01-15 00:59:59 SegmentFault 原文 http://segmentfault.com/blog/sunchengli/119000 ...
 - 如何理解T-SQL中Merge语句(二)
		
写在前面的话:上一篇写了如何理解T-SQL中Merge语句,基本把Merge语句要讲的给讲了,在文章的后面,抛出了几个结,当时没有想明白怎么去用文字表达,这一篇就来解答一下这几个结,又是一篇“天马行空 ...
 - 如何理解T-SQL中Merge语句
		
写在前面的话:之前看过Merge语句,感觉没什么用,完全可以用其他的方式来替代,最近又看了看Merge语句,确实挺好用,可以少写很多代码,看起来也很紧凑,当然也有别的优点. ====正文开始===== ...
 - 深入理解JDK中的I/O
		
深入理解JDK中的I/O 目 录 java内存模型GCHTTP协议事务隔离级并发多线程设计模式清楚redis.memcache并且知道区别mysql分表分库有接口幂等性了解jdk8稍微了解一下特性 j ...
 - 深度理解Jquery 中 offset() 方法
		
参考原文:深度理解Jquery 中 offset() 方法
 - 简单理解Struts2中拦截器与过滤器的区别及执行顺序
		
简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...
 - 深入理解CSS中的层叠上下文和层叠顺序(转)
		
by zhangxinxu from http://www.zhangxinxu.com 本文地址:http://www.zhangxinxu.com/wordpress/?p=5115 零.世间的道 ...
 
随机推荐
- Angular CurrencyPipe货币管道关于人民币符号¥的问题
			
做项目(Angular项目)时经常需要处理金额的显示,需要在金额前面加上¥,但又不想用简单在前面加"¥"这么不优雅的方式,于是想到了CurrencyPipe.毕竟,Currency ...
 - UVA - 658 最短路
			
思路:通过前后两种状态建立一条边,利用Dijsktra就可以做了. 注意利用二进制优化. AC代码 #include <cstdio> #include <cmath> #in ...
 - Redis进阶实践之十四 Redis-cli命令行工具使用详解第一部分
			
一.介绍 redis学了有一段时间了,以前都是看视频,看教程,很少看官方的东西.现在redis的东西要看的都差不多看完了.网上的东西也不多了.剩下来就看看官网的东西吧,一遍翻译,一遍测试. ...
 - DQL、DML、DDL、DCL
			
二. SQL语言的分类 SQL语言共分为四大类:数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL. 1. 数据查询语言DQL数据查询语言DQL基本结构是由SELECT子句 ...
 - GOF 23种设计模式
			
设计模式目录 创建型 1. Factory Method(工厂方法) 2. Abstract Factory(抽象工厂) 3. Builder(建造者) 4. Prototype(原型) 5. Sin ...
 - Android项目中的各个模块框架设计
			
作为Android开发,现对项目开发中的各个模块搭建,梳理如下: Android UI框架,开发人员需要达到专家级 网络框架 浏览框架 图片加载框架 图片裁剪压缩工具类 客户端并发框架 线程池设计 ( ...
 - dojo级联步骤
			
dojo级联步骤 1.数据请求回来后,检查数据格式是否满足下拉框的数据格式: 2.通过firebug进行调试,检查select下拉框子项结点是否取道: 3.查看API文档,保证改变store的方法是正 ...
 - VTK显示mhd,mha格式文件
			
下一篇文章将详细介绍mhd,mha文件的构成以及如何制作void renderMhd () { // read input image vtkSmartPointer<vtkMetaImageR ...
 - dijit.byId("grid") is undefined
			
1.错误描述 TypeError:dijit.byId(...) is undefined (68 out of range 3) 2.错误原因 var gridName = dijit ...
 - 芝麻HTTP: Python爬虫利器之Requests库的用法
			
前言 之前我们用了 urllib 库,这个作为入门的工具还是不错的,对了解一些爬虫的基本理念,掌握爬虫爬取的流程有所帮助.入门之后,我们就需要学习一些更加高级的内容和工具来方便我们的爬取.那么这一节来 ...