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

[UWP]理解ControlTemplate中的VisualTransition的更多相关文章

  1. [UWP]理解及扩展Expander

    ##1. 前言 最近在自定义Expander的样式,顺便看了看它的源码. Expander控件是一个ContentControl,它通过IsExpanded属性或者通过点击Header中的Toggle ...

  2. 如何理解javaSript中函数的参数是按值传递

    本文是我基于红宝书<Javascript高级程序设计>中的第四章,4.1.3传递参数小节P70,进一步理解javaSript中函数的参数,当传递的参数是对象时的传递方式. (结合资料的个人 ...

  3. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

  4. 如何理解T-SQL中Merge语句(二)

    写在前面的话:上一篇写了如何理解T-SQL中Merge语句,基本把Merge语句要讲的给讲了,在文章的后面,抛出了几个结,当时没有想明白怎么去用文字表达,这一篇就来解答一下这几个结,又是一篇“天马行空 ...

  5. 如何理解T-SQL中Merge语句

    写在前面的话:之前看过Merge语句,感觉没什么用,完全可以用其他的方式来替代,最近又看了看Merge语句,确实挺好用,可以少写很多代码,看起来也很紧凑,当然也有别的优点. ====正文开始===== ...

  6. 深入理解JDK中的I/O

    深入理解JDK中的I/O 目 录 java内存模型GCHTTP协议事务隔离级并发多线程设计模式清楚redis.memcache并且知道区别mysql分表分库有接口幂等性了解jdk8稍微了解一下特性 j ...

  7. 深度理解Jquery 中 offset() 方法

    参考原文:深度理解Jquery 中 offset() 方法

  8. 简单理解Struts2中拦截器与过滤器的区别及执行顺序

    简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...

  9. 深入理解CSS中的层叠上下文和层叠顺序(转)

    by zhangxinxu from http://www.zhangxinxu.com 本文地址:http://www.zhangxinxu.com/wordpress/?p=5115 零.世间的道 ...

随机推荐

  1. qt pro 设置编译参数,支持 xp

    #兼容 XP VS2015 5.01 版本 win32 { QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01 QMAKE_LFLAGS_CONSOLE = ...

  2. NOIP2017 - 宝藏

    LibreOJ链接 Description 给出一个\(n(n\leq12)\)个点\(m(m\leq1000)\)条边的带权无向图,求该图的一棵生成树,使得其边权×该边距根的深度之和最小. Solu ...

  3. BZOJ3223/洛谷P3391 - 文艺平衡树

    BZOJ链接 洛谷链接 题意 模板题啦~2 代码 //文艺平衡树 #include <cstdio> #include <algorithm> using namespace ...

  4. Day5模块-shutil模块

    参考博客:http://www.cnblogs.com/wupeiqi/articles/4963027.html shutil模块是高级的文件.文件夹.压缩处理的模块.比如文件的copy.压缩等. ...

  5. 学web前端开发有前途吗

    web前端开发现在如此火爆,可以说是引领了IT培训行业的一个潮流,那么web前端开发都要学些什么知识呢?为什么这么火有前途吗?现在行业很需要这种人才吗?还是大家盲目跟风,随大流,下面小编对web前端做 ...

  6. <CEPH中国-深圳站-技术交流会演讲PPT> YY云平台Ceph Block应用实践 & 我写的书 《CEPH实战》

    YY云平台Ceph Block应用实践 http://s3.yyclouds.com/public/YY%E4%BA%91%E5%B9%B3%E5%8F%B0Ceph%E5%AE%9E%E8%B7%B ...

  7. “大话架构”阿里架构师分享的Java程序员需要突破的技术要点

    一.源码分析 源码分析是一种临界知识,掌握了这种临界知识,能不变应万变,源码分析对于很多人来说很枯燥,生涩难懂. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 我认为是阅读源码的最核心 ...

  8. 0_OpenCV3.4.0+Visual Studio2017 + win10环境配置

    研究生学习方向是计算机视觉,因此想从传统的算法开始,于是尝试安装Opencv做一些项目.在安装过程中碰到很多问题,搭建成功后立刻记录下来,一遍以后查看. 安装环境:windows10 64bit 专业 ...

  9. NLP︱高级词向量表达(一)——GloVe(理论、相关测评结果、R&python实现、相关应用)

    有很多改进版的word2vec,但是目前还是word2vec最流行,但是Glove也有很多在提及,笔者在自己实验的时候,发现Glove也还是有很多优点以及可以深入研究对比的地方的,所以对其进行了一定的 ...

  10. 各大HotFix热补丁方案分析和比较

    最近开源界涌现了很多热补丁项目,但从方案上来说,主要包括Dexposed.AndFix.ClassLoader(来源是原QZone,现淘宝的工程师陈钟,在15年年初就已经开始实现)三种.前两个都是阿里 ...