Android動畫總結(jié)——View動畫迫淹、屬性動畫、幀動畫

在App中合理地使用動畫能夠獲得友好愉悅的用戶體驗悠就,Android中的動畫有View動畫千绪、屬性動畫、幀動畫梗脾、布局動畫荸型、轉(zhuǎn)場動畫等,在5.x以后有又新增了矢量動畫炸茧,這些動畫在平常開發(fā)中使用較為普遍瑞妇,所以有必要做一次完整的總結(jié)。


一梭冠、View動畫

View動畫定義了漸變Alpha辕狰、旋轉(zhuǎn)Rotate、縮放Scale控漠、平移Translate四種基本動畫蔓倍,并且通過這四種基本動畫的組合使用悬钳,可以實現(xiàn)多種交互效果。
View動畫使用非常簡單偶翅,不僅可以通過XML文件來定義動畫默勾,同樣可以通過Java代碼來實現(xiàn)動畫過程。

1.Xml文件定義View動畫

通過xml來定義View動畫涉及到一些公有的屬性(在AndroidStudio上不能提示):

android:duration     動畫持續(xù)時間
android:fillAfter    為true動畫結(jié)束時聚谁,View將保持動畫結(jié)束時的狀態(tài)
android:fillBefore   為true動畫結(jié)束時母剥,View將還原到開始開始時的狀態(tài)
android:repeatCount  動畫重復執(zhí)行的次數(shù)
android:repeatMode   動畫重復模式 ,重復播放時restart重頭開始形导,reverse重復播放時倒敘回放环疼,該屬性需要和android:repeatCount一起使用
android:interpolator 插值器,相當于變速器朵耕,改變動畫的不同階段的執(zhí)行速度

這些屬性是從Animation中繼承下來的炫隶,在alpharotate憔披、scale等限、translate標簽中都可以直接使用。
利用xml文件定義View動畫需要在工程的res目錄下創(chuàng)建anim文件夾芬膝,所有的xml定義的View動畫都要放在anim目錄下望门。
漸變view_anim_alpha.xml

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
       android:duration="2000"
       android:fromAlpha="1.0"
       android:toAlpha="0">
</alpha>

旋轉(zhuǎn)view_anim_rotate.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="2000"
        android:fillAfter="true"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="360">
</rotate>

縮放view_anim_scale.xml

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

平移view_anim_translate.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
           android:duration="2000"
           android:fromXDelta="0"
           android:fromYDelta="0"
           android:toXDelta="100%"
           android:toYDelta="100%">
</translate>

rotatescale動畫的android:pivotXandroid:pivotY屬性锰霜、translate動畫的android:toXDeltaandroid:toYDelta屬性的取值都可以是都可以數(shù)值筹误、百分數(shù)、百分數(shù)p癣缅,比如:50厨剪、50%50%p友存,他們?nèi)≈档拇淼囊饬x各不相同:
50表示以View左上角為原點沿坐標軸正方向(x軸向右祷膳,y軸向下)偏移50px的位置;
50%表示以View左上角為原點沿坐標軸正方向(x軸向右屡立,y軸向下)偏移View寬度或高度的50%處的位置直晨;
50%p表示以View左上角為原點沿坐標軸正方向(x軸向右,y軸向下)偏移父控件寬度或高度的50%處的位置(p表示相對于ParentView的位置)膨俐。

"50"位置點

"50%"位置點

"50%p"位置點

通過定義xml動畫資源文件勇皇,在Activity中調(diào)用:

public void clickToAlpha(View view) {
    Animation alphaAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_alpha);
    mTargetView.startAnimation(alphaAnim);
}

public void clickToRotate(View view) {
    Animation rotateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_rotate);
    mTargetView.startAnimation(rotateAnim);
}

public void clickToScale(View view) {
    Animation scaleAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_scale);
    mTargetView.startAnimation(scaleAnim);
}

public void clickToTranslate(View view) {
    Animation translateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_translate);
    mTargetView.startAnimation(translateAnim);
}

public void clickToSet(View view) {
    Animation setAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_set);
    mTargetView.startAnimation(setAnim);
}
2.Java代碼實現(xiàn)View動畫

在平常的業(yè)務(wù)邏輯中也可以直接用Java代碼來實現(xiàn)Veiw動畫,Android系統(tǒng)給我們提供了AlphaAnimation焚刺、RotateAnimation敛摘、ScaleAnimationTranslateAnimation四個動畫類分別來實現(xiàn)View的漸變乳愉、旋轉(zhuǎn)兄淫、縮放屯远、平移動畫。
漸變:

public void clickToAlpha(View view) {
    AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
    alphaAnimation.setDuration(2000);
    mTargetView.startAnimation(alphaAnimation);
}

旋轉(zhuǎn):

public void clickToRotate(View view) {
    RotateAnimation rotateAnimation = new RotateAnimation(
            0, 360,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    rotateAnimation.setDuration(2000);
    mTargetView.startAnimation(rotateAnimation);
}

縮放:

public void clickToScale(View view) {
    ScaleAnimation scaleAnimation = new ScaleAnimation(
            1, 0.5f,
            1, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    scaleAnimation.setDuration(2000);
    mTargetView.startAnimation(scaleAnimation);
}

平移:

public void clickToTranslate(View view) {
    TranslateAnimation translateAnimation = new TranslateAnimation(
            Animation.RELATIVE_TO_SELF, 0,
            Animation.RELATIVE_TO_SELF, 1,
            Animation.RELATIVE_TO_SELF, 0,
            Animation.RELATIVE_TO_SELF, 1);
    translateAnimation.setDuration(2000);
    mTargetView.startAnimation(translateAnimation);
}

組合:

public void clickToSet(View view) {
    AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
    alphaAnimation.setDuration(2000);

    RotateAnimation rotateAnimation = new RotateAnimation(
            0, 360,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    rotateAnimation.setDuration(2000);

    ScaleAnimation scaleAnimation = new ScaleAnimation(
            1, 0.5f,
            1, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    scaleAnimation.setDuration(2000);

    TranslateAnimation translateAnimation = new TranslateAnimation(
            Animation.RELATIVE_TO_SELF, 0,
            Animation.RELATIVE_TO_SELF, 1,
            Animation.RELATIVE_TO_SELF, 0,
            Animation.RELATIVE_TO_SELF, 1);
    translateAnimation.setDuration(2000);

    AnimationSet animationSet = new AnimationSet(true);
    animationSet.addAnimation(alphaAnimation);
    animationSet.addAnimation(rotateAnimation);
    animationSet.addAnimation(scaleAnimation);
    animationSet.addAnimation(translateAnimation);

    mTargetView.startAnimation(animationSet);
}
View動畫效果

View動畫可以設(shè)置一個動畫執(zhí)行的監(jiān)聽器:

animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        // 動畫開始
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        // 動畫結(jié)束
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
        //動畫重復
    }
});

通過設(shè)置監(jiān)聽器可以在動畫執(zhí)行的開始拖叙、結(jié)束氓润、重復時做一些其他的業(yè)務(wù)邏輯。


二薯鳍、屬性動畫

所謂屬性動畫,就是改變對象Object的屬性來實現(xiàn)動畫過程挨措。屬性動畫是對View的動畫的擴展,通過它可以實現(xiàn)更多漂亮的動畫效果浅役。同時屬性動畫的作用對象不僅僅是View斩松,任何對象都可以。
屬性動畫的作用效果就是:在一個指定的時間段內(nèi)將對象的一個屬性的屬性值動態(tài)地變化到另一個屬性值觉既。

1.ObjectAnimator

ObjectAnimator是最常用的屬性動畫執(zhí)行類惧盹。

private void startJavaPropertyAnimator() {
    ObjectAnimator
            .ofFloat(mImageView, "rotationY", 0f, 360f)
            .setDuration(2000)
            .start();
}

上面的代碼就是通過ObjectAnimator在2000ms內(nèi)將mImageViewrotationY屬性的屬性值從0f變化的360f

ObjectAnimator實現(xiàn)屬性動畫

ObjectAnimtor可以用ofInt瞪讼、ofFloat钧椰、ofObject等靜態(tài)方法,傳入動畫作用的目標Object符欠、屬性字段嫡霞、屬性開始值、屬性中間值希柿、屬性結(jié)束值等參數(shù)來構(gòu)造動畫對象诊沪。
在動畫更新的過程中,通過不斷去調(diào)用對象屬性的setter方法改變屬性值曾撤,不斷重繪實現(xiàn)動畫過程端姚。如果沒有給定動畫開始屬性值,那么系統(tǒng)會通過反射去獲取Object對象的初始值作為動畫的開始值挤悉。
屬性動畫也同樣可以通過xml文件來定義渐裸,同樣在工程的res目錄下創(chuàng)建animator文件夾,xml文件定義的objectAnimator動畫要放在該文件夾下尖啡。

property_animator.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="2000"
                android:propertyName="rotationY"
                android:valueFrom="0"
                android:valueTo="360"
                android:valueType="floatType">
</objectAnimator>

Java代碼調(diào)用:

private void startXmlPropertyAnimator() {
    Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(), 
    R.animator.property_animator);
    animator.setTarget(mImageView);
    animator.start();
}

最終效果如上圖橄仆。
屬性動畫也同樣可以組合使用,通過AnimatorSet類和xml文件的set標簽都可以同時改變對象的多個屬性衅斩,實現(xiàn)更加豐富的動畫效果盆顾。
通過AnimatorSet創(chuàng)建動畫集:

private void startJavaPropertyAnimatorSet() {
    Animator scaleXAnimator = ObjectAnimator.ofFloat(mImageView, "scaleX", 1, 0.5f);
    scaleXAnimator.setDuration(2000);
    Animator scaleYAnimator = ObjectAnimator.ofFloat(mImageView, "scaleY", 1, 0.5f);
    scaleYAnimator.setDuration(2000);
    Animator rotationXAnimator = ObjectAnimator.ofFloat(mImageView, "rotationX", 0, 360);
    rotationXAnimator.setDuration(2000);
    Animator rotationYAnimator = ObjectAnimator.ofFloat(mImageView, "rotationY", 0, 360);
    rotationYAnimator.setDuration(2000);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(scaleXAnimator)
            .with(scaleYAnimator)
            .before(rotationXAnimator)
            .after(rotationYAnimator);
    animatorSet.start();
}

AnimatorSet通過beforewith畏梆、after三個方法可以組合多個屬性動畫您宪,with表示與給定動畫同時執(zhí)行奈懒,before在給定動畫執(zhí)行之前執(zhí)行,after表示在給定動畫執(zhí)行之后執(zhí)行宪巨。

AnimatorSet構(gòu)建屬性動畫集

通過xml文件定義屬性動畫集:
property_animator_set.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="2000"
        android:propertyName="scaleX"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"/>
    <objectAnimator
        android:duration="2000"
        android:propertyName="scaleY"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"/>
    <objectAnimator
        android:duration="2000"
        android:propertyName="alpha"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"/>
</set>

在Java代碼中調(diào)用屬性動畫集:

private void startxmlPropertyAnimatorSet() {
    Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(), 
    R.animator.property_animator_set);
    animator.setTarget(mImageView);
    animator.start();
}
xml定義屬性動畫集

同樣磷杏,屬性動畫也可以添加動畫執(zhí)行監(jiān)聽器:

animator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        // 動畫開始
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        // 動畫結(jié)束
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        // 動畫取消
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        // 動畫重復
    }
});

在監(jiān)聽到屬性動畫開始、結(jié)束捏卓、取消极祸、重復時可以去做一些其他的邏輯業(yè)務(wù)。

2.ValueAnimator

ValueAnimatorObjectAnimator的父類怠晴,它繼承自Animator遥金。ValueAnimaotor同樣提供了ofIntofFloat蒜田、ofObject等靜態(tài)方法稿械,傳入的參數(shù)是動畫過程的開始值、中間值冲粤、結(jié)束值來構(gòu)造動畫對象美莫。可以將ValueAnimator看著一個值變化器梯捕,即在給定的時間內(nèi)將一個目標值從給定的開始值變化到給定的結(jié)束值厢呵。在使用ValueAnimator時通常需要添加一個動畫更新的監(jiān)聽器,在監(jiān)聽器中能夠獲取到執(zhí)行過程中的每一個動畫值科阎。

private void startValueAnimator() {
    ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
    valueAnimator.setDuration(300);
    valueAnimator.start();
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 動畫更新過程中的動畫值述吸,可以根據(jù)動畫值的變化來關(guān)聯(lián)對象的屬性,實現(xiàn)屬性動畫
            float value = (float) animation.getAnimatedValue();
            Log.d("ValueAnimator", "動畫值:" + value);
        }
    });
}

在300ms內(nèi)將數(shù)值0變化到1的動畫值的變化log:

02-25 23:16:57.586 D/ValueAnimator: 動畫值:0.0
02-25 23:16:57.596 D/ValueAnimator: 動畫值:0.007902175
02-25 23:16:57.616 D/ValueAnimator: 動畫值:0.029559612
02-25 23:16:57.636 D/ValueAnimator: 動畫值:0.066987276
02-25 23:16:57.646 D/ValueAnimator: 動畫值:0.118102014
02-25 23:16:57.666 D/ValueAnimator: 動畫值:0.18128797
02-25 23:16:57.686 D/ValueAnimator: 動畫值:0.2545482
02-25 23:16:57.706 D/ValueAnimator: 動畫值:0.33063102
02-25 23:16:57.716 D/ValueAnimator: 動畫值:0.4166157
02-25 23:16:57.736 D/ValueAnimator: 動畫值:0.5052359
02-25 23:16:57.746 D/ValueAnimator: 動畫值:0.5936906
02-25 23:16:57.766 D/ValueAnimator: 動畫值:0.67918396
02-25 23:16:57.786 D/ValueAnimator: 動畫值:0.7545208
02-25 23:16:57.796 D/ValueAnimator: 動畫值:0.82671034
02-25 23:16:57.826 D/ValueAnimator: 動畫值:0.88857293
02-25 23:16:57.836 D/ValueAnimator: 動畫值:0.93815327
02-25 23:16:57.856 D/ValueAnimator: 動畫值:0.9721882
02-25 23:16:57.876 D/ValueAnimator: 動畫值:0.99384415
02-25 23:16:57.886 D/ValueAnimator: 動畫值:1.0

ValueAnimator的使用一般會結(jié)合更新監(jiān)聽器AnimatorUpdateListener锣笨,大多數(shù)時候是在自定義控件時使用蝌矛。
下面是用ValueAnimator自定義控件實現(xiàn)動畫打開關(guān)閉效果。
expanded_veiw.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="@android:color/white"
              android:divider="@drawable/divider"
              android:orientation="vertical"
              android:showDividers="middle">

    <LinearLayout
        android:id="@+id/ll_expanded_question"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:gravity="center_vertical">

        <TextView
            android:id="@+id/tv_expanded_question"
            android:layout_width="0dp"
            android:layout_height="48dp"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:padding="8dp"
            android:text="如何把一本很難的書看懂错英?"
            android:textColor="#999999"
            android:textSize="16sp"/>

        <ImageView
            android:id="@+id/iv_expanded_indicator"
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:layout_marginRight="16dp"
            android:src="@drawable/img_up"/>

    </LinearLayout>


    <TextView
        android:id="@+id/tv_expanded_answer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:text="多讀幾遍入撒。真的特別有用。至少看三遍椭岩。從開頭看茅逮,看到中間,重頭再來判哥,再看得多一點献雅,在從新開始,建議看到快結(jié)束時再從新開始塌计。"
        android:textColor="#999999"
        android:textSize="16sp"/>
</LinearLayout>
public class ExpandedView extends FrameLayout {

    private TextView mTvAnswer;
    private boolean isClosed;
    private ImageView mIvIndicator;

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

    private void init(Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.expanded_view, this, true);
        LinearLayout llQuestion = (LinearLayout) view.findViewById(R.id.ll_expanded_question);
        llQuestion.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                anim();
            }
        });
        mTvAnswer = (TextView) view.findViewById(R.id.tv_expanded_answer);
        mIvIndicator = (ImageView) view.findViewById(R.id.iv_expanded_indicator);
    }

    private void anim() {
        // 指示器旋轉(zhuǎn)
        ValueAnimator valueAnimator1 = isClosed
                ? ValueAnimator.ofFloat(180, 0)
                : ValueAnimator.ofFloat(0, 180);
        valueAnimator1.setDuration(500);
        valueAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                mIvIndicator.setRotation(value);
            }
        });
        valueAnimator1.start();

        // 打開開關(guān)閉操作
        final int answerHeight = mTvAnswer.getMeasuredHeight();
        ValueAnimator valueAnimator2 = isClosed
                ? ValueAnimator.ofInt(-answerHeight, 0)
                : ValueAnimator.ofInt(0, -answerHeight);
        valueAnimator2.setDuration(500);
        final MarginLayoutParams params = (MarginLayoutParams) mTvAnswer.getLayoutParams();
        valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                params.bottomMargin = value;
                mTvAnswer.setLayoutParams(params);
            }
        });
        valueAnimator2.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isClosed = !isClosed;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        valueAnimator2.start();
    }
}
ValueAnimator自定義控件效果圖
3.TypeEvaluator

ObjectAnimatorValueAnimator都有ofObject方法挺身,傳入的都有一個TypeEvaluator類型的參數(shù)。TypeEvaluator是一個接口锌仅,里面也只有一個抽象方法:

public T evaluate(float fraction, T startValue, T endValue);

再看ofInt方法中沒有傳入該參數(shù)章钾,但實際上調(diào)用ofInt方法時墙贱,系統(tǒng)已經(jīng)有實現(xiàn)了TypeEvaluator接口的IntEvaluator,它的源碼也非常簡單:

public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
    int startInt = startValue;
    return (int)(startInt + fraction * (endValue - startInt));
}

fraction范圍為0到1贱傀,表示動畫執(zhí)行過程中已完成程度惨撇。
泛型T即為動畫執(zhí)行的屬性類型。
所以我們要用屬性動畫來執(zhí)行復雜對象的動畫過程府寒,就需要自定義TypeEvaluator魁衙,實現(xiàn)動畫邏輯。
先來定義一個對象

public class Circle {

    private int raduis;         // 半徑
    private int color;          // 顏色
    private int elevation;      // 高度

    public Circle(int raduis, int color, int elevation) {
        this.raduis = raduis;
        this.color = color;
        this.elevation = elevation;
    }

    public int getRaduis() {
        return raduis;
    }

    public void setRaduis(int raduis) {
        this.raduis = raduis;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public int getElevation() {
        return elevation;
    }

    public void setElevation(int elevation) {
        this.elevation = elevation;
    }
}

CircleEvaluator椰棘,實現(xiàn)TypeEvaluator接口:

public class CircleEvaluator implements TypeEvaluator<Circle> {
    @Override
    public Circle evaluate(float fraction, Circle startValue, Circle endValue) {

        int startRaduis = startValue.getRaduis();
        int endRaduis = endValue.getRaduis();
        int raduis = (int) (startRaduis + fraction * (endRaduis - startRaduis));

        int startColor = startValue.getColor();
        int endColor = endValue.getColor();
        int startColorRed = Color.red(startColor);
        int startColorGreen = Color.green(startColor);
        int startColorBlue = Color.blue(startColor);
        int endColorRed = Color.red(endColor);
        int endColorGreen = Color.green(endColor);
        int endColorBlue = Color.blue(endColor);
        int colorRed = (int) (startColorRed + fraction * (endColorRed - startColorRed));
        int colorGreen = (int) (startColorGreen + fraction * (endColorGreen - startColorGreen));
        int colorBlue = (int) (startColorBlue + fraction * (endColorBlue - startColorBlue));
        int color = Color.rgb(colorRed, colorGreen, colorBlue);

        int startElevation = startValue.getElevation();
        int endElevation = endValue.getElevation();
        int elevation = (int) (startElevation + fraction * (endElevation - startElevation));

        return new Circle(raduis, color, elevation);
    }
}

自定義控件CircleView纺棺,將Circle作為它的一個屬性:

public class CircleView extends View {
    private Circle circle;
    private Paint mPaint;

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        circle = new Circle(168, Color.RED, 0);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        setElevation(circle.getElevation());
        mPaint.setColor(circle.getColor());
        canvas.drawCircle(getMeasuredHeight() / 2, getMeasuredHeight() / 2, circle.getRaduis(), mPaint);
    }

    public void setCircle(Circle circle) {
        this.circle = circle;
        postInvalidate();
    }

    public Circle getCircle() {
        return circle;
    }
}

ObjectAnimator使用:

private void start1() {
    Circle startCircle = new Circle(168, Color.RED, 0);
    Circle middleCircle = new Circle(300, Color.GREEN, 15);
    Circle endCircle = new Circle(450, Color.BLUE, 30);
    ObjectAnimator.ofObject(mCircleView, "circle", new CircleEvaluator(), startCircle, middleCircle, endCircle)
            .setDuration(5000)
            .start();
}

ValueAnimator使用:

private void start2() {
    Circle startCircle = new Circle(168, Color.RED, 0);
    Circle middleCircle = new Circle(300, Color.GREEN, 15);
    Circle endCircle = new Circle(450, Color.BLUE, 30);
    ValueAnimator valueAnimator = ValueAnimator.ofObject(new CircleEvaluator(), startCircle, middleCircle, endCircle);
    valueAnimator.setDuration(5000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Circle circle = (Circle) animation.getAnimatedValue();
            mCircleView.setCircle(circle);
        }
    });
    valueAnimator.start();
}

自定義TypeEvaluator效果圖

需要注意的是,系統(tǒng)調(diào)用獲取控件setter邪狞、getter方法是通過反射獲取的,屬性的名稱必須和getter茅撞、setter方法名稱后面的字符串一致帆卓,比如上面的getter、setter方法分別為getCircle米丘、setCircle剑令,那么屬性名字就必須為circle


三拄查、幀動畫

幀動畫需要開發(fā)者制定好動畫每一幀,系統(tǒng)一幀一幀的播放圖片。

private void start1() {
    AnimationDrawable ad = new AnimationDrawable();
    for (int i = 0; i < 7; i++) {
        Drawable drawable = getResources().getDrawable(getResources().getIdentifier("ic_fingerprint_" + i, "drawable", getPackageName()));
        ad.addFrame(drawable, 100);
    }
    ad.setOneShot(false);
    mImageView.setImageDrawable(ad);
    ad.start();
}

幀動畫同樣也可以在xml文件中配置溶推,直接在工程drawable目錄新建animation-list標簽:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
<item android:drawable="@drawable/ic_fingerprint_0" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_1" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_2" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_3" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_4" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_5" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_6" android:duration="100"/>
</animation-list>
private void start2() {
    mImageView.setImageResource(R.drawable.frame_anim);
    AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getDrawable();
    animationDrawable.start();
}

其中android:onshot屬性和setOneShot方法表示是否只執(zhí)行一次否灾。
AnimationDrawable實際是上是一個Drawable動畫,動畫執(zhí)行的過程總會給你不斷重繪Drawable的每一幀圖像稍算,實現(xiàn)動態(tài)播放效果典尾。

幀動畫效果圖


源碼:https://github.com/xiaoyanger0825/AnimationSummary

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市糊探,隨后出現(xiàn)的幾起案子钾埂,更是在濱河造成了極大的恐慌,老刑警劉巖科平,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褥紫,死亡現(xiàn)場離奇詭異,居然都是意外死亡瞪慧,警方通過查閱死者的電腦和手機髓考,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汞贸,“玉大人绳军,你說我怎么就攤上這事印机。” “怎么了门驾?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵射赛,是天一觀的道長。 經(jīng)常有香客問我奶是,道長楣责,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任聂沙,我火速辦了婚禮秆麸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘及汉。我一直安慰自己沮趣,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布坷随。 她就那樣靜靜地躺著房铭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪温眉。 梳的紋絲不亂的頭發(fā)上缸匪,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音类溢,去河邊找鬼凌蔬。 笑死,一個胖子當著我的面吹牛闯冷,可吹牛的內(nèi)容都是我干的砂心。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼窃躲,長吁一口氣:“原來是場噩夢啊……” “哼计贰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蒂窒,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤躁倒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后洒琢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秧秉,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年衰抑,在試婚紗的時候發(fā)現(xiàn)自己被綠了象迎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖砾淌,靈堂內(nèi)的尸體忽然破棺而出啦撮,到底是詐尸還是另有隱情,我是刑警寧澤汪厨,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布赃春,位于F島的核電站,受9級特大地震影響劫乱,放射性物質(zhì)發(fā)生泄漏织中。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一衷戈、第九天 我趴在偏房一處隱蔽的房頂上張望狭吼。 院中可真熱鬧,春花似錦殖妇、人聲如沸刁笙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽采盒。三九已至,卻和暖如春蔚润,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尺栖。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工嫡纠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人延赌。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓除盏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親挫以。 傳聞我的和親對象是個殘疾皇子者蠕,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容