博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第二十二章:动画(十)
阅读量:7081 次
发布时间:2019-06-28

本文共 7892 字,大约阅读时间需要 26 分钟。

你自己的缓和功能

您可以轻松制作自己的缓动功能。所需要的只是一个类型为Func 的方法,它是一个带有double参数和double返回值的函数。这是一个传递函数:它应该为0的参数返回0,并且对于1的参数应该返回1.但是在这两个值之间,任何事情都会发生。
通常,您将自定义缓动函数定义为Easing构造函数的参数。这是Easing定义的唯一构造函数,但Easing类还定义了一个隐式转换
Func 到Easing。
Xamarin.Forms动画函数调用Easing对象的Ease方法。该Ease方法还有一个double参数和一个double返回值,它基本上提供了对在Easing构造函数中指定的缓动函数的公共访问。 (本章前面的图表显示了各种预定义的缓动函数是由访问各种预定义Easing对象的Ease方法的程序生成的。)
这是一个程序,它包含两个自定义缓动函数来控制Button的缩放。这些函数有点与“ease”这个词的含义相矛盾,这就是为什么程序被称为UneasyScale。 这两个缓动函数中的第一个将输入值截断为离散值0,0.2,0.4,0.6,0.8和1,因此Button的跳跃大小增加。 然后使用另一个缓动函数减小Button的大小,该函数对输入值应用一点随机变化。
这些缓动函数中的第一个被指定为Easing构造函数的lambda函数参数。 第二个是强制转换为Easing对象的方法:

public partial class UneasyScalePage : ContentPage{    Random random = new Random();    public UneasyScalePage()    {        InitializeComponent();    }    async void OnButtonClicked(object sender, EventArgs args)    {        double scale = Math.Min(Width / button.Width, Height / button.Height);        await button.ScaleTo(scale, 1000, new Easing(t => (int)(5 * t) / 5.0));        await button.ScaleTo(1, 1000, (Easing)RandomEase);    }    double RandomEase(double t)    {        return t == 0 || t == 1 ? t : t + 0.25 * (random.NextDouble() - 0.5);    }}

不幸的是,更容易制作像这样的脱节函数而不是更平滑和更有趣的传递函数。 那些往往有点复杂。

例如,假设您想要一个如下所示的缓动函数:
2019_03_06_085609
它开始快速,然后减速并逆转航向,但随后再次反转以迅速上升到最后一段。
您可能猜测这是一个多项式方程,或者至少它可以用多项式方程近似。 它有两个点,其中斜率为零,这进一步表明这是一个立方体,可以这样表示:

?(?) = ? ∙ ? ∙ ? ∙ ? + ? ∙ ? ∙ ? + ? ∙ ? + d

现在我们需要找到的是a,b,c和d的值,这些值将导致传递函数按我们的意愿运行。

对于端点,我们知道:

?(0) = 0?(1) = 1

这意味着:

? = 0

还有

1 = ? + ? + c

如果我们进一步说曲线中的两个倾角在t等于1/3和2/3,并且这些点处的f(t)值分别是2/3和1/3,则:

2/3= ? ∙1/27 + ? ∙1/9+ ? ∙1/31/3= ? ∙8/27 + ? ∙4/9+ ? ∙2/3

如果它们被转换为整数系数,那么这两个方程式更易读和可操作,所以我们得到的是具有三个未知数的三个方程式:

1 = ? + ? + ?18 = ? + 3 ∙ ? + 9 ∙ ?9 = 8 ∙ ? + 12 ∙ ? + 18 ∙ ?

通过一些操作,组合和工作,你可以找到a,b和c:

? = 9? = −27/2? =11/2

让我们看看它是否符合我们的想法。 CustomCubicEase程序具有与以前的项目相同的XAML文件。 缓动函数在此直接表示为Func 对象,因此可以方便地在两个ScaleTo调用中使用。 按钮首先按比例放大,然后在暂停一秒后,按钮缩放回正常状态:

public partial class CustomCubicEasePage : ContentPage{    public CustomCubicEasePage()    {        InitializeComponent();    }    async void OnButtonClicked(object sender, EventArgs args)    {        Func
customEase = t => 9 * t * t * t - 13.5 * t * t + 5.5 * t; double scale = Math.Min(Width / button.Width, Height / button.Height); await button.ScaleTo(scale, 1000, customEase); await Task.Delay(1000); await button.ScaleTo(1, 1000, customEase); }}

如果你不认为让自己的缓和功能变得“有趣和轻松”,那么许多标准缓和功能的一个很好的来源是网站。

如果您需要简单的谐波运动并将Math.Exp与指数增加或衰减相结合,也可以从Math.Sin和Math.Cos构造缓动函数。
让我们举一个例子:假设你想要一个按钮,当它被点击时,从它的左下角向下摆动,几乎就像按钮是贴在墙上有几个钉子的图片一样,其中一个钉子脱落了,所以 图片滑落,左下角有一个钉子。
您可以在AnimationTryout程序中按照此练习进行操作。 在Button的Clicked处理程序中,让我们首先设置AnchorX和AnchorY属性,然后调用RotateTo进行90度摆动:

button.AnchorX = 0;button.AnchorY = 1;await button.RotateTo(90, 3000);

这是动画完成时的结果:

2019_03_06_090454
但是这确实需要一个缓和功能,以便Button在稳定之前从那个角落来回摆动一下。 首先,让我们首先在RotateTo调用中添加一个do-nothing线性缓动函数:

await button.RotateTo(90, 3000, new Easing(t => t));

现在让我们添加一些正弦行为。 那是正弦或余弦。 我们希望摆动在开始时很慢,这意味着余弦而不是正弦。 让我们将参数设置为Math.Cos方法,以便当t从0变为1时,角度为0到10π。 这是余弦曲线的五个完整周期,这意味着Button来回摆动五次:

await button.RotateTo(90, 3000, new Easing(t => Math.Cos(10 * Math.PI * t)));

当然,这根本不对。 当t为零时,Math.Cos方法返回1,因此动画通过跳转到90度的值开始。 对于t的后续值,Math.Cos函数返回从1到-1的值,因此Button从90度到-90度摆动五次并回到90度,最后在90度休息。 这确实是我们希望动画结束的地方,但我们希望动画从0度开始。

不过,让我们暂时忽略这个问题。 让我们来解决最初看起来更复杂的问题。 我们不希望Button完全旋转180度五次。 我们希望按钮的摆动随着时间的推移而衰减。
有一种简单的方法可以做到这一点。 我们可以通过Math.Exp调用将Math.Cos方法与基于t的负参数相乘:

Math.Exp(-5 * t)

Math.Exp方法将数学常数e(约2.7)提高到指定的幂。当动画开始时t为0时,e为0,幂为1.当t为1时,e为负五次幂 小于.01,非常接近于零。 (在此调用中您不需要使用-5;您可以尝试查找看起来最佳的值。)

让我们将Math.Cos结果乘以Math.Exp结果:

await button.RotateTo(90, 3000, new Easing(t => Math.Cos(10 * Math.PI * t) * Math.Exp(-5 * t)));

我们非常接近。 Math.Exp确实阻止了Math.Cos调用,但是产品是向后的。当t为0时,乘积为1,当t为1时,乘积为0.我们可以通过简单地从1减去整个表达式来解决这个问题吗? 我们来试试吧:

await button.RotateTo(90, 3000,     new Easing(t => 1 - Math.Cos(10 * Math.PI * t) * Math.Exp(-5 * t)));

现在,当t为0时,缓动函数正确返回0,当t为1时,缓冲函数正确地返回1。

而且,更重要的是,宽松功能现在在视觉上也令人满意。 它看起来好像按钮从系泊中掉落并且在休息之前摇摆了几次。
现在让我们调用TranslateTo使Button退出并落到页面底部。 Button需要放多远?
Button最初位于页面的中心。 这意味着Button底部与页面之间的距离是页面高度的一半减去Button的高度:

(Height - button.Height) / 2

但是现在Button已经从其左下角摆动了90度,因此Button的宽度更接近页面底部。 这是对TranslateTo的完整调用,将Button放到页面底部并使其反弹一点:

await button.TranslateTo(0, (Height - button.Height) / 2 - button.Width,                          1000, Easing.BounceOut);

按钮就像这样休息:

2019_03_06_091202
现在让我们将Button龙骨翻过来并倒置,这意味着我们想要围绕右上角旋转按钮。 这需要更改AnchorX和AnchorY属性:

button.AnchorX = 1;button.AnchorY = 0;

但这是一个问题 - 一个大问题 - 因为AnchorX和AnchorY属性的更改实际上会改变Button的位置。 试试吧! 按钮突然跳起来向右。 Button跳转到的位置恰好是第一个RotateTo基于这些新的AnchorX和AnchorY值时的位置 - 围绕其右上角而不是左下角旋转。

你能想象出来吗? 这是一个小模型,显示按钮的原始位置,按钮从左下角顺时针旋转90度,按钮从右上角顺时针旋转90度:
2019_03_06_091420
当我们设置AnchorX和AnchorY的新值时,我们需要调整TranslationX和TranslationY属性,以便Button基本上从右上角的旋转位置移动到左下角的旋转位置。 TranslationX需要通过Button的宽度减小,然后增加其高度。 需要通过Button的高度和Button的宽度来增加TranslationY。 我们试试看:

button.TranslationX -= button.Width - button.Height;button.TranslationY += button.Width + button.Height;

当AnchorX和AnchorY属性更改为按钮的右上角时,它会保留Button的位置。

现在按钮可以在它翻倒时绕其右上角旋转,当然还有一点反弹:

await button.RotateTo(180, 1000, Easing.BounceOut);

现在Button可以登上屏幕并同时淡出:

await Task.WhenAll    (        button.FadeTo(0, 4000),        button.TranslateTo(0, -Height, 5000, Easing.CubicIn)    );

FadeTo方法为Opacity属性设置动画,在这种情况下,从默认值1到指定为第一个参数的值0。

这是完整的程序,称为SwingButton(指第一个动画),最后将Button恢复到原始位置,以便您可以再次尝试:

public partial class SwingButtonPage : ContentPage{    public SwingButtonPage()    {        InitializeComponent();    }    async void OnButtonClicked(object sender, EventArgs args)    {        // Swing down from lower-left corner.        button.AnchorX = 0;        button.AnchorY = 1;        await button.RotateTo(90, 3000,             new Easing(t => 1 - Math.Cos(10 * Math.PI * t) * Math.Exp(-5 * t)));        // Drop to the bottom of the screen.        await button.TranslateTo(0, (Height - button.Height) / 2 - button.Width,                                  1000, Easing.BounceOut);        // Prepare AnchorX and AnchorY for next rotation.        button.AnchorX = 1;        button.AnchorY = 0;        // Compensate for the change in AnchorX and AnchorY.        button.TranslationX -= button.Width - button.Height;        button.TranslationY += button.Width + button.Height;        // Fall over.        await button.RotateTo(180, 1000, Easing.BounceOut);        // Fade out while ascending to the top of the screen.        await Task.WhenAll            (                button.FadeTo(0, 4000),                button.TranslateTo(0, -Height, 5000, Easing.CubicIn)            );        // After three seconds, return the Button to normal.        await Task.Delay(3000);        button.TranslationX = 0;        button.TranslationY = 0;        button.Rotation = 0;        button.Opacity = 1;    }}

当输入为0时,缓动函数应该返回0;当输入为1时,缓动函数应该返回1,但是有可能破坏这些规则,有时这是有意义的。 例如,假设您想要一个稍微移动元素的动画 - 也许它会以某种方式振动它 - 但动画应该将元素返回到最后的原始位置。 对于类似这样的事情,当输入为0和1时,缓动函数返回0是有意义的,但这些值之间的值不是0。

这是JiggleButton背后的想法,它位于Xamarin.FormsBook.Toolkit库中。 JiggleButton派生自Button并安装Clicked处理程序,其唯一目的是在您单击按钮时摇动按钮:

namespace Xamarin.FormsBook.Toolkit{    public class JiggleButton : Button    {        bool isJiggling;        public JiggleButton()        {            Clicked += async (sender, args) =>                {                    if (isJiggling)                        return;                    isJiggling = true;                    await this.RotateTo(15, 1000, new Easing(t =>                                                    Math.Sin(Math.PI * t) *                                                    Math.Sin(Math.PI * 20 * t)));                    isJiggling = false;                };        }    }}

RotateTo方法似乎在一秒钟内将按钮旋转了15度。但是,自定义Easing对象有不同的想法。它仅由两个正弦函数的乘积组成。当t从0变为1时,第一个Math.Sin函数扫描正弦曲线的前半部分,因此当t为0时从0变为0,当t为0.5时变为1,当t为1时变为0。

第二个Math.Sin调用是抖动部分。当t从0变为1时,此调用将经历10个正弦曲线周期。如果没有第一次Math.Sin调用,这会将按钮从0度旋转到15度,然后旋转到-15度,然后再回到0度十次。但是第一个Math.Sin调用会在动画的开始和结束时抑制旋转,只允许在中间旋转15到15度。
涉及isJiggling字段的一些代码可以保护Clicked处理程序在一个新动画正在进行时启动它。这是使用await和动画方法的一个优点:您确切知道动画何时完成。
JiggleButtonDemo XAML文件创建三个JiggleButton对象,以便您可以使用它们:

转载地址:http://dvvml.baihongyu.com/

你可能感兴趣的文章
创业新机:朋友圈广告位大改,“解刨”小程序的真正价值
查看>>
小猿圈web前端之JavaScript放大镜效果
查看>>
奇点大学人工智能专家:人造智能大脑已接近现实
查看>>
Python学习笔记 - 环境搭建
查看>>
大数据算法:kNN算法
查看>>
静态路由实验题
查看>>
简单理解Ajax原理
查看>>
Delphi XE2 之 FireMonkey 入门(18) - TLang(多语言切换的实现)
查看>>
学用 ASP.Net 之 System.DateTime 结构
查看>>
我的友情链接
查看>>
互联网枭雄点评之周鸿祎 - 不甘老去的互联网老兵
查看>>
PKI_IOS证书加密L2L ×××
查看>>
web基础
查看>>
Apache和Nginx的区别
查看>>
2017.5.23 MS Power BI workshop for partner
查看>>
翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 8 章:列表操作...
查看>>
Linux常用的系统监控shell脚本
查看>>
Android Studio中使用GreenDao
查看>>
Yii 框架之采用自带的jquery库实现ajax分页
查看>>
负载均衡小demo,未实现session共享
查看>>