1. 程式人生 > >Android中的動畫和原理(屬性動畫)

Android中的動畫和原理(屬性動畫)

1、屬性動畫

屬性動畫通過改變物件的屬性來展示的動畫效果,補間動畫只是設定當前View在區域內移動,產生的動畫效果,其實原View的還在原地,沒有發生改變。
但屬性動畫改變了物件的屬性。也就是改變了物件的顏色,位置,寬高等。

2、示例

public class MainActivity extends AppCompatActivity {
    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.imageView);
        ObjectAnimator translationX = new
ObjectAnimator().ofFloat(imageView,"translationX",0,600f); ObjectAnimator translationY = new ObjectAnimator().ofFloat(imageView,"translationY",0,0); AnimatorSet animatorSet = new AnimatorSet(); //組合動畫 animatorSet.setInterpolator(new LinearInterpolator()); //設定時間插值器 animatorSet.playTogether(translationX,translationY); //設定動畫
animatorSet.setDuration(3000); //設定動畫時間 animatorSet.start(); //啟動 } }

3、插值器和估值器

1)下圖描述了一個物件,該物件的X屬性,表示螢幕上的水平位置。動畫的持續時間設定為40毫秒,移動的距離為40畫素。每10毫秒,該物件是預設幀重新整理率,物件水平移動10畫素。在40ms結束時,動畫停止,物件在水平位置40結束。這是一個線性插值器動畫的例子,以恆定速度移動。
這裡寫圖片描述

2)還可以指定動畫以進行非線性插值。圖2示出了在動畫開始時加速的物件,並在動畫結束時減速。物件仍然在40毫秒內移動40個畫素,但非線性。開始時,動畫加速,然後減速直到動畫結束。如圖2所示,動畫的開始和結束的距離小於中間的距離。

這裡寫圖片描述

4、屬性動畫

屬性動畫的重要元件
這裡寫圖片描述

1)ValueAnimator追蹤物件,比如物件的執行時間,當前的屬性值。
2)ValueAnimator封裝了TimeInterpolator,它定義了動畫的插值演算法,而且定義了一個TypeEvaluator,它計算了如何去計算屬性動畫。
3)要開始一個動畫,首先需建立一個ValueAnimator並且要定義動畫的屬性的開始和結束的值,還有動畫持續的時間。當呼叫start() 時,動畫就開始了。在動畫進行時,ValueAnimator 跟據動畫的持續時間和已經過的時間,計算出一個百分比(0和1之間),進度分數代表了動畫已進行的時間的百分比,0代表0%,1代表100%。例如,圖1中進度分數 在t = 10 ms時值為0.25,因為總時間是t = 40 ms。
4)當ValueAnimator 計算完成動畫的進度時,呼叫TimeInterpolator 去計算一個插值分數。插入分數結合所設定的時間插值把進度分數對映到一個新的分數。例如,在圖2中,因為動畫緩慢加速,在 t = 10 ms時,插值分數為0.15,小於進度分數為0.25。在圖1中,插值分數進度分數永遠相等。
5)計算插值函式時,ValueAnimator 會呼叫適當的TypeEvaluator來基於插值函式、開始值、結束值計算你在動畫的屬性的值。例如,在圖2中,插值函式值在 t = 10 ms時為0.15 ,所以些時屬性的值是6。

5、屬性動畫原理

1、start()方法動畫啟動

 @Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        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();
    }

如果有和當前執行一樣的動畫,則取消。然後就是呼叫super.start()方法。
2、start()方法

 /**
     * Start the animation playing. This version of start() takes a boolean flag that indicates
     * whether the animation should play in reverse. The flag is usually false, but may be set
     * to true if called from the reverse() method.
     *
     * <p>The animation started by calling this method will be run on the thread that called
     * this method. This thread should have a Looper on it (a runtime exception will be thrown if
     * this is not the case). Also, if the animation will animate
     * properties of objects in the view hierarchy, then the calling thread should be the UI
     * thread for that view hierarchy.</p>
     *
     * @param playBackwards Whether the ValueAnimator should start playing in reverse.
     */
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        // Special case: reversing from seek-to-0 should act as if not seeked at all.
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                // Calculate the fraction of the current iteration.
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        // Resets mLastFrameTime when start() is called, so that if the animation was running,
        // calling start() would put the animation in the
        // started-but-not-yet-reached-the-first-frame phase.
        mLastFrameTime = 0;
        AnimationHandler animationHandler = AnimationHandler.getInstance();
        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

        if (mStartDelay == 0 || mSeekFraction >= 0) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            //呼叫startAnimation方法,啟動動畫
            startAnimation();
            if (mSeekFraction == -1) {
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

可以看到,實際上動畫的執行,是呼叫父類的start()方法。
如果沒有動畫延遲,則開始執行動畫,並設定監聽事件。則呼叫startAnimation()開啟動畫

3)進入到startAnimation()方法

 private void startAnimation() {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                    System.identityHashCode(this));
        }

        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            notifyStartListeners();
        }
    }

很遺憾,在這它又只是做了初始化動畫方法,接著進入到initAnimation()

4)最終,進入到initAnimation()方法

  /**
     * This function is called immediately before processing the first animation
     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
     * function is called after that delay ends.
     * It takes care of the final initialization steps for the
     * animation.
     *
     *  <p>Overrides of this method should call the superclass method to ensure
     *  that internal mechanisms for the animation are set up correctly.</p>
     */
    @CallSuper
    void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
            //計算每幀動畫的屬性值 
                mValues[i].init();
            }
            mInitialized = true;
        }
    }

上面程式碼中mValues[i].init()方法便是計算動畫的屬性值,那麼它通過什麼來計算呢?先看mValues,屬性動畫的集合

   /**
     * The property/value sets being animated.
     */
    PropertyValuesHolder[] mValues;

也就是我們在定義屬性動畫的時候,有set方法,我們通過set方法來給動畫設定屬性,最張一步一步的完成動畫的。