Android當中的動畫3—自定義Tween Animation


前言

我們在上一遍博客當中學會了如何使用Tween Animation,這一篇博客當中我們將詳細的介紹Tween Animation動畫的工作流程,並且會介紹我們如何自定義我們自己的動畫效果,並且我們會做一個小小的Demo來讓我們更加熟練的掌握Tween Animation,同時也爲我們爲以後學習屬性動畫,打下堅實的基礎。


正文

首先,我們要自定義一個自己的動畫,我們不知道怎麼去自定義,那我們就去看看Aniamtion的源碼,我們仿照Android的源碼來自定義自己的動畫,這樣我們就可以自己去自定義我們的動畫了。下面我就以AlphaAnimaton動畫來作爲我們要去參照的源碼,爲什麼我們會選擇它呢,因爲是因爲AlphaAnimaton動畫的參數少,好方便我們去閱讀,


package android.view.animation;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;

/**
 * An animation that controls the alpha level of an object.
 * Useful for fading things in and out. This animation ends up
 * changing the alpha property of a {@link Transformation}
 *
 */
public class AlphaAnimation extends Animation {
    private float mFromAlpha;
    private float mToAlpha;

    /**
     * Constructor used when an AlphaAnimation is loaded from a resource. 
     * 
     * @param context Application context to use
     * @param attrs Attribute set from which to read values
     */
    public AlphaAnimation(Context context, AttributeSet attrs) {
        super(context, attrs);
        
        TypedArray a =
            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AlphaAnimation);
        
        mFromAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_fromAlpha, 1.0f);
        mToAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_toAlpha, 1.0f);
        
        a.recycle();
    }
    
    /**
     * Constructor to use when building an AlphaAnimation from code
     * 
     * @param fromAlpha Starting alpha value for the animation, where 1.0 means
     *        fully opaque and 0.0 means fully transparent.
     * @param toAlpha Ending alpha value for the animation.
     */
    public AlphaAnimation(float fromAlpha, float toAlpha) {
        mFromAlpha = fromAlpha;
        mToAlpha = toAlpha;
    }
    
    /**
     * Changes the alpha property of the supplied {@link Transformation}
     */
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final float alpha = mFromAlpha;
        t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
    }

    @Override
    public boolean willChangeTransformationMatrix() {
        return false;
    }

    @Override
    public boolean willChangeBounds() {
        return false;
    }

    /**
     * @hide
     */
    @Override
    public boolean hasAlpha() {
        return true;
    }
}

我們現在對我們的AlphaAnimation動畫,來進行分析,以便我們去自定義我們的動畫,首先我們知道,動畫總體的把控,就是Animation這個類,而不同動畫則有不同的實現類去實現,比如我們的透明度的動畫,


我們發現有兩個構造方法,第一個構造方法有兩個參數,其中一個就是我們在自定義控件的時候見過的AttributeSet,所以我們猜想這個構造方法就是我們在/res/anim/XXXX.xml中定義動畫時,系統會去調用的方法,而第二個,這個我們熟悉就是我們在代碼當中常常使用到的那個構造方法,現在我們來看,這兩個構造方法是告訴我們,在構造方法裏面去約束了動畫,就是透明度從幾到幾的這種約束。


下面我們可以看到有三個方法,其實樓主也不知道是幹嘛的,我們來測一下吧,

applyTransformation(float interpolatedTime, Transformation t)
willChangeTransformationMatrix()
willChangeBounds()

自己定義一個類,去仿照AlphaAnimation去寫一下,寫完以後,我們就像是用AlphaAnimation一樣,去用我們自己寫的這個測試類,下面我給大家看一下,我們的測試類。

public class TestAnimation extends Animation {


    private String Tag = "suansuan";
    private float mFromAlpha;
    private float mToAlpha;

  
    public TestAnimation(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a =
                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AlphaAnimation);

        mFromAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_fromAlpha, 1.0f);
        mToAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_toAlpha, 1.0f);
        Log.i(Tag,"------>>TestAnimation(Context context, AttributeSet attrs)");
        a.recycle();
    }


    public TestAnimation(float fromAlpha, float toAlpha) {
        mFromAlpha = fromAlpha;
        mToAlpha = toAlpha;
        Log.i(Tag,"------>>TestAnimation(float fromAlpha, float toAlpha)");
    }

  
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final float alpha = mFromAlpha;
        Log.i(Tag,"------>>applyTransformation(float interpolatedTime, Transformation t)|||||||" +
                "interpolatedTime = " + interpolatedTime);
        t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
    }

    @Override
    public boolean willChangeTransformationMatrix() {
        Log.i(Tag,"------>>willChangeTransformationMatrix()");
        return false;
    }

    @Override
    public boolean willChangeBounds() {
        Log.i(Tag,"------>>willChangeBounds()");
        return false;
    }
}

我們其實也沒有做什麼東西,我們只是在我們TestAnimation當中加入了適當的Log,好了我們現在去使用一下我們的測試類吧,

我們得出的結論就是動畫剛剛開始我們使用構造方法,還有一個叫做initialize()方法,用來初始化,讓動畫執行的時候,我們會去調用以下的三個方法,至於沒個方法是幹什麼的,我在下方說的很明白


applyTransformation(float interpolatedTime, Transformation t):
每次執行動畫,都會在執行動畫期間
不斷地去調用這個方法,而這個方法也就是我們動畫的核心代碼,在這裏面做一些操作,我們第一個參數:裏面
封裝的是時間因子,第二個參數 Transformation,裏面維護了一個矩形對象,而我們在不同的時間讓我們矩陣以
不同的形式展現出來,這就使我們的動畫。
willChangeTransformationMatrix():從我們的字面意思去理解這個方法,就是當我們的矩陣規模發生改變的
時候會回調 
willChangeBounds():我們理解就是當矩陣對象的邊界發生改變回調的方法。
結論

就是我們在構造方法,initialize方法裏面區初始化對象,使用applyTransformmation方法裏面去不斷地去刷新我們對象。

現在我們的問題出現了,我們怎麼樣通過矩形對我們的動畫去做一個變換呢,這裏我們就要熟悉圖片矩陣的變化,其實我們還可以通過一個輔助類,Camera,這裏的Camera不是我們手機當中的相機,而是底層有OpenGL的支持,去實現一些3D的效果,下面我們就去自定義一個3D的旋轉動畫,我們先來看一下效果




下面我們來看一下我們的/res/layout/activity_main.xml

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

    <ImageView
        android:id="@+id/image"
        android:padding="20dip"
        android:layout_width="match_parent"
        android:layout_height="350dip"
        android:src="@mipmap/empty_logo_black"
        />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        >

        <Button
            android:layout_width="0dip"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimary"
            android:text="自定義動畫X軸"
            android:onClick="animationX"
            android:textColor="#fff"/>

        <Button
            android:layout_width="0dip"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimary"
            android:text="自定義動畫Y軸"
            android:onClick="animationY"
            android:textColor="#fff"/>

        <Button
            android:layout_width="0dip"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimary"
            android:onClick="animationZ"
            android:text="自定義動畫Z軸"
            android:textColor="#fff"/>



    </LinearLayout>


</RelativeLayout>

使用我們的java代碼,MainActvity.java

package com.suansuan.tweenanimation;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.Animation;
import android.widget.ImageView;

import com.suansuan.tweenanimation.animation.TestAnimation;

public class MainActivity extends AppCompatActivity {

    private ImageView mImage;

    private long duration = 1000;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mImage = (ImageView) findViewById(R.id.image);
    }

    /** 自定義動畫X軸 */
    public void animationX(View view){
        startAnimation(TestAnimation.X);
    }

    /** 自定義動畫Y軸 */
    public void animationY(View view){
        startAnimation(TestAnimation.Y);
    }

    /** 自定義動畫Z軸 */
    public void animationZ(View view){
        startAnimation(TestAnimation.Z);
    }

    /** 抽取動畫重複代碼 */
    public void startAnimation(int state){
        TestAnimation testAnimation = new TestAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f, state);
        testAnimation.setDuration(duration);

        //保持最後的狀態,
        testAnimation.setFillAfter(true);
        mImage.startAnimation(testAnimation);
    }

}
最後來看我們的自定義動畫TestAnimation.java

package com.suansuan.tweenanimation.animation;

import android.graphics.Camera;
import android.graphics.Matrix;
import android.view.animation.Animation;
import android.view.animation.Transformation;

/**
 *
 * Created by suansuan on 2016/11/4.
 */

public class TestAnimation extends Animation {

    public static final int X = 1;
    public static final int Y = 2;
    public static final int Z = 3;

    private int mCurrentState = Z;

    private float mFromDegrees;
    private float mToDegrees;
    private Camera mCamera;

    private int mPivotXType = Animation.RELATIVE_TO_SELF;
    private int mPivotYType = Animation.RELATIVE_TO_SELF;

    private float mPivotXValue = 0.5f;
    private float mPivotYValue = 0.5f;

    private float mPivotX;
    private float mPivotY;

    /** 通過Java代碼去實現一個該動畫的效果 */
    public TestAnimation(float formDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue, int state){
        this.mFromDegrees = formDegrees;
        this.mToDegrees = toDegrees;

        if(state > 0 && state < 4){
            this.mCurrentState = state;
        }

        mPivotXType = pivotXType;
        mPivotYType = pivotYType;

        mPivotXValue = pivotXValue;
        mPivotYValue = pivotYValue;
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width,height,parentWidth,parentHeight);

        mCamera = new Camera();

        mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
        mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);

    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
        final Matrix matrix = t.getMatrix();

        mCamera.save();
        switch (mCurrentState){
            case X:
                mCamera.rotateX(degrees);
                break;
            case Y:
                mCamera.rotateY(degrees);
                break;
            case Z:
                mCamera.rotateZ(degrees);
                break;
        }
        mCamera.getMatrix(matrix);
        mCamera.restore();
        matrix.preTranslate(-mPivotX, -mPivotY);
        matrix.postTranslate(mPivotX, mPivotY);

    }
}

上述就是如果去通過一個Java代碼去自定義一個動畫,我們也知道,我們的動畫是可以在XML當中進行定義的,那我們如何在XML當中定義一個自定義動畫呢!下面跟着我一起來做,當然做過自定義控件的來做這個就比較簡單了。

1、我們先來自定義屬性,在/res/values/attrs.xml當中定義。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TestAnimation">
        <!-- 旋轉類型   x軸 y軸  z軸 -->
        <attr name="currentState" format="enum">
            <enum name="x" value="1"/>
            <enum name="y" value="2"/>
            <enum name="z" value="3"/>
        </attr>
        <!-- 初始角度 -->
        <attr name="fromDeg" format="float" />
        <!-- 目標角度 -->
        <attr name="toDeg" format="float" />
    </declare-styleable>
</resources>
2、在/res/anim/animation.xml定義我們的動畫

<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:rotates="http://schemas.android.com/apk/res-auto"
android:interpolator="@android:anim/linear_interpolator"
android:shareInterpolator="true">

<rotates:com.suansuan.tweenanimation.animation.TestAnimation
    rotates:currentState="x"
    rotates:fromDeg="80"
    rotates:toDeg="720"
    android:duration="400"/>

</set>
3在我們的Activity當中使用我們在xml當中定義的
Animation animation = AnimationHelper.loadAnimation(this, R.anim.animation);
animation.setFillAfter(true);
mImage.startAnimation(animation);
然後運行,卻發現:

E/AndroidRuntime(14985): Caused by: java.lang.RuntimeException: Unknown animation name: com.suansuan.tweenanimation.animation.TestAnimation
E/AndroidRuntime(14985):        at android.view.animation.AnimationUtils.createAnimationFromXml(AnimationUtils.java:124)
E/AndroidRuntime(14985):        at android.view.animation.AnimationUtils.createAnimationFromXml(AnimationUtils.java:114)
E/AndroidRuntime(14985):        at android.view.animation.AnimationUtils.createAnimationFromXml(AnimationUtils.java:91)
E/AndroidRuntime(14985):        at android.view.animation.AnimationUtils.loadAnimation(AnimationUtils.java:72)
E/AndroidRuntime(14985):        at com.suansuan.tweenanimation.MainActivity$override.animationXML(MainActivity.java:42)
E/AndroidRuntime(14985):        at com.suansuan.tweenanimation.MainActivity$override.access$dispatch(MainActivity.java)
E/AndroidRuntime(14985):        at com.suansuan.tweenanimation.MainActivity.animationXML(MainActivity.java:0)
E/AndroidRuntime(14985):        ... 14 more

不認識我們的名字,我去,我·我·我   好吧,那我們就去看看AnimationUtils的源碼把!

/** 這個就是核心的方法了,我們看下去以後,發現,果然沒有我們名字 */    
private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
            AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {

        Animation anim = null;

        // Make sure we are on a start tag.
        int type;
        int depth = parser.getDepth();

        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
               && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            String  name = parser.getName();

            if (name.equals("set")) {
                anim = new AnimationSet(c, attrs);
                createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
            } else if (name.equals("alpha")) {
                anim = new AlphaAnimation(c, attrs);
            } else if (name.equals("scale")) {
                anim = new ScaleAnimation(c, attrs);
            }  else if (name.equals("rotate")) {
                anim = new RotateAnimation(c, attrs);
            }  else if (name.equals("translate")) {
                anim = new TranslateAnimation(c, attrs);
            } else {
                //我們自定義的屬性肯定回到這裏,那我們就在前面加一個我們的名字
               throw new RuntimeException("Unknown animation name: " + parser.getName());
            }

            if (parent != null) {
                parent.addAnimation(anim);
            }
        }

        return anim;

    }

我們不可能去修改源碼,所以我們在這裏的做法就是模仿AnimationUtils去做一個自己的AnimationHelper我們只需要在else if()哪裏多加一個我們的 就OK了

else if(name.equals("com.suansuan.tweenanimation.animation.TestAnimation")){
                    anim = new TestAnimation(c,attrs);
                }
然後試試,OK  ,效果和上面一樣。完成,好了,謝謝大家觀看,希望如果有錯的話,大家可以指出 我們一塊進步,一塊快樂的碼代碼。


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