一、效果图
动画场景:该动画是在ImageView中显示,效果图中显示了三个动画:漂浮、左右招手,原动画是很流畅的,无丢帧,转成gif后大致能看出效果,不用在意卡顿。
二、实现原理
1.自定义Drawable,重写draw,根据onAnimationUpdate方法返回的动画进度,通过差值器计算出该帧drawable的状态,并绘制
2.调用Drawable.invalidateSelf触发View的重绘
三、代码分析
1.代码结构
2.MainActivity:测试动画Activity,无复杂逻辑
private void startAnimator(int type) { cancelAnimator(); switch (type) { case TYPE_START: mAnimator = new StartRobotAnimator(this.getApplicationContext()); break; case TYPE_WAVE_LEFT: mAnimator = new WaveRobotAnimator(this.getApplicationContext(), true); break; case TYPE_WAVE_RIGHT: mAnimator = new WaveRobotAnimator(this.getApplicationContext(), false); break; default: mAnimator = new StartRobotAnimator(this.getApplicationContext()); break; } mImageView.setImageDrawable(mAnimator.getDrawable()); mAnimator.start(); } private void cancelAnimator() { if (mAnimator != null) { mAnimator.cancel(); mAnimator = null; } }
提供两个方法:开启/关闭动画
3.StartRobotAnimator:动画实现类
继承ValueAnimator,定义内部类继承Drawable
上图为StartRobotAnimator类结构,内部有较多的内部类:RobotDrawable、Body、EyeLefe...
也容易理解,Robots动画内部是多个元素在动,眼睛、身体、头、左右手。每个元素对应一个内部类。这些内部类都继承Basic。Basic中保存这些元素相同的属性。
动画绘制流程:
从ValueAnimator.start-> onAnimationUpdate->
Drawable.update
Drawable.onDraw.invalidateSelf -> draw
在update时,将动画进度传给Drawable,代码片段如下:
@Override public void onAnimationUpdate(ValueAnimator animation) { mDrawable.update((int) (animation.getAnimatedValue())); mDrawable.invalidateSelf(); }
接着进入自定义Drawable的draw方法:
@Override public void draw(@NonNull Canvas canvas) { if (mWidth == 0 || mHeight == 0) { mWidth = canvas.getWidth(); mHeight = canvas.getHeight(); } canvas.save(); canvas.translate(mWidth / 2f - 126 / 2, mHeight / 2f - 164 / 2);//平移画布,保证绘制的robots在imageView中居中 float T0 = DURATION; float t = Math.min(mTime, DURATION); double translateY = (-30f * t / T0 + 30) * Math.sin(3.5 * Math.PI / T0 * t + Math.PI / 2f); canvas.translate(0, (float) translateY); //绘制头、嘴巴、左右手 mHead.draw(canvas); mMouth.draw(canvas, mTime); mHandLeft.draw(canvas, mTime); mHandRight.draw(canvas, mTime); //绘制身体、左右眼睛 mBody.draw(canvas); mEyeLeft.draw(canvas, mTime); mEyeRight.draw(canvas, mTime); canvas.restore(); }
draw方法和View内部逻辑关系比较大,原理很简单,可以自己理解。按照下面的思路理解可能更方便记忆:
对画布整体做动画使用canvas.translate/rotate/scale/alpha
对某一元素做动画使用Basic类中的Matrix。
对于Matrix,可认为有三个方法,set、pre、post。set:清空矩阵之前的设置,重置矩阵 pre:左乘 post:右乘
比如要实现先平移再旋转的效果,可以setTranslate+postRotate 也可以setRotate+preTranslte。
四、源码
附上MainActivity和自定义Animator源码
package com.zmh.animation.robots; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.ImageView; public class MainActivity extends Activity { private ImageView mImageView; private IRobotsAnimator mAnimator; private final static int TYPE_START = 0x1; private final static int TYPE_WAVE_LEFT = 0x2; private final static int TYPE_WAVE_RIGHT = 0x3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImageView = findViewById(R.id.img); mAnimator = new StartRobotAnimator(this.getApplicationContext()); mImageView.setImageDrawable(mAnimator.getDrawable()); findViewById(R.id.btn_wave_left).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAnimator(TYPE_WAVE_LEFT); } }); findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAnimator(TYPE_START); } }); findViewById(R.id.btn_wave_right).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAnimator(TYPE_WAVE_RIGHT); } }); } private void startAnimator(int type) { cancelAnimator(); switch (type) { case TYPE_START: mAnimator = new StartRobotAnimator(this.getApplicationContext()); break; case TYPE_WAVE_LEFT: mAnimator = new WaveRobotAnimator(this.getApplicationContext(), true); break; case TYPE_WAVE_RIGHT: mAnimator = new WaveRobotAnimator(this.getApplicationContext(), false); break; default: mAnimator = new StartRobotAnimator(this.getApplicationContext()); break; } mImageView.setImageDrawable(mAnimator.getDrawable()); mAnimator.start(); } private void cancelAnimator() { if (mAnimator != null) { mAnimator.cancel(); mAnimator = null; } } @Override protected void onDestroy() { super.onDestroy(); cancelAnimator(); } }
package com.zmh.animation.robots; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.PathInterpolator; class StartRobotAnimator extends ValueAnimator implements IRobotsAnimator, ValueAnimator.AnimatorUpdateListener { private RobotDrawable mDrawable; private int DURATION = 4000; StartRobotAnimator(Context context) {