这期是 HenCoder 自定义绘制的第 1-6 期:属性动画(上手篇)java
若是你没据说过 HenCoder,能够先看看这个:
HenCoder:给高级 Android 工程师的进阶手册git
前几期发布后,常常在回复里看到有人问我何时讲动画。原本我是不打算讲动画的,由于动画其实不算是自定义 View 的内容。但后来考虑了一下,动画在自定义 View 的开发中也起着很重要的做用,有的时候你对动画的了解不够,就难以实现一些自定义 View 的效果。github
因而决定:加两期,讲动画!canvas
不过并非全部的动画都讲,我要讲的是属性动画。 Android 里动画是有一些分类的:动画能够分为两类:Animation 和 Transition;其中 Animation 又能够再分为 View Animation 和 Property Animation 两类: View Animation 是纯粹基于 framework 的绘制转变,比较简单,若是你有兴趣的话能够上网搜一下它的用法;Property Animation,属性动画,这是在 Android 3.0 开始引入的新的动画形式,不过说它新只是相对的,它已经有好几年的历史了,并且如今的项目中的动画 99% 都是用的它,极少再用到 View Animation 了。属性动画不只可使用自带的 API 来实现最经常使用的动画,并且经过自定义 View 的方式来作出定制化的动画。除了这两种 Animation,还有一类动画是 Transition。 Transition 这个词的本意是转换,在 Android 里指的是切换界面时的动画效果,这个在逻辑上要复杂一点,不过它的重点是在于切换而不是动画,因此它也不是此次要讨论的内容。此次的内容只专一于一点:微信
Property Animation(属性动画)。在这一期我就基于前面几期讲过的自定义绘制,这一个自定义 View 的分支,来讲一下属性动画的原理以及使用。ide
复杂的东西用文字很难讲清楚,因此每次遇到难讲的内容我都会选择上视频,这期也不例外。post
话说作视频太费精力和时间了,这期的视频居然作了两周。之后必定要控制住本身,少作视频,否则怕会掉头发。动画
(若是你是手机看的,能够点这里去 B 站看视频)this
下面是讲义部分。强烈建议你看完上面的视频再看下面的内容,否则可能会遭遇理解障碍。spa
使用方式:View.animate()
后跟 translationX()
等方法,动画会自动执行。
view.animate().translationX(500);复制代码
具体能够跟的方法以及方法所对应的 View
中的实际操做的方法以下图所示:
从图中能够看到, View
的每一个方法都对应了 ViewPropertyAnimator
的两个方法,其中一个是带有 -By
后缀的,例如,View.setTranslationX()
对应了 ViewPropertyAnimator.translationX()
和 ViewPropertyAnimator.translationXBy()
这两个方法。其中带有 -By()
后缀的是增量版本的方法,例如,translationX(100)
表示用动画把 View
的 translationX
值渐变为 100
,而 translationXBy(100)
则表示用动画把 View
的 translationX
值渐变地增长 100
。l
这些方法的效果都简单易懂,并且视频里也有简单的演示,因此就不放示例图了。若是你想看,能够去下面的练习项目。(最好顺便也练一下代码)
使用方式:
setter
/ getter
方法;ObjectAnimator.ofXXX()
建立 ObjectAnimator
对象;start()
方法执行动画。public class SportsView extends View {
float progress = 0;
......
// 建立 getter 方法
public float getProgress() {
return progress;
}
// 建立 setter 方法
public void setProgress(float progress) {
this.progress = progress;
invalidate();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
......
canvas.drawArc(arcRectF, 135, progress * 2.7f, false, paint);
......
}
}
......
// 建立 ObjectAnimator 对象
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "progress", 0, 65);
// 执行动画
animator.start();复制代码
单位是毫秒。
// imageView1: 500 毫秒
imageView1.animate()
.translationX(500)
.setDuration(500);
// imageView2: 2 秒
ObjectAnimator animator = ObjectAnimator.ofFloat(
imageView2, "translationX", 500);
animator.setDuration(2000);
animator.start();复制代码
视频里已经说了, Interpolator
其实就是速度设置器。你在参数里填入不一样的 Interpolator
,动画就会以不一样的速度模型来执行。
// imageView1: 线性 Interpolator,匀速
imageView1.animate()
.translationX(500)
.setInterpolator(new LinearInterpolator());
// imageView: 带施法前摇和回弹的 Interpolator
ObjectAnimator animator = ObjectAnimator.ofFloat(
imageView2, "translationX", 500);
animator.setInterpolator(new AnticipateOvershootInterpolator());
animator.start();复制代码
简单介绍一下每个 Interpolator
。
先加速再减速。这是默认的 Interpolator
,也就是说若是你不设置的话,那么动画将会使用这个 Interpolator
。
视频里已经说过了,这个是一种最符合现实中物体运动的 Interpolator
,它的动画效果看起来就像是物体从速度为 0 开始逐渐加速,而后再逐渐减速直到 0 的运动。它的速度 / 时间曲线以及动画完成度 / 时间曲线都是一条正弦 / 余弦曲线(这句话看完就忘掉就行,没用)。具体的效果以下:
好像不太看得出来加速减速过程?你就将就着看吧,毕竟 gif 不是视频,要啥自行车啊。
用途:就像上面说的,它是一种最符合物理世界的模型,因此若是你要作的是最简单的状态变化(位移、放缩、旋转等等),那么通常不用设置 Interpolator
,就用这个默认的最好。
匀速。
匀速就不用解释了吧?直接上效果:
持续加速。
在整个动画过程当中,一直在加速,直到动画结束的一瞬间,直接中止。
别看见它加速骤停就以为这是个神经病模型哦,它颇有用的。它主要用在离场效果中,好比某个物体从界面中飞离,就能够用这种效果。它给人的感受就会是「这货从零起步,加速飞走了」。到了最后动画骤停的时候,物体已经飞出用户视野,看不到了,因此他们是并不会察觉到这个骤停的。
持续减速直到 0。
动画开始的时候是最高速度,而后在动画过程当中逐渐减速,直到动画结束的时候刚好减速到 0。
它的效果和上面这个 AccelerateInterpolator
相反,适用场景也和它相反:它主要用于入场效果,好比某个物体从界面的外部飞入界面后停在某处。它给人的感受会是「咦飞进来个东西,让我仔细看看,哦原来是 XXX」。
先回拉一下再进行正常动画轨迹。效果看起来有点像投掷物体或跳跃等动做前的蓄力。
若是是图中这样的平移动画,那么就是位置上的回拉;若是是放大动画,那么就是先缩小一下再放大;其余类型的动画同理。
这个 Interpolator
就有点耍花样了。没有通用的适用场景,根据具体需求和设计师的偏好而定。
动画会超过目标值一些,而后再弹回来。效果看起来有点像你一屁股坐在沙发上后又被弹起来一点的感受。
和 AnticipateInterpolator
同样,这是个耍花样的 Interpolator
,没有通用的适用场景。
上面这两个的结合版:开始前回拉,最后超过一些而后回弹。
依然耍花样,很少解释。
在目标值处弹跳。有点像玻璃球掉在地板上的效果。
耍花样 +1。
这个也是一个正弦 / 余弦曲线,不过它和 AccelerateDecelerateInterpolator
的区别是,它能够自定义曲线的周期,因此动画能够不到终点就结束,也能够到达终点后回弹,回弹的次数由曲线的周期决定,曲线的周期由 CycleInterpolator()
构造方法的参数决定。
参数为 0.5f:
参数为 2f:
自定义动画完成度 / 时间完成度曲线。
用这个 Interpolator
你能够定制出任何你想要的速度模型。定制的方式是使用一个 Path
对象来绘制出你要的动画完成度 / 时间完成度曲线。例如:
Path interpolatorPath = new Path();
...
// 匀速
interpolatorPath.lineTo(1, 1);复制代码
Path interpolatorPath = new Path();
...
// 先以「动画完成度 : 时间完成度 = 1 : 1」的速度匀速运行 25%
interpolatorPath.lineTo(0.25f, 0.25f);
// 而后瞬间跳跃到 150% 的动画完成度
interpolatorPath.moveTo(0.25f, 1.5f);
// 再匀速倒车,返回到目标点
interpolatorPath.lineTo(1, 1);复制代码
你根据需求,绘制出本身须要的 Path
,就能定制出你要的速度模型。
不过要注意,这条 Path
描述的实际上是一个 y = f(x) (0 ≤ x ≤ 1)
(y 为动画完成度,x 为时间完成度)的曲线,因此同一段时间完成度上不能有两段不一样的动画完成度(这个好理解吧?由于内容不能出现分身术呀),并且每个时间完成度的点上都必需要有对应的动画完成度(由于内容不能在某段时间段内消失呀)。因此,下面这样的 Path
是非法的,会致使程序 FC:
出现重复的动画完成度,即动画内容出现「分身」——程序 FC
有一段时间完成度没有对应的动画完成度,即动画出现「中断」——程序 FC
除了上面的这些,Android 5.0 (API 21)引入了三个新的 Interpolator
模型,并把它们加入了 support v4 包中。这三个新的 Interpolator
每一个都和以前的某个已有的 Interpolator
规则类似,只有略微的区别。
加速运动。
这个 Interpolator
的做用你不能看它的名字,一下子 fast 一下子 linear 的,彻底看不懂。其实它和 AccelerateInterpolator
同样,都是一个持续加速的运动路线。只不过 FastOutLinearInInterpolator
的曲线公式是用的贝塞尔曲线,而 AccelerateInterpolator
用的是指数曲线。具体来讲,它俩最主要的区别是 FastOutLinearInInterpolator
的初始阶段加速度比 AccelerateInterpolator
要快一些。
FastOutLinearInInterpolator
:
AccelerateInterpolator
:
能看出它俩的区别吗?
能看出来就怪了。这俩的速度模型几乎就是同样的,不信我把它们的动画完成度 / 时间完成度曲线放在一块儿给你看:
看到了吗?两条线几乎是一致的,只是红线比绿线更早地到达了较高的斜率,这说明在初始阶段,FastOutLinearInInterpolator
的加速度比 AccelerateInterpolator
更高。
那么这意味着什么呢?
意味个毛。实际上,这点区别,在实际应用中用户根本察觉不出来。并且,AccelerateInterpolator
还能够在构造方法中调节变速系数,分分钟调节到和 FastOutLinearInInterpolator
(几乎)如出一辙。因此你在使用加速模型的时候,这两个选哪一个都同样,没区别的。
那么既然都同样,我作这么多对比,讲这么些干什么呢?
由于我得让你了解。它俩虽然「用起来没区别」,但这是基于我对它足够了解所作出的判断,可我若是直接甩给你一句「它俩没区别,想用谁用谁,少废话别问那么多」,你内心确定会有一大堆疑问,在开发时用到它们的时候也会畏畏缩缩内心打鼓的,对吧?
先加速再减速。
一样也是先加速再减速的还有前面说过的 AccelerateDecelerateInterpolator
,不过它们的效果是明显不同的。FastOutSlowInInterpolator
用的是贝塞尔曲线,AccelerateDecelerateInterpolator
用的是正弦 / 余弦曲线。具体来说, FastOutSlowInInterpolator
的前期加速度要快得多。
FastOutSlowInInterpolator
:
AccelerateDecelerateInterpolator
:
不管是从动图仍是从曲线均可以看出,这两者比起来,FastOutSlowInInterpolator
的前期加速更猛一些,后期的减速过程的也减得更迅速。用更直观一点的表达就是,AccelerateDecelerateInterpolator
像是物体的自我移动,而 FastOutSlowInInterpolator
则看起来像有一股强大的外力「推」着它加速,在接近目标值以后又「拽」着它减速。总之,FastOutSlowInterpolator
看起来有一点「着急」的感受。
两者曲线对比图:
持续减速。
它和 DecelerateInterpolator
比起来,同为减速曲线,主要区别在于 LinearOutSlowInInterpolator
的初始速度更高。对于人眼的实际感受,区别其实也不大,不过仍是能看出来一些的。
LinearOutSlowInInterpolator
:
DecelerateInterpolator
:
两者曲线对比:
对于全部 Interpolator
的介绍就到这里。这些 Interpolator
,有的较为经常使用且有通用的使用场景,有的须要你本身来根据状况而定。把它们了解清楚了,对于制做出观感舒服的动画颇有好处。
给动画设置监听器,能够在关键时刻获得反馈,从而及时作出合适的操做,例如在动画的属性更新时同步更新其余数据,或者在动画结束后回收资源等。
设置监听器的方法, ViewPropertyAnimator
和 ObjectAnimator
略微不同: ViewPropertyAnimator
用的是 setListener()
和 setUpdateListener()
方法,能够设置一个监听器,要移除监听器时经过 set[Update]Listener(null)
填 null 值来移除;而 ObjectAnimator
则是用 addListener()
和 addUpdateListener()
来添加一个或多个监听器,移除监听器则是经过 remove[Update]Listener()
来指定移除对象。
另外,因为 ObjectAnimator
支持使用 pause()
方法暂停,因此它还多了一个 addPauseListener()
/ removePauseListener()
的支持;而 ViewPropertyAnimator
则独有 withStartAction()
和 withEndAction()
方法,能够设置一次性的动画开始或结束的监听。
这两个方法的名称不同,能够设置的监听器数量也不同,但它们的参数类型都是 AnimatorListener
,因此本质上其实都是同样的。 AnimatorListener
共有 4 个回调方法:
当动画开始执行时,这个方法被调用。
当动画结束时,这个方法被调用。
当动画被经过 cancel()
方法取消时,这个方法被调用。
须要说明一下的是,就算动画被取消,onAnimationEnd()
也会被调用。因此当动画被取消时,若是设置了 AnimatorListener
,那么 onAnimationCancel()
和 onAnimationEnd()
都会被调用。onAnimationCancel()
会先于 onAnimationEnd()
被调用。
当动画经过 setRepeatMode()
/ setRepeatCount()
或 repeat()
方法重复执行时,这个方法被调用。
因为 ViewPropertyAnimator
不支持重复,因此这个方法对 ViewPropertyAnimator
至关于无效。
和上面 3.1 的两个方法同样,这两个方法虽然名称和可设置的监听器数量不同,但本质其实都同样的,它们的参数都是 AnimatorUpdateListener
。它只有一个回调方法:onAnimationUpdate(ValueAnimator animation)
。
当动画的属性更新时(不严谨的说,即每过 10 毫秒,动画的完成度更新时),这个方法被调用。
方法的参数是一个 ValueAnimator
,ValueAnimator
是 ObjectAnimator
的父类,也是 ViewPropertyAnimator
的内部实现,因此这个参数其实就是 ViewPropertyAnimator
内部的那个 ValueAnimator
,或者对于 ObjectAnimator
来讲就是它本身自己。
ValueAnimator
有不少方法能够用,它能够查看当前的动画完成度、当前的属性值等等。不过 ValueAnimator
是下一期才讲的内容,因此这期就很少说了。
因为 ObjectAnimator.pause()
是下期的内容,因此这个方法在这期就不讲了。固然,若是你有兴趣的话,如今就了解一下也能够。
这两个方法是 ViewPropertyAnimator
的独有方法。它们和 set/addListener()
中回调的 onAnimationStart()
/ onAnimationEnd()
相比起来的不一样主要有两点:
withStartAction()
/ withEndAction()
是一次性的,在动画执行结束后就自动弃掉了,就算以后再重用 ViewPropertyAnimator
来作别的动画,用它们设置的回调也不会再被调用。而 set/addListener()
所设置的 AnimatorListener
是持续有效的,当动画重复执行时,回调总会被调用。
withEndAction()
设置的回调只有在动画正常结束时才会被调用,而在动画被取消时不会被执行。这点和 AnimatorListener.onAnimationEnd()
的行为是不一致的。
关于监听器,就说到这里。本期内容的讲义部分也到此结束。
为了不转头就忘,强烈建议你趁热打铁,作一下这个练习项目:HenCoderPracticeDraw
下期内容是属性动画的进阶篇,主要讲怎样针对特殊类型的属性作动画,以及怎样作复杂的动画。虽然对于不少人来讲,这期的内容学完就能够在不少人面前装逼了,但我仍是要说,下期的内容学完,你还能更上一层楼。
各位还记得几周以前讲三维旋转时的这个动画么:
等下期内容结束后,这个动画对你将是小意思。(我猜有些人在这期以后就能作出来这个,但下期以后作起来会更轻松。)
这期作了两周多,腰酸背痛加茶饭不思,感受快上西天了。
感谢个人厨师:我岳父。天天到了饭点下楼蹭饭、蹭完饭嘴一抹凳子一踢就走的感受,还真不错。
感谢个人按摩师:我老婆。
感谢个人字幕组:我老婆。
感谢我儿子终于上了幼儿园,不会有事没事就推开个人房门让我陪他玩了。
最后,我再假模假样地感谢一下催更和不催更的各位的耐心或不耐心的等待。
若是你看完以为有收获,把文章转发到你的微博、微信群、朋友圈、公众号,让其余须要的人也看到吧。