Android之Animator屬性動畫

Android之Animator屬性動畫

1、概述

  在3.0系統之前,Android給我們提供了逐幀動畫Frame Animation和補間動畫Tween Animation兩種動畫:

  • 逐幀動畫的原理很簡單,就是將一個完整的動畫拆分成一張張單獨的圖片,然後將它們連貫起來進行播放;
  • 補間動畫是專門爲View提供的動畫,可以實現View的透明度、縮放、平移和旋轉四種效果。

  補間動畫有兩個個缺陷:

  • 補間動畫只能對View設置動畫,對非View的對象不能設置動畫;
  • 補間動畫只是改變了View的顯示效果而沒有真正的改變View的屬性。例如,我們想使用補間動畫將一個按鈕從一個位置移動到一個新的位置,那麼當移動完成之後我們點擊這個按鈕,是不會觸發其點擊事件的,而當我們點擊移動前的位置時,會觸發其點擊事件,即補間動畫只是在另一個地方重新繪製了這個View,其他的東西都沒有改變。

  在3.0系統之後,Android爲我們提供了一種新的動畫——Animator屬性動畫。在屬性動畫中,我們不僅可以像補間動畫那樣設置控件的透明度、縮放、平移或旋轉的動畫,還可以做到將這些動畫聯合起來播放、將一組動畫按順序播放、控制動畫的播放速度,甚至可以對非View設置動畫等等。

  屬性動畫,顧名思義,是對對象的屬性設置的動畫。簡單的說,只要一個對象的某個屬性有set和get方法,就可以對其設置屬性動畫。一句話概括,屬性動畫就是不斷的改變一個對象的某個屬性。我們只需要告訴系統動畫的運行時長,需要執行哪種類型的動畫,以及動畫的初始值和結束只,剩下的工作就可以全部交給系統去完成了。

  正是因爲屬性動畫是對屬性的動畫,因此補間動畫的第二個缺陷就不復存在了。使用屬性動畫移動一個按鈕,那麼這個按鈕就是真的被移動了,而不僅僅是在另一個地方重新繪製了自己那麼簡單。

2、ObjectAnimator

  ObjectAnimator是屬性動畫中最常用的一個類,我們可以通過它直接控制一個對象的屬性。使用ObjectAnimator時,我們只需要我們想修改哪個對象的哪個屬性、屬性的起始值和結束值,以及動畫的持續時間,系統就可以爲我們運行動畫了。ObjectAnimator的使用方法如下。

  執行單個動畫:

ObjectAnimator.ofFloat(image, "translationX", 0f, 500f) // 初始化動畫,設置各個參數
        .setDuration(3000) // 設置動畫持續時間
        .start(); // 開始運行動畫

  上面代碼中,ofFloat()方法的第一個參數是動畫作用的對象,這裏是一個ImageView;第二個參數是屬性名稱,這裏指定的是X軸的平移;第三個參數是一個不定長參數,指定屬性的起始值和結束值;setDuration()方法指定的是動畫執行的時長,這裏是3秒鐘;最後調用start()方法,動畫就開始執行了。

  ObjectAnimator不僅有ofFloat()方法,還有很多其他方法,例如ofInt()、ofObject()、ofPropertyValuesHolder()等,這些方法會在後面詳細介紹。

  如果想要同時執行多個動畫,只需要指定多個動畫後分別調用start()方法即可,代碼如下:

ObjectAnimator.ofFloat(image, "translationX", 0f, 500f).setDuration(3000).start();
ObjectAnimator.ofFloat(image, "translationY", 0f, 500f).setDuration(3000).start();
ObjectAnimator.ofFloat(image, "rotation", 0f, 360f).setDuration(3000).start();
 爲了加深對ObejctAnimator的理解,這裏我們使用ObjectAnimator實現一個衛星菜單的效果,效果圖如下:

                         

  如上圖所示,開始的時候七個菜單圖標是隱藏在右下角的紅色按鈕底下的,如左圖所示;當我們點擊了紅色按鈕的時候,其他菜單圖標就會彈出,像衛星一樣圍繞早紅色按鈕外層,如右圖所示。實現這個效果的核心代碼如下:

複製代碼
for (int i = 0; i < menus.size(); i++) {
    ImageView menu = menus.get(i);
    double angle = Math.toRadians(i * (90 * 1.0 / (menus.size() - 1))); // 角度
    double radius = 450; // 半徑
    float distanceX = (float) (Math.cos(angle) * radius); // X座標偏移量
    float distanceY = (float) (Math.sin(angle) * radius); // Y座標偏移量
    ObjectAnimator animatorX;
    ObjectAnimator animatorY;
    if (isOpen) { // 如果菜單是打開的則關閉菜單
        animatorX = ObjectAnimator.ofFloat(menu, "translationX", -distanceX, 0f);
        animatorY = ObjectAnimator.ofFloat(menu, "translationY", -distanceY, 0f);
    } else { // 如果菜單是關閉的則打開菜單
        animatorX = ObjectAnimator.ofFloat(menu, "translationX", 0f, -distanceX);
        animatorY = ObjectAnimator.ofFloat(menu, "translationY", 0f, -distanceY);
    }
    AnimatorSet set = new AnimatorSet(); // X、Y軸同時移動
    set.playTogether(animatorX, animatorY);
    set.setDuration(500);
    set.setInterpolator(new BounceInterpolator());
    set.start();
}
複製代碼

3、組合動畫

  所謂的組合動畫,就是將多個動畫同時播放,或將一組動畫按順序播放。在上面介紹ObjectAnimator時我們已經介紹了一種方式,就是指定多個動畫後分別調用start()方法。下面介紹其他幾種方法:

  方法二:使用PropertyValuesHolder:

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("translationX", 0f, 500f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("translationY", 0f, 500f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("rotation", 0f, 360f);
ObjectAnimator.ofPropertyValuesHolder(image, holder1, holder2, holder3).setDuration(3000).start();
 方法三:使用AnimatorSet:
複製代碼
ObjectAnimator animator1 = ObjectAnimator.ofFloat(image, "translationX", 0f, 500f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(image, "translationY", 0f, 500f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(image, "rotation", 0f, 360f);
AnimatorSet set = new AnimatorSet();
set.setDuration(3000);
set.playTogether(animator1, animator2, animator3);
set.start();
複製代碼
 上面說到,屬性動畫不光可以讓一組動畫同時播放,還可以按一定順序播放動畫。按順序播放動畫的幾種方式代碼如下:

  方法一:使用AnimatorSet對象中的API實現:

複製代碼
ObjectAnimator animator1 = ObjectAnimator.ofFloat(image, "translationX", 0f, 500f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(image, "translationY", 0f, 500f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(image, "rotationX", 0f, 360f);
ObjectAnimator animator4 = ObjectAnimator.ofFloat(image, "rotationY", 0f, 360f);
AnimatorSet set = new AnimatorSet();
set.play(animator3).before(animator2).after(animator1).with(animator4);
set.setDuration(3000);
set.start();
複製代碼
 方法二:設置動畫監聽事件AnimatorListener:
複製代碼
ObjectAnimator animator1 = ObjectAnimator.ofFloat(image, "alpha", 0f, 1f);
final ObjectAnimator animator2 = ObjectAnimator.ofFloat(image, "translationX", 0f, 500f);
final ObjectAnimator animator3 = ObjectAnimator.ofFloat(image, "translationY", 0f, 500f);
animator1.setDuration(3000);
animator2.setDuration(3000);
animator3.setDuration(3000);
// 設置屬性動畫的監聽事件(使用AnimatorListener必須要監聽所有四個事件)
animator1.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        animator2.start();
    }
    @Override
    public void onAnimationEnd(Animator animation) {
        animator3.start();
    }
    @Override
    public void onAnimationCancel(Animator animation) {
    }
    @Override
    public void onAnimationRepeat(Animator animation) {
    }
});
animator1.start();
複製代碼
 方法三:設置動畫監聽事件AnimatorListenerAdapter:
複製代碼
ObjectAnimator animator1 = ObjectAnimator.ofFloat(image, "alpha", 0f, 1f);
final ObjectAnimator animator2 = ObjectAnimator.ofFloat(image, "translationX", 0f, 500f);
final ObjectAnimator animator3 = ObjectAnimator.ofFloat(image, "translationY", 0f, 500f);
animator1.setDuration(3000);
animator2.setDuration(3000);
animator3.setDuration(3000);
// 設置屬性動畫的監聽事件(使用AnimatorListenerAdapter可以選擇不監聽所有事件)
animator1.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        animator3.start(); // 在animator1執行完後執行animator3
    }
    @Override
    public void onAnimationStart(Animator animation) {
        animator2.start(); //在animator1執行的同時執行animator2
    }
});
animator1.start();
複製代碼

4、ValueAnimator

  ValueAnimator是整個屬性動畫最核心的類,ObjectAnimator類就是ValueAnimator類的一個子類。前面我們說過,屬性動畫的原理就是通過不斷改變對象的屬性值來實現過渡的,而這種過渡就是通過ValueAnimator類來負責計算的。除此之外,ValueAnimator還負責管理動畫的播放次數、播放模式和動畫監聽等。

  ValueAnimator的使用方法和ObjectAnimator的使用方法基本類似,不同的是ValueAnimator中不能指定運行動畫的對象,因此ValueAnimator往往需要設置一個動畫監聽,通過不斷監聽當前動畫運行到的屬性值來動態的進行處理。代碼如下:

複製代碼
ValueAnimator animator = ValueAnimator.ofInt(0, 100); // 產生一個從0到100變化的整數的動畫
animator.setDuration(2000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Integer value = (Integer) animation.getAnimatedValue(); // 動態的獲取當前運行到的屬性值
        ((Button) view).setText(value + "");
    }
});
animator.start(); // 開始播放動畫
複製代碼
 和ObjectAnimator一樣,除了ofFloat()之外,ValueAnimator也有一些其他的方法,如ofPropertyValuesHolder()、ofObject()、ofInt()等方法。

  除了上面的功能,ValueAnimator還可以通過setRepeatCount()和setRepeatMode()方法來設置動畫重複的次數和播放模式、通過setStartDelay()方法來設置動畫延遲播放的時間。這些方法也都可以在ObjectAnimator中使用。

5、Animator監聽器

  在“組合動畫”章節中我們已經接觸過Animator的監聽器了。常用的Animator監聽器有AnimatorListener和AnimatorListenerAdapter,都是通過Animator對象的addListener()方法設置的。

  如果添加的監聽器是AnimatorListener,那麼就必須要實現onAnimationStart()、onAnimationRepeat()、onAnimationEnd()和onAnimationCancel()四個方法,示例代碼如下:

複製代碼
animator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        // 動畫開始的監聽事件
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        // 動畫結束的監聽事件
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        // 動畫取消的監聽事件
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        // 動畫重播的監聽事件
    }
});
複製代碼
 如果添加的是AnimatorListenerAdapter,那麼就不必一一實現這四個方法,而是可以根據自己的需要自定義實現,示例代碼如下:
複製代碼
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        // 動畫結束的監聽事件
    }

    @Override
    public void onAnimationStart(Animator animation) {
        // 動畫開始的監聽事件
    }
    
    // 其他兩個事件可以選擇不實現
});
複製代碼

6、使用XML編寫動畫

  過去的補間動畫可以通過XML的方式編寫,屬性動畫也可以編寫到XML文件中。編寫到XML文件中的一個好處是可以方便的實現動畫的重用。

  我們需要在項目的res目錄下創建一個名爲animator的文件夾,在這個文件夾中定義動畫。animator動畫XML文件中可以包括以下三種標籤:

  • <animator>:相當於JAVA代碼中的ValueAnimator;
  • <objectAnimator>:相當於JAVA代碼中的ObjectAnimator;
  • <set>:相當於JAVA代碼中的AnimatorSet。

  如果我們想直接定義一個ValueAnimator,可以這樣寫:

複製代碼
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="5000"
    android:interpolator="@android:anim/bounce_interpolator"
    android:repeatCount="2"
    android:repeatMode="restart"
    android:valueFrom="0"
    android:valueTo="100"
    android:valueType="intType" />
複製代碼
 如果我們想直接定義一個ObjectAnimator,可以這樣寫:
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="5000"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:propertyName="scaleX"
    android:repeatCount="2"
    android:repeatMode="reverse"
    android:valueFrom="0"
    android:valueTo="360"
    android:valueType="floatType" />
複製代碼
 如果我們想要定義一系列動畫,可以這樣寫:
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">

    <objectAnimator
        android:duration="5000"
        android:propertyName="translationX"
        android:valueFrom="0"
        android:valueTo="500"
        android:valueType="floatType" />

    <objectAnimator
        android:duration="5000"
        android:propertyName="translationY"
        android:valueFrom="0"
        android:valueTo="500"
        android:valueType="floatType" />

    <set android:ordering="together">
        <objectAnimator
            android:duration="5000"
            android:propertyName="scaleX"
            android:valueFrom="0"
            android:valueTo="360"
            android:valueType="floatType" />

        <objectAnimator
            android:duration="5000"
            android:propertyName="scaleY"
            android:valueFrom="0"
            android:valueTo="360"
            android:valueType="floatType" />
    </set>
</set>
複製代碼
 在JAVA代碼中調用這些XML文件中定義的動畫時這樣寫:
Animator animator = AnimatorInflater.loadAnimator(MainActivity.this,R.animator.object_animator);
animator.setTarget(view);
animator.start();

7、TypeEvaluator

  TypeEvaluator的作用是告訴動畫如何從初始值過渡到結束值。

  我們一直在使用的ObjectAnimator.ofFloat()方法中其實就封裝了一個FloatEvaluator,FloatEvaluator是TypeEvaluator的一個子類,可以在指定的float類型的初始值與結束值之間進行平滑的過渡,源碼如下:

複製代碼
public class FloatEvaluator implements TypeEvaluator<Number> {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}
複製代碼

  可以看到,FloatEvaluator實現了TypeEvaluator接口並實現了器evaluate方法,在方法中第一個參數是進度,相當於百分比;第二個和第三個參數是開始值和結束值,通過這三個值來確定當前需要的屬性值並返回。

  前面說過,ValueAnimator和ObjectAnimator除了常用的ofFloat()、ofInt()方法之外,還有一個ofObject()方法,這個方法是對任意對象的屬性進行動畫操作的,但是Android不知道我們的任意對象是什麼對象,因此也不確定我們的屬性是怎樣從初始值過渡到結束值的,這個時候我們就需要實現一個自己的TypeEvaluator來告訴系統我們的對象的屬性是如何過渡的。

  例如,我們自定義一個動畫,讓一個TextView中的文本從“0%”變化到“100%”,核心代碼如下:

複製代碼
ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator () {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        int start = Integer.parseInt((String) startValue);
        int end = Integer.parseInt((String) endValue);
        int result = (int) ((start + fraction * (end - start)));
        return result + "%";
    }
}, "0", "100");
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        String result = (String) animation.getAnimatedValue();
        text.setText(result);
    }
});
animator.setDuration(5000);
animator.start();
複製代碼

8、Interpolator

  Interpolator的作用是可以控制動畫的變化速率。

  Android系統中默認爲我們提供了多種已經實現好的Interpolator,常用的如下:

複製代碼
1)  BounceInterpolator:彈跳效果;
2)  AccelerateInterpolator:逐漸加速;
3)  DecelerateInterpolator:逐漸減速;
4)  AccelerateDecelerateInterpolator:先加速後減速;
5)  OvershootInterpolator:到達目標點時“跑過頭了”,再返回到目標點;
6)  AnticipateInterpolator:移動之前先向後“助跑”;
7)  AnticipateOvershootInterpolator:OvershootInterpolator和AnticipateInterpolator的組合效果;
8)  CycleInterpolator:對於指定的動畫,正向做一遍,反響做一遍;
複製代碼

  Android中還爲我們提供了一個接口TimeInterpolator,供我們自定義插值器。我們也可以實現TimeInterpolator的一些已有實現類(如BaseInterpolator、Interpolator)來自定義插值器。

  TimeInterpolator接口中又一個抽象方法setInterpolation(),方法中又一個參數input,這個參數的值在整個動畫過程中從0勻速變化到1,也就是相當於一個百分比,指示當前動畫播放到哪了。

  如果我們在這個方法中直接返回input參數,那麼這個插值器就是一個勻速插值器,Android默認給我們提供的LinearInterpolator就是這樣寫的,源碼如下:

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    public float getInterpolation(float input) {
        return input;  //勻速
    }
}
 當然,如果我們修改input的值並返回,就會得到加速或減速的效果,如Android默認提供的AccelerateDecelerateInterpolator中的源碼:
public class AccelerateDecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;  //先加速後減速
    }
}
 有了以上的源碼作爲參考,我們就可以寫自定義的插值器了。例如,我們寫一個先減速後加速的插值器DecelerateAccelerateInterpolator,代碼如下:
複製代碼
public class DecelerateAccelerateInterpolator implements TimeInterpolator {
    @Override
    public float getInterpolation(float input) {
        float result;
        if (input <= 0.5) {
            result = (float) (Math.sin(Math.PI * input)) / 2;  //前半部分減速
        } else {
            result = (float) (2 - Math.sin(Math.PI * input)) / 2;  //後半部分加速
        }
        return result;
    }
}
複製代碼

9、ViewPropertyAnimator

  ViewPropertyAnimator是在Android 3.1的API中新加入的更加易懂、更加便於使用的動畫API。

  屬性動畫雖然可以對所有對象添加動畫效果,但我們在使用過程中還是以對View使用動畫爲多,因此Google爲我們提供了一個ViewPropertyAnimator,讓我們可以簡單的調用這套API來單獨爲View設置動畫。

  ViewPropertyAnimator讓我們可以使用鏈式編程,一行代碼爲一個View對象設置屬性,示例代碼如下:

複製代碼
textView.animate()
        .translationX(200)
        .translationY(200)
        .setDuration(2000)
        .setInterpolator(new BounceInterpolator())
        .start();
複製代碼

  當我們對一個View對象使用animate()方法時,就返回了一個ViewPropertyAnimator對象,這個對象中支持所有補間動畫的API以及屬性動畫的API。ViewPropertyAnimator是默認執行的動畫,也就是說,我們不需要在最後調用start()方法,動畫也會自動播放。

到此爲止,屬性動畫相關的知識點就介紹完了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章