android 动画深入分析----属性动画(二)《android开发艺术与探索》

属性动画:

属性动画可以对任意对象的属性进行动画,不仅仅是View,动画默认时间300ms,默认帧率10ms/帧。可达到的效果:某个时间间隔内完成对象从一个属性值到另一个属性值的改变。属性动画从API11之后才有,这制约了属性动画的使用,可以采用nineoldandroids,可以在api11之前的版本使用。nineoldandroids的网址:http://nineoldandroids.com


比较常用的几个动画类:
ValueAnimator  ObjectAnimator   AnimatorSet

举例:
1.改变目标对象(target)的属性(propertyName)的值(values)

    ObjectAnimator.ofFloat(target, propertyName, values).start();

2.改变一个对象的背景颜色

    ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", 0xffff8080,0xff8080ff);
    colorAnim.setDuration(3000);
    colorAnim.setEvaluator(new ArgbEvaluator());
    colorAnim.setRepeatCount(ValueAnimator.INFINITE);
    colorAnim.setRepeatMode(ValueAnimator.REVERSE);
    colorAnim.start();

3.动画集合
    //View在5秒内旋转,平移,缩放,透明的改变
   
    AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(btn_anim, "rotationX", 0,360),
                ObjectAnimator.ofFloat(btn_anim, "rotationY", 0,180),
                ObjectAnimator.ofFloat(btn_anim, "rotation", 0,90),
                ObjectAnimator.ofFloat(btn_anim, "tanslationX", 0,100),
                ObjectAnimator.ofFloat(btn_anim, "tanslationY", 0,100),
                ObjectAnimator.ofFloat(btn_anim, "scaleX",1,1.5f),
                ObjectAnimator.ofFloat(btn_anim, "scaleY", 1,0.5f),
                ObjectAnimator.ofFloat(btn_anim, "alpha", 1,0.25f,1)
        );
        set.setDuration(5000);
        set.start();

在res/animator也可以用xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together"<!-- "sequentially" -->>
    <objectAnimator android:propertyName=""
        android:duration=""
        android:valueFrom=""
        android:valueTo=""
        android:startOffset=""
        android:repeatCount=""
        android:repeatMode=""
        android:valueType="">
        
    <animator android:duration=""
        android:valueFrom=""
        android:valueTo=""
        android:startOffset=""
        android:repeatCount=""
        android:repeatMode=""
        android:valueType="">
</set>
<set>对应的AnimatorSet
    android:ordering对应的有2个值
        together     表示动画集合中的子动画同时播放
        sequentially 表示动画集合中的子动画按先后顺序播放    
<objectAnimator>对应的ObjectAnimator
<animator> 对应的ValueAnimator
    android:propertyName=""作用对象的属性名称
        android:duration=""    动画时长    
        android:valueFrom=""   动画起始值
        android:valueTo=""     动画结束值
        android:startOffset="" 动画延迟时间(动画开始后多长时间开始播放动画)
        android:repeatCount="" 动画的重复次数
        android:repeatMode=""  动画的重复模式
        android:valueType=""   属性的类型(intType/floatType)

举例:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <objectAnimator android:propertyName="x"
        android:duration="300"
        android:valueTo="200"
        android:valueType="intType"/>
      
   <objectAnimator android:propertyName="y"
        android:duration="300"
        android:valueTo="300"
        android:valueType="intType"/>
</set>

代码调用上面的xml
  
 AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.animator_xy);
    set.setTarget(btn_anim);
    set.start();

理解插值器和估值器:


插值器(Interpolator):

TimeInterpolator 时间插值器  根据时间的流逝的百分比来计算出当前属性值改变的百分比。

LinearInterpolator 线性插值器 :匀速动画

AccelerateDecelerateInterpolator 加速减速插值器:动画两天慢中间快

DecelerateInterpolator 减速插值器:动画越来越慢


源码:

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}


这个线性值插器的返回值和输入值是一样的

估值器(TypeEvaluator):

IntEvaluator:整型属性
FloatEvaluator:浮点型属性
ArgbEvaluator:Color型属性


源码:
public class IntEvaluator implements TypeEvaluator<Integer> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type <code>int</code> or
     *                   <code>Integer</code>
     * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code>
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}


上面三个参数分别表示 估值小数  开始值  结束值



自定义估值算法需要实现TypeEvaluator


属性动画的监听器:

AnimatorListener和AnimatorUpdateListener

   
public static interface AnimatorListener {
        void onAnimationStart(Animator animation);
        void onAnimationEnd(Animator animation);
        void onAnimationCancel(Animator animation);
        void onAnimationRepeat(Animator animation);
    }

    
 public static interface AnimatorUpdateListener {
        void onAnimationUpdate(ValueAnimator animation);
    }



对任意属性做动画:
分析属性动画原理:属性动画要求作用的对象是提供该属性的get和set方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值不一样,确切的说是随着时间的推移,所传递的值越来越接近最终值。

如果要多object的abc属性做动画必须同时满足下面两个条件:
1.object必须提供setabc的方法,如果动画的时候没有传递初始值,还需提供getabc方法,系统要去取abc属性的初始值。(如果条件不满足会Crash)
2.object的setabc对abc所做的改变必须通过某种方法反映出来,比如会带来UI改变。(这个条件不满足,动画无效,不会Crash)

属性动画的工作原理:

 ObjectAnimator.ofInt(this, "backgroundColor", 0xffff8080,0xff8080ff).start();

调用了start();

  @Override
    public void start() {
        // See if any of the current active/pending animators need to be canceled
        AnimationHandler handler = sAnimationHandler.get();
        if (handler != null) {
            int numAnims = handler.mAnimations.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
            numAnims = handler.mPendingAnimations.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
            numAnims = handler.mDelayedAnims.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
        }
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        super.start();
    }

首先判断当前的动画,等待的动画(Pending)和延迟的动画(Delayed)中有和当前动画相同的动画,那么就把相同的动画取消调,接下来是log,再接着就是调用父类 super.start();接着看ValueAnimator的start方法:
    @Override
    public void start() {
        start(false);
    }
    
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        mPlayingBackwards = playBackwards;
        if (playBackwards && mSeekFraction != -1) {
            if (mSeekFraction == 0 && mCurrentIteration == 0) {
                // special case: reversing from seek-to-0 should act as if not seeked at all
                mSeekFraction = 0;
            } else if (mRepeatCount == INFINITE) {
                mSeekFraction = 1 - (mSeekFraction % 1);
            } else {
                mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
            }
            mCurrentIteration = (int) mSeekFraction;
            mSeekFraction = mSeekFraction % 1;
        }
        if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
                (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
            // if we were seeked to some other iteration in a reversing animator,
            // figure out the correct direction to start playing based on the iteration
            if (playBackwards) {
                mPlayingBackwards = (mCurrentIteration % 2) == 0;
            } else {
                mPlayingBackwards = (mCurrentIteration % 2) != 0;
            }
        }
        int prevPlayingState = mPlayingState;
        mPlayingState = STOPPED;
        mStarted = true;
        mStartedDelay = false;
        mPaused = false;
        updateScaledDuration(); // in case the scale factor has changed since creation time
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // This sets the initial value of the animation, prior to actually starting it running
            if (prevPlayingState != SEEKED) {
                setCurrentPlayTime(0);
            }
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();
    }

可以看出属性动画是需要运行在Looper的线程中,会调用animationHandler.start();

 
@SuppressWarnings("unchecked")
    protected static class AnimationHandler implements Runnable {
        // The per-thread list of all active animations
        /** @hide */
        protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();

        // Used in doAnimationFrame() to avoid concurrent modifications of mAnimations
        private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();

        // The per-thread set of animations to be started on the next animation frame
        /** @hide */
        protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();

        /**
         * Internal per-thread collections used to avoid set collisions as animations start and end
         * while being processed.
         * @hide
         */
        protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
        private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
        private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();

        private final Choreographer mChoreographer;
        private boolean mAnimationScheduled;

        private AnimationHandler() {
            mChoreographer = Choreographer.getInstance();
        }

        /**
         * Start animating on the next frame.
         */
        public void start() {
            scheduleAnimation();
        }

        private void doAnimationFrame(long frameTime) {
            // mPendingAnimations holds any animations that have requested to be started
            // We're going to clear mPendingAnimations, but starting animation may
            // cause more to be added to the pending list (for example, if one animation
            // starting triggers another starting). So we loop until mPendingAnimations
            // is empty.
            while (mPendingAnimations.size() > 0) {
                ArrayList<ValueAnimator> pendingCopy =
                        (ArrayList<ValueAnimator>) mPendingAnimations.clone();
                mPendingAnimations.clear();
                int count = pendingCopy.size();
                for (int i = 0; i < count; ++i) {
                    ValueAnimator anim = pendingCopy.get(i);
                    // If the animation has a startDelay, place it on the delayed list
                    if (anim.mStartDelay == 0) {
                        anim.startAnimation(this);
                    } else {
                        mDelayedAnims.add(anim);
                    }
                }
            }
            // Next, process animations currently sitting on the delayed queue, adding
            // them to the active animations if they are ready
            int numDelayedAnims = mDelayedAnims.size();
            for (int i = 0; i < numDelayedAnims; ++i) {
                ValueAnimator anim = mDelayedAnims.get(i);
                if (anim.delayedAnimationFrame(frameTime)) {
                    mReadyAnims.add(anim);
                }
            }
            int numReadyAnims = mReadyAnims.size();
            if (numReadyAnims > 0) {
                for (int i = 0; i < numReadyAnims; ++i) {
                    ValueAnimator anim = mReadyAnims.get(i);
                    anim.startAnimation(this);
                    anim.mRunning = true;
                    mDelayedAnims.remove(anim);
                }
                mReadyAnims.clear();
            }

            // Now process all active animations. The return value from animationFrame()
            // tells the handler whether it should now be ended
            int numAnims = mAnimations.size();
            for (int i = 0; i < numAnims; ++i) {
                mTmpAnimations.add(mAnimations.get(i));
            }
            for (int i = 0; i < numAnims; ++i) {
                ValueAnimator anim = mTmpAnimations.get(i);
                if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
                    mEndingAnims.add(anim);
                }
            }
            mTmpAnimations.clear();
            if (mEndingAnims.size() > 0) {
                for (int i = 0; i < mEndingAnims.size(); ++i) {
                    mEndingAnims.get(i).endAnimation(this);
                }
                mEndingAnims.clear();
            }

            // If there are still active or delayed animations, schedule a future call to
            // onAnimate to process the next frame of the animations.
            if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
                scheduleAnimation();
            }
        }

        // Called by the Choreographer.
        @Override
        public void run() {
            mAnimationScheduled = false;
            doAnimationFrame(mChoreographer.getFrameTime());
        }

        private void scheduleAnimation() {
            if (!mAnimationScheduled) {
                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
                mAnimationScheduled = true;
            }
        }
    }

看出animationHandler并不是Handler 而是实现了Runable接口 run方法调用了doAnimationFrame
 final boolean doAnimationFrame(long frameTime) {
        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekFraction < 0) {
                mStartTime = frameTime;
            } else {
                long seekTime = (long) (mDuration * mSeekFraction);
                mStartTime = frameTime - seekTime;
                mSeekFraction = -1;
            }
        }
        if (mPaused) {
            if (mPauseTime < 0) {
                mPauseTime = frameTime;
            }
            return false;
        } else if (mResumed) {
            mResumed = false;
            if (mPauseTime > 0) {
                // Offset by the duration that the animation was paused
                mStartTime += (frameTime - mPauseTime);
            }
        }
        // The frame time might be before the start time during the first frame of
        // an animation.  The "current time" must always be on or after the start
        // time to avoid animating frames at negative time intervals.  In practice, this
        // is very rare and only happens when seeking backwards.
        final long currentTime = Math.max(frameTime, mStartTime);
        return animationFrame(currentTime);
    }


doAnimationFrame最后调用了animationFrame
   
boolean animationFrame(long currentTime) {
        boolean done = false;
        switch (mPlayingState) {
        case RUNNING:
        case SEEKED:
            float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
            if (mDuration == 0 && mRepeatCount != INFINITE) {
                // Skip to the end
                mCurrentIteration = mRepeatCount;
                if (!mReversing) {
                    mPlayingBackwards = false;
                }
            }
            if (fraction >= 1f) {
                if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                    // Time to repeat
                    if (mListeners != null) {
                        int numListeners = mListeners.size();
                        for (int i = 0; i < numListeners; ++i) {
                            mListeners.get(i).onAnimationRepeat(this);
                        }
                    }
                    if (mRepeatMode == REVERSE) {
                        mPlayingBackwards = !mPlayingBackwards;
                    }
                    mCurrentIteration += (int) fraction;
                    fraction = fraction % 1f;
                    mStartTime += mDuration;
                } else {
                    done = true;
                    fraction = Math.min(fraction, 1.0f);
                }
            }
            if (mPlayingBackwards) {
                fraction = 1f - fraction;
            }
            animateValue(fraction);
            break;
        }

        return done;
    }

animationFrame内部调用了 animateValue(fraction);
 void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }
calculateValue计算每一帧的属性值

PropertyValuesHolder[] oldValues = mValues;

可以发现get方法反射调用的来看set方法:

  
 private void setupValue(Object target, Keyframe kf) {
        if (mProperty != null) {
            Object value = convertBack(mProperty.get(target));
            kf.setValue(value);
        }
        try {
            if (mGetter == null) {
                Class targetClass = target.getClass();
                setupGetter(targetClass);
                if (mGetter == null) {
                    // Already logged the error - just return to avoid NPE
                    return;
                }
            }
            Object value = convertBack(mGetter.invoke(target));
            kf.setValue(value);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }



setAnimatedValue将新的属性值设置给对象可以看出Set也是反射的
    void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

使用动画注意的事项:
OOM(帧动画)

内存泄露(有一类无限循环的动画,在Activity提出时及时停止)

View动画问题:View是影像动画不是改变View的状态,有时候出现View无法隐藏,即setVisibility(View.Gonew)失效

这个时候用view.clearAnimation()清除动画。

尽量使用pd 不使用px

交互:3.0之前 View动画和属性动画,无法触发单击事件,老位置可以触发单击事件。3.0之后属性动画的单击事件是移动后的位置,但view动画还是在以前的位置。

使用动画时使用硬件加速。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值