我每周会写一篇源代码分析的文章,以后也可能会有其他主题.
如果你喜欢我写的文章的话,欢迎关注我的新浪微博@达达达达sky
地址: http://weibo.com/u/2030683111
每周我会第一时间在微博分享我写的文章,也会积极转发更多有用的知识给大家.谢谢关注_,说不定什么时候会有福利哈.
项目地址:ViewAnimator,本文分析版本: dfa45e0
1.简介
在项目开发中我们应该都接触过动画效果的开发.我们知道在
Andorid
中实现动画大致分为两类,一种是
Tween/Frame
动画,另一种是
Property Animation
也就是属性动画.关于这两种动画的使用方法我们这篇文章就不多做讨论了。 可以从这篇文章了解更多。我们这篇文章只涉及属性动画相关知识。我们今天要介绍的
ViewAnimator
就是用来简化我们写属性动画的的代码量的,它可以通过非常简洁的代码通过建造者模式调用来组合各种动画.让我们的代码简洁易读。如果你的
APP
里需要各种动画组合,
ViewAnimator
一定是你的最佳选择。
2.使用方法
想必大家都使用过属性动画了。我们来做一个最简单的位移动画:
ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "translationX",0, 500);
animator.setDuration(2000);
animator.setRepeatCount(1);
animator.setInterpolator(new BounceInterpolator());
animator.start();
上面的代码执行之后就可以使textView
从当前位置水平移动500px
,整个动画过程是2s
,并且添加了一个弹性插值器,而且使动画再重复执行一遍。这样看起来整个代码还是很清晰的,使用起来也很方便,但是如果我们要多个View
相互组合再加上各种动画,可想而知代码量会有多少了。下面我们就用属性动画来写一个下面这张图里的动画:
这张图里包含了:1.文字颜色的渐变以及背景的渐变,然后同时又textView
放大动画,和两张图片的下落动画,第一组动画结束后,圆形的图片开始旋转,然后textView
不断的显示进度.这就是所有动画,下面是我们实现的代码:
ObjectAnimator mountainTransY = ObjectAnimator.ofFloat(mountain, "translationY", - dip2px(500), 0);
ObjectAnimator mountainAlpha = ObjectAnimator.ofFloat(mountain, "alpha", 0, 1);
ObjectAnimator imageTransY = ObjectAnimator.ofFloat(image, "translationY", - dip2px(500), 0);
ObjectAnimator imageAlpha = ObjectAnimator.ofFloat(image, "alpha", 0, 1);
ObjectAnimator percentScaleX = ObjectAnimator.ofFloat(percent, "scaleX", 0, 1);
ObjectAnimator percentScaleY = ObjectAnimator.ofFloat(percent, "scaleY", 0, 1);
ObjectAnimator textColorAnimator = ObjectAnimator.ofInt(text, "textColor", Color.BLACK, Color.WHITE, Color.RED);
textColorAnimator.setEvaluator(new ArgbEvaluator());
ObjectAnimator textBackgroundAnimator = ObjectAnimator.ofInt(text, "backgroundColor", Color.WHITE, Color.BLACK, Color.YELLOW);
textBackgroundAnimator.setEvaluator(new ArgbEvaluator());
ObjectAnimator imageRotation = ObjectAnimator.ofFloat(image, "rotation", 0, 360);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
percent.setText(String.format(Locale.US, "%.02f%%", animation.getAnimatedValue()));
}
});
AnimatorSet firstSet = new AnimatorSet();
firstSet.playTogether(mountainTransY, mountainAlpha, imageTransY, imageAlpha, percentScaleX,
percentScaleY, textColorAnimator, textBackgroundAnimator);
firstSet.setInterpolator(new AccelerateDecelerateInterpolator());
firstSet.setDuration(5000);
final AnimatorSet secondSet = new AnimatorSet();
secondSet.playTogether(imageRotation, valueAnimator);
secondSet.setDuration(5000);
firstSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
secondSet.start();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
firstSet.start();
上面就是实现这个效果的所有代码.借用岳云鹏的一句话就是:"我的天哪"(请脑补配音)。这么一大坨代码。。我整整写了十几分钟。。而且从这么多代码来看上去,如果以后需要调整动画的话,无论如何也得先整个看一遍才能找到怎么调节。这样维护成本就增加了。那么如何解决这个问题呢?这就要用到我们今天要介绍的主角ViewAnimator
,下面是用ViewAnimator
来实现相同动画的代码:
ViewAnimator.animate(mountain, image)
.dp().translationY(-500, 0)
.alpha(0, 1)
.andAnimate(percent)
.scale(0, 1)
.andAnimate(text)
.textColor(Color.BLACK, Color.WHITE)
.backgroundColor(Color.WHITE, Color.BLACK)
.waitForHeight()
.interpolator(new AccelerateDecelerateInterpolator())
.duration(2000)
.thenAnimate(percent)
.custom(new AnimationListener.Update() {
@Override
public void update(TextView view, float value) {
view.setText(String.format(Locale.US, "%.02f%%", value));
}
}, 0, 1)
.andAnimate(image)
.rotation(0, 360)
.duration(2000)
.start();
真是又简洁又易读。简直"完美"(请再脑补配音)。可以看到从上到下,我们需要先通过animate(View... view)
方法将我们要进行动画的View
传入,然后通过建造者模式调用我们需要做的动画,方法名代表我们需要动画的属性,方法参数里直接传入数值即可。andAnimate(View... view)
表示同时做该view
的动画但是具体的动画可以不一样。然后通过thenAnimate(View... view)
方法就可以表示前面的动画执行完毕后再执行的动画.具体每个方法代表的意思也很清楚就是我们需要操作的属性的意思。所以整体来看代码简洁易读又好维护。
此外ViewAnimator
还封装了不少动画组合让我们拿来即用,例如:standUp()
,wave()
,shake()
等等动画。此外还支持Path
以及SVG Path
动画.更多的使用方法可以参照ViewAnimator
的README.md。下面我们就具体来看看如此好用的ViewAnimator
是如何实现的。
3.类关系图
从类图上来看
ViewAnimator
的结构也很简单明了,
ViewAnimator
和
AnimationBuilder
双向关联.
AnimationListener.Start
和
AnimationListener.Stop
两个接口是单独定义出来,分别用来在动画开始和结束时的回调。下面我们就来看看具体是如何实现的:
4.源码分析
ViewAnimator
的实现并不复杂,我相信大家都应该能看懂,但是作者的实现思路非常值得我们学习,所以我们还是按照我们一直以来的方式来看,根据我们的使用方法,来分析ViewAnimator
的调用流程来看具体的实现。
由于ViewAnimator
类和AnimationBuilder
是相互调用,所以我这里为了防止理解错误,在我们看到的执行的方法都写在了对应的类里,并省略了其他方法。
1.ViewAnimator.animate(mountain, image);的实现:
public class ViewAnimator {
...
private List animationList = new ArrayList<>();
public static AnimationBuilder animate(View... view) {
//创建一个ViewAnimator对象.
ViewAnimator viewAnimator = new ViewAnimator();
//通过addAnimationBuilder方法返回一个AnimationBuilder对象
return viewAnimator.addAnimationBuilder(view);
}
public AnimationBuilder addAnimationBuilder(View... views) {
//创建一个animationBuilder对象并添加到animationList中去
AnimationBuilder animationBuilder = new AnimationBuilder(this, views);
animationList.add(animationBuilder);
return animationBuilder;
}
...
}
public class AnimationBuilder {
...
private final ViewAnimator viewAnimator;
private final View[] views;
public AnimationBuilder(ViewAnimator viewAnimator, View... views) {
//分别赋值viewAnimator和views
this.viewAnimator = viewAnimator;
this.views = views;
}
...
}
可以看到这一步初始化了一个ViewAnimator
对象和一个AnimationBuilder
,并将AnimationBuilder
对象保存在了ViewAnimator
的animationList
数组里,Views
则保存在了AnimationBuilder
对象里.
2.dp().translationY(-500, 0).alpha(0, 1);的实现:
由于返回了一个AnimationBuilder
对象,所以dp()
方法肯定在AnimationBuilder
里实现:
public class AnimationBuilder {
...
public AnimationBuilder dp() {
//标记nextValueWillBeDp
nextValueWillBeDp = true;
return this;
}
public AnimationBuilder translationY(float... y) {
return property("translationY", y);
}
public AnimationBuilder alpha(float... alpha) {
return property("alpha", alpha);
}
public AnimationBuilder property(String propertyName, float... values) {
//遍历views中的所有view,依次实例化ObjectAnimator对象
//并添加到AnimationBuilder的animatorList对象中.
for (View view : views) {
this.animatorList.add(ObjectAnimator.ofFloat(view, propertyName, getValues(values)));
}
return this;
}
...
}
先是标记了nextValueWillBeDp
,然后translationY(float... y)
和alpha(float... alpha)
方法都是调用了property(String propertyName, float... values)
方法,然后在这个方法里去实例化对应的ObjectAnimator
对象,这样就避免了我们重复写很多创建ObjectAnimator
对象的代码了,所以我们类似的操作下面这些属性时都会调用这个方法:
- translationY
- translationX
- alpha
- scaleX
- scaleY
- rotationX
- rotationY
- rotation
3.andAnimate(text).textColor(Color.BLACK, Color.WHITE).backgroundColor(Color.WHITE, Color.BLACK)的实现:
public class AnimationBuilder {
...
public AnimationBuilder andAnimate(View... views) {
return viewAnimator.addAnimationBuilder(views);
}
...
}
public class ViewAnimator {
...
public AnimationBuilder addAnimationBuilder(View... views) {
//创建一个animationBuilder对象并添加到animationList中去
AnimationBuilder animationBuilder = new AnimationBuilder(this, views);
animationList.add(animationBuilder);
return animationBuilder;
}
...
}
注意这里是先在AnimationBuilder
拿viewAnimator
初始化了一个新的AnimationBuilder
对象并返回了,当然也同样添加进了animationList
,所以下面的textColor(Color.BLACK, Color.WHITE).backgroundColor(Color.WHITE, Color.BLACK)
就会实例化text
对应的ObjectAnimator
对象了,代码这里我们就不贴了,我们继续往下看。
4.waitForHeight().interpolator(new Interpolator()).duration(2000);方法的实现
public class AnimationBuilder {
...
public AnimationBuilder waitForHeight() {
//waitForHeight表示当View开始绘制的时候再开始动画.
waitForHeight = true;
return this;
}
public AnimationBuilder interpolator(Interpolator interpolator) {
//赋值插值器,直接赋值给了viewAnimator中的interpolator对象
viewAnimator.interpolator(interpolator);
return this;
}
public AnimationBuilder duration(long duration) {
//设定动画持续时间,也是直接赋值给了viewAnimator的duration对象
viewAnimator.duration(duration);
return this;
}
...
}
这些都很简单,我们继续往下看thenAnimate(percent).custom(...);
方法。
5.thenAnimate(percent).custom(...);方法的实现
public class AnimationBuilder {
...
public AnimationBuilder thenAnimate(View... views) {
//直接调用viewAnimator的thenAnimate()方法
return viewAnimator.thenAnimate(views);
}
...
}
public class ViewAnimator {
...
public AnimationBuilder thenAnimate(View... views) {
//再创建一个nextViewAnimator对象
ViewAnimator nextViewAnimator = new ViewAnimator();
//将nextViewAnimator赋值给当前ViewAnimator对象的next.
this.next = nextViewAnimator;
nextViewAnimator.prev = this;
//return一个nextViewAnimator创建的AnimationBuilder对象
return nextViewAnimator.addAnimationBuilder(views);
}
...
}
可以看到这里是又创建了一个新的ViewAnimator
对象和一个新的AnimationBuilder
对象,注意这里的next
和prev
赋值,其实就是数据结构中双向链表的思想,这里我们在动画开始的时候就可以根据prev
来找到最早的ViewAnimator
对象,然后再用next
就可以将动画顺序执行了。
5.custom(...);方法的实现
在使用方法里我们是这样调用的:
.custom(new AnimationListener.Update() {
@Override
public void update(TextView view, float value) {
view.setText(String.format(Locale.US, "%.02f%%", value));
}
}, 0, 1)
这样就实现了textView
从0-1的进度显示.原理如下:
public class AnimationListener {
...
public interface Update{
void update(V view, float value);
}
...
}
public class AnimationBuilder {
...
public AnimationBuilder custom(final AnimationListener.Update update, float... values) {
//遍历所有view,实例化valueAnimator,并在onAnimationUpdate()回调接口里,回调
//update接口,最后把valueAnimator添加到animatorList中去
for (final View view : views) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(getValues(values));
if (update != null)
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//noinspection unchecked
update.update(view, (Float) animation.getAnimatedValue());
}
});
add(valueAnimator);
}
return this;
}
...
}
我们可以看到update
接口里可以传入任何View
的子类,但是其实在AnimationBuilder
里会遍历所有当前动画View
的并全部添加了这个valueAnimator
,这样做有一个问题就是,虽然作者想传入泛型是想不在回调方法里强制转换从而直接做操作,但是这样做是不安全的,如果现在我把thenAnimate(percent).custom(...);
方法写成thenAnimate(percent, image).custom(...);
运行时立马会报ClassCastException
:
java.lang.ClassCastException: android.support.v7.widget.AppCompatImageView cannot be cast to android.widget.TextView
因为由于每个做动画的view
都添加了这个回调,再回调处理的时候又会直接当成TextView
来处理所以会崩溃。所以我们在使用的时候一定要注意这个。当然在下文的个人评价中我也会给出解决办法,这里我们知道就好了。
下面的调用很相似我们就不看了,我们直接来看start()
方法.
5.start();方法的实现
首先是在AnimationBuilder
中直接调用了ViewAnimator
的start()
方法:
public void start() {
viewAnimator.start();
}
再来看ViewAnimator
的start()
方法:
public class ViewAnimator {
...
public ViewAnimator start() {
if (prev != null) {
//如果有上一个ViewAnimator则先调用上一个的start()方法
prev.start();
} else {
//创建AnimatorSet对象
animatorSet = createAnimatorSet();
//如果需要等待view绘制则监听onPreDraw()方法,
if (waitForThisViewHeight != null) {
waitForThisViewHeight.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
animatorSet.start();
waitForThisViewHeight.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
} else {
//直接开始
animatorSet.start();
}
}
return this;
}
protected AnimatorSet createAnimatorSet() {
//新建一个animators列表
List animators = new ArrayList<>();
//将所有的animationBuilder对象中的Animator对象添加
for (AnimationBuilder animationBuilder : animationList) {
animators.addAll(animationBuilder.createAnimators());
}
//如果标记了waitForHeight,
//则返回animationBuilder里View数组的第一个view
for (AnimationBuilder animationBuilder : animationList) {
if (animationBuilder.isWaitForHeight()) {
waitForThisViewHeight = animationBuilder.getView();
break;
}
}
//如果有ValueAnimator 则单独设置重复模式和重复次数
for (Animator animator : animators) {
if (animator instanceof ValueAnimator) {
ValueAnimator valueAnimator = (ValueAnimator) animator;
valueAnimator.setRepeatCount(repeatCount);
valueAnimator.setRepeatMode(repeatMode);
}
}
//设置AnimatorSet的参数
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animators);
animatorSet.setDuration(duration);
animatorSet.setStartDelay(startDelay);
if (interpolator != null)
animatorSet.setInterpolator(interpolator);
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//回调Start接口
if (startListener != null) startListener.onStart();
}
@Override
public void onAnimationEnd(Animator animation) {
//回调Stop接口
if (stopListener != null) stopListener.onStop();
//如果有下一个ViewAnimator则继续执行
if (next != null) {
next.prev = null;
next.start();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
return animatorSet;
}
...
}
从上到下应该很清晰的看出,其实就是在内部创建了AnimatorSet
对象,然后设置一些参数,最后执行,然后再在onAnimationEnd(Animator animation)
的接口里检查是否还有动画,从而一直链式的执行。
上面就是整个的ViewAnimator
主要的实现了,虽然看上去并不难,但是也不是很容易就能写出来的。值得我们好好学习。
5.个人评价
最近在我负责的项目里,我们设计了大量的组合动画与交互,如果使用原生的方法会使代码量特别大,而且相当难维护,因此我使用了ViewAnimator
简化了大量的动画代码,而且使代码更易读,可维护性就更好了。如果你的项目里也有比较多的动画,强烈推荐ViewAnimator
而且这个库并没有几个类,占用的体积非常小,推荐成为项目标配。
最后还有两点要说的问题就是:
1.AnimationListener.Update的问题
这个问题我们在上面已经说过了,使用不当会造成崩溃,而且我们把泛型作为参数传入之后,我觉得意义并不大,完全可以在回调接口里直接根据value
直接操作我们的View
,但是由于ViewAnimator
里还有其他地方依赖Update
接口,所以我把AnimationListener.Update
修改成了下面这样。经测试使用完全没有问题。
public class AnimationListener {
public interface Update {
void update(View view, float value);
}
}
2.为单独的AnimationBuilder添加Interpolator的问题.
在使用中发现如果我同时组合了好几个动画之后,只能为这些同时动画的属性添加同一个Interpolator
这样不满足我想同时动画多个View
但又要不同的Interpolator
需求.所以我就在ViewAnimator
的基础上添加了单个Interpolator
的功能,而且给ViewAnimator
的作者发了pull request
.详细原理我就不讲了,比较简单,大家可以在我fork
的分支上查看具体的实现方法:
Commit地址在这. 好了今天就写到这吧。
我每周会写一篇源代码分析的文章,以后也可能会有其他主题.
如果你喜欢我写的文章的话,欢迎关注我的新浪微博@达达达达sky
地址: http://weibo.com/u/2030683111
每周我会第一时间在微博分享我写的文章,也会积极转发更多有用的知识给大家.谢谢关注_,说不定什么时候会有福利哈.