上一篇烂文中,老周给大伙伴们介绍过了几个比较好玩的动画。本篇咱们深化主题,说一说基于表达式的动画。这名字好理解,就是你可以用公式 / 等式来产生动画的目标值。比如,你想让某个可视化对象的高度减半,你的表达可以这样写: width / 2,其中,width 表示某对象的宽度。

既然说到基于表达式的动画了,就得介绍一个重要的类型:ExpressionAnimation,它专用来实现表达式动画的。它有一个 Expression 属性,字符串类型,用来设置计算动画目标值的等式。

有关表达式的语法,老周就不废话了,其实语法和 C 类语言差不多。大伙在用的时候,也不用去记的,你只要查看 ExpressionAnimation 类的文档,就能看到完整的帮助内容了。不记得怎么写的时候查看一下帮助文档就可以了。

注意,ExpressionAnimation 产生的动画,你是不能控制其时间长度的,它会由系统来进行计算,而且这个动画很好玩,它可以跟踪参数的变化,当引用参数发生变化时,会更新动画。这类似于数据绑定的功能。

下面我们举个例子。

XAML 代码很简单。

    <Grid Name="root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Rectangle Name="rect" Fill="Green" Width="200" Height="200"/>
</Grid>

页面的根元素是 Grid,然后在里面放一个矩形。

待会儿我们用动画来旋转这个矩形,怎么转呢,计算公式如下:

    Grid的宽度 ÷ Grid的高度 × 360

这个公式会得出矩形要旋转的角度(单位为度)。Grid 的宽度和高度不需要我们写代码去设置,因为默认是填充对齐的,所以,只要我们调整窗口的大小,Grid 的大小就会跟着变。

转到代码文件,在页面类的构造函数中,输入这些代码。

        public MainPage()
{
this.InitializeComponent(); Visual v_root = ElementCompositionPreview.GetElementVisual(root);
Visual v_rect = ElementCompositionPreview.GetElementVisual(rect); var compositor = v_rect.Compositor; // 旋转动画
ExpressionAnimation anmitRot = compositor.CreateExpressionAnimation();
anmitRot.Expression = "360 * container.Size.X / container.Size.Y";
anmitRot.SetReferenceParameter(
"container", v_root);

v_rect.StartAnimation(nameof(Visual.RotationAngleInDegrees), anmitRot);
}

请你注意看那个表达式:360 * container.Size.X / container.Size.Y,可能你会疑问,这个 container 是什么鬼?这个不是鬼,是我随便取的名字,你爱取其他名字都行,比如,你可以写成 360 * dog.Size.X / dog.Size.Y。这其实是个占位符,它实际上是指向代表 Grid 的可视化对象,在运行时,会用真实的对象替换掉这个占位符。那么,这个参数占位符怎么替换呢?

你有没有发现,CompositionAnimation 类有一堆方法,命名很 TMD 有规律,全是一家人,都叫 Set*****Parameter,看到了没?

你以前是不是不知道这些方法有毛用,现在你应该猜它们有什么用了。对啊,就是用来设置替换占位符的实际值的。比如,我们这个示例子,它的表达式是这样的:

360 * container.Size.X / container.Size.Y

其实这个 container 就是指代码中的 v_root 对象,所以我们用这一行代码,就可以在运行阶段,用 v_root把 container 占位符替换掉,变成:

360 * v_root.Size.X / v_root.Size.Y

 anmitRot.SetReferenceParameter("container", v_root);

而 v_root 就是 XAML 中的 Grid 对象,所以,这样就实现了Grid的宽度除以Grid的高度的计算了,再乘以 360 就完事了。

现在你明白了吧,世上很多事情,别想得太复杂,其实人生中很多事情是很简单,就是人总喜欢搞复杂了。

这个表达式计算出来的结果是 float 类型的值,这个你应该能理解的,因为它计算之后就是一个数值,不可能会产生一个 Vector2 值的。接着,我们把这个动画与 v_rect,注意是rect,因为我们要旋转的是矩形,不是Grid,用 StartAnimation 方法使之与 RotationAngleInDegrees 属性绑定就好了,RotationAngleInDegrees 表示的角度,不是弧度。

 v_rect.StartAnimation(nameof(Visual.RotationAngleInDegrees), anmitRot);

好,现在这个东东已经可以运行的了,试试看。

只要调整窗口的大小就可以了,Grid 的大小会自动更新。

你是不是对这个效果不太满意?你会看到,妈的,旋转的中心怎么不是在矩形中央?看着不爽。对的,默认是在左上角的,你懂的。所以,我们可以再加一个 Expression 动画,把旋转中心点移到矩形中央。

        public MainPage()
{
…… Visual v_root = ElementCompositionPreview.GetElementVisual(root);
Visual v_rect = ElementCompositionPreview.GetElementVisual(rect); var compositor = v_rect.Compositor; // 中心点
ExpressionAnimation anmtCP = compositor.CreateExpressionAnimation("Vector3(this.Target.Size.X / 2, this.Target.Size.Y / 2, 0)");
v_rect.StartAnimation("CenterPoint", anmtCP); ……
}

这个表达式里面,用到了一个函数,叫 Vector3,它类似于调用 Vector3 结构的构造函数 new Vector3(...),所以这个表达式计算后会返回一个 Vector3 类型的值,它需要 X,Y,Z 三个值,因为Visual 类要修改中心点,是设置 CenterPoint 属性的,而 CenterPoint 属性是 Vector3 类型的,所以我们动画产生的值,必须与目标值的类型匹配。

Z轴上我们不必理它,默认 0 就可以了,主要是把矩形的宽度和高度分别除以 2。

各位可能注意到了,表达式中用了 this.Target,它指向的是 v_rect,因为这个动画是应用到 v_rect 上的(它调用了 StartAnimation 方法),所以,这个target 就是应用动画的对象,这就等于,CenterPoint 的值取自 Size / 2。

现在,我们再运行一下,旋转中心就在矩形的中央了。

好玩吧,其实啊,还有一件事要告诉你,表达式不仅在 ExpressionAnimation 动画中可用,在关键帧动画中也能用的。只是有个区别,ExpressionAnimation 动画它会对计算进行跟踪,能做到 “绑定” 的效果,但关键帧动画中是不会跟踪计算的,即所设置的表达式是一次性的。

下面给大伙们演示一下如何在关键帧动画中使用表达式。

在 XAML 文档中,放一个矩形,宽度不要设置得太大,后面咱们用动画来放大它。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Rectangle Name="rect" Width="20" Height="120" Fill="Red" HorizontalAlignment="Left"/>
<Button Grid.Row="1" Margin="12" Content="Play" Width="300" Click="OnClick" HorizontalAlignment="Center"/>
</Grid>

现在处理一个按钮的 Click 事件。用关键帧动画来对矩形进行横向(X轴)放大。

        private void OnClick(object sender, RoutedEventArgs e)
{
Visual vs_rect = ElementCompositionPreview.GetElementVisual(rect);
Compositor compositor = vs_rect.Compositor; ScalarKeyFrameAnimation animat = compositor.CreateScalarKeyFrameAnimation();
animat.Duration = TimeSpan.FromSeconds(1d);
// 插入关键帧
animat.InsertExpressionKeyFrame(0f, "this.Target.Scale.X");
animat.InsertExpressionKeyFrame(1f, "this.Target.Scale.X >= max ? min : this.Target.Scale.X + val");
// max、min、val 都是占位符
// 替换占位符
animat.SetScalarParameter("max", 30f);
animat.SetScalarParameter("min", 1f);
animat.SetScalarParameter("val"
, 3f);
vs_rect.StartAnimation("Scale.X", animat);
}

这个关键帧动画有两个关键帧,第一帧位于动画开始处,目标值就是矩形在X轴上的当前缩放倍数。第二个关键帧在动画的结尾处,表达式有点复杂。老周单独写一遍给你看看。

this.Target.Scale.X >= max ? min : this.Target.Scale.X + val

这里用到了三目运算符,其实和C类语言一样,condition ? ifTrue : ifFalse,如果矩形的X轴上的缩放值大于/等于 max 的话,那就直接返回 min ,否则就把缩放倍数加上 val。Visual 类的 Scale 属性类型为 Vector3 ,它有 X,Y,Z 三个值,表示对象在三个轴方向上的缩放倍数。在这个例子中,咱们只处理 X 轴上的缩放。

其中,max、min、val 三个值都是我随便命名的占位符。所以要用具体的值去替换。

            animat.SetScalarParameter("max", 30f);
animat.SetScalarParameter("min", 1f);
animat.SetScalarParameter("val", 3f);

替换之后,这个表达式在运行时就像这样:

this.Target.Scale.X >=  ?  : this.Target.Scale.X + 

故,当矩形放大到 30 倍以后,会跳回到 1 倍。可以看看运行效果。

好玩吧。本篇的最后一环,老周必须再给大伙介绍一个类,因为这个类在动画应用中也相当重要的。它叫 CompositionPropertySet,由于它是从 CompositionObject 类派生,所以,如果调用动画对象的 SetReferenceParameter 方法去设置命名参数时,ExpressionAnimation 动画能够自动跟踪 CompositionPropertySet 对象的更新。

CompositionPropertySet 用法有点像字典,Key 是字符串,你可以使用 Insert**** 方法来插入各种类型的值,可以用 TryGet**** 方法来检索。

有些时候,动画所跟踪的对象不一定都是 Visual 对象的,可能是一些不可见的对象,比如某个颜色,某个数值,这种情形下,使用 CompositionPropertySet 类是一个很高大上的选择。

不知道咋用?没事,下面还是老规矩,动手做,学编程不动手,学三辈子也学不会。

先看看 XAML 代码。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Border Width="300" Height="300" BorderBrush="Blue" BorderThickness="5" Name="bd" Padding="15" PointerMoved="bd_PointerMoved">
<Ellipse Name="ell" Fill="Orange"/>
</Border>
</Grid>

很简单,当鼠标在 Border 上移动时,用动画改变 Ellipse 的不透明度。计算方法很简单,就是用当前的指针坐标的 X 值除以 Y 值,即 x / y,得出 Opacity 的值。

转到代码文件,要在页面类级别声明一个变量。

    public sealed partial class MainPage : Page
{
CompositionPropertySet compPropset = null;

……
}

因为后面我们在代码中要动态修改它,所以要放到类级别。

然后,在页面类的构造函数中设置一下动画。

        public MainPage()
{
this.InitializeComponent(); // 获取相应的 Visual 对象
Visual vs_ell = ElementCompositionPreview.GetElementVisual(ell);
// 创建 PropertySet 实例
compPropset = vs_ell.Compositor.CreatePropertySet();
// 设置默认值
compPropset.InsertVector2("MyValue", new Vector2(1f, 1f));
// 创建动画
ExpressionAnimation animat = vs_ell.Compositor.CreateExpressionAnimation();
animat.Expression = "p.MyValue.X / p.MyValue.Y";
// 设置参数
animat.SetReferenceParameter("p", compPropset);
// 启动动画
vs_ell.StartAnimation("Opacity", animat);
}

调用 Compositor.CreatePropertySet 方法创建 CompositionPropertySet 的实例。为了稍后我们在设置动画表达式时能够访问到里面的值,要先设置一个默认的值。

  compPropset.InsertVector2("MyValue", new Vector2(1f, 1f));

这个值的名字就叫 MyValue,在动画表达式中,可以直接用 . 运算符来访问,如上面代码中。

p.MyValue.X / p.MyValue.Y

p 稍后会用实际参数替换。

处理 PointerMoved 事件。

        private void bd_PointerMoved(object sender, PointerRoutedEventArgs e)
{
// 获取相对于 Border 的当前坐标
var pt = e.GetCurrentPoint(bd).Position;
// 更新 CompositionPropertySet 中的值
compPropset.InsertVector2("MyValue", new Vector2((float)pt.X, (float)pt.Y));
}

首先要获取到指针相对于 Border 元素的坐标,然后更新 PropertySet 中的 MyValue 的值,更新方法直接用 InsertVector2 方法来替换原来的值就可以了,注意,属性名称 MyValue 不要写错,一定要前后一致。

只要在程序代码中更新 CompositionPropertySet 对象,动画就能够自动更新了。

看看效果。

好了,本篇咱们就聊到这里了,相信你学会使用表达式后,你就能弄出各种强大的动画效果。

【Win 10 应用开发】UI Composition 札记(七):基于表达式的动画的更多相关文章

  1. 【Win 10 应用开发】UI Composition 札记(六):动画

    动画在 XAML 中也有,而且基本上与 WPF 中的用法一样.不过,在 UWP 中,动画还有一种表现方式—— 通过 UI Composition 来创建. 基于 UI Composition 的动画, ...

  2. 【Win 10 应用开发】UI Composition 札记(一):视图框架的实现

    在开始今天的内容之前,老周先说一个问题,这个问题记得以前有人提过的. 设置 Windows.ApplicationModel.Core.CoreApplicationView.TitleBar.Ext ...

  3. 【Win 10 应用开发】UI Composition 札记(三):与 XAML 集成

    除了 DirectX 游戏开发,我们一般很少单独使用 UI Composition ,因此,与 XAML 互动并集成是必然结果.这样能够把两者的优势混合使用,让UI布局能够更灵活. 说到与 XAML ...

  4. 【Win 10 应用开发】UI Composition 札记(二):基本构件

    在上一篇中,老周用一个示例,演示了框架视图的创建过程,在本篇中,老周将给大伙伴们说一下 Composition 构建 UI 的一些“零件”. UI Composition 有一个核心类——对,就是 C ...

  5. 【Win 10 应用开发】UI Composition 札记(四):绘制图形

    使用 Win 2D 组件,就可以很轻松地绘制各种图形,哪怕你没有 D2D 相关基础,也不必写很复杂的 C++ 代码. 先来说说如何获取 Win 2D 组件.很简单,创建 UWP 应用项目后,你打开“解 ...

  6. 【Win 10 应用开发】UI Composition 札记(五):灯光

    UI Composition 除了能够为 UI 元素建立三维空间外,还有相当重要的一个部件——灯光.宇宙万物的精彩缤纷,皆源于光明,光,使我们看到各种东西,除了黑洞之外的世界都是五彩斑谰的.故而,真要 ...

  7. 【Win 10 应用开发】启动远程设备上的应用

    这个功能必须在“红石-1”(build 14393)以上的系统版中才能使用,运行在一台设备上的应用,可以通过URI来启动另一台设备上的应用.激活远程应用需要以下前提: 系统必须是build 14393 ...

  8. 【Win 10应用开发】认识一下UAP项目

    Windows 10 SDK预览版需要10030以上版本号的Win 10预览版系统才能使用.之前我安装的9926的系统,然后安装VS 2015 CTP 6,再装Win 10 SDK,但是在新建项目后, ...

  9. 【Win 10 应用开发】在代码中加载文本资源

    记得前一次,老周给大伙,不,小伙伴们介绍了如何填写 .resw 文件,并且在 XAML 中使用 x:Uid 标记来加载.也顺便给大伙儿分析了运行时是如何解析 .resw 文件的. 本来说好了,后续老周 ...

  10. 【Win 10 应用开发】导入.pfx证书

    这个功能其实并不常用,一般开发较少涉及到证书,不过,简单了解一下还是有必要的. 先来说说制作测试证书的方法,这里老周讲两种方法,可以生成用于测试的.pfx文件. 产生证书,大家都知道有个makecer ...

随机推荐

  1. CoreData归纳使用

    1.CoreData简介 2.CoreData数据模型 3.CoreData的主要对象 4.使用CoreData实现数据存储 一.CoreData简介 CoreData用做数据持久化,是数据持久化的最 ...

  2. Python基础2 编码和逻辑运算符

    编码: AscII码 :标准ASCII码是采用7位二进制码来编码的,当用1个字节(8位二进制码)来表示ASCII码时,就在最高位添加1个0. 一个英文字母占一个字节 8位(bit)==一个字节(byt ...

  3. win10 uwp 列表模板选择器

    本文主要讲ListView等列表可以根据内容不同,使用不同模板的列表模板选择器,DataTemplateSelector. 如果在 UWP 需要定义某些列的显示和其他列不同,或者某些行的显示和其他行不 ...

  4. canvas绘制太阳系

    原文地址:http://jeffzhong.space/2017/10/26/solar/ 学习canvas有一段时间了,顺便写个小项目练手,该项目用到的知识点包括: ES6面向对象 基本的三角函数 ...

  5. MySQL索引(1)

    所有MySQL列类型都可以被索引,对相关列使用索引是提高SELECT操作性能的最佳途径.根据存储引擎可以定义 每个表的最大索引数和最大索引长度,每种存储引擎(如MyISAM.InnoDB.BDB.ME ...

  6. MongoDB覆盖索引查询

    官方的MongoDB的文档中说明,覆盖查询是以下的查询: 1. 所有的查询字段是索引的一部分 2. 所有的查询返回字段在同一个索引中 由于所有出现在查询中的字段是索引的一部分, MongoDB 无需在 ...

  7. LeetCode 581. Shortest Unsorted Continuous Subarray (最短无序连续子数组)

    Given an integer array, you need to find one continuous subarray that if you only sort this subarray ...

  8. Hql整理

    一.实体类直接查询 hql语句:(没有select * 表示默认选择全部属性) public static String GET_ALLUSERINFO="from UserEntity&q ...

  9. session设置过期的方法(转载)

    这篇文章主要介绍了php中实现精确设置session过期时间的方法,需要的朋友可以参考下   大多数据情况下我们对于session过期时间使用的是默认设置的时间,而对于一些有特殊要求的情况下我们可以设 ...

  10. mysql服务处理流程

    先把错误日志定位 就是找的错误日志 然后必要的时候 重新启动服务器 排除其他的干扰 把错误日志 挪到旧文件 清空错误日志 然后试着启动 看干净的错误日志 然后 问题就解决了