動畫優化的實踐總結

動畫類型

  1. 幀動畫:通過序列幀實現,間隔一段時間播放一張圖片。實現簡單,但是性能最差
  2. 補間動畫:輸入動畫類型(透明度,大小,移動,旋轉),開始參數和結束參數。通過插值器控制變化速度。實現相對簡單,但是並沒有真正改變view的所在位置,只是顯示變化。
  3. 屬性動畫:ValueAnimator,ObjectAnimator和animatorSet。動畫優化的核心,見後續詳情

幀動畫優化

實現

動畫xml文件:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">

    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="200" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="300" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="400" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="600" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="700" />
    <item
        android:drawable="@mipmap/ic_launcher"
        android:duration="800" />

</animation-list>

調用

        imageView.setImageResource(R.drawable.anim_test);
        AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
        animationDrawable.start();

關注點:

  • oneshot控制是否循環,建議不用試hanlder控制循環
  • 每幀的時間都可以不一樣。可以用於控制停頓
  • 代碼動態添加幀:AnimationDrawable.addFrame(Drawable,time)
  • 所有的view的繪製,都需要獲取焦點才能成功(詳見window機制)。所以需要調用
    animationDrawable.start();才能執行動畫

優化

幀動畫實際上是很難優化的,因爲固定要讀取bitmap。主要有以下兩種優化方式:

  • 內存優化:控制bitmap的總數,不使用的bitmap主動回收了,但是這樣會導致性能的下降。適合與低內存的手機;另一方面,動畫人員給的序列幀是通過工具生成的,有可能生成相同的圖片。我們可以複用他們,比如上下左右對稱的圖片,旋轉一週,肯定有存在相同的4張圖片。
  • 讀取優化:提前讀取一些圖片。

補間動畫

類型:直接引用他人總結的

實現:

  1. 在xml中配置,然後通過 AnimationUtils.loadAnimation(this, R.anim.view_animation)加載
  2. 代碼中設置起始參數,結束參數和插值器

詳情參考:https://www.jianshu.com/p/733532041f46

個人認爲關於補間動畫的實現已經寫的很詳細了。

 

關注點

  1. 補間動畫適合於效果單一的動畫,比如單純的平移,旋轉等
  2. 如果效果複雜。建議修改爲屬性動畫再優化,因爲每個類型,每個view就需要一個相應動畫。這樣就會出現很多的動畫實例

屬性動畫

  1. ValueAnimator:值動畫,實際上只是根據時間規律生成一系列數值(int,float,alpha,argb等),然後我們就可以把這些值設置給相應的view操作。讓view動起來
  2. ObjectAnimator:對象動畫。是ValueAnimator的實現類之一,需要綁定view對象,通過set/get方法自動把生成的一系列數值設置給相應的參數。set/get方法支持原有的方法,也支持自定義。
  3. animatorSet:動畫組合。用戶組合多個屬性動畫,讓他們同時進行或者排序進行。比如多個動畫同時進行,通過animatorSet就可以一時間產生多種數值,提高效率。原來的方式爲各自的動畫各自產生序列。

適用場景:

ObjectAnimator適用於只有一個view。如果多個view,爲了更好的性能,則建議適用ValueAnimator

ValueAnimator使用相對麻煩一些,適合於多view

優化

原則:儘量減少動畫對象

居於這個原則有以下優化方法:

  1. 使用animatorSet:通過set統一控制的動畫的執行和順序,但是animatorSet並不支持循環。如果非要循環,建議外部加一層handle,因爲動畫有可能被中斷。
  2. 對於單view,多效果使用PropertyValuesHolder結合ObjectAnimator。可以只用一個動畫對象實現多個效果。以下實現消失動畫(逐漸變小,逐漸透明直至完全消失)
            PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofFloat("scale", 1,0);
            PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofFloat("alpha", 1,0);
    
            ObjectAnimator.ofPropertyValuesHolder(this, scaleValuesHolder, alphaValuesHolder).start();
  3. 對於多view,多效果使用PropertyValuesHolder結合ValueAnimator。可以只用一個動畫對象實現多個view,多個效果。以下實現兩個view的消失動畫(逐漸變小,逐漸透明直至完全消失)
     final TextView textView1 = findViewById(R.id.tv_test);
            final TextView textView2 = findViewById(R.id.tv_test2);
    
            PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofFloat("scale", 1,0);
            PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofFloat("alpha", 1,0);
    
            ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleValuesHolder,alphaValuesHolder);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    textView1.setAlpha((float)animation.getAnimatedValue("alpha"));
                    textView1.setScaleX((float)animation.getAnimatedValue("scale"));
                    textView1.setScaleY((float)animation.getAnimatedValue("scale"));
    
                    textView2.setAlpha((float)animation.getAnimatedValue("alpha"));
                    textView2.setScaleX((float)animation.getAnimatedValue("scale"));
                    textView2.setScaleY((float)animation.getAnimatedValue("scale"));
                }
            });

     

  4. 如果動畫更復雜,比如先執行scale(500ms),動畫開始後100ms執行透明度動畫(400ms),然後停頓1.5s再循環。這樣的效果以上的方式都無法通過一個動畫實現。可以使用keyframe。
            final TextView textView1 = findViewById(R.id.tv_test);
            Keyframe scaleKf0 = Keyframe.ofFloat(0f, 0);
            Keyframe scaleKf1 = Keyframe.ofFloat(0.25f, 1);
            Keyframe scaleKf2 = Keyframe.ofFloat(1f, 1);
            PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofKeyframe("scale", scaleKf0, scaleKf1, scaleKf2);
    
            Keyframe alphaKf0 = Keyframe.ofFloat(0f, 0);
            Keyframe alphaKf1 = Keyframe.ofFloat(0.05f, 0);
            Keyframe alphaKf2 = Keyframe.ofFloat(0.25f, 1);
            Keyframe alphaKf3 = Keyframe.ofFloat(1f, 1);
            PropertyValuesHolder alphaValuesHolder = PropertyValuesHolder.ofKeyframe("alpha",alphaKf0, alphaKf1, alphaKf2, alphaKf3);
    
            ObjectAnimator.ofPropertyValuesHolder(textView1,scaleValuesHolder,alphaValuesHolder);

    keyframe第一個參數爲進度百分比(受插值器控制,如果不是LinearInterpolator,可能很快就到達一半了),第二參數爲value。上一幀到下一幀如果不一樣可以實現動畫,如果一樣不處理。還可以獨立設置每一個關鍵幀的插值器。同樣,單view使用ObjectAnimator,多view使用valueAnimator。再複雜的動畫都可以通過keyframe用最少的動畫實現效果。

最終篇-渲染優化

以上的優化思路還停留在不改變view渲染的。有幾個動畫元素就用多少個view。只是減少了動畫對象。但是根據view的渲染機制,並沒有優化多少,就是生成插值的內存和性能優化了。效果並不是很好。

以下實踐總結的優化:

  • 通過canvas,Paint畫圖/畫圓/畫矩形等等,結合valueAnimator,改變值以改變位置,大小或者形狀。調用invalidate刷新view。
  • 通過canvas,Paint畫圖/畫圓/畫矩形等等,結合ObjectAnimator,提供相應的set/get方法。

該方式使用較爲複雜,可以結合以上的所以情況,無論多麼複雜,都可以用一個view,最少的動畫對象實現。需要深刻了解自定義view,view渲染和動畫機制。將三者結合,實現最佳的性能。

還有很多細節,set/get方法不能混淆。使用keyframe時,如果效果沒有發現變化,就不用調用invalidate

以下是實現兩個圓,同時平移和縮放。

package com.example.admin.myapplication;

import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class MyAnimView extends View {
    private Paint mPaint;

    private float mX;
    private float mY;
    private float mRadius;

    public MyAnimView(Context context) {
        super(context);
        init();
    }


    public MyAnimView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
    }

    public void startAnim() {
        PropertyValuesHolder scaleValuesHolder = PropertyValuesHolder.ofFloat("scale", 0, 100);
        PropertyValuesHolder moveValuesHolder = PropertyValuesHolder.ofFloat("move", 0, 200);

        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleValuesHolder, moveValuesHolder);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mRadius = (float) animation.getAnimatedValue("scale");

                mX = (float) animation.getAnimatedValue("move");
                mY = (float) animation.getAnimatedValue("move");
                invalidate();
            }
        });
        valueAnimator.setDuration(1000);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.start();
    }

    @Override
    public void onDraw(Canvas canvas) {
        canvas.drawCircle(mX, mY, mRadius, mPaint);
        canvas.drawCircle(mX + 100, mY + 100, mRadius, mPaint);
    }
}

總結

場景 實現方式 說明
時間緊張,沒追求 幀動畫 實現簡單,性能極差
一個view,簡單的動效

補間動畫

實現簡單,性能一半,不能改變點擊響應區域
一個view,簡單的動效,需要改變點擊響應區域 ObjectAnimator 實現較簡單,性能一般
一個view,多個動效 ObjectAnimator結合PropertyValuesHolder 實現難度一般,性能優秀
多個view,多個動效 valueAnimator結合PropertyValuesHolder 實現難度一般,性能優秀
一個view,多個動效,但是啓動時間,結束時間不一樣 ObjectAnimator,PropertyValuesHolder結合keyframe 實現難度較難,性能很優秀
多個view,多個動效,但是啓動時間,結束時間不一樣 valueAnimator,PropertyValuesHolder結合keyframe 實現難度較難,性能很優秀
複雜的動畫的最佳性能優化 canvas,paint結合valueAnimator或者PropertyValuesHolder 實現最難,性能最佳

如果能用簡單的動畫實現就簡單實現,追求性能的時候再考慮複雜的實現的。

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