PhysicsBasedAnimation學習

概述

Google I/O '17推出了許多新的特性,在動畫這一塊又有新的API供開發者使用,具體視頻請見Android Animations Spring to Life (Google I/O '17),主要介紹了Physics-based Animations,在動畫API中引入了DynamicAnimation,并介紹了它的兩個子類FlingAnimationSpringAnimation的使用,開發者可以使用新的API創建更加動態化的動畫。

是什么

Physics-based Animations,翻譯過來就是基于物理的動畫,官網上有很詳細的介紹,在日常生活中當一個事物發生變化的時候,物理性的過渡或者說符合自然性的過渡,更容易讓我們感知察覺,同樣,更自然、不間斷、有良好發展趨勢的動畫會給我們帶來更好的用戶體驗。Physics-based Animations是根據物理學的基本原理構建的動畫,動畫由力產生,當力趨于平衡時動畫處于靜止。讓我們重新撿起高中半吊子水平的物理知識,比如給物體在某個方向上施加一個力,物體有了速度,會在該方向上運動,如果停止施力,最后物體會由于摩擦力的影響,速度逐漸減小,運動一段時間后處于靜止狀態。Physics-based Animations概括起來就是下面幾點:

  • 動畫由力驅動
  • 力決定了動畫的加速和減速
  • 在每一幀中動畫值和速度都會更新
  • 當受力達到平衡時動畫停止

好處

使用Physics-based Animations api創建的動畫可以追蹤速度,在運動過程中動態地改變動畫的目標值,正確規劃路線,使動畫看起來更加自然。看下兩組動畫


使用ObjectAnimator創建的動畫1

使用physics-based APIs創建的動畫2

對比了兩組動畫的差別,圖1動畫無法追蹤速度,在進行下一幀的時候它的速度幾乎還是從0開始的,速度值突然的變化給用戶不連貫的視覺體驗。圖2動畫可以追蹤速度,在第二階段力的方向改變,導致原先的速度發生變化,圖片看起來很自然地移動到新的位置


動畫1的速度曲線圖
動畫2的速度曲線圖

怎么用

  • Android Studio 3.0 Canary 4
  • 在Android Studio的build.gradle中添加依賴
dependencies {
    ...
    implementation 'com.android.support:support-dynamic-animation:26.0.0-beta2'
}

Fling Animation

看下怎樣創建FlingAnimation:

ImageView img = root.findViewById(R.id.img_simple_fling);
FlingAnimation flingAnimation = new FlingAnimation(img, DynamicAnimation.X);
flingAnimation.setStartVelocity(500f);
flingAnimation.setFriction(0.5f);
flingAnimation.start();

效果如下:

解釋下上面的代碼:
創建一個FlingAnimation實例,默認情況下該實例的初速度是0pixels/s,因此我們需要調用setStartVelocity()方法給它賦予一個大于0的初速度,否則它是不會動的;另外這里介紹下Friction,翻譯過來就是摩擦力的意思,在現實生活中如果一個物體保持一個速度在無摩擦力的情況下會一直運動下去,這里也是(比如這里設置Fraction為0.01f,發現小球滾到屏幕外了),我們需要給該實例設置一個摩擦系數,設置的值越大,說明摩擦力越大,動畫越快停下來,默認該值為1;最后調用start()方法開始動畫。

Spring Animation

看下怎樣創建SpringAnimation:

ImageView img = root.findViewById(R.id.img_simple_spring);
SpringAnimation springAnimation = new SpringAnimation(img, DynamicAnimation.X);
springAnimation.setStartVelocity(2000);

SpringForce springForce = new SpringForce();   
springForce.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
springForce.setStiffness(SpringForce.STIFFNESS_LOW);
springForce.setFinalPosition(img.getX());

springAnimation.setSpring(springForce);
springAnimation.start();

效果如下:

解釋下上面的代碼:
和FlingAnimation一樣,創建完SpringAnimation后我們需要設置初速度,接著創建了一個
SpringForce實例,并設置了DampingRatio(彈性阻尼)和Stiffness(生硬度),
DampingRatio可以理解成反彈次數,系統中有以下幾個可選,

public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2F;
public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5F;
public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75F;
public static final float DAMPING_RATIO_NO_BOUNCY = 1.0F;

默認設置為DAMPING_RATIO_MEDIUM_BOUNCY,在官網上貼了四張很Q彈的圖片,分別對應不同值的效果,該值越大,反彈次數越少,值為1時不反彈。
Stiffness可以理解成要恢復成未拉伸狀態所需的時間,系統中有以下幾個可選,

public static final float STIFFNESS_HIGH = 10000.0F;
public static final float STIFFNESS_MEDIUM = 1500.0F;
public static final float STIFFNESS_LOW = 200.0F;
public static final float STIFFNESS_VERY_LOW = 50.0F;

默認設置為STIFFNESS_MEDIUM,在官網同樣貼了四張對應不同值得對比圖,該值越大,恢復到之前狀態的時間就越短。可以修改DampingRatioStiffness查看效果

setFinalPosition()方法指定最后靜止時的位置。

創建自定義的動畫屬性

SpringAniamtion和FlingAnimation的構造函數只能接收一個可動畫屬性參數,如ALPHAROTATIONSCALE等,如果要同時為多個屬性生成動畫,一種方法是創建多個對應類的實例,然后傳入要改變的動畫值,這種做法比較麻煩,我們可以創建一個新的屬性,該屬性封裝了我們想改變的其他動畫屬性值,做法如下:

FloatPropertyCompat<View> scale = 
        new FloatPropertyCompat<View>("scale") {
            @Override
            public float getValue(View view) {
                // return the value of any one property
                return view.getScaleX();
            }
 
            @Override
            public void setValue(View view, float value) {
                // Apply the same value to two properties
                view.setScaleX(value);
                view.setScaleY(value);
            }
        };

創建FloatPropertyCompat實例,在setValue()方法中更新要修改的動畫屬性,在getValue()方法中返回當前屬性值,示例代碼統一改變了SCALE_XSCALE_Y屬性,自定義屬性創建好之后可以像其他動畫屬性一樣使用它,

SpringAnimation stretchAnimation =
            new SpringAnimation(emoji, scale);

在創建使用自定義屬性的動畫時,最好也調用setMinimumVisibleChange()方法并傳遞一個有意義的值,以確保動畫不會消耗太多的CPU性能

stretchAnimation.setMinimumVisibleChange(
                DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);

效果如下:

動畫監聽

DynamicAnimation提供了兩個動畫監聽器OnAnimationUpdateListenerOnAnimationEndListener,從名字也可以猜到前者監聽動畫值改變,后者監聽動畫結束狀態。添加動畫變化監聽需要調用addUpdateListener()方法,重寫onAnimationUpdate()方法執行具體操作;添加動畫結束監聽需要調用addEndListener()方法,重寫onAnimationEnd()方法執行具體操作;若要移除動畫監聽,則需要調用removeUpdateListener()removeEndListener()。監聽動畫結束的使用場景:

// Change icon before animation starts
emoji.setImageResource(
        R.drawable.ic_sentiment_very_satisfied_black_56dp);
 
// Start animation
springAnimation.start();
 
springAnimation.addEndListener(
    new DynamicAnimation.OnAnimationEndListener() {
        @Override
        public void onAnimationEnd(DynamicAnimation animation, 
                                boolean canceled,
                                float value, float velocity) {
            // Change icon after animation ends
            emoji.setImageResource(
                    R.drawable.ic_sentiment_neutral_black_56dp);
        }
});

當動畫結束時變換表情,效果如下:

監聽動畫變化的使用場景:

// Creating two views to demonstrate the registration of the update listener.
final View view1 = findViewById(R.id.view1);
final View view2 = findViewById(R.id.view2);

// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
final SpringAnimation anim1X = new SpringAnimation(view1,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim1Y = new SpringAnimation(view1,
    DynamicAnimation.TRANSLATION_Y);
final SpringAnimation anim2X = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim2Y = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_Y);

// Registering the update listener
anim1X.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

// Overriding the method to notify view2 about the change in the view1’s property.
    @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2X.animateToFinalPosition(value);
    }
});

anim1Y.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

  @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2Y.animateToFinalPosition(value);
    }
});

我們可以結合animateToFinalPosition()方法實現鏈式彈力動畫效果,即一個View的動畫依賴于另一個,效果如下:

Thanks to

GitHub Demo傳送門

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容