Android 屬性動畫框架 ObjectAnimator、ValueAnimator ,這一篇就夠了
前言
我們都知道 Android 自帶了 Roate Scale Translate Alpha 多種框架動畫,我們可以通過她們實現豐富的動畫效果,但是這些寬家動畫卻有一個致命的弱點,它們只是改變了 View 顯示的大小,而沒有改變 View 的響應區域。這時以 ObjectAnimator、ValueAnimator 為代表的屬性動畫也就應運而生了。
簡單效果
工作原理
屬性動畫字如其名,是通過改變 View 的屬性值來改變控制元件的形態,說白了就是通過反射技術來獲取控制元件的一些屬性如寬度、高度等的 get 和 set 方法,從而實現所謂的動畫效果。所以,這就需要我們的 View (如自定義 View 中)具有 set 和 get 方法,如果沒有則會導致程式的 Clash 。
- 首先,系統通過 get 方法獲得屬性值
- 系統在時間插值器的作用下,更變屬性值
- 系統呼叫 set 方法,將屬性值重新賦予控制元件
由此也可以看出:屬性動畫直接改變了控制元件的屬性,所以動畫結束後控制元件也就發生了永久性的變化。
使用 ObjectAnimator 實現四種動畫
這裡我打算通過使用 ObjectAnimator 實現四大動畫框架:
- alpha
- scaleX/scaleY
- translateX/translateY
- rotation
給大家講解下 ObjectAnimator 使用
private void iniAnimation(){ // 透明度動畫 ObjectAnimator.ofFloat(mAlphaImage, "alpha", 1, 0, 1) .setDuration(4000) .start(); // 縮放 final AnimatorSet animatorSet = new AnimatorSet(); mScaleImage.setPivotX(mScaleImage.getWidth()+250); mScaleImage.setPivotY(mScaleImage.getHeight()+250); animatorSet.playTogether( ObjectAnimator.ofFloat(mScaleImage, "scaleX", 1, 0) .setDuration(2000), ObjectAnimator.ofFloat(mScaleImage, "scaleY", 1, 0) .setDuration(2000) ); animatorSet.start(); // 平移 translation final AnimatorSet translationAnimatorSet = new AnimatorSet(); translationAnimatorSet.playTogether( ObjectAnimator.ofFloat(mTranslationImage, "translationX", 20, 100) .setDuration(2000), ObjectAnimator.ofFloat(mTranslationImage, "translationY", 20,100) .setDuration(2000) ); translationAnimatorSet.start(); // 利用 ObjectAnimator 實現旋轉動畫 final AnimatorSet rotateAnimationSet = new AnimatorSet(); rotateAnimationSet.playTogether( ObjectAnimator.ofFloat(mRotationImage, "rotation",0, 360) .setDuration(2000) ); rotateAnimationSet.start(); }
以上程式碼就通過了 ObjectAnimator 實現了,四大效果,實現過程基本可以歸納為
- 建立 AnimatorSet 物件
- 設定,變化發生的軸心(部分需要)
- 設定所需要發生改變的動畫(通常在 playTogether() 方法中)
- 開啟動畫
最後的執行效果如開頭動畫所示
同樣的,我們可以在一個 playTogether 方法中新增多個動畫,這樣就能實現多動畫組合的效果。這裡就不在贅述了,大家可以自己試試看(我 GIF 圖中,右下角的動畫,就是旋轉 + 透明度)
使用 ValueAnimator 實現屬性動畫
ValueAnimator 是 ObjectAnimator 的父類,他兩之間的區別是,ObjectAnimator 在ValueAnimator 的基礎上,通過反射技術實現了動畫功能,也就像我剛剛所舉的例子,子要給了 ObjectAnimator 兩個值(from,to),在確定動畫型別(“scale,translate”),他就能自動生成動畫。
例項
最近看各大廠商,似乎都迷上了對 FloatingActionButton 進行操作,我就也來趁波熱點。
實現方法
這個效果既可以通過動畫框架實現,也可通過屬性動畫實現,這裡我給大家講下實現的方法。
首先是思路
由於這裡我們是採用 ValueAnimator 實現的,所以更具 ValueAnimator 的特性,在我們對其設定完時間插值器之後,它會規律的返回一系列數。所以我們只要更具這一系列數對控制元件的屬性進行設定即可。
private FloatingActionButton fab;
private ImageView imageView;
private int buttonSize = 0, imageSize = 0;
private float startY = 0;
private float endY = 0;
private ValueAnimator createValueAnimate(final View view, int start, int end){
ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = (int) animation.getAnimatedValue();
params.width = (int) animation.getAnimatedValue();
view.setLayoutParams(params);
}
});
return valueAnimator;
}
可以看到我們傳入三個引數,這裡我做的是縮放動畫,所以給的分別是控制元件,控制元件當前大小和控制元件目標大小。然後根據監聽器返回的值進行設定即可。
呼叫方面
我這裡實現的是上拉隱藏和下拉顯示,所以我們需要判斷下 Y軸 滑動方向:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_UP:
startY = event.getY();
if ((startY - endY) < 0){
// 縮小
animationDown(fab, buttonSize);
animationDown(imageView, imageSize);
}else if ((startY - endY) > 0){
// 放大
animationUp(fab, buttonSize);
animationUp(imageView, imageSize);
}
break;
case MotionEvent.ACTION_DOWN:
endY = event.getY();
break;
}
return super.onTouchEvent(event);
}
private void animationDown(final View view, int originalSize){
ValueAnimator animator = createValueAnimate(view, originalSize, 0);
animator.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setVisibility(View.GONE);
}
});
animator.setInterpolator(new BounceInterpolator());
animator.setDuration(500).start();
}
private void animationUp(final View view, int originalSize){
view.setVisibility(View.VISIBLE);
ValueAnimator animator = createValueAnimate(view, 0, originalSize);
animator.setInterpolator(new BounceInterpolator());
animator.setDuration(500).start();
}
這裡我們會發現,由於是屬性動畫,所以改變的直接就是控制元件的大小,這就導致了一個問題,如果是實時的獲取控制元件大小。那麼我們在執行完多小動畫,也就是 animationDown 後,就無法在獲得控制元件原始大小了。
所以這裡我們在 onResume 方法中獲取控制元件大小:
@Override
protected void onResume() {
super.onResume();
fab.post(new Runnable() {
@Override
public void run() {
buttonSize = fab.getHeight();
}
});
imageView.post(new Runnable() {
@Override
public void run() {
imageSize = imageView.getHeight();
}
});
}
實戰演練
屬性動畫可以作為 ViewGroup 增加活減少控制元件是的動畫,是的介面的變換不是那麼的突兀,其實細心的同學可能有發現,android 是自帶切換效果的,但是形式比較單一,所以這裡我通過自定義 ObjectAnimator 的方法。
最後效果
實現過程其實非常簡單:
- 首先 例項化出來一個 LayoutTransition 物件
- 接著 通過 ObjectAnimator.ofPropertyValuesHolder() 例項化出來一個用於載入動畫的
- ObjectAnimator 物件
- 然後 在 ObjectAnimator.ofPropertyValuesHolder() 中設定一系列的動畫效果
- 用 setAnimation 方法將該 ObjectAnimator 物件設定為 transition 的動畫
- 為 ObjectAnimator 物件設定 Duration 執行時間
- 設定動畫延時 setStartDelay
用同樣的方法設定 remove 動畫
LayoutTransition transition = new LayoutTransition();
ObjectAnimator appendAnimator = ObjectAnimator.ofPropertyValuesHolder(
(ImageView) null,
PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f),
PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f),
PropertyValuesHolder.ofFloat("alpha" , 0.0f, 1.0f)
);
appendAnimator.setInterpolator(new BounceInterpolator());
transition.setAnimator(LayoutTransition.APPEARING, appendAnimator);
transition.setDuration(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.APPEARING));
transition.setStartDelay(LayoutTransition.APPEARING, transition.getStartDelay(LayoutTransition.APPEARING));
ObjectAnimator removeAnimator = ObjectAnimator.ofPropertyValuesHolder(
(ImageView) null,
PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f),
PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f),
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.0f)
);
removeAnimator.setInterpolator(new BounceInterpolator());
transition.setAnimator(LayoutTransition.DISAPPEARING, removeAnimator);
transition.setDuration(LayoutTransition.DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));
transition.setStartDelay(LayoutTransition.DISAPPEARING, transition.getStartDelay(LayoutTransition.DISAPPEARING));
最後通過 setLayoutTransition 將這個 LayoutTransition 物件付給你的 ViewGroup 即可
layout = findViewById(R.id.layout);
layout.setLayoutTransition(transition);
測試環節
測試是分為新增控制元件和移除控制元件,功能在活動中動態的執行:
新增方法:
- 首先建立好一個控制元件,這裡我拿的是 (ImageView 舉例)
- 將其 大小、內容等屬性設定完成
- 呼叫 LinearLayout 的 addView 方法新增控制元件到佈局的指定位置
移除方法
- 首先判斷該線下佈局中是否有控制元件
- 再有的情況下定點移除控制元件
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_add_image:
ImageView imageView = new ImageView(ExtendActivity.this);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setImageResource(R.drawable.heart);
ViewGroup.LayoutParams params = new LinearLayout.LayoutParams(200, 200);
imageView.setLayoutParams(params);
layout.addView(imageView,0);
break;
case R.id.btn_remove_image:
int count = layout.getChildCount();
if (count > 0){
layout.removeViewAt(0);
}
break;
}
}
專案 Demo 點選前往https://github.com/FishInWater-1999/android_view_user_defined_first
到此為止所有屬性動畫的使用基本介紹完畢
由於是個人學習的總結,如果有問題或是我個人疏漏,希望大家在評論區給我留言
祝大家程式設計愉快,少碼 bug ,哈哈