技術學習記錄

[Android]轉載-Android樣式的開發:Property Animation篇

轉載自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類提供了屬性動畫的核心功能,包括計算動畫值、動畫時間細節、是否重複等。執行屬性動畫分兩個步驟:

  1. 計算動畫值
  2. 將動畫值應用到對象和屬性上

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表示完全透明
  • pivotXpivotY:旋轉的軸點和縮放的基準點,默認是View的中心點
  • scaleXscaleY:基於pivotX和pivotY的縮放,1表示無縮放,小於1表示收縮,大於1則放大
  • rotationrotationXrotationY:基於軸點(pivotX和pivotY)的旋轉,rotation為平面的旋轉,rotationX和rotationY為立體的旋轉
  • translationXtranslationY:View的螢幕位置坐標變化量,以layout容器的左上角為坐標原點
  • xy: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


發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *