1. 程式人生 > >Android 動畫知識總結

Android 動畫知識總結

動畫

概述

Android中的動畫可用分為 三 類: View 動畫、幀動畫、屬性動畫。

View動畫 包括平移、縮放、旋轉、透明度。支援自定義View滑動。

幀動畫 通過播放一系列影象從而產生動畫的效果。如果圖片過大,很容易發生 oom 。

屬性動畫 通過改變View的屬性而達到動畫的效果(API 11 的新特性 3.0)

View動畫

View動畫分為四類:

  • TranslateAnimation(位移動畫)
  • ScaleAnimation(縮放動畫)
  • RotateAnimation(旋轉動畫)
  • AlphaAnimation(透明動畫)

這四種動畫 既可以通過xml 的形式定義,也可以通過 程式碼來動態的建立。

xml 的形式定義 需要在 res資料夾下新建 anim資料夾,然後建立xml檔案。 即 res/anim/filename.xml

下面是四種動畫的定義格式 以及 set 動畫集。


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration=" "
    android:shareInterpolator
="[true | false]">
<translate android:fromXDelta="float" android:toXDelta="float" android:fromYDelta="float" android:toYDelta="float" /> <scale android:fromXScale="float" android:toXScale="float" android:fromYScale
="float" android:toYScale="float" android:pivotX="float" android:pivotY="float" />
<rotate android:fromDegrees="float" android:toDegrees="float" android:pivotX="float" android:pivotY="float" /> <alpha android:fromAlpha="float" android:toAlpha="float" /> </set>

android:interpolator 表示動畫集合所使用的插值器。

android:shareInterpolator 表示是否共享一個插值器。如果集合不指定插值器,那麼子動畫就需要單獨指定所需的插值器或 使用預設值。

平移 ——> TranslateAnimation類。使一個View 在水平和豎直方向上完成平移的動畫效果。屬性含義:

  • android:fromXDelta=”float” 表示x 的起始值,比如0;
  • android:toXDelta=”float” 表示 y 的起始值,比如100;
  • android:fromYDelta=”float” 表示 y 的 其實值;
  • android:toYDelta=”float” 表示 y 的結束值

縮放動畫 ——> ScaleAnimation。使一個View 具有放大或者縮小的動畫效果。 屬性含義:

  • android:fromXScale=”float” 表示水平方向縮放的起始值,比如 0.5;
  • android:toXScale=”float” 表示水平方向縮放的結束值,比如 1.2;
  • android:fromYScale=”float” 表示豎直方向縮放的起始值;
  • android:toYScale=”float” 表示豎直方向縮放的結束值;
  • android:pivotX=”float” 表示縮放軸點的x座標。會影響縮放效果
  • android:pivotY=”float” 表示縮放軸點的y座標. 會影響縮放效果

預設是 View的中心點。

旋轉動畫 ——> RotateAnimation 使一個View 具有旋轉的動畫效果。屬性含義:

  • android:fromDegrees=”float” 旋轉的開始的角度,比如 0
  • android:toDegrees=”float” 旋轉的結束角度,比如 180
  • android:pivotX=”float” 旋轉的軸點的x座標
  • android:pivotY=”float” 旋轉的軸點的y座標

旋轉的軸點會影響到旋轉的具體效果。軸點即 旋轉軸,View圍繞這旋轉軸旋轉。

透明度動畫 ——> AlphaAnimation 改變View的透明度。 屬性含義:

  • android:fromAlpha=”float” 透明度的起始值,比如 0.1
  • android:toAlpha=”float” 透明度的結束值,比如 1.

android:duration=” “ 表動畫的時長。

android:fillAfter=”true” 動畫執行完畢是否保持。

程式碼中如何載入呢??

通過AnimationUtils.loadAnimation(this,R.anim.xxx) 來載入。並呼叫控制元件的startAnimation來開啟一個動畫。

除了使用Xml 的形式也可以通過 程式碼去動態的建立一個動畫。 傳遞的引數和屬性一樣。不再贅述。

動畫的相關Listener回撥

 public static interface AnimationListener {

        void onAnimationStart(Animation animation);

        void onAnimationEnd(Animation animation);

        void onAnimationRepeat(Animation animation);
    }

自定義View動畫

需要繼承自 Animation 這個抽象類。 重寫它的 initialize 和 applyTransformation 方法。

applyTransformation 中進行相應的矩陣變換。很多時候採用 Camera 來簡化矩陣的變換過程。

可以參看 ApiDemo 中的動畫實現。 比如 Rotate3dAnimation.

幀動畫

幀動畫 —— 順序播放一組預先定義好的圖片。類似電影播放。 AnimationDrawable. 在res/drawable 下建立 xx.xml 檔案

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="xx" android:duration="200"/>
    <item android:drawable="xx" android:duration="200"/>
    <item android:drawable="xx" android:duration="200"/>
    <item android:drawable="xx" android:duration="200"/>
</animation-list>

將Drawable 作為 View 的背景並通過 Drawable 來播放動畫即可。

imageView.setBackgroundResource(R.drawable.anim_drawable);

        AnimationDrawable drawable = (AnimationDrawable) imageView.getBackground();
        drawable.start();

幀動畫的使用比較簡單,但是比較容易引起 OOM。 所以在使用幀動畫時儘量表面使用過多尺寸較大的圖片。

View動畫的特殊使用場景。

1.LayoutAnimation

這個動畫作用於 ViewGroup。 為ViewGroup 指定一個動畫, 這樣子控制元件出場時就會有 動畫效果。

LayoutAnimation 也是一種View動畫。

步驟:

1.定義LayoutAnimation 在 anim/ 目錄下。

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

delay 屬性表示子元素的開始動畫的時間延遲。比如子元素的入場動畫時間為300ms,那麼 0.5表示每個子元素都需要延遲 150ms 才能播放入場動畫。 第一個延遲150ms 第二個 延遲300ms。

animationOrder 動畫播放的順序, normal 、reverse、 random。

animation 指定子元素的具體入場動畫

2.為子元素指定具體的入場動畫。

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration=" "

    android:shareInterpolator="[true | false]">


    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float"
        />

    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float"
        />
</set>

3.為 viewGroup 指定 android:layoutAnimation 屬性。

除了在 xml 中指定 LayoutAnimation。 還可以通過 LayoutAnimationController 來實現。

ListView listView = (ListView) layout.findViewById(R.id.list);
        Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
        LayoutAnimationController controller = new LayoutAnimationController(animation);
        controller.setDelay(0.5f);
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
        listView.setLayoutAnimation(controller);

2.Activity的切換效果

通過overridePendingTransition(enterAnim,exitAnim) 必須在 startActivity 和 finish 之後呼叫。

屬性動畫

Api 11 退出的,Android3.0。

屬性動畫可以對任何物件做動畫,也可以沒有物件。屬性動畫有 ValueAnimator、ObjectAnimator、AnimatorSet等概念。

屬性動畫的預設時間是300ms,預設幀率是 10ms/幀。 屬性動畫的效果是: 在一個時間間隔內完成物件從一個屬性值到另一個屬性值的改變。

如果需要做動畫的相容,可以使用nineoldandroids類庫。 但是該動畫庫,在android3.0及以上的系統是使用屬性動畫,但在3.0以下任然是View動畫。

常用的幾個動畫類: ValueAnimatorObjectAnimatorAnimatorSet .

ObjectAnimator 繼承自 ValueAnimator 。 AnimatorSet 是動畫集合,可以定義一組動畫。

1.如下 該控制元件在 5s 內 沿著 y 軸方向移動 500的距離。

ObjectAnimator.ofFloat(mButton3,"translationY",500).setDuration(5000).start();

2.在5s內顏色從 GREEN 到 RED 的變換。

ObjectAnimator.ofInt(mButton3,"backgroundColor",Color.GREEN,Color.RED).setDuration(5000).start();

3.動畫集合,對控制元件的一系列操作操作。

AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(

                ObjectAnimator.ofFloat(mButton3, "translationY", 500),
                   ObjectAnimator.ofInt(mButton3, "backgroundColor", Color.GREEN, Color.RED),
                   ObjectAnimator.ofFloat(mButton3, "rotationX", 0, 360),
                ObjectAnimator.ofFloat(mButton3, "rotationY", 0, 180)
        );
        animatorSet.setDuration(5000).start();

屬性動畫除了通過程式碼實現外, 還可以通過 XML 來定義。

屬性動畫的定義需要在 res/animator 目錄下。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially | together"
    >

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float|int|color"
        android:valueTo="float|int|color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode="reverse | reverse"
        android:valueType="floatType | intType"
        />

    <animator
        android:duration="int"
        android:valueFrom="float|int|color"
        android:valueTo="float|int|color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode="reverse | reverse"
        android:valueType="floatType | intType"
        />

</set>

Xml 定義了 ValueAnimator、ObjectAnimator 以及 AnimatorSet. 標籤對應 AnimatorSet。 標籤對應 ValueAnimator, 對應 ObjectAnimator.

標籤的 ordering屬性有兩個值: together 和 sequentially。 together 表示動畫一起同時播放。
sequentially 表示按照前後順序播放。預設是 together。

對於repeatCount 屬性表示動畫迴圈的次數, 預設值為0, 其中 -1 表示無限迴圈

repeatMode 表示動畫迴圈的模式, 有兩個選項: repeat 和 reverse, 表示連續重複還是逆向重複 連續重複 即每次都是從頭開始。 逆向重複 表示第一次播放完以後,第二次會倒著播放,第三次再重頭開始…

程式碼中如何載入動畫呢??? 如下:

Animator animator = AnimatorInflater.loadAnimator(context, R.anim.xxx);
animator.setTarget(imageView);
animator.start();

在實際的開發中建議採用 程式碼動態實現屬性動畫, 實現比較簡單 而且要靈活一些。 很多時候一個屬性的起始值是無法提前確定的等等。

插值器 和 估值器

TimeInterpolator 是插值器的超類, 翻譯過來是 時間插值器。 它的作用是根據時間流逝的百分比來計算出當前屬性值改變的百分比。系統預置的有 LinearInterpolator(線性插值器: 勻速動畫)、AccelerateDecelerateInterpolator(加減速插值器,開始和結束慢,中間快), DecelerateInterpolator (減速插值器, 動畫越來越慢。)

TypeEvaluator 中文翻譯為型別估值演算法。 也稱作估值器. 它的作用是根據當前屬性改變的百分比來計算改變後的屬性值。

public class IntEvaluator implements TypeEvaluator<Integer> {

    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

evaluate 的三個引數分別表示 估值小數、 開始值 和 結束值。

可以自定義插值器和 估值器。

自定義插值器需要實現 Interpolator 或者 TimeInterpolator.

自定義估值演算法需要實現 TypeEvaluator. 如果對其他型別(非 int、float、Color)做動畫,那麼必要實現自定義型別估值演算法。

屬性動畫的監聽器

有兩個介面:

  • AnimatorUpdateListener
  • AnimatorListener

AnimatorListener的定義如下,包括開始、結束、取消、以及重複播放:

public static interface AnimatorListener {

        void onAnimationStart(Animator animation);

        void onAnimationEnd(Animator animation);

        void onAnimationCancel(Animator animation);

        void onAnimationRepeat(Animator animation);
    }

為了方便開發系統還提供了 AnimatorListenerAdapter 這個類。它是 AnimatorListener 的介面卡類。實現了 AnimatorListener,並做了空實現,這樣我們就可以選擇性的實現上面的方法了。

AnimatorUpdateListener 的定義如下:

public static interface AnimatorUpdateListener {

        void onAnimationUpdate(ValueAnimator animation);

    }

AnimatorUpdateListener 會監聽整個動畫的過程,動畫是由許多幀組成的,每播放一幀, onAnimationUpdate 就會被呼叫一次。

對任意屬性做動畫

比如 要對 object 的屬性 abc 做動畫, 如果想讓動畫生效,必須滿足兩個條件:

1.object 必須要提供setAbc 方法, 如果動畫沒有傳遞初始值, 那麼還需要提供 getAbc方法, 因為系統要去取 abc屬性的初始值(如果這條不滿足,程式直接Crash)

2.object的 setAbc 對屬性abc 所做的改變必須能夠通過某種方法反應出來。比如會帶來 ui 之類的改變(如果不滿足,動畫無效果,不會crash)

比如對 Button 的 setWidth 做動畫, 則沒有效果。 這是因為雖然內部提供了 setWidth 和 getWidth ,但並不是改變檢視的大小。

解決方案:

1.給你的物件加上 get 和 set 方法,如果你有許可權的話。

2.用一個類來包裝原始物件,間接為期提供 get 和 set 方法。

3.採用ValueAnimator 監聽動畫過程,自己實現屬性的改變。

屬性動畫的工作原理

ok, 我們從 ObjectAnimator 的 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();
    }

首先判斷當前動畫、等待的動畫、和延遲的動畫 中有和當前動畫相同的動畫,那麼就把相同的動畫給取消掉。 接著就呼叫了父類的 super.start() 方法。 即 ValueAnimator 的 start 方法。

 private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mPlayingBackwards = playBackwards;
        mCurrentIteration = 0;
        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
            setCurrentPlayTime(0);
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();
    }

首先校驗了Looper。 說明屬性動畫 需要執行在有 Looper 的執行緒中。 上述程式碼最後會呼叫 AnimationHandler 的start 方法。 這個 AnimationHandler 並不是 Handler 實質上是一個 Runnable.

protected static class AnimationHandler implements Runnable {}

在 AnimationHandler 的 start() 方法中 進行了一系列的呼叫, 調到了 native 層。 Jni 層最終還是要調回來的。 它的 run 會被呼叫, 這個Runnable 涉及和底層的互動。 我們直接忽略這部分。 c 層 會呼叫這個的 run 方法,如下所示:

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

直接看重點, ValueAnimator 的 doAnimationFrame 方法:

 /**
     * Processes a frame of the animation, adjusting the start time if needed.
     *
     * @param frameTime The frame time.
     * @return true if the animation has ended.
     */
    final boolean doAnimationFrame(long frameTime) {
        if (mPlayingState == STOPPED) {
            mPlayingState = RUNNING;
            if (mSeekTime < 0) {
                mStartTime = frameTime;
            } else {
                mStartTime = frameTime - mSeekTime;
                // Now that we're playing, reset the seek time
                mSeekTime = -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);
    }

上述程式碼的末尾呼叫了 animationFrame 方法, 而 animationFrame 內部呼叫了 animateValue 下面看 animateValue的程式碼:

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 會計算每幀動畫所對應的屬性值。

下面著重看一下到底在哪裡呼叫 屬性的 get 和 set 方法。 如果沒有設定初始值 則get方法將會被呼叫。

我們來看 PropertyValuesHolder 的 setupValue方法。

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());
        }
    }

當下一幀到來的時候, PropertyValuesHolder 中的 setAnimatedValue 方法會將新的屬性值 設定給物件。通過 反射來進行設定的。

 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());
            }
        }
    }

使用動畫的注意事項

1.OOM 問題

主要出現在幀動畫中,圖片數量多時且圖片較大 就容易出現 OOM 需要注意。避免使用 幀動畫。

2.記憶體洩漏

屬性動畫中 無限的迴圈動畫,在Activity退出時要及時停止。否則導致 Activity 無法釋放。

3.相容性問題

在3.0一下系統的相容性問題。

4.View動畫問題

View動畫是對 View 的影像動畫。 並不是真正地改變View的狀態。因此有時候會出現 動畫完成後 View 無法隱藏的現象, 即 setVisibility 失效了。 只需要呼叫 clearAnimation 清除 View動畫即可。

5.不要使用px

要儘量使用 dp 。使用 Px 會導致在不同的裝置上有不同的效果。