Android過場動畫基礎教程

lusing發表於2016-01-19

Tween(補間)動畫基礎

在討論系統動畫之前,我們先複習一下Tween動畫,也就是俗稱的補間動畫的基礎。

補間動畫的型別和屬性

補間動畫有4種型別:

  • Alpha: 淡入淡出,改變透明度
  • Scale: 大小縮放
  • Translate: 位移變化
  • Rotate:旋轉

Translate動畫的屬性

  • android:fromXDelta:X軸的起始座標
  • android:toXDelta:X軸的結束座標
  • android:fromYDelta:Y軸的起始座標
  • android:toYDelta:Y軸的結束座標
  • android:duration:動畫時長

例:

    <translate
        android:duration="320"
        android:fillAfter="true"
        android:fillBefore="true"
        android:fillEnabled="true"
        android:fromXDelta="100%"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:toXDelta="0" />

所以上面的動畫是從螢幕最右(100%)到最左(0)進行位置移動。

我們再看Android L的activity open enter動畫:

Alpha動畫的屬性

  • android:fromAlpha:起始時的透明度,1為不透明,0為全透明。
  • android:toAlpha:結束時的透明度

例:

    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:interpolator="@android:anim/decelerate_interpolator"
            android:fillEnabled="true"
            android:fillBefore="false" android:fillAfter="true"
            android:duration="200"/>

這個動畫是由200ms的從全透明變成不透明。

Scale動畫的屬性

  • android:fromXScale:起始X座標
  • android:toXScale:結束X座標
  • android:fromYScale:起始Y座標
  • android:toYScale:結束Y座標
  • android:pivotX:X軸的軸座標,也就是說X軸以這個軸為對稱軸
  • android:pivotY:Y軸的軸座標
    <scale
        android:duration="300"
        android:fillAfter="true"
        android:fillBefore="false"
        android:fillEnabled="true"
        android:fromXScale=".8"
        android:fromYScale=".8"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:pivotX="50%p"
        android:pivotY="50%p"
        android:toXScale="1.0"
        android:toYScale="1.0" />

於是上面的動畫解釋成,從X軸0.8位置,Y軸0.8位置,以父控制元件的50%為對稱軸,向X軸1.0位置,Y軸1.0位置進行放大。

Interpolator

Interpolator的用途在於控制插值顯示的速度,可以支援加速減速的效果。

從API11開始,所有的Interpolator都是從TimeInterpolator介面派生出來的。TimeInterpolator只定義了一個方法:

public abstract float getInterpolation (float input)

輸入值是[0.0,1.0]區間的一個數,表示動畫的進度。0.0表示動畫開始,1.0表示動畫結束。返回值是輸入值的函式,值域也在[0.0,1.0]中。

Interpolator介面實現了TimeInterpolator介面,但是並沒有新增新的方法。Interpolator介面在API1時候就已經有了,只是繼承關係不同。

從API 22開始,增加了BaseInterpolator抽象類http://developer.android.com/reference/android/view/animation/BaseInterpolator.html,實現了Interpolator介面。

  • LinearInterpolator
    最省事兒的了。函式{noformat}y=x{noformat}
public float getInterpolation(float input) {
    return input;
}
  • AccelerateInterpolator
    構造時可以指定係數,當係數為1.0f時,效果為y=x^2的拋物線,非1.0f時,則為x的係數次方,可以為非整數。實現加速效果。

    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

例,改變階數,按y=x^2.5曲線,來自frameworks/base/core/res/res/interpolator/accelerate_quint.xml

<?xml version="1.0" encoding="utf-8"?>
<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
        android:factor="2.5" />
  • DecelerateInterpolator
    1.0f時,產生倒過來的y=x^2拋物線。實現減速效果
    public float getInterpolation(float input) {
        float result;
        if (mFactor == 1.0f) {
            result = (float)(1.0f - (1.0f - input) * (1.0f - input));
        } else {
            result = (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
        }
        return result;
    }

例:frameworks/base/core/res/res/interpolator/decelerate_quint.xml

<?xml version="1.0" encoding="utf-8"?>
<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:factor="2.5" />
  • AccelerateDecelerateInterpolator

以0.5為界,先快後慢。官方文件上沒有演算法介紹,於是我們自己看code.

public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
  • CycleInterpolator
  • 迴圈的,好辦啊,找個週期性函式就是了,比如正弦函式。
public float getInterpolation(float input) {
    return (float)(Math.sin(2 * mCycles * Math.PI * input));
}
  • AnticipateInterpolator

高階效果開始了,張力出馬。這個是開始的時候向後,然後向前甩的效果。公式:a(t) = t t ((tension + 1) * t – tension)。

public float getInterpolation(float t) {
    return t * t * ((mTension + 1) * t - mTension);
}
  • OvershootInterpolator

向前甩一定值後再回到原來位置

public float getInterpolation(float t) {
    // _o(t) = t * t * ((tension + 1) * t + tension)
    // o(t) = _o(t - 1) + 1
    t -= 1.0f;
    return t * t * ((mTension + 1) * t + mTension) + 1.0f;
}
  • AnticipateOvershootInterpolator

開始的時候向後然後向前甩一定值後返回最後的值

public float getInterpolation(float t) {
    // a(t, s) = t * t * ((s + 1) * t - s)
    // o(t, s) = t * t * ((s + 1) * t + s)
    // f(t) = 0.5 * a(t * 2, tension * extraTension), when t < 0.5
    // f(t) = 0.5 * (o(t * 2 - 2, tension * extraTension) + 2), when t <= 1.0
    if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
    else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
}
  • BounceInterpolator

動畫結束的時候彈起

    private static float bounce(float t) {
        return t * t * 8.0f;
    }

    public float getInterpolation(float t) {
        // _b(t) = t * t * 8
        // bs(t) = _b(t) for t < 0.3535
        // bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
        // bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
        // bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
        // b(t) = bs(t * 1.1226)
        t *= 1.1226f;
        if (t < 0.3535f) return bounce(t);
        else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
        else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
        else return bounce(t - 1.0435f) + 0.95f;
    }
  • PathInterpolator

一個新的基於貝塞爾曲線或路徑物件的插入器
用法示例:frameworks/base/core/res/res/interpolator/linear_out_slow_in.xml

<?xml version="1.0" encoding="UTF-8"?>
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:controlX1="0"
    android:controlX2="0.2"
    android:controlY1="0"
    android:controlY2="1" />

如何使用Interpolator

直接引用android:anim的值

例:

        android:interpolator="@android:anim/decelerate_interpolator"

寫個xml來設引數

例:

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

Activity的4種過場動畫

Activity有4種過場動畫可以定義:

  • activityOpenEnterAnimation:表示新的activity建立進入效果
  • activityOpenExitAnimation:表示activity還沒有finish()下退出效果
  • activityCloseEnterAnimation:表示上一個activity返回進入效果
  • activityCloseExitAnimation:表示的是activity finish()之後退出效果

Android 4.4的4種過場動畫

Android 4.4的activityOpenEnterAnimation

<?xml version="1.0" encoding="UTF-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false"
    android:zAdjustment="top" >

    <alpha
        android:duration="300"
        android:fillAfter="true"
        android:fillBefore="false"
        android:fillEnabled="true"
        android:fromAlpha="0.0"
        android:interpolator="@interpolator/decelerate_cubic"
        android:toAlpha="1.0" />

    <scale
        android:duration="300"
        android:fillAfter="true"
        android:fillBefore="false"
        android:fillEnabled="true"
        android:fromXScale=".8"
        android:fromYScale=".8"
        android:interpolator="@interpolator/decelerate_cubic"
        android:pivotX="50%p"
        android:pivotY="50%p"
        android:toXScale="1.0"
        android:toYScale="1.0" />

</set>

其中,decelerate_cubic是以1.5倍速的因子減速。

<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
        android:factor="1.5" />

Android 4.4的activityOpenExitAnimation

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="#ff000000"
    android:zAdjustment="normal" >

    <alpha
        android:duration="300"
        android:fillAfter="true"
        android:fillBefore="false"
        android:fillEnabled="true"
        android:fromAlpha="1.0"
        android:interpolator="@interpolator/decelerate_quint"
        android:toAlpha="0.0" />

</set>

Android 4.4上的open exit動畫只有300ms的透明度從全不透明變成全透明,採用quint,2.5的速度減速。

Android 4.4的activityCloseEnterAnimation

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

    <alpha
        android:duration="300"
        android:fillAfter="true"
        android:fillBefore="true"
        android:fillEnabled="true"
        android:fromAlpha="1.0"
        android:toAlpha="1.0" />

</set>

300ms啥也沒幹啊。。。

Android 4.4的activityCloseExitAnimation

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false"
    android:zAdjustment="top" >

    <alpha
        android:duration="300"
        android:fillAfter="true"
        android:fillBefore="true"
        android:fillEnabled="true"
        android:fromAlpha="1.0"
        android:interpolator="@interpolator/decelerate_cubic"
        android:toAlpha="0.0" />

    <scale
        android:duration="300"
        android:fillAfter="true"
        android:fillBefore="true"
        android:fillEnabled="true"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:interpolator="@interpolator/decelerate_cubic"
        android:pivotX="50%p"
        android:pivotY="50%p"
        android:toXScale=".8"
        android:toYScale=".8" />

</set>

跟open enter的動畫剛好是相反的,從1.01.0縮小到0.80.8。

Android 5.1的4種過場動畫

Android 5.1的activityOpenEnterAnimation

<?xml version="1.0" encoding="UTF-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false"
    android:zAdjustment="top" >
    <alpha
        android:duration="200"
        android:fillAfter="true"
        android:fillBefore="false"
        android:fillEnabled="true"
        android:fromAlpha="0.0"
        android:interpolator="@interpolator/decelerate_quart"
        android:toAlpha="1.0" />
    <translate
        android:duration="350"
        android:fillAfter="true"
        android:fillBefore="true"
        android:fillEnabled="true"
        android:fromYDelta="8%"
        android:interpolator="@interpolator/decelerate_quint"
        android:toYDelta="0" />
</set>

Android 5.1的activityOpenExitAnimation

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="#ff000000"
    android:zAdjustment="normal" >

    <alpha
        android:duration="217"
        android:fillAfter="true"
        android:fillBefore="false"
        android:fillEnabled="true"
        android:fromAlpha="1.0"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:toAlpha="0.7" />

</set>

Android 5.1上的open exit動畫變成217ms的透明度從全不透明變成0.7

Android 5.1的activityCloseEnterAnimation

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

    <alpha
        android:duration="250"
        android:fillAfter="true"
        android:fillBefore="true"
        android:fillEnabled="true"
        android:fromAlpha="0.7"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:toAlpha="1.0" />

</set>

跟activityOpenExitAnimation的區別在於,250ms從0.7變成全不透明。

Android 5.1的activityCloseExitAnimation

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="false"
    android:zAdjustment="top" >

    <alpha
        android:duration="150"
        android:fillAfter="true"
        android:fillBefore="false"
        android:fillEnabled="true"
        android:fromAlpha="1.0"
        android:interpolator="@android:anim/linear_interpolator"
        android:startOffset="100"
        android:toAlpha="0.0" />

    <translate
        android:duration="250"
        android:fillAfter="true"
        android:fillBefore="true"
        android:fillEnabled="true"
        android:fromYDelta="0%"
        android:interpolator="@interpolator/accelerate_quart"
        android:toYDelta="8%" />

</set>

跟open enter的動畫剛好是相反的,從0開始向下移8%。但是時長有變化,漸變變成線性的了,退出變成加速。

<?xml version="1.0" encoding="UTF-8"?>
<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:factor="2.0" />

framework相關程式碼分析

跟視窗動畫相關的方法主要在frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java中。
我們先看下核心邏輯中的

boolean applyAnimationLocked(int transit, boolean isEntrance) 

transit型別

型別

  • TRANSIT_UNSET:初始值,尚未設定
  • TRANSIT_NONE:沒有動畫
  • TRANSIT_ACTIVITY_OPEN:在同一task中在最頂端開啟一個視窗
  • TRANSIT_ACTIVITY_CLOSE:關閉當前活動視窗,恢復同一個task中的上一個視窗
  • TRANSIT_TASK_OPEN:新建任務並建立視窗
  • TRANSIT_TASK_CLOSE:關閉當前活動視窗,回到上一個任務
  • TRANSIT_TASK_TO_FRONT:將任務移至最頂
  • TRANSIT_TASK_TO_BACK:將當前任務移至最末
  • TRANSIT_WALLPAPER_CLOSE:關閉到無牆紙的應用
  • TRANSIT_WALLPAPER_OPEN:起動牆紙應用
  • TRANSIT_WALLPAPER_INTRA_OPEN:都有牆紙開啟
  • TRANSIT_WALLPAPER_INTRA_CLOSE:有牆紙關閉
  • TRANSIT_TASK_OPEN_BEHIND:在新任務啟動但是在後臺,一閃而過
  • TRANSIT_TASK_IN_PLACE:就在現場畫動畫

附原始碼:

    /** Not set up for a transition. */
    public static final int TRANSIT_UNSET = -1;
    /** No animation for transition. */
    public static final int TRANSIT_NONE = 0;
    /** A window in a new activity is being opened on top of an existing one in the same task. */
    public static final int TRANSIT_ACTIVITY_OPEN = 6;
    /** The window in the top-most activity is being closed to reveal the
     * previous activity in the same task. */
    public static final int TRANSIT_ACTIVITY_CLOSE = 7;
    /** A window in a new task is being opened on top of an existing one
     * in another activity`s task. */
    public static final int TRANSIT_TASK_OPEN = 8;
    /** A window in the top-most activity is being closed to reveal the
     * previous activity in a different task. */
    public static final int TRANSIT_TASK_CLOSE = 9;
    /** A window in an existing task is being displayed on top of an existing one
     * in another activity`s task. */
    public static final int TRANSIT_TASK_TO_FRONT = 10;
    /** A window in an existing task is being put below all other tasks. */
    public static final int TRANSIT_TASK_TO_BACK = 11;
    /** A window in a new activity that doesn`t have a wallpaper is being opened on top of one that
     * does, effectively closing the wallpaper. */
    public static final int TRANSIT_WALLPAPER_CLOSE = 12;
    /** A window in a new activity that does have a wallpaper is being opened on one that didn`t,
     * effectively opening the wallpaper. */
    public static final int TRANSIT_WALLPAPER_OPEN = 13;
    /** A window in a new activity is being opened on top of an existing one, and both are on top
     * of the wallpaper. */
    public static final int TRANSIT_WALLPAPER_INTRA_OPEN = 14;
    /** The window in the top-most activity is being closed to reveal the previous activity, and
     * both are on top of the wallpaper. */
    public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15;
    /** A window in a new task is being opened behind an existing one in another activity`s task.
     * The new window will show briefly and then be gone. */
    public static final int TRANSIT_TASK_OPEN_BEHIND = 16;
    /** A window in a task is being animated in-place. */
    public static final int TRANSIT_TASK_IN_PLACE = 17;

動畫的簡要流程

app transition的處理

WindowManagerService.handleAppTransitionReadyLocked -> WindowManagerService.setTokenVisibilityLocked -> WindowManagerService.applyAnimationLocked

for (i=0; i<NN; i++) {
    AppWindowToken wtoken = mOpeningApps.get(i);
    final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
    if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
    appAnimator.clearThumbnail();
    wtoken.inPendingTransaction = false;
    appAnimator.animation = null;
    setTokenVisibilityLocked(wtoken, animLp, true, transit, false);
    wtoken.updateReportedVisibilityLocked();
    wtoken.waitingToShow = false;
...
applyAnimation: anim=android.view.animation.AnimationSet@432426b0 animAttr=0x4 transit=4102 isEntrance=true

showAllWindows引出

01-01 12:03:40.911 V/WindowStateAnimator(  747): [xulun]applyAnimation: win=WindowStateAnimator{4315ff60 com.yunos.alicontacts/com.yunos.alicontacts.activities.ContactEditorActivity} anim=0 attr=0x0 a=null transit=1 isEntrance=true
Callers
com.android.server.wm.WindowStateAnimator.applyEnterAnimationLocked:1592
com.android.server.wm.WindowStateAnimator.performShowLocked:1494
com.android.server.wm.AppWindowAnimator.showAllWindowsLocked:313

結束app transition

for (i=0; i<NN; i++) {
    AppWindowToken wtoken = mClosingApps.get(i);
    if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
    wtoken.mAppAnimator.clearThumbnail();
    wtoken.inPendingTransaction = false;
    wtoken.mAppAnimator.animation = null;
    setTokenVisibilityLocked(wtoken, animLp, false, transit, false);
    wtoken.updateReportedVisibilityLocked();
    wtoken.waitingToHide = false;
    // Force the allDrawn flag, because we want to start
    // this guy`s animations regardless of whether it`s
    // gotten drawn.
    wtoken.allDrawn = true;
    wtoken.deferClearAllDrawn = false;
}
01-01 12:03:40.915 V/AppTransition(  747): applyAnimation: anim=android.view.animation.AnimationSet@431b2648 animAttr=0x5 transit=4102 isEntrance=false 
01-01 12:03:41.058 V/WindowStateAnimator(  747): [xulun]applyAnimation: win=WindowStateAnimator{4323e988 com.yunos.alicontacts/com.yunos.alicontacts.CallDetailActivity} anim=0 attr=0x1 a=null transit=2 isEntrance=false
Callers
com.android.server.wm.WindowManagerService.relayoutWindow:3392
com.android.server.wm.Session.relayout:191
android.view.IWindowSession$Stub.onTransact:235

Activity過載動畫的方法

overridePendingTransition(R.anim.alpha_in_animation, R.anim.alpha_out_animation);


相關文章