1. 程式人生 > >Android動畫(二)-屬性動畫

Android動畫(二)-屬性動畫

概述

上一篇主要介紹了ViewAnim和幀動畫,篇幅有點長,另起一篇。上篇介紹的兩種動畫開發中用到的不多,主要還是本篇的屬性動畫使用比較廣。

1 補間動畫

1.1 Property Anim

開發中Property Anim使用比View Anim要更為廣泛,主要還是出於剛剛提到過的View Anim執行之後View的位置沒有變化。
有的時候我們確實是需要改變View位置的。

1.1.1 Object Anim

使用之前先介紹一些屬性的含義

(1) translationX 和 translationY:這兩個屬性控制著 View 的螢幕位置座標變化量,以 layout 容器的左上角為座標原點;

(2) rotation、rotationX 和 rotationY:這三個屬性控制著 2D 旋轉角度(rotation屬性)和圍繞某樞軸點的 3D 旋轉角度;

(3) scaleX、scaleY:這兩個屬性控制著 View 圍繞某樞軸點的 2D 縮放比例;

(4) pivotX 和 pivotY: 這兩個屬性控制著樞軸點的位置,前述的旋轉和縮放都是以此點為中心展開的,預設的樞軸點是 View 物件的中心點;

(5) x 和 y:這是指 View 在容器內的最終位置,等於 View 左上角相對於容器的座標加上 translationX 和 translationY 後的值;

(6)alpha:表示 View 的 alpha 透明度。預設值為 1 (不透明),為 0 則表示完全透明(看不見);

補間動畫能實現的他都可以實現,舉個栗子!

例1:從當前位置移動到Y軸300的位置

這裡寫圖片描述

    private void startObjAnim() {
        // 第一個引數是執行動畫的View,第二個引數是該View需要改變的屬性,第三個引數是執行開始Y座標,以螢幕座標系為參照,第四個引數是移動到的位置
        ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(mContentIv, "Y", mContentIv.getY(), 300);
        objectAnimatorY.setDuration(2000
);//動畫執行時間 objectAnimatorY.setRepeatCount(2);//重複次數 objectAnimatorY.setRepeatMode(ValueAnimator.REVERSE);//重複模式 objectAnimatorY.setInterpolator(new BounceInterpolator());//插值器 objectAnimatorY.start(); }

這個例子不難看懂,但是如果想同時移動X,Y,並且要閃爍旋轉呢?

例2:從當前位置移動到(100,300),移動中閃爍旋轉

這裡就不能再用AnimationSet了,因為ObjectAnimator是Animator的子類,我們使用AnimatorSet

    private void startObjAnim1() {
        AnimatorSet animatorSet = new AnimatorSet();
        ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(mContentIv, "Y", mContentIv.getY(), 300);
        ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(mContentIv, "X", mContentIv.getX(), 100);
        ObjectAnimator objectAnimatorRx = ObjectAnimator.ofFloat(mContentIv, "rotationX", 0f, 180f);
        ObjectAnimator objectAnimatorSx = ObjectAnimator.ofFloat(mContentIv, "scaleX", 1f, 0.5f);
        ObjectAnimator objectAnimatorA = ObjectAnimator.ofFloat(mContentIv, "alpha", 1f, 0.5f);
        animatorSet.play(objectAnimatorX).with(objectAnimatorY).with(objectAnimatorA).with(objectAnimatorSx).with(objectAnimatorRx);
        animatorSet.setDuration(3000);
        animatorSet.start();
    }

不難看懂,Animator一路構造,也可以指定在某個動畫之前或之後呼叫,將with()換為befer()|after()。

之前在用ViewAnim做旋轉的時候提了一個面試題,旋轉之後保持現在的位置,那麼用Property Anim就可以做了。

例3:播放按鈕

這裡寫圖片描述

    //開始
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private void startObjAnim2() {
        if (objectAnimatorR == null) {
            objectAnimatorR = ObjectAnimator.ofFloat(mContentIv, "rotation", 0f, 360f);
            objectAnimatorR.setDuration(3000);
            objectAnimatorR.setRepeatCount(3);
            objectAnimatorR.start();
        }else{
            objectAnimatorR.resume();
        }
    }

    //暫停
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private void switchAnimStop(String selectedItem) {
        if (objectAnimatorR != null && objectAnimatorR.isStarted()) {
            objectAnimatorR.pause();
        }
    }

主要就是Animator提供了一個pause和resume方法,簡單看下內部做了什麼事情。

    /**
     * 暫停執行中的動畫,如果動畫已經結束或還沒開始,則該方法無效。
     * 可以通過呼叫resume()方法來繼續執行動畫
     */
    public void pause() {
        if (isStarted() && !mPaused) {
            mPaused = true;
            if (mPauseListeners != null) {
                ArrayList<AnimatorPauseListener> tmpListeners =
                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
                int numListeners = tmpListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    tmpListeners.get(i).onAnimationPause(this);
                }
            }
        }
    }

在執行pause的時候將mPaused置為true,動畫暫停了,可以思考一下,動畫執行中也是逐幀執行,應該是一個迴圈,並且還判斷了當前的pause狀態。那麼我們接著看這個猜想是否正確,看下動畫是如何執行的。

我們呼叫start方法,動畫開始執行。

 private void start(boolean playBackwards) {
        ...
        mStarted = true;
        mPaused = false;
        mRunning = false;
        ...
        AnimationHandler animationHandler = AnimationHandler.getInstance();
        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

        if (mStartDelay == 0 || mSeekFraction >= 0) {
            startAnimation();
            if (mSeekFraction == -1) {
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

這裡去掉了一些程式碼和註釋,我們想一下動畫執行也是很多工幀,應該也是一個執行緒去處理這個事情。

點進startAnimation和setCurrentFraction方法去看,都是一些設定isRunning,mSeekFraction等一些值的操作,沒有看到Thread,Runnable這些的影子。於是我們看下AnimationHandler.addAnimationFrameCallback

    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }

        if (delay > 0) {
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }

mAnimationCallbacks這個明顯是個集合放了很多監聽器,我們在第一次呼叫的時候走的應該是mAnimationCallbacks.size() == 0這個邏輯,那麼下面那個Provider是個什麼鬼??

點進去看他new 了一個MyFrameCallbackProvider,這裡面有一個類Choreographer,在點下去看!

介紹是 * Coordinates the timing of animations, input and drawing.關聯了動畫時間,輸出和繪製!好像是我們在找的東西!再往下看他的介紹,確認了他就是讓動畫執行的類。

啊哈!看到了ThreadLocal,終於被我們找到了!但是並不是我們需要的。在看,postFrameCallback,一路點下去有個doCallbacks的方法,看一下。

 void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
            ... //這段去掉,我們只看CALLBACK_ANIMATION的部分

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                c.run(frameTimeNanos);
            }

        } ...//省略了finally處理,
    }

看到呼叫了一個run方法,點進去看下,看到他呼叫了傳入回撥的doFrame方法回傳了frameTimeNanos,如果不是FRAME_CALLBACK_TOKEN則呼叫runnable的run

計算完grameTimeNanos後回撥給了AnimatorHandler執行doAnimationFrame,並繼續下一幀的計算

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

看下doAnimationFrame回撥到了ValueAnimtor裡面的方法

    public final void doAnimationFrame(long frameTime) {
       ...
        if (mPaused) {
            mPauseTime = frameTime;
            handler.removeCallback(this);
            return;
        } else if (mResumed) {
            mResumed = false;
            if (mPauseTime > 0) {
                // Offset by the duration that the animation was paused
                mStartTime += (frameTime - mPauseTime);
                mStartTimeCommitted = false; // allow start time to be compensated for jank
            }
            handler.addOneShotCommitCallback(this);
        }
      ...
    }

終於被我們找到了如果是暫停狀態則移除回撥監聽,不會在繪製,當我們在呼叫resume的時候有添加了callback繼續繪製,直到動畫完成。

不知不覺看了有點多,小結一下

  • ObjectAnim是通過執行緒計算每一幀繪製的
  • 在呼叫pause時候任務還在進行,但是不會走回調顯示的方法
  • 重新resume之後接著上次的位置繼續繪製直到動畫結束

因此我們的需求可以滿足,也可以看出來其實pause之後動畫任務還是在消耗資源

1.1.2 ValueAnimtor實現動畫

經過上面的分析也不難看出ValueAnimtor是ObjAnim的父類,他們都是Animtor的子類。當然使用ObjectAnimtor要比ValueAnimtor方便,這裡也介紹下ValueAnimtor是怎麼用的吧~

例1:將View縮放到自身的一半,用ValueAnimtor實現

這裡寫圖片描述

    private void startValueAnim1() {
        //1. 建立控制代碼
        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 0.5f);
        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1f, 0.5f);
        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleX, scaleY);
        //2. 設定屬性變化監聽
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //3. 獲取變化值並設定給View
                float animatorValueScaleX =  (float) animation.getAnimatedValue("scaleX");
                float animatorValueScaleY = (float) animation.getAnimatedValue("scaleY");
                mContentIv.setScaleX(animatorValueScaleX);
                mContentIv.setScaleY(animatorValueScaleY);
            }
        });
        valueAnimator.setDuration(2000);
        valueAnimator.setRepeatCount(2);
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.start();
    }

1.2 Layout Anim

除了上述的幾種動畫外,還有一種動畫,在開發中也遇到了,下面也介紹一下佈局動畫。

1.2.1 LayoutAnimation

LayoutAnimation 是API Level 1 就已經有的,LayoutAnimation是對於ViewGroup控制元件所有的child view的操作,也就是說它是用來控制ViewGroup中所有的child view 顯示的動畫。

這裡寫圖片描述

XML實現方式

1.在res/anim包下面申明動畫效果 anim_top_to_down.xml

<?xml version="1.0" encoding="utf-8"?>
<!--向下移動到介面一半並透明-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:repeatMode="reverse"
     android:duration="3000">
    <translate
        android:fromYDelta="0"
        android:toYDelta="50%"/>
    <alpha
        android:fromAlpha="1"
        android:toAlpha="0.5"/>
</set>

2.在res/anim包下宣告anim_cust_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
                 android:animation="@anim/anim_top_to_down"
                 android:animationOrder="reverse"
                 android:delay="30%"/>

3.在佈局檔案中新增
android:layoutAnimation="@anim/anim_cust_layout"

程式碼實現

   //通過載入XML動畫設定檔案來建立一個Animation物件;
   Animation animation=AnimationUtils.loadAnimation(this, R.anim.slide_right);   //得到一個LayoutAnimationController物件;
   LayoutAnimationController controller = new LayoutAnimationController(animation);   //設定控制元件顯示的順序;
   controller.setOrder(LayoutAnimationController.ORDER_REVERSE);   //設定控制元件顯示間隔時間;
   controller.setDelay(0.3);   //為ListView設定LayoutAnimationController屬性;
   listView.setLayoutAnimation(controller);
   listView.startLayoutAnimation();

1.2.2 LayoutTransition

LayoutTransition 是API Level 11 才出現的。LayoutTransition的動畫效果,只有當ViewGroup中有View新增、刪除、隱藏、顯示的時候才會體現出來。

  • 在xml中android:animateLayoutChanges="true"
  • 為ViewGroup新增動畫 LayoutTransition mTransitioner = new LayoutTransition();
    mViewGroup.setLayoutTransition(mTransitioner);

其實也是ObjectAnimtor實現的。

學習的時候在網上看到一個例子,也記錄一下。

這裡寫圖片描述

原文地址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0619/3090.html

文中是使用佈局動畫改編每個佈局的播放順序實現的。我們換一種方式

使用了ObjAnim的方式,自己感覺這樣確實很蠢,使用了動畫延遲

    public void scaleView(List<View> vs) {
        int l = 0;
        AnimatorSet set = new AnimatorSet();
        for (int i = 0; i < vs.size(); i++) {
            View v = vs.get(i);
            if (i == vs.size() / 2)
                l = 100;
            l += 100;
            AnimatorSet itemSet = new AnimatorSet();
            ObjectAnimator scaleX = ObjectAnimator.ofFloat(v, "scaleX", 1f, 0f);
            scaleX.setRepeatMode(ValueAnimator.RESTART);
            ObjectAnimator scaleY = ObjectAnimator.ofFloat(v, "scaleY", 1f, 0f);
            scaleY.setRepeatMode(ValueAnimator.RESTART);
            ObjectAnimator alpha = ObjectAnimator.ofFloat(v, "alpha", 1f, 0f);
            alpha.setRepeatMode(ValueAnimator.RESTART);
            itemSet.play(scaleX).with(scaleY).with(alpha);
            itemSet.setDuration(250);
            itemSet.setStartDelay(l);
            set.play(itemSet);
        }
        set.start();
    }

1.3 插值器(Interpolator)

計算執行速度的類。

1.3.1 常見插值器

  • AccelerateDecelerateInterpolator 在動畫開始與介紹的地方速率改變比較慢,在中間的時候加速
  • AccelerateInterpolator 在動畫開始的地方速率改變比較慢,然後開始加速
  • AnticipateInterpolator 開始的時候向後甩一點然後向前
  • AnticipateOvershootInterpolator 開始的時候向後甩一點然後向前超過設定值一點然後返回
  • BounceInterpolator 動畫結束的時候彈起,類似皮球落地
  • CycleInterpolator 動畫迴圈播放特定的次數回到原點,速率改變沿著正弦曲線
  • DecelerateInterpolator 在動畫開始的地方快然後慢
  • LinearInterpolator 以常量速率改變
  • OvershootInterpolator 向前超過設定值一點然後返回

此處參考:http://blog.csdn.net/daydayplayphone/article/details/52503665

有圖可以自己去看更直觀,其實很簡單,常用的記住就好,英文意思很明確了

下面我們看下如何自己定義一個插值器!

1.3.2自定義插值器

自定義插值器需要實現 Interpolator / TimeInterpolator介面 & 複寫getInterpolation()

  • 補間動畫 實現 Interpolator介面;屬性動畫實現TimeInterpolator介面
  • TimeInterpolator介面是屬性動畫中新增的,用於相容Interpolator介面,這使得所有過去的Interpolator實現類都可以直接在屬性動畫使用
  /**
     * 插值分數
     * @param input 值在0~1之間,表明當前點在動畫中的位置,0是起始點,1是結束點
     * @return 插值分數< 0低於目標,>1則超過目標
     */
    float getInterpolation(float input);

1.4 估值器(TypeEvaluator)

作用:設定 屬性值 從初始值過渡到結束值 的變化具體數值

  • 插值器(Interpolator)決定 值 的變化規律(勻速、加速blabla),即決定的是變化趨勢;而接下來的具體變化數值則交給估值器
  • 協助插值器 實現非線性運動的動畫效果
// 在第4個引數中傳入對應估值器類的物件
ObjectAnimator anim = ObjectAnimator.ofObject(myView2, "height", new Evaluator(),13);

系統內建的估值器有3個:
- IntEvaluator:以整型的形式從初始值 - 結束值 進行過渡
- FloatEvaluator:以浮點型的形式從初始值 - 結束值 進行過渡
- ArgbEvaluator:以Argb型別的形式從初始值 - 結束值 進行過渡

*注 那麼插值器的input值 和 估值器fraction有什麼關係呢?

答:input的值決定了fraction的值:input值經過計算後傳入到插值器的getInterpolation(),然後通過實現getInterpolation()中的邏輯演算法,根據input值來計算出一個返回值,而這個返回值就是fraction了