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

上一篇烂文中,老周给大伙伴们介绍过了几个比较好玩的动画。本篇咱们深化主题,说一说基于表达式的动画。这名字好理解,就是你可以用公式 / 等式来产生动画的目标值。比如,你想让某个可视化对象的高度减半,你的表达可以这样写: 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 >= 30 ? 1 : this.Target.Scale.X + 3

故,当矩形放大到 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 对象,动画就能够自动更新了。

看看效果。

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

时间: 2024-10-23 01:13:27

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

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

动画在 XAML 中也有,而且基本上与 WPF 中的用法一样.不过,在 UWP 中,动画还有一种表现方式—— 通过 UI Composition 来创建. 基于 UI Composition 的动画,相对于 XAML 动画,有以下优点: 1.不使用 UI 线程,XAML 动画是共享 UI 线程的,而 Composition 中的动画是使用辅助线程的. 2.Composition 动画支持表达式(计算公式)来产生动画,相对灵活. 老周的建议是:两者都用,因为基于 XAML 和基于 Composit

【Win 10 应用开发】打印UI元素

原文:[Win 10 应用开发]打印UI元素 Windows App支持将UI界面进行打印的功能,这与浏览器中的打印网页的用途相近,其好处就是“所见即所得”,直接把界面上呈现的内容打印下来,比重新创建打印图像方便得多. 要在通用App中实现打印,主要依靠以下几个类型: PrintManager:位于Windows.Graphics.Printing命名空间,主要负责显示打印对话框,设置打印源等操作.在使用时,首先调用GetForCurrentView静态方法得到一个PrintManager实例:

【Win 10应用开发】如何知道当前APP在哪个平台设备上运行

[Win 10应用开发]如何知道当前APP在哪个平台设备上运行 在做Win10开发的时候,我们可能经常会需要获得当前程序在在哪个平台设备上运行,用于UI和相关API的调用,那么可以通过什么方式知道当前APP运行的平台呢? 今天这里提供两个方法给大家做参考: 方法一:DeviceFamily 通过Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily,来获取当前的平台设备,目前只可以得到两个值Windows.Mobile或Wind

【Win 10应用开发】实现全屏播放的方法

原文:[Win 10应用开发]实现全屏播放的方法 有人会问,以前的MediaElement控件不是有现成的一排操作按钮吗?而且可以直接进入全屏播放.是的,我们知道,以往的Store App都是在全屏模式下运行的,只要MediaElement控件填满整个窗口,就等于全屏播放了,但是,Win10应用是窗口化的,将MediaElement控件的IsFullWindow属性设置为true后,就会这样: 从上面的截图看,MediaElement控件只是覆盖整个窗口而已,并没有实现全屏.那有办法让它全屏播放

【Win 10 应用开发】Toast通知激活应用——前台&amp;后台

原文:[Win 10 应用开发]Toast通知激活应用--前台&后台 老周最近热衷于讲故事,接下来还是讲故事时间. 有人问我:你上大学的时候,有加入过学生会吗?读大学有没有必要加入学生会? 哎哟,这怎么回答呢,从短期来说,加入学生会有点用,至少可以娱乐一下,运气好的话,说不定能遇到红颜知己,但这概率相当低.从长远发展看嘛,是没什么用.老周当年读了四年本科,在学生会混了四年,什么名堂也没混出来. 一方面老周向来不求虚名,所以也没去参选所谓的什么部长.主席之类的,这些“官衔”听起来很高大上,实际上很

【Win 10应用开发】如何知道UAP在哪个平台上运行

原文:[Win 10应用开发]如何知道UAP在哪个平台上运行 面向22世纪的现代化应用程序可以同时在多种设备上运行,于是有朋友会有一个疑问:有时候,我们还真的需要判断一下,UAP应用程序在哪个平台上运行.尽管大多情况下我们不必要这样做,但某些特殊情况还得考虑.比如一串数据列表,我希望如果在桌面上运行时就以横向列表展现:但要是运行在手机上就以纵向列表展现. 也就是说,其实我们只需分析两种情况即可: 一.桌面.(台式机.笔记本.平板.游戏机.发广告专用机.导航器……) 二.移动环境.其实就是手机.

【Win 10 应用开发】RTM版的UAP项目解剖

原文:[Win 10 应用开发]RTM版的UAP项目解剖 Windows 10 发布后,其实SDK也偷偷地在VS的自定义安装列表中出现了,今天开发人员中心也更新了下载.正式版的SDK在API结构上和以前预览的时候是一样的,只是版本变成10240罢了,所以大家不要问老周有什么新的API. API虽然没变,但VS中的应用程序项目是有了新变化.毕竟以前都是预览的,而现在是“正规军”,以前练兵时都用土豆枪,现在都是真刀真枪干了,故而应用程序项目是有变化的. 以前老周跟大家讲的修改项目模板,去掉遥测类库的

Android开发艺术探索——第七章:Android动画深入分析

Android开发艺术探索--第七章:Android动画深入分析 Android的动画可以分成三种,view动画,帧动画,还有属性动画,其实帧动画也是属于view动画的一种,,只不过他和传统的平移之类的动画不太一样的是表现形式上有点不一样,view动画是通过对场景的不断图像交换而产生的动画效果,而帧动画就是播放一大段图片,很显然,图片多了会OOM,属性动画通过动态的改变对象的属性达到动画效果,也是api11的新特性,在低版本无法使用属性动画,但是我们依旧有一些兼容库,OK,我们还是继续来看下详细

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

在开始今天的内容之前,老周先说一个问题,这个问题记得以前有人提过的. 设置 Windows.ApplicationModel.Core.CoreApplicationView.TitleBar.ExtendViewIntoTitleBar 属性可以让应用窗口中的内容扩展到标题栏.简单地说,就是你的UI区域可以扩大,并填充到标题栏,这在开发自定义标题栏或弄个什么毛玻璃效果时很有用. 不过,这个 ExtendViewIntoTitleBar 属性有个“八阿哥”,一旦你设置之后,系统会对其进行记录,很

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

除了 DirectX 游戏开发,我们一般很少单独使用 UI Composition ,因此,与 XAML 互动并集成是必然结果.这样能够把两者的优势混合使用,让UI布局能够更灵活. 说到与 XAML 的集成,则我们必须先认识一位伙计,他非常重要,位于 Windows.UI.Xaml.Hosting 命名空间下,名叫 ElementCompositionPreview ,有了它,我们才可以在 XAML 元素与 Composition UI 元素之间游走.来看看它都公开了哪些成员. public s