前言
我們在上一遍博客當中學會了如何使用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最後來看我們的自定義動畫TestAnimation.javapackage 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); } }
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當中定義。
2、在/res/anim/animation.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>
3在我們的Activity當中使用我們在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>
然後運行,卻發現: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了
然後試試,OK ,效果和上面一樣。完成,好了,謝謝大家觀看,希望如果有錯的話,大家可以指出 我們一塊進步,一塊快樂的碼代碼。else if(name.equals("com.suansuan.tweenanimation.animation.TestAnimation")){ anim = new TestAnimation(c,attrs); }