安卓开发|自定义Android属性动画框架

【安卓开发|自定义Android属性动画框架】通过本篇文章,你将会了解

  • 安卓属性动画的基本架构
  • 插值器和估值器在动画中的作用
  • 手撸属性动画
设想一下,如果你是google的工程师,让你去设计一个属性动画,你该如何设计?在设计属性动画时我们应该要考虑哪些问题?
  • 生成动画的api调用约简单越好
  • 一个View可以有多个动画,但同时只能有一个在运行
  • 动画的执行不能依赖自身的for循环
  • 如何让动画动起来
我们先来看下属性动画的种类
  • 平移动画
  • 透明度动画
  • 缩放动画
  • 旋转动画
  • 帧动画
属性动画的使用
ObjectAnimator animator = ObjectAnimator.ofFloat(view,"scale",1f,2f,3f); animator.setInterpolator(new LinearInterpolator()); animator.setDuration(500); animator.start()

动画的本质
动画实际上是改变View在某一时间点上的样式属性,比如在0.1s的时候View的x坐标为50px,在0.2s的时候View的x坐标变为150px,在0.3s的时候View的x坐标变为250px,肉眼看就会感觉View在向右移动。
实际上是通过一个线程每隔一段时间通过调用view.setX(index++)来改变属性值产生动画效果。
动画实际上是一个复杂的流程,需要考虑的因素比较多,在开发者层面不建议直接调用view.setX()来实现动画。
动画架构分析
安卓开发|自定义Android属性动画框架
文章图片

根据上面的架构图,我们将动画任务拆成若干个关键帧,每个关键帧在不同的时间点执行自己的动画,最终将整个动画完成,但每两个关键帧之间是有时间间隔的,我们要实现一个补帧的操作来过渡两个关键帧动画,使动画看起来衔接平滑自然。
这里可能大家会有一个疑问:为什么要将动画分解成不同的关键帧?原因是动画完成是需要时间开销的。如果不给出关键帧动画,动画的过程将无法控制,而且在不同的时间点,控件的状态也不一样。
代码设计架构图
安卓开发|自定义Android属性动画框架
文章图片

撸代码
1、首先我们来模拟VSync信号,每隔16ms发送一个信号去遍历animationFrameCallbackList执行动画Callback,定义一个VSyncManager类来模拟
public class VSyncManager { private List list = new ArrayList<>(); public static VSyncManager getInstance() { return Holder.instance; }private VSyncManager() { new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(16); } catch (Exception e) { e.printStackTrace(); } for (AnimationFrameCallback animationFrameCallback : list) { animationFrameCallback.doAnimationFrame(System.currentTimeMillis()); } } } }).start(); }interface AnimationFrameCallback { boolean doAnimationFrame(long currentTime); }public void add(AnimationFrameCallback animationFrameCallback) { list.add(animationFrameCallback); }static class Holder { static final VSyncManager instance = new VSyncManager(); } }

定义一个时间插值器TimeInterpolator
public interface TimeInterpolator { float getInterpolator(float input); }

创建一个线性插值器LinearInterpolator实现TimeInterpolator,插值器的输出我们定义为输入的一般,你可以设置你想要的任何值
public class LinearInterpolator implements TimeInterpolator { @Override public float getInterpolator(float input) { return 0.5f*input; } }

接着定义我们的关键帧实体类MyFloatKeyFrame,主要用来存储三个属性:当前动画执行的进度百分比,当前帧对应的View的属性值,当前帧对应的属性值的类型
public class MyFloatKeyFrame { //当前的百分比 float fraction; //当前帧对应的属性值 float mValue; //当前帧对应得值得类型 Class mValueType; public MyFloatKeyFrame(float fraction, float mValue) { this.fraction = fraction; this.mValue = https://www.it610.com/article/mValue; mValueType = float.class; }public float getValue() { return mValue; }public void setValue(float mValue) { this.mValue = mValue; }public float getFraction() { return fraction; }public void setFraction(float fraction) { this.fraction = fraction; } }

再接着定义关键帧集合,用来初始化关键帧信息并且返回对应的View的属性值
public class MyKeyframeSet { //类型估值器 TypeEvaluator mEvaluator; List mKeyFrames; public MyKeyframeSet(MyFloatKeyFrame... keyFrame) { this.mEvaluator = new FloatEvaluator(); mKeyFrames = Arrays.asList(keyFrame); }//关键帧初始化 public static MyKeyframeSet ofFloat(float[] values) { if (values.length <= 0) { return null; } int numKeyframes = values.length; //循环赋值 MyFloatKeyFrame keyFrame[] = new MyFloatKeyFrame[numKeyframes]; keyFrame[0] = new MyFloatKeyFrame(0, values[0]); for (int i = 1; i < numKeyframes; i++) { keyFrame[i] = new MyFloatKeyFrame((float) i / (numKeyframes - 1), values[i]); } return new MyKeyframeSet(keyFrame); }//获取当前百分比对应得具体属性值 public Object getValue(float fraction) { MyFloatKeyFrame prevKeyFrame = mKeyFrames.get(0); for (int i = 0; i < mKeyFrames.size(); i++) { MyFloatKeyFrame nextKeyFrame = mKeyFrames.get(i); if (fraction < nextKeyFrame.getFraction()) { //当前百分比在此之间 //计算间隔百分比 float intervalFraction = (fraction - prevKeyFrame.getFraction()) / (nextKeyFrame.getFraction() - prevKeyFrame.getFraction()); //通过估值器返回对应得值 return mEvaluator == null ? prevKeyFrame.getValue() + intervalFraction * (nextKeyFrame.getValue() - prevKeyFrame.getValue()) : ((Number) mEvaluator.evaluate(intervalFraction, prevKeyFrame.getValue(), nextKeyFrame.getValue())).floatValue(); } prevKeyFrame = nextKeyFrame; } //对应得帧不够 return mKeyFrames.get(mKeyFrames.size() - 1).getValue(); } }

根据当前动画执行进度百分比fraction获取对应得具体属性值的相关计算逻辑可以参考下图
安卓开发|自定义Android属性动画框架
文章图片

接下来我们来定义动画任务属性值管理类MyFloatPropertyValuesHolder,主要作用是通过反射获取控件对应的方法,然后通过调用该方法(如setScale)给控件设置相应的属性值
public class MyFloatPropertyValuesHolder { //属性名 String mPropertyName; //属性类型 float Class mValueType; //反射 Method mSetter = null; //关键帧管理类 MyKeyframeSet mKeyframeSet; public MyFloatPropertyValuesHolder(String propertyName, float... values) { this.mPropertyName = propertyName; mValueType = float.class; //交给关键帧管理初始化 mKeyframeSet = MyKeyframeSet.ofFloat(values); }//通过反射获取控件对应的方法 public void setupSetter() { char firstLetter = Character.toUpperCase(mPropertyName.charAt(0)); String theRest = mPropertyName.substring(1); //setScaleX String methodName = "set" + firstLetter + theRest; try { mSetter = View.class.getMethod(methodName, float.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } }//给控件设置相应的属性值 public void setAnimatedValue(View view, float fraction) { Object value = https://www.it610.com/article/mKeyframeSet.getValue(fraction); try { mSetter.invoke(view, value); } catch (Exception e) { e.printStackTrace(); } } }

最后定义我们对开发者暴露的MyObjectAnimator类,功能类似Android源码的的ObjectAnimator类,给开发人员调用设置属性动画的api
public class MyObjectAnimator implements VSYNCManager.AnimationFrameCallback { //动画时长 private long mDuration = 0; //需要执行动画的对象 private WeakReference mTarget; //属性值管理类 private MyFloatPropertyValuesHolder mFloatPropertyValuesHolder; private int index = 0; private TimeInterpolator interpolator; public long getDuration() { return mDuration; }public void setDuration(long mDuration) { this.mDuration = mDuration; }public int getIndex() { return index; }public void setIndex(int index) { this.index = index; }public TimeInterpolator getInterpolator() { return interpolator; }public void setInterpolator(TimeInterpolator interpolator) { this.interpolator = interpolator; }public MyObjectAnimator(View target, String propertyName, float... values) { mTarget = new WeakReference<>(target); mFloatPropertyValuesHolder = new MyFloatPropertyValuesHolder(propertyName, values); }public static MyObjectAnimator ofFloat(View target, String propertyName, float... values) { MyObjectAnimator anim = new MyObjectAnimator(target, propertyName, values); return anim; }//每隔16ms执行一次 @Override public boolean doAnimationFrame(long currentTime) { //后续的效果渲染 //动画的总帧数 float total = mDuration / 16; //拿到执行百分比 (index)/total float fraction = (index++) / total; //通过插值器去改变对应的执行百分比 if (interpolator != null) { fraction = interpolator.getInterpolator(fraction); } //循环 repeat if (index >= total) { index = 0; } //交给mFloatPropertyValuesHolder,改变对应的属性值 mFloatPropertyValuesHolder.setAnimatedValue(mTarget.get(), fraction); return false; }//开启动画 public void start() { //交给mFloatPropertyValuesHolder改变对应的属性值 mFloatPropertyValuesHolder.setupSetter(); VSYNCManager.getInstance().add(this); } }

最后我们来使用下MyObjectAnimator来看看动画效果
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = findViewById(R.id.bottom); MyObjectAnimator animator = MyObjectAnimator.ofFloat(button, "ScaleX", 1f, 2f, 3f, 1f); animator.setInterpolator(new LineInterpolator()); animator.setDuration(3000); animator.start(); }

布局文件如下

效果如下图,对button进行横向缩放,和使用原生的ObjectAnimator实现的效果基本一致
安卓开发|自定义Android属性动画框架
文章图片

    推荐阅读