自定义属性动画框架

作者: Joker_Wan | 来源:发表于2019-12-09 12:50 被阅读0次

通过本篇文章,你将会了解

  • 安卓属性动画的基本架构
  • 插值器和估值器在动画中的作用
  • 手撸属性动画

设想一下,如果你是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()来实现动画。

动画架构分析

image.png

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

代码设计架构图

image.png

撸代码

1、首先我们来模拟VSync信号,每隔16ms发送一个信号去遍历animationFrameCallbackList执行动画Callback,定义一个VSyncManager类来模拟

public class VSyncManager {
    private List<AnimationFrameCallback> 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<MyFloatKeyFrame> 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获取对应得具体属性值的相关计算逻辑可以参考下图


image.png

接下来我们来定义动画任务属性值管理类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<View> 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();
    }

布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/bottom"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#008500"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

效果如下图,对button进行横向缩放,和使用原生的ObjectAnimator实现的效果基本一致

ObjectAnimator.gif

相关文章

  • Vue1.0学习小结2

    目录 生命周期 计算属性 自定义方法与属性 数据监听 动画 组件 slot 路由 1.生命周期 用Vue框架,熟悉...

  • 属性动画常用属性和方法

    属性动画 实现Animation框架的的功能属性动画常用属性演示动画的监听事件 ImageView imagevi...

  • JS属性动画框架

    使用js实现的属性动画框架: obj // 动画对象 json //属性表 {属性名:动画结束值} callbac...

  • Android - 自定义View和属性动画 ValueAnim

    自定义View和属性动画ValueAnimator实现圆点指示器 自定义View和属性动画相结合实现支持动态修改指...

  • 三谈属性动画——Keyframe以及ViewPropertyAn

    经过初识属性动画——使用Animator创建动画和再谈属性动画——介绍以及自定义Interpolator插值器,对...

  • Android 自定义属性实现3D翻转动效

    上一篇文章介绍了属性动画以及如何自定义属性动画,如果关于自定义属性动画你还存在疑问请回去查看上一篇:Android...

  • 自定义属性动画框架

    通过本篇文章,你将会了解 安卓属性动画的基本架构 插值器和估值器在动画中的作用 手撸属性动画 设想一下,如果你是g...

  • Android学习感悟之属性动画

    本篇包括Android属性动画的基本使用,理解插值器和估值器,自定义属性动画 简介 属性动画是Android3.0...

  • Android知识总结

    一、Android动画 Android 属性动画:这是一篇很详细的 属性动画 总结&攻略 二、自定义View 爱哥...

  • 动画深入研究

    前言 分类 View动画,帧动画,自定义View动画,属性动画 View动画 平移,缩放,旋转,透明Transla...

网友评论

    本文标题:自定义属性动画框架

    本文链接:https://www.haomeiwen.com/subject/jkybgctx.html