1. 程式人生 > >自定義控制元件三部曲之動畫篇(十)——聯合動畫的XML實現與使用示例

自定義控制元件三部曲之動畫篇(十)——聯合動畫的XML實現與使用示例

前言:不畏人生,或許才能方得始終;大膽拼,大膽闖是要有一定資本的,在能力不到的時候,就只有選擇忍氣吞聲!

上篇給大家講了有關AnimatorSet的程式碼實現方法,這篇我們就分別來看看如何利用xml來實現ValueAnimator、ObjectAnimator和AnimatorSet;
在文章最後,將利用AnimatorSet來實現一個路徑動畫,效果圖如下:
這裡寫圖片描述
(這裡實現的是一個動畫選單,在點選選單按鈕時,彈出各個選單)

一、聯合動畫的XML實現

在xml中對應animator總共有三個標籤,分別是

<animator />:對應ValueAnimator
<objectAnimator
/>
:對應ObjectAnimator <set />:對應AnimatorSet

下面我們逐個來看各個標籤的用法

1、animator

(1)、animator所有欄位及意義

下面是完整的animator所有的欄位及取值範圍:

<animator
    android:duration="int"
    android:valueFrom="float | int | color"
    android:valueTo="float | int | color"
    android:startOffset="int"
    android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"] android:valueType=["intType" | "floatType"] android:interpolator=["@android:interpolator/XXX"]/>
  • android:duration:每次動畫播放的時長
  • android:valueFrom:初始動化值;取值範圍為float,int和color,如果取值為float對應的值樣式應該為89.0,取值為Int時,對應的值樣式為:89;當取值為clolor時,對應的值樣式為 #333333;
  • android:valueTo:
    動畫結束值;取值範圍同樣是float,int和color這三種類型的值;
  • android:startOffset:動畫啟用延時;對應程式碼中的startDelay(long delay)函式;
  • android:repeatCount:動畫重複次數
  • android:repeatMode:動畫重複模式,取值為repeat和reverse;repeat表示正序重播,reverse表示倒序重播
  • android:valueType:表示引數值型別,取值為intType和floatType;與android:valueFrom、android:valueTo相對應。如果這裡的取值為intType,那麼android:valueFrom、android:valueTo的值也就要對應的是int型別的數值。如果這裡的數值是floatType,那麼android:valueFrom、android:valueTo的值也要對應的設定為float型別的值。非常注意的是,如果android:valueFrom、android:valueTo的值設定為color型別的值,那麼不需要設定這個引數;
  • android:interpolator:設定加速器;有關係統加速器所對應的xml值對照表如下:
    這裡寫圖片描述
(2)、將xml載入到程式中

在定義了一個xml後,我們需要將其載入到程式中,使用的方法如下:

ValueAnimator valueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(MyActivity.this,R.animator.animator);
valueAnimator.start();

通過loadAnimator將animator動畫的xml檔案,載入進來,根據型別進行強轉。

(3)、簡單示例

下面我們就舉個例子來看看如何來使用xml生成對應的animator動畫
先看看整體效果圖:
這裡寫圖片描述
在效果圖中可以看到,我們生成了一個動畫,動態了改變了當前控制元件的座標位置。
我們先在res/animator資料夾下生成一個動畫的xml檔案:

<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
          android:valueFrom="0"
          android:valueTo="300"
          android:duration="1000"
          android:valueType="intType"
          android:interpolator="@android:anim/bounce_interpolator"/>

在這裡,我們將valueType設定為intType,所以對應的android:valueFrom、android:valueTo都必須是int型別的值;插值器使用bounce回彈插值器
然後看看載入到程式中過程:

ValueAnimator valueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(MyActivity.this,
       R.animator.animator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
   public void onAnimationUpdate(ValueAnimator animation) {
       int offset = (int)animation.getAnimatedValue();
       mTv1.layout( offset,offset,mTv1.getWidth()+offset,mTv1.getHeight() + offset);
   }
});
valueAnimator.start();

由於我們xml中根屬性是<animator/>所以它對應的是ValueAnimator,所以在載入後,將其強轉為valueAnimator;然後對其新增控制元件監聽。在監聽時,動態改變當前textview的位置。有關這些程式碼就不再細講了,如果看到前面的文章,這段程式碼應該是無比熟悉的。
最後的效果就是開頭時所演示的效果。
原始碼在文章底部給出
有關android:valueFrom、android:valueTo取值為color屬性時的用法,這裡就不講了,因為在xml中使用color屬性,我也不會用;嘗試了下,不成功,也不想嘗試了,沒什麼太大意義,下面我們會講如何在objectanimator中使用color屬性;

2、objectAnimator

(1)欄位意義及使用方法

同樣,我們先來看看它的所有標籤的意義:

<objectAnimator
    android:propertyName="string"
    android:duration="int"
    android:valueFrom="float | int | color"
    android:valueTo="float | int | color"
    android:startOffset="int"
    android:repeatCount="int"
    android:repeatMode=["repeat" | "reverse"]
    android:valueType=["intType" | "floatType"]
    android:interpolator=["@android:interpolator/XXX"]/>

意義:
- android:propertyName:對應屬性名,即ObjectAnimator所需要操作的屬性名。
其它欄位的意義與animator的意義與取值是一樣的,下面再重新列舉一下。
- android:duration:每次動畫播放的時長
- android:valueFrom:初始動化值;取值範圍為float,int和color;
- android:valueTo:動畫結束值;取值範圍同樣是float,int和color這三種類型的值;
- android:startOffset:動畫啟用延時;對應程式碼中的startDelay(long delay)函式;
- android:repeatCount:動畫重複次數
- android:repeatMode:動畫重複模式,取值為repeat和reverse;repeat表示正序重播,reverse表示倒序重播
- android:valueType:表示引數值型別,取值為intType和floatType;與android:valueFrom、android:valueTo相對應。如果這裡的取值為intType,那麼android:valueFrom、android:valueTo的值也就要對應的是int型別的數值。如果這裡的數值是floatType,那麼android:valueFrom、android:valueTo的值也要對應的設定為float型別的值。非常注意的是,如果android:valueFrom、android:valueTo的值設定為color型別的值,那麼不需要設定這個引數;
- android:interpolator:設定加速器;

下面我們就看看如何使用:

ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(MyActivity.this,
        R.animator.object_animator);
animator.setTarget(mTv1);
animator.start();

同樣是使用loadAnimator載入對應的xml動畫。然後使用animator.setTarget(mTv1);繫結上動畫目標。因為在xml中,沒有設定目標的引數,所以我們必須通過程式碼將目標控制元件與動畫繫結。

(2)、使用示例

我們先寫一個動畫的xml:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:propertyName="TranslationY"
                android:duration="2000"
                android:valueFrom="0.0"
                android:valueTo="400.0"
                android:interpolator="@android:anim/accelerate_interpolator"
                android:valueType="floatType"
                android:repeatCount="1"
                android:repeatMode="reverse"
                android:startOffset="2000"/>

在這個xml中,我們定義了更改屬性為TranslationY,即改變縱座標;時長為2000毫秒。從0變到400;使用的插值器是加速插值器,對應的值型別為float型別。
有些同學可能會問,為什麼是float型別,因為setTranslationY函式的引數是float型別的,宣告如下:

public void setTranslationY(float translationY)

最後是設定重複次數和重複模式。將動畫啟用延時設定為2000毫秒;
然後是載入動畫:

ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(MyActivity.this,
        R.animator.object_animator);
animator.setTarget(mTv1);
animator.start();

效果圖如下:
這裡寫圖片描述
在點選後,延時2000毫秒後,開始執行。逆序重複執行一次。
原始碼在文章底部給出

(3)、使用color屬性示例

這裡我們就演示一下如何使用android:valueFrom、android:valueTo的color屬性用法,
我們建立一個objectAnimator的動畫檔案:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:propertyName="BackgroundColor"
                android:duration="5000"
                android:valueFrom="#ffff00ff"
                android:valueTo="#ffffff00"/>

設定屬性名為BackgroundColor,即對應的set函式為setBackgroundColor(int color);
android:valueFrom和android:valueTo的取值都為顏色值,即#開頭的八位數值;即ARGB值;
使用方法不變:

ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(MyActivity.this,
        R.animator.color_animator);
animator.setTarget(mTv1);
animator.start();

效果圖如下:
這裡寫圖片描述
從效果圖中可以看到,雖然實現了顏色變化,但會一直閃;所以直接利用xml實現的動畫效果並不怎麼好,所以如果想要實現顏色變化,還是利用程式碼來實現吧。前面的文章中,我們已經講過如何利用ValueAnimator和ObjectAnimator來實現顏色過渡和原理了。大家可以翻看下。
原始碼在文章底部給出

3、set

(1)欄位意義及使用方法

這個是AnimatorSet所對應的標籤。它只有一個屬性:

<set
  android:ordering=["together" | "sequentially"]>

android:ordering:表示動畫開始順序。together表示同時開始動畫,sequentially表示逐個開始動畫;
載入方式為:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(MyActivity.this,
        R.animator.set_animator);
set.setTarget(mTv1);
set.start();

同樣是通過loadAnimator載入動畫,然後將其強轉為AnimatorSet;

(2)、示例

在res/animator資料夾下新建一個檔案(set_animator.xml):

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:ordering="together">
    <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueFrom="0"
            android:valueTo="400"
            android:valueType="floatType"/>
    <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueFrom="0"
            android:valueTo="300"
            android:valueType="floatType"/>
</set>

這裡有兩個objectAnimator動畫,一個改變值x座標,一個改變值y座標;取值分別為0-400和0-300;
然後在程式碼中載入:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(MyActivity.this,
        R.animator.set_animator);
set.setTarget(mTv1);
set.start();

動畫效果如下:
這裡寫圖片描述
原始碼在文章底部給出

4、總結

最後總結一下,所有animator標籤及取值範圍如下:

<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

各欄位的取值意義在上面講解時已經給出,大家可以翻回去看看。

二、開篇示例——AnimatorSet應用

在講完了XML使用方法之後,AnimatorSet的部分就完全結束了,下面我們就利用學到的知識來看一下開篇時的那個效果是如何實現的吧。
這裡寫圖片描述
我們先來分析下這個效果,在使用者點選按鈕時,把選單彈出來;彈出來的時候,動畫一點從小變到大,一邊透明度從0變到1.關鍵問題是,怎麼樣實現各個選單以當前點選按鈕為圓心排列在圓形上;

1、原理

在開始寫程式碼之前,我們先講講,如何根據圓半徑來定位每個圖片的位置,先看下圖:
這裡寫圖片描述
在上面的圖中,我們可以清晰的看到,假如當前選單與Y軸的夾角是a度,那麼這個選單所移動的X軸距離為radius * sin(a);Y軸的移動距離為radius * cos(a);
這是非常簡單的三角函式的計算。想必這塊大家理解起來是沒有問題的。
那麼第一個問題來了,這個夾角a是多少度呢?
很顯然,這裡所有的選單的夾角之和是90度。我們總共有五個選單項,把90度夾角做了4等分。所以夾角a的度數為90/4 = 22;所以這五個選單,第一個選單的夾角是0度,第二個選單的夾角是22度,第三個選單的夾角是22*2度,第四個夾角是22*3度,第五個的夾角是22*4度.
我們假設index表示當前選單的位置索引,從0開始,即第一個選單的索引是0,第二個選單的索引是1,第三個選單的索引是2……,而當前的選單與y軸的夾角恰好佔了22度的index份;所以當前選單與Y軸的夾角為22 * index;這個公式非常重要,大家在這裡一定要理解,下面程式碼中會用到。
第二個問題來了,如何求對應角度的sin,cos值
想必很多同學都知道,JAVA中有一個Math類,它其中有四個函式:

/**
 * 求對應弧度的正弦值
 */
double sin(double d)
/**
 * 求對應弧度的餘弦值
 */
double cos(double d)
/**
 * 求對應弧度的正切值
 */
double tan(double d)

這裡要非常注意的是,這三個函式的輸入引數不是度數,而是對應的度數的弧度值!
角度與其對應的弧度值對應關係如下:
這裡寫圖片描述
在Math中有兩種方法可以得到弧度值:
第一種方法:在Math中,Math.PI不僅代表圓周率π,也代表180度角所對應的弧度值。所以Math.sin(Math.PI)就表示180度的正弦值,Math.sin(Math.PI/2)就表示90度的正弦值。
第二種方法:根據度數獲得弧度值
在Math中也提供了一個方法

/**
 * Math中根據度數得到弧度值的函式
 */
double toRadians(double angdeg)

這個函式就是Math中根據度數得到弧度值的函式,引數angdeg指度數,返回值是對應的弧度值。
所以比如我們要求22度對應的弧度值就是Math.toRadians(22);所以如果我們要求22度所對應的正弦值就是Math.sin(Math.toRadians(22))
在講了如何根據半徑求得每個選單項的位置之後,我們來看看示例工程的程式碼。

2、佈局程式碼(main.xml)

佈局程式碼很簡單,就是利用FrameLayout將所有的選單都蓋在按鈕的下面,效果圖如下:
這裡寫圖片描述
對應程式碼為:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_marginBottom="10dp"
             android:layout_marginRight="10dp">

    <Button
            android:id="@+id/menu"
            style="@style/MenuStyle"
            android:background="@drawable/menu"/>

    <Button
            android:id="@+id/item1"
            style="@style/MenuItemStyle"
            android:background="@drawable/circle1"
            android:visibility="gone"/>

    <Button
            android:id="@+id/item2"
            style="@style/MenuItemStyle"
            android:background="@drawable/circle2"
            android:visibility="gone"/>

    <Button
            android:id="@+id/item3"
            style="@style/MenuItemStyle"
            android:background="@drawable/circle3"
            android:visibility="gone"/>

    <Button
            android:id="@+id/item4"
            style="@style/MenuItemStyle"
            android:background="@drawable/circle4"
            android:visibility="gone"/>

    <Button
            android:id="@+id/item5"
            style="@style/MenuItemStyle"
            android:background="@drawable/circle5"
            android:visibility="gone"/>

</FrameLayout>

其中的style程式碼為:

<resources>
    <style name="MenuStyle">
        <item name="android:layout_width">50dp</item>
        <item name="android:layout_height">50dp</item>
        <item name="android:layout_gravity">right|bottom</item>
    </style>

    <style name="MenuItemStyle">
        <item name="android:layout_width">45dp</item>
        <item name="android:layout_height">45dp</item>
        <item name="android:layout_gravity">right|bottom</item>
    </style>
</resources>

佈局是沒什麼難度的,下面我們就來看看MyActivity中的處理。

3、MyActivity.java

(1)、先看看框架部分:
public class MyActivity extends Activity implements View.OnClickListener{
    private static final String TAG = "MainActivity";

    private Button mMenuButton;
    private Button mItemButton1;
    private Button mItemButton2;
    private Button mItemButton3;
    private Button mItemButton4;
    private Button mItemButton5;

    private boolean mIsMenuOpen = false;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initView();
    }

    private void initView() {
        mMenuButton = (Button) findViewById(R.id.menu);
        mMenuButton.setOnClickListener(this);

        mItemButton1 = (Button) findViewById(R.id.item1);
        mItemButton1.setOnClickListener(this);

        mItemButton2 = (Button) findViewById(R.id.item2);
        mItemButton2.setOnClickListener(this);

        mItemButton3 = (Button) findViewById(R.id.item3);
        mItemButton3.setOnClickListener(this);

        mItemButton4 = (Button) findViewById(R.id.item4);
        mItemButton4.setOnClickListener(this);

        mItemButton5 = (Button) findViewById(R.id.item5);
        mItemButton5.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v == mMenuButton) {
            if (!mIsMenuOpen) {
                mIsMenuOpen = true;
                doAnimateOpen(mItemButton1, 0, 5, 300);
                doAnimateOpen(mItemButton2, 1, 5, 300);
                doAnimateOpen(mItemButton3, 2, 5, 300);
                doAnimateOpen(mItemButton4, 3, 5, 300);
                doAnimateOpen(mItemButton5, 4, 5, 300);
            } else {
                mIsMenuOpen = false;
                doAnimateClose(mItemButton1, 0, 5, 300);
                doAnimateClose(mItemButton2, 1, 5, 300);
                doAnimateClose(mItemButton3, 2, 5, 300);
                doAnimateClose(mItemButton4, 3, 5, 300);
                doAnimateClose(mItemButton5, 4, 5, 300);
            }
        } else {
            Toast.makeText(this, "你點選了" + v, Toast.LENGTH_SHORT).show();
        }
    }
    ………………
}    

這部分程式碼很簡單,就是利用findviewById來找到每個選單的例項,然後對他們新增點選響應:

public void onClick(View v) {
    if (v == mMenuButton) {
        if (!mIsMenuOpen) {
            mIsMenuOpen = true;
            doAnimateOpen(mItemButton1, 0, 5, 300);
            …………
        } else {
            mIsMenuOpen = false;
            doAnimateClose(mItemButton1, 0, 5, 300);
            …………
        }
    } else {
        Toast.makeText(this, "你點選了" + v, Toast.LENGTH_SHORT).show();
    }
}

其中彈出主選單的按鈕是mMenuButton,當點選mMenuButton時,利用mIsMenuOpen來標識當前是否已經彈出選單;如果沒有彈出,則利用doAnimateOpen(mItemButton1, 0, 5, 300)將mItemButton1彈出來;其它按鈕類似。如果已經彈出來,則利用doAnimateClose(mItemButton1, 0, 5, 300);將mItemButton1收回。
下面我們就分別來看看doAnimateOpen()和doAnimateClose()的程式碼;

(2)、doAnimateOpen()——彈出選單

先貼出完整程式碼:

private void doAnimateOpen(View view, int index, int total, int radius) {
    if (view.getVisibility() != View.VISIBLE) {
        view.setVisibility(View.VISIBLE);
    }
    double degree = Math.toRadians(90)/(total - 1) * index;
    int translationX = -(int) (radius * Math.sin(degree));
    int translationY = -(int) (radius * Math.cos(degree));

    AnimatorSet set = new AnimatorSet();
    //包含平移、縮放和透明度動畫
    set.playTogether(
            ObjectAnimator.ofFloat(view, "translationX", 0, translationX),
            ObjectAnimator.ofFloat(view, "translationY", 0, translationY),
            ObjectAnimator.ofFloat(view, "scaleX", 0f, 1f),
            ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f),
            ObjectAnimator.ofFloat(view, "alpha", 0f, 1));
    //動畫週期為500ms
    set.setDuration(1 * 500).start();
}

我們倒過來看,先看動畫部分:

set.playTogether(
        ObjectAnimator.ofFloat(view, "translationX", 0, translationX),
        ObjectAnimator.ofFloat(view, "translationY", 0, translationY),
        ObjectAnimator.ofFloat(view, "scaleX", 0f, 1f),
        ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f),
        ObjectAnimator.ofFloat(view, "alpha", 0f, 1));

這裡構造的動畫是利用translationX和translationY將控制元件移動到指定位置。同時,scaleX、scaleY、alpha都從0變到1;最關鍵的部分是如何得到translationX和translationY的值。
在這部分的開篇,我們首先講了,如何講了

translationX = radius * sin(a)
translationY = radius * cos(a)

我們來看看在程式碼中如何去做的:

double degree = Math.toRadians(90)/(total - 1) * index;
int translationX = -(int) (radius * Math.sin(degree));
int translationY = -(int) (radius * Math.cos(degree));

首先,是求得兩個選單的夾角,即公式裡的a值。Math.toRadians(90)/(total - 1)表示90度被分成了total-1份,其中每一份的弧度值;
我們前面講過,假設每一份的弧度值是22度,那麼當前選單與Y軸的夾角就是22 * index度。這裡類似,當前選單與y軸的弧度值就是Math.toRadians(90)/(total - 1) * index
在求得夾角以後,直接利用translationX = radius * sin(a)就可以得到x軸的移動距離,但又因為選單是向左移動了translationX距離。所以根據座標系向下為正,向右為正的原則。這裡的移動距離translationX應該是負值。我們需要的translationY,因為是向上移動,所以也是負值:

int translationX = -(int) (radius * Math.sin(degree));
int translationY = -(int) (radius * Math.cos(degree));

在理解了彈出的部分之後,收回的程式碼就好理解了

(3)、doAnimateClose()——收回選單

收回選單就是把彈出選單的動畫反過來,讓它從translateX,translateY的位置上回到0點,scaleX、scaleY、alpha的值從1變到0即可:

private void doAnimateClose(final View view, int index, int total,
                           int radius) {
   if (view.getVisibility() != View.VISIBLE) {
       view.setVisibility(View.VISIBLE);
   }
   double degree = Math.PI * index / ((total - 1) * 2);
   int translationX = -(int) (radius * Math.sin(degree));
   int translationY = -(int) (radius * Math.cos(degree));
   AnimatorSet set = new AnimatorSet();
   //包含平移、縮放和透明度動畫
   set.playTogether(
           ObjectAnimator.ofFloat(view, "translationX", translationX, 0),
           ObjectAnimator.ofFloat(view, "translationY", translationY, 0),
           ObjectAnimator.ofFloat(view, "scaleX", 1f, 0f),
           ObjectAnimator.ofFloat(view, "scaleY", 1f, 0f),
           ObjectAnimator.ofFloat(view, "alpha", 1f, 0f));

   set.setDuration(1 * 500).start();
}

這段程式碼是很容易理解的,但我在這裡求degree的時候,換了一種方法:

 double degree = Math.PI * index / ((total - 1) * 2);

其實這句程式碼與上面的

double degree = Math.toRadians(90)/(total - 1) * index;

是同一個意思。
還記得,我們在講原理的時候,提到過Math.PI不僅表示圓周率,也表示180度所對應的弧度。
所以Math.toRadians(90)就等於Math.PI/2,這樣,這兩個公式就是完全一樣的了。

(4)、存在問題

到這裡,這個路徑動畫就講解完了,但如果多使用幾次就會發現,這裡的動畫存在問題:當選單收回以後,再點選選單展開時所在的位置,仍然會響應點選事件:

這裡寫圖片描述

問題在於,在將選單縮回時:

set.playTogether(
         …………
        ObjectAnimator.ofFloat(view, "scaleX", 1f, 0f),
        ObjectAnimator.ofFloat(view, "scaleY", 1f, 0f));

在setScaleX\setScaleY動畫時,由於我們將控制元件縮小到0,但是動畫在控制元件被縮小到0以後,對它所做的屬性動畫,並不會實際改變控制元件的位置,這就像檢視動畫一樣,雖然動畫把控制元件移走了,但是響應點選事件的位置仍是在原來位置。這是Android的一個bug;所以,為了解決這個問題,可以有兩種解決方案;

方案一:

第一個方案比較簡單,在縮小時,既然在縮小到0以後,存在bug,那我們只需要不把控制元件縮小到0就可以了:

set.playTogether(
        ObjectAnimator.ofFloat(view, "translationX", translationX, 0),
        ObjectAnimator.ofFloat(view, "translationY", translationY, 0),
        ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.1f),
        ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.1f),
        ObjectAnimator.ofFloat(view, "alpha", 1f, 0f));

這裡縮小到0.1大小,視覺上看不出來,也正好規避了縮小到0以後不會實際改變控制元件位置的問題。

方案二:

方案一是在縮小時做補救,而另一個方案而是監聽動畫狀態,在動畫結束時,將控制元件放大即可。

set.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setScaleX(1.0f);
        view.setScaleY(1.0f);

    }
    //其它監聽函式只重寫,不實現,程式碼省略
    …………
});

原始碼在文章底部給出
好了,到這裡有關AnimatorSet的部分就講完了,下篇給大家講講有關viewGroup動畫相關的知識。

(BlogAnimatorSetDemo中存在的問題,在原始碼中是沒有修改的,大家注意自行修復)

原始碼內容:
1、BlogXMLAnimator:第一部分:聯合動畫的XML實現對應原始碼
2、BlogAnimatorSetDemo:第二部分:開篇示例——AnimatorSet應用對應原始碼