ANDROID樣式的開發:PROPERTY ANIMATION篇

轉自:Keegan小鋼 http://keeganlee.me/post/android/20151026

Android樣式的開發:shape篇
Android樣式的開發:selector篇
Android樣式的開發:layer-list篇
Android樣式的開發:drawable彙總篇
Android樣式的開發:View Animation篇
Android樣式的開發:Property Animation篇
Android樣式的開發:Style篇

前篇文章說過,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地址:https://github.com/keeganlee/kstyle.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章