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

代码设计架构图
自定义Android属性动画框架_第2张图片

撸代码

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 = 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属性动画框架_第3张图片

接下来我们来定义动画任务属性值管理类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 = 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属性动画框架_第4张图片

你可能感兴趣的:(安卓开发,架构,属性动画,自定义动画框架,手撸属性动画,属性动画设计,动画架构)