这是我这个系列的目录,有兴趣的可以看下: android 动画系列 - 目录
啊,终于来到了属性动画这里了,属性动画是3.0之后推出的,目的就是用来替换 tween 动画, tween 动画的缺点实在让人不能忍啊。
- 首先就是拓展性太差,只能写移动、缩放、旋转、渐变四种动画,以及这四种动画的组合,不支持自定义View的拓展。
- 其次一个致命的缺点就是动画只是屏幕绘制上的动画,控件的属性并没有改变,一个经典的问题就是一个Button从一个地方移动到另一个地方,点击事件还是在原来的地方。
所以Tween动画我们现在也基本放弃了,所有使用Tween动画的场景都可以用属性动画来替代。
属性动画呢,就灵活多了,属性动画顾名思义就是使用动画的方式来操作对象的属性,这样就不必限制在tween 动画的4种效果,可以玩更多的了,比如说颜色动画,path 动画等等很多的玩法。
在本质上讲属性动画就是使用数值发生器根据变化曲线和时间生成一系列数值,作用在你需要的对象的属性上形成动画,注意看这里,属性动画的本质不再是强调动画本身了,而是变成了数值发生器,不管你想干什么,属性动画只关心生成数值数列给你去使用,这就是属性动画拓展性的根本,只关心数值的生成。
废话不多说我们直接来看属性动画的使用,首先属性动画的核心类有3个:
- ValueAnimator
- ObjectAnimator
- AnimatorSet
tween 动画学完后这个AnimatorSet大家应该猜到是什么了吧,就是属性动画集合呗,把一堆动画放在一起执行,属性动画的结合提供了几种很灵活的动画执行方式,具体的后面讲。
属性动画的核心:ValueAnimator 数值发生器,他就是上面说的生成数值序列的类。
ObjectAnimator是ValueAnimator的子类,是对ValueAnimator操作的封装,使用起来更简便,一般都是用来对某一个对象的属性做动画的。
下面我们从难易程度来看这3个类
ObjectAnimator
上面说了ObjectAnimator是ValueAnimator的子类,是对ValueAnimator操作的封装,目的就是让我们的属性动画操作更加简单,这符合 java 简单化的思想,大伙仔细想想,简单好用就是我们码代码的终极追求之一啊,下面来看看用法。
和 tween 动画一样,还是以位移 translate 举例
ObjectAnimator animator = ObjectAnimator.ofFloat(testView, "translationX", 200);
animator.setDuration(600);
animator.setRepeatMode(ObjectAnimator.REVERSE);
animator.setRepeatCount(1);
animator.start();
这就是一个位移动画,向右移动200px,效果图:
ObjectAnimator.ofFloat(Object target, String propertyName, float... values)
这就是 ObjectAnimator 的构造方法,有3个参数,targe 是动画作用的 view,propertyName 是我们操作的属性名称,上面我们操作的就是 view 的 translationX x 轴位移属性,大伙要是忘了属性名称,直接点一下看看就知道了
最后面的 values 可变参数是属性值,传值上可以传单个也可以传多个值,这里就要说一下了:
- 传单个值:表示使用 view 当前的属性值作为起始,传入的值作为结速值
- 传2个值:表示前一个是属性开始值,后一个是属性结束值
- 传2个以上值:表示第一个是属性开始值,最后一个值是属性结束值,中间的数是动画变化的中间值
传单个值上面我们已经看过了,那么来看看剩下的2种传值方式:
ObjectAnimator animator = ObjectAnimator.ofFloat(testView, "translationX", -200,200);
传2个值效果图:
那么来看看传2个以上值是个什么样子滴
ObjectAnimator animator = ObjectAnimator.ofFloat(testView, "translationX", 0,200,500,200);
传2个以上值效果图:
看过图大家都应该很清楚的了解了期中的规律,传值这里大家要牢记啊。
缩放,旋转中心点?
哈哈,还记得 tween 动画的旋转和缩放的中心点在哪里吗,那么属性动画呢,我们来试下就知道了
ObjectAnimator animator = ObjectAnimator.ofFloat(testView, "rotation", 0, 360);
animator.setDuration(600);
animator.start();
旋转效果图:
ok,测试一个就可以了,默认中心点在 view 的中心,为啥会这样呢,这是跟属性动画的坐标系有关系。tween 动画的坐标以 动画view左上角为原点,坐标系是固定的。属性动画操作的是 view的属性,view 采用视图坐标系,在该坐标系中描述位置的属性有好几套,为了不至于混乱,所以中心点只能放在 view 的中心,换位思考下,要是你来写这个 api,也是只能放在中心了。我最喜欢在学习 google的 api 时思考下作者的思路,挺有意思的哈...
属性动画常用属性
属性动画就是操作view 的属性的,这里列出常用的属性供大家参考,思考下通过这些属性我们可以实现什么效果,有些动画效果就是通过操作我们意想不到的属性来实现的。
常用的propertyName:
- rotationX 围绕x轴旋转
- rotationY 围绕y轴旋转
- rotation 围绕轴点旋转
- translationX 在x轴方向上平移
- translationY 在y轴方向上平移
- scaleX 在x轴方向缩放
- scaleY 在y轴方向缩放
- alpha 透明度
- width 宽度
- height 高度
视图坐标系
上面在说属性动画,缩放/旋转中心点时说到了一个问题,view 使用视图坐标系,描述 view 坐标的属性有好几种,这里我们一定要分清楚这些属性都表示哪段距离,要不你会发现你再做动画时可能事与愿违。这里科普下 view 的坐标系,摘自: 开发艺术探索
这里我们不会说的很详细,只说属性动画会用到的属性相关的v视图坐标体系。3.0之后,view 添加了4个属性:
- x : view左上角的坐标,view 到父控件的x 轴距离
- y : view左上角的坐标,view 到父控件的y 轴距离
- translationX :view 相对于父控件 x 轴偏移量,默认0
- translationY :view 相对于父控件 y 轴偏移量,默认0
我设计了一组对比,看图
红色的 view 外面套了一层蓝色 view 作为父控件,父控件距离顶部有一段距离,我们打印一下蓝色 view 此时的坐标,包括上面4个属性和 蓝色 view x/y 的绝对坐标
rootView.post(new Runnable() {
@Override
public void run() {
StringBuffer info = new StringBuffer();
int[] sizeOnScreen = new int[2];
viewTest.getLocationOnScreen(sizeOnScreen);
info.append("screenX=" + sizeOnScreen[0] + "\n");
info.append("screenY=" + sizeOnScreen[1] + "\n");
info.append("x=" + viewTest.getX() + "\n");
info.append("y=" + viewTest.getY() + "\n");
info.append("translationX=" + viewTest.getTranslationX() + "\n");
info.append("translationY=" + viewTest.getTranslationY() + "\n");
info.append("100dp=" + SizeActivity.this.getResources().getDisplayMetrics().density * 100 + "px");
textInfo.setText(info.toString());
}
});
结果图:
看结果证明 view 的视图坐标系的确是相对于父控件来说的,view 的上述4个属性一定搞清楚,乱了就麻烦了。
ofFloat/ofInt
大伙注意没有上面的属性动画都是使用 ofFloat 的:『 ObjectAnimator.ofFloat() 』,这是因为 view 的属性大部分都是 float 值,上面列出的常用的属性可都是 float 的,所以做动画也得用 float 的。那么我为啥还要说这个呢,因为有时我们会对颜色做动画,view 的颜色值可是 int 的,要使用 ofInt,你要是用 ofFloat 会直接报类型转换错误的,这点需要注意啊。
ObjectAnimator animator = ObjectAnimator.ofInt(testView, "backgroundColor", Color.parseColor("#FF4081"), Color.parseColor("#3F51B5"));
animator.setEvaluator(new ArgbEvaluator());
animator.setDuration(500);
animator.setRepeatMode(ObjectAnimator.REVERSE);
animator.setRepeatCount(1);
animator.start();
效果图:
ps:可算把上面的那一堆说完了,肯定有人说我啰嗦,但是呢,这都是我曾经有疑问的地方,我想肯定有需要的朋友,所以写的很啰嗦。
ValueAnimator
ValueAnimator 是属性动画的根基了,是应用最广广泛的了,尤其是在自定义 view 中呀。上面使用的ObjectAnimator 都是基于ValueAnimator的。
ValueAnimator也可以叫数值发生器,ValueAnimator的功能就是在两个数值范围内,顺序地产生过渡数值,过渡速率可以通过Intepolator来控制,过渡时间通过duration来设置,最终产生一组有特定变化速率的数值序列。那这个数值序列产生了干嘛呢?你想干嘛就干嘛。
那我们就在 textview 身上玩玩
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 8888);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// 当前的数值
int animatedValue = (int) valueAnimator.getAnimatedValue();
// 当前动画的进度
float animatedFraction = valueAnimator.getAnimatedFraction();
textTest.setText(String.valueOf(animatedValue));
}
});
valueAnimator.start();
效果图:
呵呵,看着挺有意思的,么么哒!ValueAnimator的核心就是[ addUpdateListene ],数据更新接口,每16ms 更新一帧,调用一次这个方法,这个回调方法里我们想干什么都可以,非常自由,这就是属性动画最利于扩展的地方。
本篇是介绍基础部分,ValueAnimator结合自定义 view 部分可以看这个练手项目: AndroidAdvanceAnimation
AnimatorSet
先看代码:
public void togetherRun(View view) {
ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX", 1.0f, 2f);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY", 1.0f, 2f);
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(2000);
animSet.setInterpolator(new LinearInterpolator());
//两个动画同时执行
animSet.playTogether(anim1, anim2);
animSet.start();
}
public void playWithAfter(View view) {
float cx = mBlueBall.getX();
ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX", 1.0f, 2f);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY", 1.0f, 2f);
ObjectAnimator anim3 = ObjectAnimator.ofFloat(mBlueBall, "x", cx , 0f);
ObjectAnimator anim4 = ObjectAnimator.ofFloat(mBlueBall, "x", cx);
/**
* anim1,anim2,anim3同时执行
* anim4接着执行
*/
AnimatorSet animSet = new AnimatorSet();
animSet.play(anim1).with(anim2);
animSet.play(anim2).with(anim3);
animSet.play(anim4).after(anim3);
animSet.setDuration(1000);
animSet.start();
}
属性动画集合,前面说过属性动画的集合提供比 tween 动画集合灵活的多的执行方式:
- animSet.playTogether(anim1... anim5) : 动画一起执行
- animSet.playSequentially(anim1... anim5) : 按动画的插入顺序,顺序执行
上面2个简单一看就知道了,我得仔细说下上面代码中的 with/after/before 方式,其中 after 和 before的概念很容易混淆
- animSet.play(anim1).with(anim2) = 1和2同时执行
- animSet.play(anim4).after(anim3) = 4在3之后执行,换句话说就是3执行完了在执行4
- animSet.play(anim4).before(anim3) = 4在3之前执行,换句话说就是4执行完了在执行3
- 最重要一点:animSet.play().with() 是支持这样链式编程的,但是不支持无限链式变成,比如 animSet.play(anim1).with(anim2).before(anim3).before(anim5) 这样是不行的,系统不会根据你写的这一长串来决定先后的顺序,所以麻烦你按照上面例子的写法,多写几行
效果图我就不贴了,今天莫名的好烦,干啥都感觉很不爽!!!
path 动画
属性动画中添加了一个一个意想不到的动画,path 动画,可以让 view 沿着 path 的路径做动画,说实话真是佩服 google 的脑洞,大 google 威武
先来看代码:
testView.post(new Runnable() {
@Override
public void run() {
// 获取view 的初始位置
temX = testView.getX();
temY = testView.getY();
}
});
Path path = new Path();
path.moveTo(temX, temY);
path.lineTo(temX + 1000, temY + 1000);
ObjectAnimator animator = ObjectAnimator.ofFloat(testView, View.X, View.Y, path);
animator.setDuration(500);
animator.setRepeatMode(ObjectAnimator.REVERSE);
animator.setRepeatCount(1);
animator.start();
效果图:
path 大家都知道吧,可以使用 path 绘制复杂的多边形,这里需要注意啊,path 的默认起始点是0,0,你要是不写 moveTo,就是在父控件坐标系0,0的位置开始,这里我获取了 view 的初始坐标。
有4个参数,第一个是目标 view,后面2个是需要操作的 view 的位置坐标,最后是 path,这个方法很简单,没啥难度,主要在于你的脑洞有大多哈。
属性动画监听接口
属性动画动画的三个类 ValueAnimator/ObjectAnimator/AnimatorSet 都直接或是见解继承自 Animator 这个基类,所以添加监听的方式都是一样的
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
toast("动画开始");
}
@Override
public void onAnimationEnd(Animator animator) {
toast("动画结束");
}
@Override
public void onAnimationCancel(Animator animator) {
toast("动画取消");
}
@Override
public void onAnimationRepeat(Animator animator) {
toast("动画重建");
}
});
要是嫌麻烦还有一个监听接口的适配器,看 google 起名字多规范,其实我挺羡慕这项技能的...
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
toast("动画结束");
}
});
好了,监听这不多说了,一看就懂的。
属性动画跑在那个线程,帧数?
为啥我会说着这个问题呢,因为在学习过程中,看到过有人说属性动画是100帧/s,这不科学啊,要是能跑到100帧那肯定不在 UI 线程里了,可是没听说啊,我只是看到 系统的SVG动画在7.0之后是跑在单独的线程里的,所以这里特意测试了一下
count = 0;
ValueAnimator animator = ValueAnimator.ofFloat(1, 0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
count++;
float currentAnimatedValue = (float) valueAnimator.getAnimatedValue();
float animatedFraction = valueAnimator.getAnimatedFraction();
Log.d("test", "属性动画第:" + count + "次执行," + "线程:" + Thread.currentThread().getName() + ", currentAnimatedValue:" + currentAnimatedValue + ",animatedFraction:" + animatedFraction);
}
});
animator.setDuration(300).start();
跑了一个 ValueAnimator 动画,300秒,看看会执行多少次,所在线程就明白了,因为属性动画都是继承自 Animator ,跑这一个就可以了
结果图:
15ms 刷新一帧,也是跑在UI 线程里的,谣言不攻自破,大伙要是碰到有混乱的地方,不妨自己跑下就明白了。
KeyFrame
KeyFrame 是属性动画中的关键帧的意思,在属性动画的源码中也是有关键帧这个东东的,这个东东的意义在于我们可以决定在时间流速的某个节点,属性值变化率是多少,具体看这里,翻到最下面就是:android动画详解(二)
参考资料
- Android 动画基础知识学习(下)
- Android高级动画(1)
- android动画详解(二)