Android属性动画优化(更高效的使用属性动画)

版权声明:转载请注明出处。 https://blog.csdn.net/rentee/article/details/52251829

属性动画的基础知识可以上郭霖的blog补下。地址:http://blog.csdn.net/guolin_blog/article/details/43536355


以下是自己另附的一些优化策略。
1.使用PropertyValuesHolder
想必大家都这样写过:

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

但是这样写会产生两个ObjectAnimator对象,效率较低,官方建立这样写:

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);

ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();

这里使用了PropertyValuesHolder,只产生一个ObjectAnimator对象,更加高效。

一个view同时发生多种效果时,建议这种写法。


2.使用Keyframe
同样,我们肯定也这样写过:

AnimatorSet animSet = new AnimatorSet();

ObjectAnimator transYFirstAnim = ObjectAnimator.ofFloat(mView, "translationY", 0, 100);
ObjectAnimator transYSecondAnim = ObjectAnimator.ofFloat(mView, "translationY", 100, 0);

animSet.playSequentially(transYFirstAnim, transYSecondAnim);

产生两个ObjectAnimator对象,一个AnimatorSet,代码繁琐,对象冗杂。
这种情况建议使用Keyframe编写其行为。从词义上来理解Keyframe是关键帧,下面是使用关键帧的例子:

Keyframe k0 = Keyframe.ofFloat(0f, 0); //第一个参数为“何时”,第二个参数为“何地”
Keyframe k1 = Keyframe.ofFloat(0.5f, 100);
Keyframe k2 = Keyframe.ofFloat(1f, 0);

PropertyValuesHolder p = PropertyValuesHolder.ofKeyframe("translationY", k0, k1, k2);

ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mView, p);
objectAnimator.start();

所以效果就是:
开始时 位置为0;
动画开始1/2时 位置为100;
动画结束时 位置为0。

一个view的单个属性先后发生一系列变化时,建议使用Keyframe达到效果。

总结就是,如果是同一个view的一系列动画,均可使用以上组合方式达到只使用一个ObjectAnimator的效果 。多个view的动画用AnimatorSet进行动画组合和排序,代码架构和执行效率基本能达到最优化。


3.使用AnimatorListenerAdapter代替AnimatorListener
由于AnimatorListener是接口,所以实现它得实现它所有的方法,而我们有时只用到它的个别回调(如:onAnimationStart),使用它会导致代码看起来非常冗杂。而AnimatorListenerAdapter是默认实现了AnimatorListener的一个抽象类,你可以按需要重写其中的方法,代码会优雅一点。


4.使用ViewPropertyAnimator
属性动画的机制已经不是再针对于View而进行设计的了,而是一种不断地对值进行操作的机制,它可以将值赋值到指定对象的指定属性上。
但是,在绝大多数情况下,还是对View进行动画操作的。Android开发团队也是意识到了这一点,于是在Android 3.1系统当中补充了ViewPropertyAnimator这个机制。

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, “alpha”, 0f); animator.start();
等同
textview.animate().alpha(0f);

animate()方法就是在Android 3.1系统上新增的一个方法,这个方法的返回值是一个ViewPropertyAnimator对象。(简明方便)
textview.animate().x(500).y(500).setDuration(5000).setInterpolator(new BounceInterpolator());


5.属性动画其他一些实用方法:(暂时只讲了API 14—Android 4.0及以下的部分)

  1. setStartDelay():可以设置动画开始前的延迟时间。注意:此方法API 14加入;如果给动画添加了AnimatorListener,Listener的onAnimationStart方法会在动画最开始的时候回调,而不是delay一段时间后回调。

  2. isStarted()(API 14)与isRunning():这个得与setStartDelay()放在一起讲,也就是说,当动画在delay中并没有真正开始时,isStarted返回false,而isRunning返回true。也就是,isRunning的周期是大于isStarted的。

  3. cancel()与end():cancel方法会立即停止动画,并且停留在当前帧。end方法会立即停止动画,并且将动画迅速置到最后一帧的状态。

  4. setupStartValues()与setupEndValues():设置动画进行到的当前值为开始值(结束值)。示例如下:

以下这个示例中,当view移动了2.5s的时候,cancel当前动画,并重新设定起始位置为当前位置,结束位置为400,再开始此动画。

final ObjectAnimator o = ObjectAnimator.ofFloat(mTextView, "x", 0, 500);
o.setDuration(5000);
o.start();

mTextView.postDelayed(new Runnable() {
      @Override
      public void run() {
           if (o.isRunning()) {
                o.cancel();
            }
            o.setupStartValues();
            o.setFloatValues(400);
            o.start();
      }
},2500);

6.做一个属性动画的方式:(摘自Android开发艺术探索)
1.给你的对象加上get和set方法,前提是你有权限。
2.用一个类来包装原始对象,间接为其提供get和set方法。

    /**
     * 用一个类来包装原始对象,间接为其提供get和set方法。
     */
    private static class ViewWrapper {
        private View mTarget;

        public ViewWrapper(View target) {
            mTarget = target;
        }

        public int getWidth() {
            return mTarget.getLayoutParams().width;
        }

        public void setWidth(int width) {
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }

    /**
     * TextView不能直接设置width,这里即采用了包装的方式,对textview的width进行属性动画
     */
    private void animViewWidthByWidth() {
        ViewWrapper wrapper = new ViewWrapper(mTextView);
        ObjectAnimator widthAnim = ObjectAnimator.ofObject(wrapper, "width", new IntEvaluator(), mTextView2.getWidth(), mTextView2.getWidth() * 2);
        widthAnim.setDuration(1000);
        widthAnim.setRepeatCount(100);
        widthAnim.start();
    }

3.采用ValueAnimator,监听动画过程,自己实现属性的改变。

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float currentValue = (float) animation.getAnimatedValue();
        //此处即可根据currentValue的值自己监听过程,实现计算。
        Log.d("TAG", "cuurent value is " + currentValue);
    }
});
anim.start();

7.哪些线程能做属性动画
属性动画的start()方法,首先就有判定:

private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
    ......
}

说明,需要有Looper的线程才可以做属性动画。很明显,Android原生环境中,主线程和HandlerThread是可以做属性动画的,当然,也可以自己实现一个有Looper的Thread。


8.属性动画的内存泄露问题
许多时候用到了无限循环的动画,我们会这样写:animator.setRepeatCount(ValueAnimator.INFINITE);
这样写如果没有及时取消,会导致此属性动画持有被动画对象的引用而导致内存泄露,故在activity生命周期结束时,如onDestroy方法中,及时的cancel掉这种动画。
补间动画无此问题,它是给view做动画,在view的onDetachedFromWindow方法会取消掉动画,所以不会导致内存泄露。

猜你喜欢

转载自blog.csdn.net/rentee/article/details/52251829