自从Android3.0 版本加入属性动画后,在平时的开发中多多少少也使用了,但是从来没有对其做一个系统的分析和总结,最近刚好有点时间,来对这个话题做一个分析和总结。
动画对于GUI程序总是一个绕不过的话题,适当的动画可以使我们的人机交互界面更加的生动有趣,所以Android也提供了一套自己的动画体系。
至Android3.0以后(其实都不用再提这一点了,因为到目前为止,Android版本基本上已经收缩到4.0以上了),Android动画体系分为:
其实这就是废话,肯定是View 动画存在一些问题,才引入新的解决方案。重点是View 动画存在哪些问题,而属性动画如何解决的这些问题,这个才是我们需要关注的重点,因为只有了解了一项技术的使用场景才能在日常的开发当中游刃有余的使用这些技术。
View 动画存在以下这些问题:
button
从A位置移动到了B位置,当我们点击B位置上的按钮时候什么都不会发生,而点击A位置却触发了button
的点击事件。属性动画就是为了解决以上问题而被引入的,但是View动画仍然有自己存在的价值,它仍然有适合自己的使用场景,而其运行效率以及使用复杂度都低于属性动画,所以还是应了那句老话,合适的就是最好的。我们开发中应该最求使用合适的技术来解决相应的问题。
首先我们应该明白我们所说的动画是如何产生的。动画的本质可以理解为一系列连续图片(帧)快速不断的通过一个窗口展示在你的眼前。而人眼是有“视觉暂留”的,即人眼在某个视像消失后,仍可使该物像在视网膜上滞留0.1-0.4秒左右。这样当上一张图片的残像没有消失之前,下一张图片就有进入了你的眼睛,而这两张图片的变化的轻微的,所以你感觉动画丝滑细腻,无卡顿。
Android中的属性动画也逃不出这个套路。在一定时间间隔内(动画时长),通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。
举个例子,Button
具有一个控制背景颜色的属性“backgroundColor”,那么属性动画就是通过不断的改变该属性的值,改变一次就相当于生成一幅图片展示在用户眼前,这样通过在一定的时间内以一定的频率不断重复这个动作,直到属性值达到了预先设置的值,动画结束。
上一幅Carson_Ho博客(https://blog.csdn.net/carson_ho/article/details/72909894)的一张流程图,画的很清楚,就是有点糊。
一项技术首先要会使用,然后才是深入理解其内部细节,进而用的更好!
属性动画主要有三个类,Animator
、 ValueAnimator
、 ObjectAnimator
,这三个类从右往左依次继承关系,那有点编程经验的同学应该知道哪个使用起来最简单了吧,没错就是ObjectAnimator
,因为他最具体,最针对某一特定情形,那样它就被封装的最完善,当然代价就是会损失一定的灵活性。那我们先从简单的开始看。
ObjectAnimator
有好多公共静态方法可以构造动画对象,如下图所示
我们只介绍第一个,也是最简单最常用的一个,其他的思想类似,请自行研究。属性动画既可以像view动画那样以xml
的形式构建,也可以使用java代码构建。然而在实际开发中我们更多的是使用java
代码构建的方式,所以此处只介绍使用java
代码实现属性动画的方法。
public static ObjectAnimator ofFloat(Object target, String propertyName, float… values)
参数1:操作的对象,例如我接下来要操作一个Textview
参数2:操作的属性名称
参数3:操作属性的变化路径值,例如只传入一个参数a,则表示此属性值从当前值变化到目 标值a;如果传入两个参数a,b,则表示此属性值从a变化到b;如果传入3个参数值a,b,c,则此属性值从a变化到b再变化到c,以此类推。
当构建出ObjectAnimator
后就可以对其做一下自定义的配置了,例如,动画执行时长,动画延迟开始的时间,动画重复的策略,重复的次数等。
正所谓:Talk is cheap, show me the code,来咱们看代码
ObjectAnimator tAni= ObjectAnimator.ofFloat(tvOpr,"translationY",600);
tAni.setDuration(2000);//设置动画时长2秒,如果不设置会采用系统默认值300
tAni.start();
上面的代码在2秒的时间内将tvOpr-一个TextView
从它当前的位置沿着Y轴移动到了600px的位置。如下图所示
ObjectAnimator rAni= ObjectAnimator.ofFloat(tvOpr,"rotationY",0,360);
rAni.setDuration(2000);
rAni.start();
上面的代码在2秒的时间内将tvOpr-一个TextView
绕Y轴从0旋转到360度。如下图所示
我们还可以使用AnimatorSet
类将不同的动画组合起来
AnimatorSet aSet=new AnimatorSet();
ObjectAnimator tAni2= ObjectAnimator.ofFloat(tvOpr,"translationY",600);
tAni2.setDuration(2000);
ObjectAnimator rAni2= ObjectAnimator.ofFloat(tvOpr,"rotationY",0,360);
rAni2.setDuration(2000);
aSet.playTogether(
tAni2,
rAni2
);
aSet.setDuration(2000).start();
上面的代码在2秒内完成了旋转和纵向平移的动画,如下图所示
通过以上的示例代码相信大家已经知道如何使用ObjectAnimator
了,值得注意的是,我们要改变的那个属性必须有Set()
和Get()
方法,Get()
方法在传入初始值的情况下可以省略,但是最好两个都有。如果要操作一个没这两个方法的对象怎么办呢,待深入分析的部分讲解。
ValueAnimator
本身并不作用于任何对象,其是对一个值进行动画,而我们可以监听到这个过程,然后在此过程中修改我们想要做动画的属性值。
ValueAnimator
有几个公共静态方法可以构造动画对象,如下图所示
我们以ofInt
来做一个说明
public static ValueAnimator ofInt(int… values)
private void changeAlpha()
{
// 步骤1:设置动画属性的变化路径
ValueAnimator anim = ValueAnimator.ofInt(255,0, 255);
// ofInt()作用有两个
// 1. 创建动画实例
// 2. 将传入的多个Int参数进行平滑过渡
// 如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推
// ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值过渡到结束值
// 步骤2:设置动画的播放各种属性
anim.setDuration(4000);// 设置动画运行的时长
anim.setStartDelay(0);// 设置动画延迟播放时间
anim.setRepeatCount(0);// 设置动画重复播放次数 = 重放次数+1 动画播放次数 = infinite时,动画无限重复
anim.setRepeatMode(ValueAnimator.REVERSE);//RESTART(默认):正序重放 REVERSE:倒序回放
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
int currentValue = (Integer) animation.getAnimatedValue();//步骤3: 获得改变后的值0到255
float fraction=animation.getAnimatedFraction();//当前动画进度
System.out.println(currentValue);// 输出改变后的值
// 步骤4:将改变后的值赋给对象的属性值
tvOpr.getBackground().setAlpha(currentValue);
// 步骤5:刷新视图,即重新绘制,从而实现动画效果
tvOpr.requestLayout();
}
});
anim.start();
}
上面的代码在4秒的时间内将tvOpr-一个TextView
背景透明度从完全不透明变到完全透明又变到完全不透明,如下图所示
由于属性动画的工作原理比较简单,就是通过不断的调用要做动画的属性的Set()
方法来改变此属性的值,进而造成了动画的效果。值得深入探讨的地方集中在如下两方面:
第一问题由属性动画中的插值器(Interpolator)来解决,第二个问题由估值器(TypeEvaluator)来解决 这两个很重要,正是由于他们的存在我们才可以实现非匀速的动画效果。TypeEvaluator
依赖Interpolator
工作,在TypeEvaluator
的估值函数中,是需要Interpolator
的计算结果的。Android已经为我们内置几个插值器与估值器
插值器:LinearInterpolator
,AccelerateDecelerateInterpolator
,DecelerateInterpolator
,AccelerateInterpolator
估值器:IntEvaluator
,FloatEvaluator
,ArgbEvaluator
下面我们通过一个自定义二次贝塞尔曲线的View来说明插值器与估值器的使用方式,此View 出自chenwei.li的同学之手,我只是更正了其中的一些错误。冒着博客篇幅太长的风险,还是决定将完整代码贴出来。
public class BezierMoveView extends View implements View.OnClickListener {
//开始点和结束点
private int mStartXPoint;
private int mStartYPoint;
private int mEndXPoint;
private int mEndYPoint;
//控制点
private int mConXPoint;
private int mConYPoint;
//移动点
private int mMoveXPoint;
private int mMoveYPoint;
//路径和画笔
private Path mPath;
private Paint mPaint;
//圆形半径,画笔
private int mCircleRadius;
private Paint mCirlcePaint;
public BezierMoveView(Context context) {
super(context);
init(context);
}
public BezierMoveView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public BezierMoveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
/**
* 进行初始化的一些操作
*/
private void init(Context context) {
//设置各点的位置
mStartXPoint = 100;
mStartYPoint = 100;
mEndXPoint = 600;
mEndYPoint = 600;
mConXPoint = 400;
mConYPoint = 0;
mMoveXPoint = 100;
mMoveYPoint = 100;
//路径,画笔设置
mPath = new Path();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mCircleRadius = 20;
mCirlcePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirlcePaint.setColor(Color.BLUE);
mCirlcePaint.setStyle(Paint.Style.FILL);
setOnClickListener(this);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
//贝塞尔曲线
mPath.moveTo(mStartXPoint, mStartYPoint);
mPath.quadTo(mConXPoint, mConYPoint, mEndXPoint, mEndYPoint);
canvas.drawPath(mPath, mPaint);
//画圆
canvas.drawCircle(mStartXPoint, mStartYPoint, mCircleRadius, mCirlcePaint);
canvas.drawCircle(mMoveXPoint, mMoveYPoint, mCircleRadius, mCirlcePaint);
canvas.drawCircle(mEndXPoint, mEndYPoint, mCircleRadius, mCirlcePaint);
}
@Override
public void onClick(View v) {
ValueAnimator valueAnimator = ValueAnimator.ofObject(new CirclePointEvaluator(), new Point(mStartXPoint, mStartYPoint),
new Point(mEndXPoint, mEndYPoint));
valueAnimator.setDuration(2000);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point point = (Point) animation.getAnimatedValue();
mMoveXPoint = point.x;
mMoveYPoint = point.y;
invalidate();
}
});
valueAnimator.start();
}
public class CirclePointEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float t, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
float temp = 1 - t;
int x = (int) (temp * temp * startPoint.x + 2 * t * temp * mConXPoint + t * t * endPoint.x);
int y = (int) (temp * temp * startPoint.y + 2 * t * temp * mConYPoint + t * t * endPoint.y);
return new Point(x,y);
}
}
}
重点看代码靠下位置的onClick
和下面的自定义估值器代码。当我们点击这个view的时候,会触发一个动画,这个动画使用如下方法设置插值器,此处为加速减速插值器
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
,而且设置了自定义的CirclePointEvaluator
估值器,动画每执行一帧,监听就会被回调,然后在回调方法onAnimationUpdate()
中获得了当前的属性值,更改小球的位置坐标,然后触发重绘。
所以使用ValueAnimator
可以做出非常绚烂的效果。
如果动画设置为无限循环,在退出Activity时注意销毁,防止内存泄漏。
又一次验证了我们老祖宗那句老话:万事开头难。曾几何时,我对Android的属性动画是怀着一个恐惧和抵触的心里,不愿意静下心来研究总结。希望自己做事再专注一点,踏实一点。
源代码下载地址