安卓動畫使用小結

最愛媽媽本尊發表於2017-12-25

動畫的分類

總的來說,Android動畫可以分為兩類,最初的傳統動畫和Android3.0 之後出現的屬性動畫; 傳統動畫又包括 幀動畫(Frame Animation)和補間動畫(Tweened Animation)。

幀動畫

幀動畫更多的依賴於完善的UI資源,他的原理就是將一張張單獨的圖片連貫的進行播放, 從而在視覺上產生一種動畫的效果;有點類似於某些軟體製作gif動畫的方式。

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/a_0"
        android:duration="100" />
    <item
        android:drawable="@drawable/a_1"
        android:duration="100" />
    <item
        android:drawable="@drawable/a_2"
        android:duration="100" />
</animation-list>

複製程式碼
protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame_animation);
        ImageView animationImg1 = (ImageView) findViewById(R.id.animation1);
        animationImg1.setImageResource(R.drawable.frame_anim1);
        AnimationDrawable animationDrawable1 = (AnimationDrawable) animationImg1.getDrawable();
        animationDrawable1.start();
    }
複製程式碼

把ui給的幀動畫的所有圖片按照順序解除安裝一個animation-list中,duration為每一張圖片顯示的時間,推薦60即可,最後找到AnimationDrawable,呼叫strat方法就行,幀動畫不呼叫start方法只會顯示第一幀圖片,不會自動播放。

注意: 幀動畫容易引起oom,所以幀動畫的所有圖片頭要進行壓縮,再放到工程中,而起儘量不要有太多張數。推薦一個線上壓縮工具。TinyPNG,使用非常方便。

補間動畫

補間動畫又可以分為四種形式,分別是 alpha(淡入淡出),translate(位移),scale(縮放大小),rotate(旋轉)。 補間動畫的實現,一般會採用xml 檔案的形式;程式碼會更容易書寫和閱讀,同時也更容易複用。

XML 實現

首先,在res/anim/ 資料夾下定義如下的動畫實現方式

alpha_anim.xml 動畫實現

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="1.0"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:toAlpha="0.0" />
複製程式碼

scale.xml 動畫實現

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXScale="0.0"
    android:fromYScale="0.0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1.0"
    android:toYScale="1.0"/>

複製程式碼

然後,在Activity中

Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.alpha_anim);
img = (ImageView) findViewById(R.id.img);
img.startAnimation(animation);
複製程式碼

這樣就可以實現ImageView alpha 透明變化的動畫效果。

也可以使用set 標籤將多個動畫組合

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

複製程式碼

各個動畫屬性的含義結合動畫自身的特點應該很好理解,就不一一闡述了;這裡主要說一下interpolator 和 pivot。

Interpolator 主要作用是可以控制動畫的變化速率 ,就是動畫進行的快慢節奏。Android 系統已經為我們提供了一些Interpolator ,比如 accelerate_decelerate_interpolator,accelerate_interpolator等。更多的interpolator 及其含義可以在Android SDK 中檢視。同時這個Interpolator也是可以自定義的,這個後面還會提到。

pivot 決定了當前動畫執行的參考位置,pivot 這個屬性主要是在translate 和 scale 動畫中,這兩種動畫都牽扯到view 的“物理位置“發生變化,所以需要一個參考點。而pivotX和pivotY就共同決定了這個點;它的值可以是float或者是百分比數值。

我們以pivotX為例:

安卓動畫使用小結

pivotY 也是相同的原理,只不過變成的縱向的位置。

Java Code 實現

有時候,動畫的屬性值可能需要動態的調整,這個時候使用xml 就不合適了,需要使用java程式碼實現

private void RotateAnimation() {
        animation = new RotateAnimation(-deValue, deValue, Animation.RELATIVE_TO_SELF,
                pxValue, Animation.RELATIVE_TO_SELF, pyValue);
        animation.setDuration(timeValue);

        if (keep.isChecked()) {
            animation.setFillAfter(true);
        } else {
            animation.setFillAfter(false);
        }
        if (loop.isChecked()) {
            animation.setRepeatCount(-1);
        } else {
            animation.setRepeatCount(0);
        }

        if (reverse.isChecked()) {
            animation.setRepeatMode(Animation.REVERSE);
        } else {
            animation.setRepeatMode(Animation.RESTART);
        }
        img.startAnimation(animation);
    }

複製程式碼

這裡animation.setFillAfter決定了動畫在播放結束時是否保持最終的狀態;animation.setRepeatCount和animation.setRepeatMode 決定了動畫的重複次數及重複方式,具體細節可檢視原始碼理解。

屬性動畫

屬性動畫,顧名思義它是對於物件屬性的動畫。因此,所有補間動畫的內容,都可以通過屬性動畫實現。

屬性動畫入門

private void RotateAnimation() {
        ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
        anim.setDuration(1000);
        anim.start();
    }

    private void AlpahAnimation() {
        ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f);
        anim.setRepeatCount(-1);
        anim.setRepeatMode(ObjectAnimator.REVERSE);
        anim.setDuration(2000);
        anim.start();
    }

複製程式碼

這兩個方法用屬性動畫的方式分別實現了旋轉動畫和淡入淡出動畫,其中setDuration、setRepeatMode及setRepeatCount和補間動畫中的概念是一樣的。

可以看到,屬性動畫貌似強大了許多,實現很方便,同時動畫可變化的值也有了更多的選擇,動畫所能呈現的細節也更多。

當然屬性動畫也是可以組合實現的

ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.5f, 0.8f, 1.0f);
                ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(myView, "scaleX", 0.0f, 1.0f);
                ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(myView, "scaleY", 0.0f, 2.0f);
                ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(myView, "rotation", 0, 360);
                ObjectAnimator transXAnim = ObjectAnimator.ofFloat(myView, "translationX", 100, 400);
                ObjectAnimator transYAnim = ObjectAnimator.ofFloat(myView, "tranlsationY", 100, 750);
                AnimatorSet set = new AnimatorSet();
                set.playTogether(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim);
//                set.playSequentially(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim);
                set.setDuration(3000);
                set.start();

複製程式碼

可以看到這些動畫可以同時播放,或者是按序播放。

屬性動畫——ValueAnimator

ValueAnimator是整個屬性動畫機制當中最核心的一個類,屬性動畫的執行機制是通過不斷地對值進行操作來實現的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的。它的內部使用一種時間迴圈的機制來計算值與值之間的動畫過渡,我們只需要將初始值和結束值提供給ValueAnimator,並且告訴它動畫所需執行的時長,那麼ValueAnimator就會自動幫我們完成從初始值平滑地過渡到結束值這樣的效果。除此之外,ValueAnimator還負責管理動畫的播放次數、播放模式、以及對動畫設定監聽器等,確實是一個非常重要的類。

使用案列


ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  
anim.setDuration(300);  
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
    @Override  
    public void onAnimationUpdate(ValueAnimator animation) {  
        float currentValue = (float) animation.getAnimatedValue();  
        Log.d("TAG", "cuurent value is " + currentValue);  
    }  
});  
anim.start();  
複製程式碼

這個列子代表動畫從0到1動畫時長為300毫秒,onAnimationUpdate方法中回撥的是中間的變化數值,可以基於這個變化值不停的改變view的屬性即可。

那麼除此之外,我們還可以呼叫setStartDelay()方法來設定動畫延遲播放的時間,呼叫setRepeatCount()和setRepeatMode()方法來設定動畫迴圈播放的次數以及迴圈播放的模式,迴圈模式包括RESTART和REVERSE兩種,分別表示重新播放和倒序播放的意思。這些方法都很簡單,我就不再進行詳細講解了

屬性動畫——ObjectAnimator

相比於ValueAnimator,ObjectAnimator可能才是我們最常接觸到的類,因為ValueAnimator只不過是對值進行了一個平滑的動畫過渡,但我們實際使用到這種功能的場景好像並不多。而ObjectAnimator則就不同了,它是可以直接對任意物件的任意屬性進行動畫操作的,比如說View的alpha屬性。

例子:將一個TextView在5秒中內從常規變換成全透明,再從全透明變換成常規

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
animator.setDuration(5000);  
animator.start(); 
複製程式碼

將TextView進行一次360度的旋轉

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);  
animator.setDuration(5000);  
animator.start();  
複製程式碼

第一個引數為使用動畫的view,第二個為動畫操作的屬性,後面的引數為屬性的變化趨勢。

注意: 其實ObjectAnimator內部的工作機制並不是直接對我們傳入的屬性名進行操作的,而是會去尋找這個屬性名對應的get和set方法,如果沒有get和set方法,屬性動畫並不會起作用,不信可以自己試試。

組合動畫——AnimatorSet

實現組合動畫功能主要需要藉助AnimatorSet這個類,這個類提供了一個play()方法,如果我們向這個方法中傳入一個Animator物件(ValueAnimator或ObjectAnimator)將會返回一個AnimatorSet.Builder的例項,AnimatorSet.Builder中包括以下四個方法: after(Animator anim) 將現有動畫插入到傳入的動畫之後執行 after(long delay) 將現有動畫延遲指定毫秒後執行 before(Animator anim) 將現有動畫插入到傳入的動畫之前執行 with(Animator anim) 將現有動畫和傳入的動畫同時執行

例子:讓TextView先從螢幕外移動進螢幕,然後開始旋轉360度,旋轉的同時進行淡入淡出操作

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);  
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);  
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
AnimatorSet animSet = new AnimatorSet();  
animSet.play(rotate).with(fadeInOut).after(moveIn);  
animSet.setDuration(5000);  
animSet.start();  
複製程式碼

Animator監聽器

在很多時候,我們希望可以監聽到動畫的各種事件,比如動畫何時開始,何時結束,然後在開始或者結束的時候去執行一些邏輯處理。這個功能是完全可以實現的,Animator類當中提供了一個addListener()方法,這個方法接收一個AnimatorListener,我們只需要去實現這個AnimatorListener就可以監聽動畫的各種事件了。

anim.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationRepeat(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationEnd(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationCancel(Animator animation) {  
    }  
});  
複製程式碼

onAnimationStart()方法會在動畫開始的時候呼叫,onAnimationRepeat()方法會在動畫重複執行的時候呼叫,onAnimationEnd()方法會在動畫結束的時候呼叫,onAnimationCancel()方法會在動畫被取消的時候呼叫。

AnimatorListenerAdapter

使用這個類就可以解決掉實現介面繁瑣的問題了,如下所示:

anim.addListener(new AnimatorListenerAdapter() {  
});  
複製程式碼

這裡我們向addListener()方法中傳入這個介面卡物件,由於AnimatorListenerAdapter中已經將每個介面都實現好了,所以這裡不用實現任何一個方法也不會報錯。那麼如果我想監聽動畫結束這個事件,就只需要單獨重寫這一個方法就可以了

ValueAnimator的高階用法——TypeEvaluator

那麼TypeEvaluator的作用到底是什麼呢?簡單來說,就是告訴動畫系統如何從初始值過度到結束值。

實現位置1平滑過渡到位置2

來先定義一個Point類

public class Point {  
  
    private float x;  
  
    private float y;  
  
    public Point(float x, float y) {  
        this.x = x;  
        this.y = y;  
    }  
  
    public float getX() {  
        return x;  
    }  
  
    public float getY() {  
        return y;  
    }  
  
}  
複製程式碼

接下來定義PointEvaluator

public class PointEvaluator implements TypeEvaluator{  
  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        Point startPoint = (Point) startValue;  
        Point endPoint = (Point) endValue;  
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());  
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());  
        Point point = new Point(x, y);  
        return point;  
    }  
  
}  
複製程式碼

PointEvaluator編寫完成了,接下來我們就可以非常輕鬆地對Point物件進行動畫操作了,比如說我們有兩個Point物件,現在需要將Point1通過動畫平滑過度到Point2

Point point1 = new Point(0, 0);  
Point point2 = new Point(300, 300);  
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);  
anim.setDuration(5000);  
anim.start();
複製程式碼

注意:要能看到動畫效果,需要監聽動畫的變化過程,不停的變化值複製給view的屬性才行。

Interpolator的用法

Interpolator 被用來修飾動畫效果,定義動畫的變化率,可以使存在的動畫效果accelerated(加速),decelerated(減速),repeated(重複),bounced(彈跳)等。

AccelerateDecelerateInterpolator 在動畫開始與結束的地方速率改變比較慢,在中間的時候加速 AccelerateInterpolator 在動畫開始的地方速率改變比較慢,然後開始加速 AnticipateInterpolator 開始的時候向後然後向前甩

AnticipateOvershootInterpolator 開始的時候向後然後向前甩一定值後返回最後的值 BounceInterpolator 動畫結束的時候彈起

CycleInterpolator 動畫迴圈播放特定的次數,速率改變沿著正弦曲線

DecelerateInterpolator 在動畫開始的地方快然後慢

LinearInterpolator 以常量速率改變

OvershootInterpolator 向前甩一定值後再回到原來位置

如果android定義的interpolators不符合你的效果也可以自定義interpolators

使用如下:

 anim.setInterpolator(new DecelerateAccelerateInterpolator());  
複製程式碼

ViewPropertyAnimator的用法

ViewPropertyAnimator提供了更加易懂、更加物件導向的API,如下所示:

textview.animate().alpha(0f);  
複製程式碼

果然非常簡單!不過textview.animate()這個方法是怎麼回事呢?animate()方法就是在Android 3.1系統上新增的一個方法,這個方法的返回值是一個ViewPropertyAnimator物件,也就是說拿到這個物件之後我們就可以呼叫它的各種方法來實現動畫效果了,這裡我們呼叫了alpha()方法並轉入0,表示將當前的textview變成透明狀態。

怎麼樣?比起使用ObjectAnimator,ViewPropertyAnimator的用法明顯更加簡單易懂吧。除此之外,ViewPropertyAnimator還可以很輕鬆地將多個動畫組合到一起,比如我們想要讓textview運動到500,500這個座標點上,就可以這樣寫:

textview.animate().x(500).y(500);  
複製程式碼

那麼怎樣去設定動畫的執行時長呢?很簡單,也是通過連綴的方式設定即可,比如我們想要讓動畫執行5秒鐘,就可以這樣寫:

textview.animate().x(500).y(500).setDuration(5000)  
        .setInterpolator(new BounceInterpolator());  
複製程式碼

注意:

整個ViewPropertyAnimator的功能都是建立在View類新增的animate()方法之上的,這個方法會建立並返回一個ViewPropertyAnimator的例項,之後的呼叫的所有方法,設定的所有屬性都是通過這個例項完成的。 大家注意到,在使用ViewPropertyAnimator時,我們自始至終沒有呼叫過start()方法,這是因為新的介面中使用了隱式啟動動畫的功能,只要我們將動畫定義完成之後,動畫就會自動啟動。並且這個機制對於組合動畫也同樣有效,只要我們不斷地連綴新的方法,那麼動畫就不會立刻執行,等到所有在ViewPropertyAnimator上設定的方法都執行完畢後,動畫就會自動啟動。當然如果不想使用這一預設機制的話,我們也可以顯式地呼叫start()方法來啟動動畫。 ViewPropertyAnimator的所有介面都是使用連綴的語法來設計的,每個方法的返回值都是它自身的例項,因此呼叫完一個方法之後可以直接連綴呼叫它的另一個方法,這樣把所有的功能都串接起來,我們甚至可以僅通過一行程式碼就完成任意複雜度的動畫功能。

lottie動畫

該動畫主要是靠ui設定師完成,通過安裝在AE上的一款名叫bodymovin的外掛,能夠將AE中的動畫工程檔案轉換成通用的json格式描述檔案,bodymovin外掛本身是用於在網頁上呈現各種AE效果的一個開源庫,lottie做的事情就是實現了一個能夠在不同移動端平臺上呈現AE動畫的方式.從而達到動畫檔案的一次繪製、一次轉換、隨處可用的效果. 當然,就如Java一次編譯,隨處執行一樣,lottie本身這個動畫播放庫並不是跨平臺的.

Lottie專案地址: github.com/airbnb/lott…

詳細使用請參考:www.jianshu.com/p/cae606f45…

總結:

對於屬性動畫的時候,最好寫成一個能複用的工具類,方便不同的地方使用。補間動畫雖然xml使用很方便,但是複用性很低,為了專案長遠的發展,還是應該做封裝處理,lottie動畫要看公司的選擇,這個動畫節約了程式設計師很多的時間,程式設計師可以用更多的時間來做效能的調優和業務的優化。

相關文章