轉載自Keegan小鋼
原文鏈接:http://keeganlee.me/post/android/20151026
前篇文章說過,Android框架還提供了兩種動畫體系,前一篇已經總結了視圖動畫(View Animation)的用法,本篇則接著總結另一種動畫體系——屬性動畫(Property Animation)的用法。
視圖動畫只能作用於View,而且視圖動畫改變的只是View的繪製效果,View真正的屬性並沒有改變。比如,一個按鈕做平移的動畫,雖然按鈕的確做了平移,但按鈕可點擊的區域並沒隨著平移而改變,還是在原來的位置。而屬性動畫則可以改變真正的屬性,從而實現按鈕平移時點擊區域也跟著平移。通俗點說,屬性動畫其實就是在一定時間內,按照一定規律來改變對象的屬性,從而使對象展現出動畫效果。
屬性動畫是在android 3.0引入的動畫體系,如果還想適配基本已經滅絕的2.x版本,只好繞道了。 屬性動畫和視圖動畫一樣,可以通過xml文件定義,不同的是,視圖動畫的xml文件放於res/anim/目錄下,而屬性動畫的xml文件則放於res/animator/目錄下。一個是anim,一個是animator,別搞錯了。同樣的,在Java程式碼裡引用屬性動畫的xml文件時,則用R.animator.filename,不同於視圖動畫,引用時為R.anim.filename。
屬性動畫主要有三個元素:<animator>、<objectAnimator>、<set>。 相對應的有三個類:ValueAnimator、ObjectAnimator、AnimatorSet。
ValueAnimator是基本的動畫類,處理值動畫,通過監聽某一值的變化,進行相應的操作。ObjectAnimator是ValueAnimator**的子類,處理對象動畫。AnimatorSet則為動畫集,可以組合另外兩種動畫或動畫集。相應的三個標籤元素的關係也一樣。
樣式開發主要還是用xml的形式,所以這裡主要還是講標籤的用法。
<animator>
<animator>標籤與對應的ValueAnimator類提供了屬性動畫的核心功能,包括計算動畫值、動畫時間細節、是否重複等。執行屬性動畫分兩個步驟:
- 計算動畫值
- 將動畫值應用到對象和屬性上
ValuAnimiator只完成第一步,即只計算值,要實現第二步則需要在值變化的監聽器裡自行更新對象屬性。 通過<animator>標籤可以很方便的對ValuAnimiator**進行設置,可設置的屬性如下:
- android:duration 動畫從開始到結束持續的時長,單位為毫秒
- android:startOffset 設置動畫執行之前的等待時長,單位為毫秒
- android:repeatCount 設置動畫重複執行的次數,默認為0,即不重複;可設為-1或infinite,表示無限重複
- android:repeatMode 設置動畫重複執行的模式,可設為以下兩個值其中之一:
- restart 動畫重複執行時從起點開始,默認為該值
- reverse 動畫會反方向執行
- android:valueFrom 動畫開始的值,可以為int值、float值或color值
- android:valueTo 動畫結束的值,可以為int值、float值或color值
- android:valueType 動畫值類型,若為color值,則無需設置該屬性
- intType 指定動畫值,即以上兩個value屬性的值為整型
- floatType 指定動畫值,即以上兩個value屬性的值為浮點型,默認值
- android:interpolator 設置動畫速率的變化,比如加速、減速、勻速等,需要指定Interpolator資源。具體用法在View Animation篇已經講過,這裡不再重複
接著,用一個實例講解具體的用法吧。在這個例子裡,將一個按鈕的寬度進行縮放,從100%縮放到20%。 xml文件的程式碼如下:
<!-- res/animator/value_animator.xml -->
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:valueFrom="100"
android:valueTo="20"
android:valueType="intType" />
可看到,值的變化從100到20,動畫時長3000毫秒,以下則是目標按鈕的xml程式碼:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_normal"
android:onClick="onScaleWidth"
android:text="點我"
android:textColor="@android:color/white" />
按鈕默認是填充螢幕寬度的,點擊時的執行方法為onScaleWidth,以下則是onScaleWidth方法的程式碼:
public void onScaleWidth(final View view) {
// 獲取螢幕寬度
final int maxWidth = getWindowManager().getDefaultDisplay().getWidth();
ValueAnimator valueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.value_animator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
// 當前動畫值,即為當前寬度比例值
int currentValue = (Integer) animator.getAnimatedValue();
// 根據比例更改目標view的寬度
view.getLayoutParams().width = maxWidth * currentValue / 100;
view.requestLayout();
}
});
valueAnimator.start();
}
從View Animation篇中已經知道,視圖動畫是通過AnimationUtils**類的loadAnimation()方法獲取xml文件相對應的Animation類實例,而屬性動畫則是通過AnimatorInflater類的loadAnimation()方法獲取相應的Animator類實例。 另外,ValueAnimator通過添加AnimatorUpdateListener監聽器監聽值的變化,從而再手動更新目標對象的屬性。 最後,通過調用valueAnimator.start()方法啟動動畫。
<objectAnimator>
<objectAnimator>標籤對應的類為ObjectAnimator,為ValueAnimator的子類。<objectAnimator>標籤與<animator>標籤不同的是,<objectAnimator>可以直接指定動畫的目標對象的屬性。標籤可設置的屬性除了和<animator>一樣的那些,另外多了一個:
- android:propertyName 目標對象的屬性名,要求目標對象必須提供該屬性的setter方法,如果動畫的時候沒有初始值,還需要提供getter方法
還是用實例說明具體用法,還是用上面的例子,將一個按鈕的寬度進行縮放,從100%縮放到20%,但這次改用<objectAnimator>實現。 以下為xml文件的程式碼:
<!-- res/animator/object_animator.xml -->
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:propertyName="width"
android:valueFrom="100"
android:valueTo="20"
android:valueType="intType" />
與<animator>的例子相比,就只是多了一個android:propertyName**的屬性,設置值為width。也就是說,動畫改變的屬性為width,值將從100逐漸減到20。另外,值是從setWidth()傳遞過去的,再從getWidth()獲取。而且,這裡設置的值代表的是比例值,因此,還需要進行計算轉化為實際的寬度值。最後,對象實際的寬度值為view.getLayoutParams().width。因此,我將用一個包裝類來包裝原始的view對象,對其提供setWidth()和getWidth()方法,程式碼如下:
private static class ViewWrapper {
private View target; //目標對象
private int maxWidth; //最長寬度值
public ViewWrapper(View target, int maxWidth) {
this.target = target;
this.maxWidth = maxWidth;
}
public int getWidth() {
return target.getLayoutParams().width;
}
public void setWidth(int widthValue) {
//widthValue的值從100到20變化
target.getLayoutParams().width = maxWidth * widthValue / 100;
target.requestLayout();
}
}
上面setWidth()的程式碼裡,根據比例值轉化為了實際的寬度值。最後,動畫處理的程式碼如下:
public void onScaleWidth(View view) {
// 獲取螢幕寬度
int maxWidth = getWindowManager().getDefaultDisplay().getWidth();
// 將目標view進行包裝
ViewWrapper wrapper = new ViewWrapper(view, maxWidth);
// 將xml轉化為ObjectAnimator對象
ObjectAnimator objectAnimator = (ObjectAnimator) AnimatorInflater.loadAnimator(this, R.animator.object_animator);
// 設置動畫的目標對象為包裝後的view
objectAnimator.setTarget(wrapper);
// 啟動動畫
objectAnimator.start();
}
ObjectAnimator提供了屬性的設置,但相應的需要有該屬性的setter和getter方法。而ValueAnimator則只是定義了值的變化,並不指定目標屬性,所以也不需要提供setter和getter方法,但只能在AnimatorUpdateListener監聽器裡手動更新屬性。不過,也因為沒有指定屬性,所以其實更具靈活性了,你可以在監聽器里根據值的變化做任何事情,比如更新多個屬性,比如在縮放寬度的同時做垂直移動。
為了對View更方便的設置屬性動畫,Android系統也提供了View的一些屬性和相應的setter和getter方法:
- alpha:透明度,默認為1,表示不透明,0表示完全透明
- pivotX 和 pivotY:旋轉的軸點和縮放的基準點,默認是View的中心點
- scaleX 和 scaleY:基於pivotX和pivotY的縮放,1表示無縮放,小於1表示收縮,大於1則放大
- rotation、rotationX 和 rotationY:基於軸點(pivotX和pivotY)的旋轉,rotation為平面的旋轉,rotationX和rotationY為立體的旋轉
- translationX 和 translationY:View的螢幕位置坐標變化量,以layout容器的左上角為坐標原點
- x 和 y:View在父容器內的最終位置,是左上角坐標和偏移量(translationX,translationY)的和
<set>
<set>標籤對應於AnimatorSet類,可以將多個動畫組合成一個動畫集,如上面提到的在縮放寬度的同時做垂直移動,可以將一個縮放寬度的動畫和一個垂直移動的動畫組合在一起。 <set>標籤有一個屬性可以設置動畫的時序關係:
- android:ordering 設置動畫的時序關係,取值可為以下兩個值之一:
- together 動畫同時執行,默認值
- sequentially 動畫按順序執行
那如果想有些動畫同時執行,有些按順序執行,該怎麼辦呢?因為<set>標籤是可以嵌套其他<set>標籤的,也就是說可以將同時執行的組合在一個<set>標籤,再嵌在按順序執行的<set>標籤內。
看實例程式碼吧,以下為xml文件:
<!-- res/animator/animator_set.xml -->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:duration="3000"
android:propertyName="width"
android:valueFrom="100"
android:valueTo="20"
android:valueType="intType" />
<objectAnimator
android:duration="3000"
android:propertyName="marginTop"
android:valueFrom="0"
android:valueTo="100"
android:valueType="intType" />
</set>
以上程式碼可實現兩個同時執行的動畫,一個將width從100縮放到20,一個將marginTop從0增加到100。多了一個marginTop屬性,那麼,在ViewWrapper添加setMarginTop()方法,添加後的ViewWrapper類程式碼如下:
private static class ViewWrapper {
private View target;
private int maxWidth;
public ViewWrapper(View target, int maxWidth) {
this.target = target;
this.maxWidth = maxWidth;
}
public int getWidth() {
return target.getLayoutParams().width;
}
public void setWidth(int widthValue) {
target.getLayoutParams().width = maxWidth * widthValue / 100;
target.requestLayout();
}
public void setMarginTop(int margin) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) target.getLayoutParams();
layoutParams.setMargins(0, margin, 0, 0);
target.setLayoutParams(layoutParams);
}
}
最後,動畫處理的程式碼:
public void onScaleWidth(View view) {
// 獲取螢幕寬度
int maxWidth = getWindowManager().getDefaultDisplay().getWidth();
// 將目標view進行包裝
ViewWrapper wrapper = new ViewWrapper(view, maxWidth);
// 將xml轉化為ObjectAnimator對象
AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.animator_set);
// 設置動畫的目標對象為包裝後的view
animatorSet.setTarget(wrapper);
// 啟動動畫
animatorSet.start();
}
這樣就搞定了,實現了寬度縮放和垂直移動的效果。
寫在最後
至此,視圖動畫和屬性動畫基本的用法都總結完了。示例程式碼可從github上查看,github地址: