概述
Google I/O '17推出了許多新的特性,在動畫這一塊又有新的API供開發者使用,具體視頻請見Android Animations Spring to Life (Google I/O '17),主要介紹了Physics-based Animations,在動畫API中引入了DynamicAnimation
,并介紹了它的兩個子類FlingAnimation
和SpringAnimation
的使用,開發者可以使用新的API創建更加動態化的動畫。
是什么
Physics-based Animations,翻譯過來就是基于物理的動畫,官網上有很詳細的介紹,在日常生活中當一個事物發生變化的時候,物理性的過渡或者說符合自然性的過渡,更容易讓我們感知察覺,同樣,更自然、不間斷、有良好發展趨勢的動畫會給我們帶來更好的用戶體驗。Physics-based Animations是根據物理學的基本原理構建的動畫,動畫由力產生,當力趨于平衡時動畫處于靜止。讓我們重新撿起高中半吊子水平的物理知識,比如給物體在某個方向上施加一個力,物體有了速度,會在該方向上運動,如果停止施力,最后物體會由于摩擦力的影響,速度逐漸減小,運動一段時間后處于靜止狀態。Physics-based Animations概括起來就是下面幾點:
- 動畫由力驅動
- 力決定了動畫的加速和減速
- 在每一幀中動畫值和速度都會更新
- 當受力達到平衡時動畫停止
好處
使用Physics-based Animations api創建的動畫可以追蹤速度,在運動過程中動態地改變動畫的目標值,正確規劃路線,使動畫看起來更加自然。看下兩組動畫
對比了兩組動畫的差別,圖1動畫無法追蹤速度,在進行下一幀的時候它的速度幾乎還是從0開始的,速度值突然的變化給用戶不連貫的視覺體驗。圖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
,在官網同樣貼了四張對應不同值得對比圖,該值越大,恢復到之前狀態的時間就越短。可以修改DampingRatio
或Stiffness
查看效果
setFinalPosition()
方法指定最后靜止時的位置。
創建自定義的動畫屬性
SpringAniamtion和FlingAnimation的構造函數只能接收一個可動畫屬性參數,如ALPHA
、ROTATION
、SCALE
等,如果要同時為多個屬性生成動畫,一種方法是創建多個對應類的實例,然后傳入要改變的動畫值,這種做法比較麻煩,我們可以創建一個新的屬性,該屬性封裝了我們想改變的其他動畫屬性值,做法如下:
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_X
和SCALE_Y
屬性,自定義屬性創建好之后可以像其他動畫屬性一樣使用它,
SpringAnimation stretchAnimation =
new SpringAnimation(emoji, scale);
在創建使用自定義屬性的動畫時,最好也調用setMinimumVisibleChange()
方法并傳遞一個有意義的值,以確保動畫不會消耗太多的CPU性能
stretchAnimation.setMinimumVisibleChange(
DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
效果如下:
動畫監聽
DynamicAnimation
提供了兩個動畫監聽器OnAnimationUpdateListener
和 OnAnimationEndListener
,從名字也可以猜到前者監聽動畫值改變,后者監聽動畫結束狀態。添加動畫變化監聽需要調用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的動畫依賴于另一個,效果如下: