前言
我们在上一遍博客当中学会了如何使用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); }