Android動畫全解

鋸齒流沙發表於2017-12-26

Android View加入動畫之後使其對使用者更加友好,使用者體驗也得到極大的增強,特別是Android 3.0之後,加入的動畫新成員——屬性動畫,使其更加具備互動的特性,而且通過動畫可以做出各種比較炫比較酷的效果。如果沒有動畫,那麼View的表現比較生硬,給使用者的體驗很不友好。Android動畫使其可以做到可以和iphone一樣的友好體驗,在Android5.0之後,加入了Android Material Design,其使用者體驗得到了更加友好的發揮,甚至超過了iphone,所以Android動畫是學習Android的重點之一。

Android動畫可以分為三種:View動畫、幀動畫和屬性動畫,上面已經提到了屬性動畫是Android API11之後新增的,如果想要相容API11以下的版本,可以使用NineOldAndroids動畫庫。

View動畫

View動畫是通過View的平移、旋轉、縮放和改變透明度來實現動畫,這也是一種漸進式動畫。

View動畫原理:每次繪製檢視時,View的所在ViewGroup中的drawChild函式獲取該View的Animation的Transformation值,然後呼叫canvas.concat(TransformToApply .getMatrix()),然後通過矩陣運算完成動畫幀,如果動畫沒有完成,就繼續呼叫invalidate()函式,啟動下次繪製來驅動動畫,從而完成整個動畫的繪製。

View動畫提供了TranslateAnimation、RotateAnimation、ScaleAnimation和AlphaAnimation四種動畫。通過這四種動畫基本上能夠完成所有的View的特效效果,不過View動畫明顯的缺點就是不具備互動性。而Android3.0新增的屬性動畫就具有互動性。

TranslateAnimation

TranslateAnimation也就是平移動畫,顯然該動畫能夠將View在水平或者垂直方向上進行移動。

如在X軸上移動200px


    /**
     * translateAnimation
     * @return
     */
    private Animation getTranslate(){

        /*TranslateAnimation(int fromXType, float fromXValue,
                                int toXType, float toXValue,
                                int fromYType,float fromYValue,
                                int toYType, float toYValue)
                               */
        TranslateAnimation translateAnimation = new TranslateAnimation(
                Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 200,
                Animation.RELATIVE_TO_SELF, 0,
                Animation.RELATIVE_TO_SELF, 0);
        translateAnimation.setDuration(300);//動畫時間
        translateAnimation.setFillAfter(false);//動畫結束後View是否保持動畫結束時的狀態
        return translateAnimation;
    }
複製程式碼

啟動該動畫

TranslateAnimation anim = (TranslateAnimation) getTranslate(); mTargetView.startAnimation(anim);

也可以在xml中寫:在專案res下新建一個anim目錄,在目錄下新建一個.xml檔案。

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
           android:duration="300"
           android:fromXDelta="0%p"
           android:interpolator="@android:anim/accelerate_decelerate_interpolator"
           android:toXDelta="100%p"/>
複製程式碼

android:fromXDelta:起始點X軸座標,可以是數值、百分數、百分數p 三種樣式,比如 30、30%、30%p。 android:fromYDelta:起始點Y軸從標,可以是數值、百分數、百分數p 三種樣式; 當為數值時,表示在當前View的左上角,即原點處加上50px; 如果是50%,表示在當前控制元件的左上角加上自己寬度的50%; 如果是50%p,表示在當前的左上角加上父控制元件寬度的50%。 android:toXDelta:結束點X軸座標 android:toYDelta:結束點Y軸座標

使用:

TranslateAnimation anim = (TranslateAnimation) AnimationUtils.loadAnimation(this, R.anim.my_animation); mTargetView.startAnimation(anim);

RotateAnimation

RotateAnimation旋轉動畫,能夠將View進行旋轉,達到動畫的效果。

    /**
     * 圍繞自身中點
     * 旋轉270度
     *
     * @return
     */
    private Animation getRotate() {
        RotateAnimation rotateAnimation = new RotateAnimation(
                0, 270,//開始角度和旋轉的到的度數
                Animation.RELATIVE_TO_SELF, 0.5f,//x
                Animation.RELATIVE_TO_SELF, 0.5f);//y
        rotateAnimation.setDuration(1000);
        rotateAnimation.setFillAfter(true);
        return rotateAnimation;
    }
複製程式碼

在xml中表示:

<?xml version="1.0" encoding="utf-8"?>
<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="270"
    android:pivotX="50%"
    android:pivotY="50%"
    android:duration="300"
    android:fillAfter="true"/>
複製程式碼

ScaleAnimation

ScaleAnimation對View進行縮放的動畫。

    /**
     * 縮放
     *
     * @return
     */
    private Animation getScaleAnim() {
       /* (float fromX, float toX,
        float fromY, float toY,
        int pivotXType, float pivotXValue,
        int pivotYType, float pivotYValue)*/
        ScaleAnimation scaleAnimation = new ScaleAnimation(
                1.0f, 0.0f, 1.0f, 0.0f,//xy軸上的縮放起始值
                Animation.RELATIVE_TO_SELF, 0.5f,//x軸的縮放中心
                Animation.RELATIVE_TO_SELF, 0.5f);//y軸的縮放中心
        scaleAnimation.setDuration(300);
        scaleAnimation.setFillAfter(false);
        return scaleAnimation;
    }
複製程式碼

在xml中表示:

<?xml version="1.0" encoding="utf-8"?>
<scale
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:pivotX="50%"
    android:pivotY="50%"
    android:fromXScale="1.0"
    android:fromYScale="1.0"
    android:toXScale="0.0"
    android:toYScale="0.0"
    android:duration="300"
    android:fillAfter="true"/>
複製程式碼

android:fromXScale:起始的X方向上相對自身的縮放比例; android:toXScale:結尾的X方向上相對自身的縮放比例,浮點值; android:fromYScale:起始的Y方向上相對自身的縮放比例,浮點值, android:toYScale:結尾的Y方向上相對自身的縮放比例,浮點值; android:pivotX:縮放起點X軸座標,可以是數值、百分數、百分數p 三種樣式; android:pivotY:縮放起點Y軸座標,取值及意義跟android:pivotX一樣。

ScaleAnimation

AlphaAnimation是可以對View進行透明度變化的動畫。

    /**
     * 改變透明度動畫
     * @return
     */
    private Animation getAlpha(){
        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        alphaAnimation.setDuration(300);
        alphaAnimation.setFillAfter(true);
        return alphaAnimation;
    }
複製程式碼

在xml中表示:

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

以上便是View動畫提供了TranslateAnimation、RotateAnimation、ScaleAnimation和AlphaAnimation四種動畫,上文中的動畫已經經過驗證的了。但是上文中的動畫都是單一形式的,那麼有沒有提供一種方法,能夠混合一起播放以上View動畫提供的四種動畫的呢?答案是:當然有,那就是使用AnimationSet動畫集合。

網上也有相當多的文章是關於AnimationSet的使用和介紹的,那麼什麼是AnimationSet呢?其實就是一個動畫集合,能夠將View動畫的提供的幾種動畫可以一起播放,從而產生更炫的動畫效果。AnimationSet也可以在xml檔案中設定,其對應的標籤就是標籤。

AnimationSet set = new AnimationSet(false);
ScaleAnimation anim01 = (ScaleAnimation) getScaleAnim();
RotateAnimation anim02 = (RotateAnimation) getRotate();
AlphaAnimation anim03 = (AlphaAnimation) getAlpha();
set.addAnimation(anim01);
set.addAnimation(anim02);
set.addAnimation(anim03);
set.setDuration(300);
mTargetView.startAnimation(set);
複製程式碼

在xml中表示:

<?xml version="1.0" encoding="utf-8"?>
<set
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:fillAfter="true">
    <alpha
        android:toAlpha="0.0"
        android:fromAlpha="1.0"/>
    <rotate
        android:fromDegrees="0"
        android:toDegrees="270"
        android:pivotX="50%"
        android:pivotY="50%"/>
    <scale
        android:pivotY="50%"
        android:pivotX="50%"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:toXScale="0.0"
        android:toYScale="0.0"/>
</set>
複製程式碼
set的屬性有:

android:duration:動畫持續時間,以毫秒為單位 ;

android:fillAfter:如果設定為true,控制元件動畫結束時,將保持動畫最後時的狀態;

android:fillBefore:如果設定為true,控制元件動畫結束時,還原到開始動畫前的狀態;

android:fillEnabled:與android:fillBefore效果相同,都是在動畫結束時,將控制元件還原到初始化狀態;

android:repeatCount 重複次數;

android:repeatMode:重複型別,有reverse和restart兩個值,reverse表示倒序回放,restart表示重新放一遍,必須與repeatCount一起使用才能看到效果。因為這裡的意義是重複的型別,即回放時的動作;

android:interpolator:設定插值器。

使用:

AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(this, R.anim.my_animation); mTargetView.startAnimation(set);

上文中就是View動畫的種類和集合的基本用法,除了以上的知識還有一個重要的知識點就是動畫的監聽。動畫的監聽可實現很多需求,比如在動畫開始或者結束後要做的事情,或者在動畫不斷回撥的方法onAnimationRepeat中實現一些動畫不能實現的效果。

新增動畫監聽很簡單,如下所示:

set.setAnimationListener(new Animation.AnimationListener() {
      @Override
      public void onAnimationStart(Animation animation) {

      }

      @Override
      public void onAnimationEnd(Animation animation) {

      }

      @Override
      public void onAnimationRepeat(Animation animation) {

       }
 });
複製程式碼

自定義屬性動畫

在View動畫中除了上文中的四種動畫之外,其實自定義動畫也算是View動畫的一種,自定義需要繼承Animation類,並重寫覆蓋initialize和applyTransformation方法。在initialize方法中主要是做一些初始化的工作,applyTransformation通過矩陣變換來實現動畫效果,一般採用Camera來簡化矩陣變換過程。

applyTransformation(float interpolatedTime, Transformation t)方法有兩個引數 interpolatedTime:動畫當前完成的百分比和當前時間所對應的插值所計算得來的,取值0到1.0,也就是插值器的時間因子。

Transformation :矩陣的封裝類,可以使用這個類獲得當前的矩陣物件

Matrix matrix = t.getMatrix();

然後通過矩陣變化實現動畫。

public class MyAnimation extends Animation {

    private float mWidth;
    private float mHeight;
    private Camera mCamera;
    private float mRorateX = 270.0f;

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        setDuration(300);
        setFillAfter(true);
        mWidth = width/2;
        mHeight = height/2;
        mCamera = new Camera();
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
        Matrix matrix = t.getMatrix();
        mCamera.save();
        mCamera.rotateX(mRorateX*interpolatedTime);
        mCamera.getMatrix(matrix);
        mCamera.restore();
        //通過pre方法設定矩陣作用前的偏移量來改變旋轉中心
        matrix.preTranslate(mWidth,mHeight);
        matrix.postTranslate(-mWidth,-mHeight);
    }
}
複製程式碼

以上程式碼就是沿著X軸方向旋轉。

幀動畫

幀動畫是順序播放一組預先定義好的圖片,類似電影,逐幀播放圖片。

<?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/loadingmore01" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore02" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore03" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore04" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore05" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore06" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore07" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore08" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore09" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore10" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore11" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore12" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore13" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore14" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore15" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore16" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore17" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore18" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore19" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore20" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore21" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore22" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore23" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore24" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore25" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore26" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore27" android:duration="30"/>

</animation-list>
複製程式碼

使用

animView = new ImageView(context); animView.setImageResource(R.drawable.anim_loading_more); animationDrawable = (AnimationDrawable) animView.getDrawable(); animationDrawable.start();

幀動畫的使用比較簡單,以上就是定義一個幀動畫並使用。

屬性動畫

屬性動畫是Android API11新加入的動畫,屬性動畫能夠對任何物件做動畫,只要物件有這個屬性,並且提供get和set方法即可,因為是API11才加入的動畫,所以如果想向下相容,可以使用nineoldandroids動畫庫來相容。

屬性動畫提供了ValueAnimator、ObjectAnimator和AnimatorSet,通過他們可以使物件實現炫麗的動畫效果。屬性動畫有一個最大的特點就是其可以實現與使用者互動,這也是其他動畫所不具備的特點。

ObjectAnimator

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView,"translationY",0,2000,0); anim.start();

這是ObjectAnimator的簡單用法,但是已經顯示了動畫效果。

ofFloat方法的引數分析:ofFloat(Object target, String propertyName, float... values) 第一個引數:動畫物件; 第二個引數:物件的屬性,要執行動畫的屬性,這裡我改變的是Y軸上的移動距離; 第三個引數:可變引數,可以有多個值,即物件屬性要執行動畫的值。上例中表示的是屬性動畫在Y軸上先移動到2000px的位置,然後再執行動畫移動到初始位置。

注意:這裡的ObjectAnimator動畫並沒有指定執行時間,是因為屬性動畫的預設時間間隔300ms,預設幀率為10ms/幀。

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView,"translationY",0,2000,-200,0); anim.setDuration(500); anim.start();

上例執行的動畫會有彈動的效果,動畫執行translationY,先移動到2000px的位置,然後再移動-200px的位置,最後回到初始的位置。而且設定了動畫執行的時間。

旋轉動畫:

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView,"rotation",0,360,-200,0);
anim.setDuration(2000);
anim.start();
複製程式碼

改變透明度:

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView,"alpha",1.0f,0.0f,0.8f);
anim.setDuration(2000);
anim.start();
複製程式碼

縮放:

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView, "scaleX",1,0,1);
anim.setDuration(2000);
anim.start();

ObjectAnimator anim1 = ObjectAnimator.ofFloat(mTargetView, "scaleY",1,0,1);
anim1.setDuration(2000);
anim1.start();
複製程式碼

第三參數列示縮放的倍數。

改變背景顏色

ObjectAnimator anim = ObjectAnimator.ofInt(mStart, "backgroundColor",0xff0000,0xffff8080,0xff000000);
anim.setDuration(2000);
anim.start();
複製程式碼

ObjectAnimator 同時執行多個屬性動畫:

PropertyValuesHolder px = PropertyValuesHolder.ofFloat("scaleX",1,0,1);
PropertyValuesHolder py = PropertyValuesHolder.ofFloat("scaleY",1,0,1);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTargetView, px,py);
anim.setDuration(2000);
anim.start();
複製程式碼

ValueAnimator

ValueAnimator只針對值,只是對值做動畫運算,而不是針對控制元件,沒有跟任何的控制元件相關聯,需要監聽ValueAnimator的動畫過程來自己對控制元件做操作。

ValueAnimator anim = ValueAnimator.ofFloat(0,2000,-200,0);
anim.setDuration(2000);
anim.start();
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
       @Override
       public void onAnimationUpdate(ValueAnimator valueAnimator) {
            int curValueFloat = (int)valueAnimator.getAnimatedValue();
             //layout(int l, int t, int r, int b)
              Log.i("tag","value"+curValueFloat);
       }
 });
複製程式碼

讀者可以參考自定義控制元件三部曲之動畫篇(四)——ValueAnimator基本使用和 Android 屬性動畫(Property Animation) 完全解析 (上)這兩篇文章,就已足夠了解ValueAnimator了。

AnimatorSet

AnimatorSet是屬性動畫的集合,可以給View設定一組的屬性動畫,也可以指定播放順序,是否一起播放或者是否延遲播放。

PropertyValuesHolder px = PropertyValuesHolder.ofFloat("scaleX",1.0f,0.0f,1.0f);
PropertyValuesHolder py = PropertyValuesHolder.ofFloat("scaleY",1.0f,0.0f,1.0f);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTargetView, px,py);

ObjectAnimator scale = ObjectAnimator.ofFloat(mTargetView,"rotation",0.0f,360.0f,-360.0f);

AnimatorSet set = new AnimatorSet();
set.setDuration(2000);
set.playTogether(anim,scale);//一起播放
set.start();
複製程式碼

使用playTogether方法,可以設定一起播放設定進去的動畫。也可以使用以下語句設定一起播放動畫

set.play(anim).with(scale);

按順序播放動畫:

set.playSequentially(anim,scale);

除了使用playSequentially,還可以使用如下語句來設定按順序播放:

set.play(scale).after(anim);

動畫監聽AnimatorListener

ObjectAnimator translate = ObjectAnimator.ofFloat(goDescri, "translationX", -50);
translate.setInterpolator(new AccelerateInterpolator());
translate.setDuration(1500);

ObjectAnimator alpha1 = ObjectAnimator.ofFloat(goDescri, "alpha", 0.0f,1.0f);
alpha1.setInterpolator(new AccelerateInterpolator());
alpha1.setDuration(1000);

ObjectAnimator alpha2 = ObjectAnimator.ofFloat(goDescri, "alpha", 1.0f,0.0f);
alpha2.setInterpolator(new AccelerateInterpolator());
alpha2.setDuration(500);

AnimatorSet mAnimatorSet = new AnimatorSet();
mAnimatorSet.setInterpolator(new AccelerateInterpolator());
mAnimatorSet.play(translate).with(alpha1);
mAnimatorSet.play(alpha2).after(alpha1);
mAnimatorSet.addListener(new Animator.AnimatorListener()
{
	@Override
	public void onAnimationStart(Animator animation)
	{

	}

	@Override
	public void onAnimationEnd(Animator animation)
	{
		mAnimatorSet.start();
	}

	@Override
	public void onAnimationCancel(Animator animation)
	{

	}

	@Override
	public void onAnimationRepeat(Animator animation)
	{

	}
});
複製程式碼

上述程式碼通過監聽AnimatorListener,在動畫結束時重新啟動動畫,而從達到動畫順序迴圈播放的效果。

迴圈播放還可以如下設定:

PropertyValuesHolder px = PropertyValuesHolder.ofFloat("scaleX",1.0f,0.0f,1.0f);
PropertyValuesHolder py = PropertyValuesHolder.ofFloat("scaleY",1.0f,0.0f,1.0f);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTargetView, px,py);
anim.setRepeatCount(ObjectAnimator.INFINITE);//播放次數
anim.setRepeatMode(ObjectAnimator.REVERSE);//播放順序,順序和倒敘

ObjectAnimator scale = ObjectAnimator.ofFloat(mTargetView,"rotation",0.0f,360.0f,-360.0f);
scale.setRepeatCount(ObjectAnimator.INFINITE);
scale.setRepeatMode(ObjectAnimator.REVERSE);

AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(scale,anim);
set.start();
複製程式碼

通過設定播放次數anim.setRepeatCount(ObjectAnimator.INFINITE); 並且設定播放順序anim.setRepeatMode(ObjectAnimator.REVERSE);來達到迴圈播放動畫,這兩個函式在AnimatorSet時沒有。

插值器和估值器

插值器(Interpolator)可以分為時間插值器(TimeInterpolator)、線性插值器(LinearInterpolator)、加速減速插值器(AccelerateDecerateInterpolator)、減速插值器(DecerateInterpolator)。

時間插值器:根據時間流逝的百分比計算出當前屬性值改變的百分比; 線性插值器:使動畫勻速; 加速減速插值器:使動畫兩頭慢,中間快; 減速插值器:使動畫越來越慢。

時間插值器

根據時間流逝的百分比計算出當前屬性值改變的百分比

AnimatorSet set = new AnimatorSet();
set.setDuration(2000);
set.playTogether(scale,anim);
set.setInterpolator(new TimeInterpolator() {
    @Override
    public float getInterpolation(float v) {
          Log.i("tag","v----------->"+v);
          return v;
  }
});
複製程式碼

加速減速估值器

AnimatorSet set = new AnimatorSet();
set.setDuration(2000);
set.playTogether(scale,anim);
set.setInterpolator(new AccelerateDecelerateInterpolator());
set.start();
複製程式碼

自定義估值器

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;
        }
    }
複製程式碼

以上自定義插值器實現了先減速後加速。

使用

set.setInterpolator(new DecelerateAccelerateInterpolator());

讀者可以閱讀這篇文章,此文章詳細的介紹了插值器和估值器[Android 動畫:你真的會使用插值器與估值器嗎?(含詳細例項教學)(http://www.jianshu.com/p/2f19fe1e3ca1)

估值器

估值器(TypeEvaluator):根據當前屬性的改變的百分比來計算改變後的屬性值,系統預置的估值器有:IntEvaluator(針對整形屬性)、FloatEvaluator(針對浮點型屬性)和ArgbEvaluator(針對Color屬性)。

對任意物件屬性做動畫

既然屬性動畫能夠對任何物件做動畫,只要物件有這個屬性,並且提供get和set方法即可。 如果這個物件沒有get和set方法,怎麼辦呢? 1)給物件加上get和set方法; 2)用個類來包裝原始物件,提供get和set方法;

private static class ViewWrapper{
        private View target;

        public ViewWrapper(View target) {
            this.target = target;
        }

        public int getWidth(){
            return target.getLayoutParams().width;
        }
        public void setWidth(int width){
            target.getLayoutParams().width = width;
            target.requestLayout();
        }


    }
複製程式碼

使用

ViewWrapper wrapper = new ViewWrapper(mTargetView); ObjectAnimator.ofInt(wrapper,"width",2000).start();

除了以上的方法外還可以使用ValueAnimator來監聽值的改變,然後做改變,達到動畫的效果。

關於這一點知識,讀者可以參考《Android開發藝術探索》的第七章《Android動畫深入分析》。

ViewAnimationUtils

Android5.0新特性增加了一種動畫框架,這就是ViewAnimationUtils,我們可以利用ViewAnimationUtils來做一些動畫效果,比如水波紋,揭露等動畫效果。

public final class ViewAnimationUtils {
    private ViewAnimationUtils() {}
    public static Animator createCircularReveal(View view,
            int centerX,  int centerY, float startRadius, float endRadius) {
        return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);
    }
}
複製程式碼

上面程式碼就是ViewAnimationUtils類全部程式碼,該動畫工具只有createCircularReveal方法來建立動畫效果。從方法中,可以知道最後交給了RevealAnimator類來完成動畫效果。

createCircularReveal方法說明:

view:動畫作用的View;

centerX:擴散的中心點的X軸座標;

centerY:擴散的中心點的Y軸座標;

startRadius:開始擴散初始半徑;

endRadius:擴散結束半徑;

使用:

Animator animator = ViewAnimationUtils.createCircularReveal(mStart, mStart.getWidth()/2, mStart.getHeight()/2, 0, mStart.getHeight());
animator.setDuration(1000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
複製程式碼

上面程式碼就是mStart做水波紋擴散的效果。

mStart的揭露動畫:

Animator animator = ViewAnimationUtils.createCircularReveal(btn, 0, 0, 0, (float)Math.hypot(btn.getWidth(), btn.getHeight()));
animator.setDuration(1000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
複製程式碼

轉場動畫

Android的轉場動畫分為兩種:普通轉場和共享元素轉場。

普通轉場

普通轉場通過overridePendingTransition設定Activity的關閉和顯示動畫,如下所示:

startActivity(new Intent(this, Animator2Activity.class));
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
複製程式碼

通過以上程式碼,跳轉Activity時:當前的Activity動畫淡出,新的Activity淡入,完成淡出淡入的Activity轉場動畫。

Activity退出時同樣可以設定轉場動畫

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }
複製程式碼

這是API21系統自帶的轉場動畫,只要在API21或者以上才有。 fade_in:

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@interpolator/decelerate_quad"
        android:fromAlpha="0.0" android:toAlpha="1.0"
        android:duration="@android:integer/config_longAnimTime" />
複製程式碼

fade_out:

<alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@interpolator/accelerate_quad" 
    android:fromAlpha="1.0"
    android:toAlpha="0.0"
    android:duration="@android:integer/config_mediumAnimTime" 
/>
複製程式碼

滑入滑出轉場

startActivity(new Intent(this, Animator2Activity.class));
overridePendingTransition(android.R.anim.slide_in_left,android.R.anim.slide_out_right);
複製程式碼

除了以上系統自帶的轉場動畫,也可以在res的anim目錄下定義動畫xml來實現自定義的轉場動畫。

scale_in.xml

<?xml version="1.0" encoding="utf-8"?>
<scale
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime"
    android:fromXScale="0%"
    android:fromYScale="0%"
    android:toYScale="100%"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="100%"/>
複製程式碼

scale_out.xml

<?xml version="1.0" encoding="utf-8"?>
<scale
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime"
    android:fromXScale="100%"
    android:fromYScale="100%"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="0%"
    android:toYScale="0%"/>
複製程式碼

使用的時候直接在overridePendingTransition指定動畫即可

startActivity(new Intent(this, Animator2Activity.class));
overridePendingTransition(R.anim.scale_in, R.anim.scale_out);
複製程式碼

底部滑入滑出 slide_in_botton.xml

<?xml version="1.0" encoding="utf-8"?>
<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime"
    android:fromYDelta="100%"
    android:toYDelta="0%"
    />
複製程式碼

slide_out_botton.xml

<?xml version="1.0" encoding="utf-8"?>
<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime"
    android:fromYDelta="0%"
    android:toYDelta="100%"
    />
複製程式碼

共享元素轉場

共享元素轉場是指:可以把兩個Activity當中的相同的元素關聯起來做連貫的變換動畫。 共享元素轉場是Android5.0或以上才顯示的,而使用共享元素轉場需要條件: A、必須給兩個Activity設定Window.FEATURE_CONTENT_TRANSITIONS,讓Activity允許使用轉場動畫。 而設定Window.FEATURE_CONTENT_TRANSITIONS有兩個方法: 1)通過在setContentView方法之前設 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 2)在主題修改 true

B、給兩個Activity當中的共享元素view都設定同一個名字(android:transitionName)

共享元素分為單共享元素和多個共享元素。

單共享元素

public class AnimationActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mStart;
    private ImageView mTargetView;
    private Button btn;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        setContentView(R.layout.activity_animation);
        initView();
    }

    private void initView() {
        mStart = (Button) this.findViewById(R.id.animation_btn);
        mStart.setOnClickListener(this);
        btn = (Button) this.findViewById(R.id.animation_btn01);
        btn.setOnClickListener(this);
        mTargetView = (ImageView) this.findViewById(R.id.animation_target);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onClick(View view) {
        if (view == mStart) {
            ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(
                    this, mTargetView, "transition_iv");
            Intent intent = new Intent(this, Animator2Activity.class);
            startActivity(intent, optionsCompat.toBundle());
        } else if (view == btn) {

        }
    }
}
複製程式碼

設定getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 然後使用ActivityOptionsCompat.makeSceneTransitionAnimation來建立ActivityOptionsCompat物件。

makeSceneTransitionAnimation

makeSceneTransitionAnimation方法已經做好版本判斷了,我們看下該方法的引數。

(Activity activity,View sharedElement, String sharedElementName) activity:當前activity的物件 sharedElement:共享元素的View sharedElementName:也就是在sharedElement中設定android:transitionName名字。

activity_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="20dp"
    tools:context="com.main.animation.AnimationActivity">

    <Button
        android:id="@+id/animation_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:text="startAnimation"/>

    <Button
        android:id="@+id/animation_btn01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/animation_btn"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:text="Text"/>

    <ImageView
        android:id="@+id/animation_target"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_alignParentBottom="true"
        android:transitionName="transition_iv"
        android:layout_centerHorizontal="true"
        android:scaleType="centerCrop"
        android:src="@drawable/animationtarget"/>

</RelativeLayout>

複製程式碼

在xml中需要注意的就是貢獻元素的View需要設定 android:transitionName。

跳轉的Activity

public class Animator2Activity extends AppCompatActivity {

    private ImageView target;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //設定允許使用轉場動畫
        getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        setContentView(R.layout.activity_animator2);
        target = (ImageView)this.findViewById(R.id.animation2_target);
    }


    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }
}
複製程式碼

同樣需要設Window.FEATURE_CONTENT_TRANSITIONS。

activity_animator2.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.main.animation.Animator2Activity">

    <ImageView
        android:id="@+id/animation2_target"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:scaleType="centerCrop"
        android:src="@drawable/animationtarget"
        android:transitionName="transition_iv"
        tools:layout_editor_absoluteX="8dp"
        tools:layout_editor_absoluteY="0dp"/>

</RelativeLayout>

複製程式碼

同樣需要在共享的View設定android:transitionName,而且名字需要相同。

說明: 按返回鍵的時候自動實現了返回的共享元素轉場動畫,具體原因看原始碼:

public void onBackPressed() {
        finishAfterTransition();
    }
    public void finishAfterTransition() {
        if (!mActivityTransitionState.startExitBackTransition(this)) {
            finish();
        }
    }
複製程式碼

當然你可以自己呼叫finishAfterTransition()來結束activity,也是有動畫。

多元素共享轉場

基本上跟單元素轉場一樣,唯一的不同點就是使用Pair.create來設定多個元素轉場。

ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
                    .makeSceneTransitionAnimation(this, Pair.create((View)mTargetView, "transition_iv"),
Pair.create((View)text, "transition_text"));
Intent intent = new Intent(this, Animator2Activity.class);
startActivity(intent, optionsCompat.toBundle());
複製程式碼

以上就是多元素共享轉場的核心程式碼。

使用RecyclerView實現共享元素轉場動畫。

TranslationActivity:

public class TranslationActivity extends AppCompatActivity {
    private RecyclerView mList;
    private List<String> mDatas = new ArrayList<>();
    private MyAdapter mMyAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_translation);
        mList = (RecyclerView)this.findViewById(R.id.translation_list);
        mList.setLayoutManager(new LinearLayoutManager(this));
        for (int i = 0; i < 30; i++) {
            String tx = "呵呵呵"+i;
            mDatas.add(tx);
        }
        mMyAdapter = new MyAdapter(this,mDatas);
        mList.setAdapter(mMyAdapter);
        mMyAdapter.setOnItemOnclickListener(new MyAdapter.OnItemOnclickListener() {
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onClickItem(ImageView iv, TextView tv, String text) {
                ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
                        .makeSceneTransitionAnimation(TranslationActivity.this, Pair.create((View)iv, "transition_iv"),
                                Pair.create((View)tv, "transition_text"));
                Intent intent = new Intent(TranslationActivity.this, Animator2Activity.class);
                intent.putExtra("tag",text);
                startActivity(intent, optionsCompat.toBundle());
            }
        });

    }


    private static class MyAdapter extends RecyclerView.Adapter{

        private List<String>datas;
        private Context mContext;


        private OnItemOnclickListener mOnItemOnclickListener;

        public void setOnItemOnclickListener(OnItemOnclickListener onItemOnclickListener) {
            mOnItemOnclickListener = onItemOnclickListener;
        }

        public MyAdapter(Context context,List<String> datas) {
            this.datas = datas;
            mContext = context;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(mContext).inflate(R.layout.transition_item,null);
            return new MyHolder(view);
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            MyHolder myHolder = (MyHolder)holder;
            myHolder.text.setText(datas.get(position));
            myHolder.tag = datas.get(position);
        }

        @Override
        public int getItemCount() {
            if (datas != null && datas.size()>0){
                return datas.size();
            }
            return 0;
        }

        private class MyHolder extends RecyclerView.ViewHolder{
            private TextView text;
            private String tag;
            private ImageView iv;
            public MyHolder(View itemView) {
                super(itemView);
                text = (TextView)itemView.findViewById(R.id.transition_tx);
                iv = (ImageView) itemView.findViewById(R.id.transition_iv);
                itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if (mOnItemOnclickListener != null){
                            mOnItemOnclickListener.onClickItem(iv,text,tag);
                        }
                    }
                });
            }
        }

        private View.OnClickListener mOnClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        };

        public interface OnItemOnclickListener{
            void onClickItem(ImageView iv,TextView tv,String text);
        }

    }


}
複製程式碼

主要是設定RecyclerView,實現RecyclerView.Adapter,然後設定Item的點選,RecyclerView沒有為Item提供點選,所以要實現點選回撥。 在回撥設定共享元素轉場動畫

ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
                        .makeSceneTransitionAnimation(TranslationActivity.this, Pair.create((View)iv, "transition_iv"),Pair.create((View)tv, "transition_text"));
Intent intent = new Intent(TranslationActivity.this, Animator2Activity.class);
intent.putExtra("tag",text);
startActivity(intent, optionsCompat.toBundle());
複製程式碼

注意: 回撥的時候需要把Item的共享元素作為引數傳遞過來,否則設定不了。

translation_list.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="20dp"
    android:orientation="vertical"
    tools:context="com.main.animation.TranslationActivity">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/translation_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

複製程式碼

Item條目的佈局transition_item.xml:

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

    <ImageView
        android:id="@+id/transition_iv"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_marginTop="10dp"
        android:scaleType="centerCrop"
        android:src="@drawable/animationtarget"
        android:transitionName="transition_iv"/>

    <TextView
        android:id="@+id/transition_tx"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="tv"
        android:textColor="#ffffff"
        android:textSize="30dp"
        android:transitionName="transition_text"/>

</RelativeLayout>

複製程式碼

同樣共享元素需要設定android:transitionName。

這裡的跳轉的頁面直接跳轉到上面的Animator2Activity頁面。

效果介面:

View

View

由於沒有錄製視訊,所以讀者很難看出效果,不過讀者可以根據上面程式碼,自行實戰一把,同時我的也希望讀者可以自己動手實戰,鞏固和理解該知識點。

相關文章