Android 开发中,老是须要一些动画来优化用户的交互体验,提升用户满意度。所以,Google 为咱们提供了一些用于处理动画效果的动画框架。Android 的动画框架分为两类:面试
既然有了传统动画框架,Google 为何还要创造一个属性动画框架呢?性能优化
咱们下面举个例子来讲明一下传统动画的局限性。架构
在布局中加入一个 ImageView 和一个 Button,点击 ImageView 后弹出一个 Toast,点击 Button 后使 ImageView 展示一个向右平移的动画效果。框架
下面是使用传统动画实现的代码:异步
TranslateAnimation animation = new TranslateAnimation(0,200,0,0); // 平移动画x轴移动200,y轴不动
animation.setDuration(1000); // 动画时长
animation.setFillAfter(true); // 使动画结束后停留在结束的位置
mIvPicture.startAnimation(animation);
复制代码
运行后,ImageView 确实进行了咱们预期的平移的效果。但是当咱们尝试点击 ImageView 当前的位置时,却没有 Toast 弹出。咱们再尝试去点击 ImageView 开始动画前的位置,却成功弹出了 Toast。ide
这就是传统动画很大的局限性:函数
所以,Google 为咱们提供了一套全新的属性动画框架,来让咱们实现更丰富的动画效果。布局
ObjectAnimator 是属性动画中,最简单也最经常使用的一个对象。性能
平移优化
前文提到的使 ImageVIew 向右平移 200 像素的动画效果,使用属性动画只须要很简单的几句代码便可实现:
ObjectAnimator.ofFloat(mIvPicture,"translationX",0F,200F)
.setDuration(1000)
.start();
复制代码
咱们来分析一下这一句代码。咱们调用了ofFloat代码,并传入三个参数。
第一个参数是动画须要操纵的目标,在这里是咱们的 ImageView。
第二个参数是所须要操纵的目标所具有的属性名称。
第三个参数是这个动画变化的取值范围。
最后设置一下它的动画的属性,即可以 start 了。
此次咱们再次点击 ImageView 目前的位置,成功地弹出了 Toast。这证明了属性动画是经过改变物体的属性来达到动画效果的理论。
当咱们须要改变 y 坐标时,只须要把 "translationX" 变为 "translationY" 便可。
其实 ,只要Google对一个对象的某个属性提供了get和set方法,咱们就可使用这个属性来实现动画效果。
其实咱们还能用 X Y 两个属性实现以前的动画效果,那么对象属性中 X 的 Y 与 translationX translationY 有什么区别呢?
旋转
旋转属性使用的是 "rotation" 属性,后面的变换范围的单位是角度。
好比想让 ImageView 旋转90度,只须要
ObjectAnimator.ofFloat(mIvPicture,"rotation",0F,90F)
.setDuration(1000)
.start();
复制代码
其余
其实属性动画能操纵的属性,只要具备 set、get 方法,均可以进行操纵。如 scaleX、scaleY 等等...
插值器
Android 为咱们内置了插值器,使咱们的动画更为天然。好比可让咱们的平移动画像物体的重力加速度由快到慢的 Accelerate 等等
Android中内置了七种插值器,分别是
要应用插值器,能够调用 ObjectAnimator 的 setInterpolator 方法, new 出对应的插值器做为参数(xxxInterpolator)。好比下面这段代码:
animator.setInterpolator(new AccelerateInterpolator());
复制代码
经过插值器,咱们可让动画的效果更佳天然。
当咱们把几种动画按顺序写下时,运行程序,会发现效果是三种属性动画的叠加。由此能够发现,属性动画在调用 start 方法后,其实是一个异步的过程。所以咱们就能够看到三个属性动画同时做用的效果。经过这样的方法,其实就能够实现多种属性动画同时做用的效果:
ObjectAnimator.ofFloat(mIvPicture,"translationX",0F,200F).setDuration(1000).start();
ObjectAnimator.ofFloat(mIvPicture,"rotationX",0F,360F).setDuration(1000).start();
ObjectAnimator.ofFloat(mIvPicture,"translationY",0F,200F).setDuration(1000).start();
复制代码
其实 Google 为咱们提供了更好的方法,来实现这样的效果。
咱们可使用 PropertyValuesHolder 来实现。其构造函数仅仅比 ObjectAnimator 少了一个做用对象参数。以后经过ObjectAnimator 的 ofPropertyValuesHolder 方法,传入做用对象以及要同时做用的 PropertyValuesHolder 便可执行。能够看到下面的代码示例:
PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("translationX",0F,200F);
PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("rotationX",0F,360F);
PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("translationY",0F,200F);
ObjectAnimator.ofPropertyValuesHolder(mIvPicture,p1,p2,p3).setDuration(1000).start();
复制代码
运行后能够发现,与以前的效果是相同的。
那既然两种方法效果同样,这样相比以前有什么好处么?
咱们其实还能够经过 AnimatorSet,来实现一样的效果。这里咱们调用了 set 的 playTogether 方法,使得这些方法同时执行:
AnimatorSet set = new AnimatorSet();
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mIvPicture, "translationX", 0F, 200F);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mIvPicture, "rotationX", 0F, 360F);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mIvPicture, "translationY", 0F, 200F);
set.playTogether(animator1,animator2,animator3);
set.setDuration(1000);
set.start();
复制代码
除了 playTogether 方法外,AnimatorSet 还提供了 playSequentially 方法,它可使得动画按顺序执行。具体顺序取决于调用时的参数顺序。
AnimatorSet set = new AnimatorSet();
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mIvPicture, "translationX", 0F, 200F);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mIvPicture, "rotationX", 0F, 360F);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mIvPicture, "translationY", 0F, 200F);
set.playSequentially(animator1,animator2,animator3);
set.setDuration(1000);
set.start();
复制代码
咱们除了能够用上述方法来让动画按顺序执行外,也能够经过 AnimatorSet 的 play、with、after、before 等方法相组合来控制动画播放关系。
例如以下的代码就能够实现先平移,再旋转的效果
set.play(animator1).with(animator3);
set.play(animator2).after(animator1);
复制代码
经过下面的代码,咱们能够实现按钮按下后渐隐的效果。
mBtnPress.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator animator = ObjectAnimator.ofFloat(mBtnPress,"alpha",1F,0F);
animator.setDuration(1000);
animator.start();
}
});
复制代码
但若是咱们想要在动画播放完成后再执行一些操做的话,又该如何实现呢?
一个AnimatorListener,须要实现四个方法,分别是:
它们的回调时机咱们根据字面意思即可以理解。大部分时候,咱们须要实现的是onAnimationEnd方法。
animator.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
Toast.makeText(MainActivity.this,"Animation End",Toast.LENGTH_SHORT).show();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
复制代码
若是每次监听都须要实现这么多方法,未免太麻烦了一点。所以 Android 为咱们提供了另外一种方法来添加动画的监听事件:在添加 AnimatorListener 的时候,传入 AnimatorListenerAdapter 便可。这样咱们就只须要实现本身须要的方法便可。
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
Toast.makeText(MainActivity.this,"Animation End",Toast.LENGTH_SHORT).show();
}
});
复制代码
ValueAnimator 自己不做用于任何一个属性,也不提供任何一种动画。它就是一个数值发生器,能够产生想要的各类数值。Android 系统为它提供了不少计算数值的方法,如 int、float 等等。咱们也能够本身实现计算数值的方法。其实,在属性动画中,如何产生每一步的动画效果,都是经过 ValueAnimator 计算出来的。
好比咱们要实现一个从 0-100 的位移动画。随着动画时间的持续,它产生的值也会从 0-100 递增。经过这个 ValueAnimator 产生的值,再进行属性的设置便可。
那么 ValueAnimator 到底是如何产生这些值的呢?
因为 ValueAnimator 不做用于任何一个属性,也不提供任何一种动画。所以并无 ObjectAnimator 使用得普遍。
实际上,ObjectAnimator 就是基于 ValueAnimator 进行的一次封装。咱们能够查看 ObjectAnimator 的源码,会发现它继承自 ValueAnimator,是它的一个子类。正是 ValueAnimator 产生的变化值,才使得 ObjectAnimator 能够将它应用于各个属性。
咱们能够经过 ValueAnimator 的 ofXXX 产生一个 XXX 类型的值(如ofInt),而后为 ValueAnimator 添加一个更新的回调事件。在回调事件中,经过参数 animation 的 getAnimationValue() 方法,来获取对应的 value。有了这个值,咱们就能够实现咱们全部想要的动画效果。
好比此处就经过 ValueAnimator 实现了一个计时器的动画效果。
ValueAnimator animator = ValueAnimator.ofInt(0,100);
animator.setDuration(5000);
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
mButton.setText(""+value);
}
});
animator.start();
复制代码
前面提到,ValueAnimator 能够建立自定义的数值生成器,作法就是调用 ValueAnimator 的 ofObject 方法,建立一个 TypeEvaluator 做为参数。以后咱们能够经过重写 TypeEvaluator 的 evaluate 方法,来按照本身的规则返回具体的值。
ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator() {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
//计算
return null; //返回值
}
});
复制代码
咱们来看一下 evaluate 方法的几个参数
经过这三个值,咱们就能够通过计算产生全部咱们想要的值。
其实,经过 TypeEvaluator,咱们不光能产生普通的数据,还能结合泛型,咱们还能定义更加复杂的数据:
咱们能够在建立 TypeEvaluator 时指定具体类型,来达到更丰富的效果。好比这里就用到了一个名为 PointF 的数据类型:
ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator<PointF>() {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
//计算
return null; //返回值
}
});
复制代码
最后送福利了,如今关注我而且加入群聊能够获取包含源码解析,自定义View,动画实现,架构分享等。 内容难度适中,篇幅精炼,天天只需花上十几分钟阅读便可。 你们能够跟我一块儿探讨,欢迎加群探讨,有flutter—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿~ 群号:661841852