Android属性动画全解析

479 阅读5分钟

属性动画之ViewPropertyAnimator

这是属性动画最简单的一种

代码也最简单

   iv.animate().translationY(500);

通常一些简单的android 原生的view动画 我们都优先考虑这种方法,因为真的很方便啊。

 /**
     * This method returns a ViewPropertyAnimator object, which can be used to animate
     * specific properties on this View.
     *
     * @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
     */
    public ViewPropertyAnimator animate() {
        if (mAnimator == null) {
            mAnimator = new ViewPropertyAnimator(this);
        }
        return mAnimator;
    }

看下这个函数返回就知道了。

有很多方法可以供我们选择,具体各位自查。

总体来说ViewPropertyAnimator 使用简单也比较好理解,也支持链式调用。不再多说

ObjectAnimator

ViewPropertyAnimator虽然好用,但是自定义view很难使用这个,且支持的属性有限。很多情况我们要自己支持一些属性

就得用到ObjectAnimator

总体来说 分几步:

  1. 动画执行过程中要改变的属性 必须有gettter和setter方法
  2. ObjectAnimator.ofXXX() 创建 ObjectAnimator 对象
  3. 最后start执行动画即可

看下代码:

public class LoadingView extends View {

    Paint mPaint = new Paint();

    public float getProgress() {
        return progress;
    }

    public void setProgress(float progress) {
        this.progress = progress;
        //setter方法是肯定会被ObjectAnimator调用的,调用完以后 我们要主动invalidate方法
        //onDraw方法才会主动执行,否则,只改变一个属性的值而不重绘 肯定是没效果的。
        //这也就是为什么属性动画 不是直接更改属性的值,而要调用属性的setter方法,因为直接
        //更改属性的值 invalidate没地方调用了,动画自然没效果了。
        invalidate();
    }

    /**
     * 进度条
     */
    float progress = 0;

    public LoadingView(Context context) {
        super(context);
    }

    public LoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //这里没有用0,0  width,hegiht 这2个点来定位这个矩形 是因为我们的圆形边有宽度,所以要
        //稍微窄一点 不然的话 边界处会有丢失的部分 很难看
        RectF oval = new RectF(10, 10,
                getWidth()-10, getHeight()-10);
        //先画圆弧
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(20);
        canvas.drawArc(oval, -90, 360 * progress/100, false, mPaint);
        //再画文字
        mPaint.reset();
        mPaint.setColor(Color.BLUE);
        mPaint.setTextSize(80);
        mPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText((int)progress + "%", oval.centerX(), oval.centerY(), mPaint);

    }
}
  // 创建 ObjectAnimator 对象
                ObjectAnimator animator = ObjectAnimator.ofFloat(loadingView, "progress", 0, 85);
                animator.setDuration(3000);
                animator.start();

ofXXX有很多方法,可以满足我们任何自定义view属性的要求。

Interpolator 插值器

这个其实挺好理解的,打个比方 一个人从起跑点0m处跑到终点100m处。可以有很多种跑法

  1. 一直加速跑 跑到终点
  2. 跑到一半减速再加速到终点
  3. 跑到一半停下来休息一下 再跑到终点
  4. .....等等 有无限种跑法 ,全看你自己想怎么跑。甚至都可以跑过终点跑到150m处再跑回去

Interpolator 也是这样,Interpolator 就是设置你动画执行过程的,以不同的速度模型来将你的动画执行完毕

系统自带的Interpolator已经足够多,我不准备一一介绍了,网上太多资料了,自己跑跑看就可以了。

PathInterpolator这个较为特殊,尤其是配合 贝塞尔曲线使用的时候 会有很多酷炫的特效,我写个简单的 demo 大家体会一下这其中的奥妙即可。 讲白了还是那个跑步的跑法 可以由我们自己设定

  Path interpolatorPath = new Path();
                // 先以「动画完成度 : 时间完成度 = 1 : 1」的速度匀速运行 25% 50的百分之25 就是12.5
                interpolatorPath.lineTo(0.25f, 0.25f);
                // 然后瞬间跳跃到 100% 的动画完成度  在这里其实也就是从50 直接跳跃到100 也就是圆形直接画满
                interpolatorPath.moveTo(0.25f, 2.0f);
                // 再匀速倒车,返回到目标点     画满以后 再回到 目标值
                interpolatorPath.lineTo(1, 1);

                // 创建 ObjectAnimator 对象
                ObjectAnimator animator = ObjectAnimator.ofFloat(loadingView, "progress", 0, 50);
                animator.setDuration(3000);
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                    PathInterpolator pathInterpolator=new PathInterpolator(interpolatorPath);
                    animator.setInterpolator(pathInterpolator);
                }
                animator.start();

ofXXX方法无法满足我咋办?Evaluator来帮你

比如说我自定义了某个view,这个view要完成我想要的动画需要改变的属性是一个自定义对象那咋办呢?自定义Evaluator呗

稍微改变下我们的代码:

public class LoadingView extends View {

    Paint mPaint = new Paint();

    public CustomProperty getCustomProperty() {
        return customProperty;
    }

    public void setCustomProperty(CustomProperty customProperty) {
        this.customProperty = customProperty;
        invalidate();
    }

    /**
     * 进度条

     */
    CustomProperty customProperty = new CustomProperty(0,0);

    public LoadingView(Context context) {
        super(context);
    }

    public LoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        RectF oval = new RectF(30, 30,
                getWidth()-30, getHeight()-30);
        //先画圆弧
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(customProperty.getStrokeWidth());
        canvas.drawArc(oval, -90, 360 * customProperty.getProgress()/100, false, mPaint);
    }
}

自定义属性

public class CustomProperty {
    float progress;

    public CustomProperty(float progress, int strokeWidth) {
        this.progress = progress;
        this.strokeWidth = strokeWidth;
    }

    public float getProgress() {
        return progress;
    }

    public void setProgress(float progress) {
        this.progress = progress;
    }

    public int getStrokeWidth() {
        return strokeWidth;
    }

    public void setStrokeWidth(int strokeWidth) {
        this.strokeWidth = strokeWidth;
    }

    @Override
    public String toString() {
        return "CustomProperty{" +
                "progress=" + progress +
                ", strokeWidth=" + strokeWidth +
                '}';
    }

    int strokeWidth;
}


最重要的自定义Evaluator

class CustomPropertyEvaluator implements TypeEvaluator<CustomProperty>
    {
        CustomProperty customProperty=new CustomProperty(0,0);

        @Override
        public CustomProperty evaluate(float fraction, CustomProperty startValue, CustomProperty endValue) {

            float progress=startValue.progress+ fraction*endValue.getProgress();
            int strokeWidth=(int)(startValue.strokeWidth+fraction*endValue.getStrokeWidth());
            customProperty.setProgress(progress);
            customProperty.setStrokeWidth(strokeWidth);
            return customProperty;
        }
    }

最后调用动画

 // 创建 ObjectAnimator 对象
                ObjectAnimator animator = ObjectAnimator.ofObject(loadingView, "customProperty", new CustomPropertyEvaluator(),new CustomProperty(0,0), new CustomProperty(75,30));
                animator.setDuration(3000);
                animator.start();

然后看下我们的效果:

PropertyValuesHolder 组合动画

组合动画无非就是动画执行的顺序集合,大概也就是分三种,先说前两种

一起执行和顺序执行

  //ofPropertyValuesHolder 代表一起执行动画的集合,holder1 holder2 holder3 可以一起执行 共享一个插值器 interpolator
                PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);
                PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);
                PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);
                ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(loadingView, holder1, holder2, holder3);
                animator.start();

                AnimatorSet animatorSet=new AnimatorSet();
                ObjectAnimator animator1 = ObjectAnimator.ofPropertyValuesHolder(loadingView, holder1, holder2, holder3);
                ObjectAnimator animator2 = ObjectAnimator.ofPropertyValuesHolder(loadingView, holder1, holder2);
                //如果需要动画依次播放:
                animatorSet.playSequentially(animator1,animator2);
                //也可以指定顺序
                animatorSet.play(animator1).before(animator2);
                animatorSet.start();

比较好理解是吧。这里就不上效果图了,自己试试就知道。

最后一种关键帧动画着重说一下,还记得前面插值器的介绍吗?有一个path插值器的,我们写了个demo带有回弹效果的, 利用关键帧动画的写法 可以不用那么复杂的插值器即可完成

//从0开始
                Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
                //时间走到一半 我们应该圆圈画完
                Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
                //时间走完的时候 我们的圆圈应该回到一半的位置
                Keyframe keyframe3 = Keyframe.ofFloat(1, 50);
                PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);
                ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(loadingView, holder);
                animator.setDuration(3000);
                animator.start();