SpringAnimation彈簧動畫
Android最近更新了Support Library包,在25.3中新增了一個動畫效果,名為SpringAnimation(彈簧動畫)。
使用步驟一:
需要25的編譯環境,同時要求最低版本為16及以上,所以使用上還是有一些限制。如果有特殊需要,可以通過tools:overrideLibrary
來做對應的兼容適配。
build.xml:
minSdkVersion 16
compileSdkVersion 25
compile 'com.android.support:support-dynamic-animation:25.3.0'
導入之后就能看到對應的動畫類SpringAnimation
android.support.animation.SpringAnimation
使用步驟二:
SpringAnimation提供了兩個構造方法:
public SpringAnimation(View v, ViewProperty property)
public SpringAnimation(View v, ViewProperty property, float finalPosition)
分別是操作對應的View,對應的變化屬性及最終的位置。
ViewProperty從現有支持的看,包括(Z軸的支持需要API >= 21):
TRANSLATION_X
TRANSLATION_Y
TRANSLATION_Z
SCALE_X
SCALE_Y
ROTATION
ROTATION_X
ROTATION_Y
X
Y
Z
ALPHA
SCROLL_X
SCROLL_Y
然后在SpringAnimation中有一個SpringForce對象,負責對應的變量設置及位置計算。其中包括兩個個關鍵變量
-
Stiffness
剛度(勁度/彈性),剛度越大,形變產生的里也就越大,體現在效果上就是運動越快 -
DampingRatio
阻尼系數,系數越大,動畫停止的越快。從理論上講分為三種情況 Overdamped過阻尼(ζ > 1)、Critically damped臨界阻尼(ζ = 1)、Underdamped欠阻尼狀態(0<ζ <1)。
不過估計看到這,大家肯定還是一臉懵逼。所以,這里先稍微解釋一下彈簧跟阻尼的概念:
- 從原理上看,當彈簧處于平衡位置(不受力的自然位置)時,如果受到擠壓或者拉伸后,當放手后彈簧會恢復原狀。
- 彈回來的過程是「彈性勢能」轉化成「動能」,當重新恢復到平衡點時,因為還有速度,所以會繼續向前運動,這個時候「動能」又對應的轉化為「彈性勢能」。
- 如果能量轉換沒有損失,將不斷的伸長縮短,也就是不斷的來來回回。
- 但能量轉換是有損失的,所以縮回來的距離會不斷縮小,彈出去的距離也一樣,這個每次損耗縮小的距離比,其實就是阻尼。
這樣解釋后,大家對于關鍵的參數應該有了一定的了解。那接下來我們就繼續實戰。
使用步驟三:
嘗試模仿Dribbble上的一個作品,設計效果圖如下:
分析整個動畫效果,主體上分為兩個部分:
- 各元素從下往上移動的彈簧效果;SpringAnimation可以實現對應的效果。
- 移動過程中的透明度變化;ValueAnimation用來實現透明度Alpha從0到1的變化,從而實現淡入的感覺。
然后簡單的說明下SpringAnimation的使用方法:
SpringAnimation signUpBtnAnimY = new SpringAnimation(mSignUpBtn,SpringAnimation.TRANSLATION_Y,0);
signUpBtnAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
signUpBtnAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
signUpBtnAnimY.setStartVelocity(10000);
signUpBtnAnimY.start();
之前介紹SpringAnimation的時候聊過了幾個影響實際效果的因素,這里再根據具體使用來說明下:
(1) DampingRatio阻尼系數,通過getSpring().setDampingRatio
方法來設置。
從效果上看,ζ = 0的時候就是無限來回運動,0< ζ <1的時候會出現來回減弱的振蕩最后停止,ζ >= 1的時候會在靠近原位置的時候提前減速后停止。
在SpringForce中有對應的常量設置:
/**
* Damping ratio for a very bouncy spring. Note for under-damped springs
* (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.
*/
public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f;
/**
* Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring
* force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio,
* the more bouncy the spring.
*/
public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f;
/**
* Damping ratio for a spring with low bounciness. Note for under-damped springs
* (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness.
*/
public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f;
/**
* Damping ratio for a spring with no bounciness. This damping ratio will create a critically
* damped spring that returns to equilibrium within the shortest amount of time without
* oscillating.
*/
public static final float DAMPING_RATIO_NO_BOUNCY = 1f;
(2) Stiffness剛度,通過getSpring().setStiffness
方法來設置。
在SpringForce中有對應的常量設置:
/**
* Stiffness constant for extremely stiff spring.
*/
public static final float STIFFNESS_HIGH = 10_000f;
/**
* Stiffness constant for medium stiff spring. This is the default stiffness for spring force.
*/
public static final float STIFFNESS_MEDIUM = 1500f;
/**
* Stiffness constant for a spring with low stiffness.
*/
public static final float STIFFNESS_LOW = 200f;
/**
* Stiffness constant for a spring with very low stiffness.
*/
public static final float STIFFNESS_VERY_LOW = 50f;
(3) StartVelocity開始速度,單位是px/second. 正數是彈簧收縮的方向,負數則相反。
根據效果要求設置完對應的參數后,調用start方法后即可執行對應的動畫。
需要注意的是SpringAnimation動畫是無法設置執行時間的,所以如果有同期需要執行的動畫,可以評估對應的執行時間或者通過updateListener來做對應的處理。
簡略的實現
public class MainActivity extends AppCompatActivity {
private Button mSignUpBtn;
private ImageView mLeftLogoImg;
private ImageView mRightLogoImg;
private TextView mDescTitleTextView;
private LinearLayout mLettersLayout;
private LinearLayout mSignInLayout;
private ArrayList<SpringAnimation> mLetterAnims;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// hide the status ui.
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
// get the screen height.
DisplayMetrics dm = getResources().getDisplayMetrics();
int screenHeight = dm.heightPixels;
// letters about 'Converse'
mLettersLayout = (LinearLayout) findViewById(R.id.letter_layout);
mLetterAnims = new ArrayList<>();
for (int i = 0; i < mLettersLayout.getChildCount(); i++) {
View letterView = mLettersLayout.getChildAt(i);
letterView.setTranslationY(screenHeight);
SpringAnimation letterAnimY = new SpringAnimation(letterView, SpringAnimation.TRANSLATION_Y, 0);
letterAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
letterAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
mLetterAnims.add(letterAnimY);
}
// text about 'Native messaging'
mDescTitleTextView = (TextView) findViewById(R.id.desc_title_textview);
mDescTitleTextView.setTranslationY(500f);
mDescTitleTextView.setAlpha(0f);
final SpringAnimation descTitleAnimY = new SpringAnimation(mDescTitleTextView, DynamicAnimation.TRANSLATION_Y, 0);
descTitleAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
descTitleAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
final ValueAnimator descTitleAlphaAnim = ObjectAnimator.ofFloat(0f, 1f);
descTitleAlphaAnim.setDuration(300);
descTitleAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mDescTitleTextView.setAlpha((Float) valueAnimator.getAnimatedValue());
}
});
// the button of sign up
mSignUpBtn = (Button) findViewById(R.id.sign_up_btn);
mSignUpBtn.setTranslationY(500f);
final SpringAnimation signUpBtnAnimY = new SpringAnimation(mSignUpBtn, SpringAnimation.TRANSLATION_Y, 0);
signUpBtnAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
signUpBtnAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
// the bottom text about 'Have an account? sign in'
mSignInLayout = (LinearLayout) findViewById(R.id.signin_layout);
mSignInLayout.setTranslationY(500f);
final SpringAnimation signInLayoutAnimY = new SpringAnimation(mSignInLayout, SpringAnimation.TRANSLATION_Y, 0);
signInLayoutAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
signInLayoutAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
// top logo by left
mLeftLogoImg = (ImageView) findViewById(R.id.left_logo_imageview);
mLeftLogoImg.setTranslationY(400f);
mLeftLogoImg.setAlpha(0f);
final SpringAnimation leftLogoAnimY = new SpringAnimation(mLeftLogoImg, SpringAnimation.TRANSLATION_Y, 0);
leftLogoAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
leftLogoAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
leftLogoAnimY.setStartVelocity(-2000);
// top logo by right
mRightLogoImg = (ImageView) findViewById(R.id.right_logo_imageview);
mRightLogoImg.setTranslationY(400f);
mRightLogoImg.setAlpha(0f);
final SpringAnimation rightLogoAnimY = new SpringAnimation(mRightLogoImg, SpringAnimation.TRANSLATION_Y, 0);
rightLogoAnimY.getSpring().setStiffness(SpringForce.STIFFNESS_VERY_LOW);
rightLogoAnimY.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
rightLogoAnimY.setStartVelocity(-2000);
final ValueAnimator logoAlphaAnim = ObjectAnimator.ofFloat(0f, 1f);
logoAlphaAnim.setDuration(600);
logoAlphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mLeftLogoImg.setAlpha((Float) valueAnimator.getAnimatedValue());
mRightLogoImg.setAlpha((Float) valueAnimator.getAnimatedValue());
}
});
mRightLogoImg.postDelayed(new Runnable() {
@Override
public void run() {
leftLogoAnimY.start();
mRightLogoImg.postDelayed(new Runnable() {
@Override
public void run() {
rightLogoAnimY.start();
}
}, 150);
mDescTitleTextView.postDelayed(new Runnable() {
@Override
public void run() {
descTitleAlphaAnim.setStartDelay(100);
descTitleAlphaAnim.start();
descTitleAnimY.start();
signUpBtnAnimY.start();
signInLayoutAnimY.start();
}
}, 300);
for (final SpringAnimation letterAnim : mLetterAnims) {
mLettersLayout.postDelayed(new Runnable() {
@Override
public void run() {
letterAnim.start();
}
}, 12 * mLetterAnims.indexOf(letterAnim));
}
logoAlphaAnim.start();
}
}, 1000);
}
}
最終實現的效果如下圖,實現的有點粗糙,希望能給大家帶來解決需求上的新思路。