Android中的补间动画分为下面几种:
(1)AlphaAnimation :透明度改变的动画。
(2)ScaleAnimation:大小缩放的动画。
(3)TranslateAnimation:位移变化的动画。
(4)RotateAnimation:旋转动画。
然而在实际项目中透明度、缩放、位移、旋转这几种动画并不能满足我们的需求,比如我们需要一个类似下面的3D旋转动画。
这时候就需要用到自定义动画,自定义动画需要继承Animation,并重写applyTransformation(float interpolatedTime, Transformation t)方法和initialize方法。
applyTransformation方法中的两个参数说明:
interpolatedTime: 该参数代表了时间的进行程度(如:你设置的时间是1000ms,
那么interploatedTime就会从0开始一直到1,当该参数为1时表明动画结束)
Transformation:
代表补间动画在不同时刻对图形或组建的变形程度。该对象中封装了一个Matrix对象,对它所包含的Matrix对象进行位移、倾斜、旋转等变换时,Transformation将会控制对应的图片或视图进行相应的变换。
initialize(int width, int height, int parentWidth, int parentHeight)函数,这是一个回调函数告诉Animation目标View的大小参数,在这里可以初始化一些相关的参数,例如设置动画持续时间、设置Interpolator、设置动画的参考点等。
为了控制图片或View进行三维空间的变换,还需要借助于Android提供的一个Camera类,该类是一个空间变换工具,作用有点类似于Matrix,提供了如下常用的方法。
getMatrix(Matrix matrix) :将Camera所做的变换应用到指定的maxtrix上
rotateX(float deg):将目标组件沿X轴旋转
rotateY(float deg)、
rotateZ(float deg)
translate(float x, float y, float z):把目标组件在三维空间类进行位移变换。
applyToCanvas(Canvas canvas):把Camera所做的变换应用到Canvas上。
初级应用——代码中创建动画
下面我们先来个简单的实现, 只在activity中创建动画 ,而不使用xml文件的方式来创建动画。
具体实现如下:
自定义rotate3dAnimation 继承自Animation ,并重写applyTransformation(float interpolatedTime, Transformation t)方法。
public class Rotate3dAnimation extends Animation { // 旋转点类型 默认为 ABSOLUTE private int mPivotXType = ABSOLUTE; private int mPivotYType = ABSOLUTE; private float mPivotXValue = 0.0f; private float mPivotYValue = 0.0f; private float mFromDegrees; private float mToDegrees; private float mPivotX; private float mPivotY; private Camera mCamera; private int mRollType; /** * 旋转轴 */ public static final int ROLL_BY_X = 0; public static final int ROLL_BY_Y = 1; public static final int ROLL_BY_Z = 2; public Rotate3dAnimation(int rollType, float fromDegrees, float toDegrees) { mRollType = rollType; mFromDegrees = fromDegrees; mToDegrees = toDegrees; mPivotX = 0.0f; mPivotY = 0.0f; } public Rotate3dAnimation(int rollType, float fromDegrees, float toDegrees, float pivotX, float pivotY) { mRollType = rollType; mFromDegrees = fromDegrees; mToDegrees = toDegrees; mPivotXType = ABSOLUTE; mPivotYType = ABSOLUTE; mPivotXValue = pivotX; mPivotYValue = pivotY; initializePivotPoint(); } public Rotate3dAnimation(int rollType, float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) { mRollType = rollType; mFromDegrees = fromDegrees; mToDegrees = toDegrees; mPivotXValue = pivotXValue; mPivotXType = pivotXType; mPivotYValue = pivotYValue; mPivotYType = pivotYType; initializePivotPoint(); } private void initializePivotPoint() { if (mPivotXType == ABSOLUTE) { mPivotX = mPivotXValue; } if (mPivotYType == ABSOLUTE) { mPivotY = mPivotYValue; } } // Animation类中的初始化方法 有点类似于onMeasure @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) { final float fromDegrees = mFromDegrees; float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); final Matrix matrix = t.getMatrix(); mCamera.save(); switch (mRollType) { case ROLL_BY_X: //绕X轴旋转 mCamera.rotateX(degrees); break; case ROLL_BY_Y: //绕Y轴旋转 mCamera.rotateY(degrees); break; case ROLL_BY_Z: //绕Z轴旋转 mCamera.rotateZ(degrees); break; } mCamera.getMatrix(matrix); mCamera.restore(); matrix.preTranslate(-mPivotX, -mPivotY); matrix.postTranslate(mPivotX, mPivotY); } }
在activity中的使用方法和 使用ScaleAnimation等动画没什么两样。
activity中的代码如下:
public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView img = (ImageView) findViewById(R.id.img); Rotate3dAnimation animation = new Rotate3dAnimation(Rotate3dAnimation.ROLL_BY_X,0f,360f); animation.setFillAfter(true); animation.setDuration(1000); img.startAnimation(animation); } }
现在我们已经初步掌握了自定义动画类的使用,但是仅在代码中创建动画是不够的,我们很多情况下也需要在xml文件中创建动画。那该怎么办呢?
高级应用——XML创建动画
使用XML创建动画的过程有点类似于自定义控件的使用。
(一) 在attrs.xml文件中设置自定义属性
attrs.xml
<resources> <declare-styleable name="Rotate3dAnimation"> <!-- 旋转类型 x轴 y轴 z轴 --> <attr name="rollType" format="enum"> <enum name="x" value="0"/> <enum name="y" value="1"/> <enum name="z" value="2"/> </attr> <!-- 初始角度 --> <attr name="fromDeg" format="float" /> <!-- 目标角度 --> <attr name="toDeg" format="float" /> <!-- 旋转点 --> <attr name="pivotX" format="fraction"/> <attr name="pivotY" format="fraction" /> </declare-styleable> </resources>
(二) 获取自定义属性
下面 我们就需要修改下我们的rotate3dAnimation类,在其中获取xml文件中声明的自定义属性并解析。
Rotate3dAnimation.class
package com.demo.customanimation; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Camera; import android.graphics.Matrix; import android.util.AttributeSet; import android.util.TypedValue; import android.view.animation.Animation; import android.view.animation.Transformation; public class Rotate3dAnimation extends Animation { // 旋转点类型 默认为 ABSOLUTE private int mPivotXType = ABSOLUTE; private int mPivotYType = ABSOLUTE; private float mPivotXValue = 0.0f; private float mPivotYValue = 0.0f; private float mFromDegrees; private float mToDegrees; private float mPivotX; private float mPivotY; private Camera mCamera; private int mRollType; /** * 旋转轴 */ public static final int ROLL_BY_X = 0; public static final int ROLL_BY_Y = 1; public static final int ROLL_BY_Z = 2; //获取并解析自定义属性, 与在自定义控件中的使用相同 public Rotate3dAnimation(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Rotate3dAnimation); mFromDegrees = a.getFloat(R.styleable.Rotate3dAnimation_fromDeg, 0.0f); mToDegrees = a.getFloat(R.styleable.Rotate3dAnimation_toDeg, 0.0f); mRollType = a.getInt(R.styleable.Rotate3dAnimation_rollType, ROLL_BY_X); Description d = parseValue(a .peekValue(R.styleable.Rotate3dAnimation_pivotX)); mPivotXType = d.type; mPivotXValue = d.value; d = parseValue(a.peekValue(R.styleable.Rotate3dAnimation_pivotY)); mPivotYType = d.type; mPivotYValue = d.value; a.recycle(); // 初始化旋转点 initializePivotPoint(); } public Rotate3dAnimation(int rollType, float fromDegrees, float toDegrees) { mRollType = rollType; mFromDegrees = fromDegrees; mToDegrees = toDegrees; mPivotX = 0.0f; mPivotY = 0.0f; } public Rotate3dAnimation(int rollType, float fromDegrees, float toDegrees, float pivotX, float pivotY) { mRollType = rollType; mFromDegrees = fromDegrees; mToDegrees = toDegrees; mPivotXType = ABSOLUTE; mPivotYType = ABSOLUTE; mPivotXValue = pivotX; mPivotYValue = pivotY; initializePivotPoint(); } public Rotate3dAnimation(int rollType, float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) { mRollType = rollType; mFromDegrees = fromDegrees; mToDegrees = toDegrees; mPivotXValue = pivotXValue; mPivotXType = pivotXType; mPivotYValue = pivotYValue; mPivotYType = pivotYType; initializePivotPoint(); } private void initializePivotPoint() { if (mPivotXType == ABSOLUTE) { mPivotX = mPivotXValue; } if (mPivotYType == ABSOLUTE) { mPivotY = mPivotYValue; } } // Animation类中的初始化方法 有点类似于onMeasure @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); } protected static class Description { public int type; public float value; } Description parseValue(TypedValue value) { Description d = new Description(); if (value == null) { d.type = ABSOLUTE; d.value = 0; } else { if (value.type == TypedValue.TYPE_FRACTION) { d.type = (value.data & TypedValue.COMPLEX_UNIT_MASK) == TypedValue.COMPLEX_UNIT_FRACTION_PARENT ? RELATIVE_TO_PARENT : RELATIVE_TO_SELF; d.value = TypedValue.complexToFloat(value.data); return d; } else if (value.type == TypedValue.TYPE_FLOAT) { d.type = ABSOLUTE; d.value = value.getFloat(); return d; } else if (value.type >= TypedValue.TYPE_FIRST_INT && value.type <= TypedValue.TYPE_LAST_INT) { d.type = ABSOLUTE; d.value = value.data; return d; } } d.type = ABSOLUTE; d.value = 0.0f; return d; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { final float fromDegrees = mFromDegrees; float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); final Matrix matrix = t.getMatrix(); mCamera.save(); switch (mRollType) { case ROLL_BY_X: mCamera.rotateX(degrees); break; case ROLL_BY_Y: mCamera.rotateY(degrees); break; case ROLL_BY_Z: mCamera.rotateZ(degrees); break; } mCamera.getMatrix(matrix); mCamera.restore(); matrix.preTranslate(-mPivotX, -mPivotY); matrix.postTranslate(mPivotX, mPivotY); } }
最后 ,我们在anim动画文件中使用我们自定义的动画类和属性就好了。
注意,在xml中使用自定义动画类的时候,需要自定义我们的命名空间,在使用动画标签的时候需要加上命名控件:包名。
rotate3d.xml
<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.demo.customanimation.Rotate3dAnimation rotates:rollType="x" rotates:fromDeg="100" rotates:toDeg="0" rotates:pivotX="50%" rotates:pivotY="50%" android:duration="400"/> </set>
接下来,在activity中使用AnimationUtil的loadAnimation方法来加载我们的xml动画文件。
public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView img = (ImageView) findViewById(R.id.img); Animation animation = AnimationUtils.loadAnimation(this, R.anim.rotate3d); animation.setFillAfter(true); animation.setDuration(1000); img.startAnimation(animation); } }
好了,“大功告成”(真的这样么。。)! 运行一下试试!!!
哎? 怎么回事? 运行竟然报错了????!!!!
这是什么原因呢?
修改AnimationUtils 源码
通过查看AnimationUtils.loadAnimation源代码我们知道,在其从xml载入动画类的时候,只认alpha、scale、rotate、translate这几个SDK自带的动画类,而我们写入的自定义动画类Rotate3dAnimation会导致其报Unknown animation name的异常。官方SDK也没有提供解决这个问题的其他API方法,那么怎么解决呢? 很简单,只需在原有的AnimationUtils.loadAnimation源码上改动一行,通过java中的反射机制,通过包名从ClassLoader载入自定义动画类即可。将其源码拷贝过来,实现一个自己的loadAnimation方法,如下:
package com.demo.customanimation; import java.io.IOException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.res.Resources.NotFoundException; import android.content.res.XmlResourceParser; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Xml; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.GridLayoutAnimationController; import android.view.animation.LayoutAnimationController; import android.view.animation.RotateAnimation; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; public class MyAnimationUtil { /** * These flags are used when parsing AnimatorSet objects */ private static final int TOGETHER = 0; private static final int SEQUENTIALLY = 1; /** * Returns the current animation time in milliseconds. This time should be * used when invoking {@link Animation#setStartTime(long)}. Refer to * {@link android.os.SystemClock} for more information about the different * available clocks. The clock used by this method is <em>not</em> the * "wall" clock (it is not {@link System#currentTimeMillis}). * * @return the current animation time in milliseconds * * @see android.os.SystemClock */ public static long currentAnimationTimeMillis() { return SystemClock.uptimeMillis(); } /** * Loads an {@link Animation} object from a resource * * @param context * Application context used to access resources * @param id * The resource id of the animation to load * @return The animation object reference by the specified id * @throws NotFoundException * when the animation cannot be loaded */ public static Animation loadAnimation(Context context, int id) throws NotFoundException { XmlResourceParser parser = null; try { parser = context.getResources().getAnimation(id); return createAnimationFromXml(context, parser); } catch (XmlPullParserException ex) { NotFoundException rnf = new NotFoundException( "Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } catch (IOException ex) { NotFoundException rnf = new NotFoundException( "Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } finally { if (parser != null) parser.close(); } } private static Animation createAnimationFromXml(Context c, XmlPullParser parser) throws XmlPullParserException, IOException { return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser)); } // 从动画的XML文件创建动画 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(); /** * 如果是set标签 则创建AnimationSet动画集合 然后递归调用 参数 c context attrs 属性集 * 代表duration startOffset等属性 */ if (name.equals("set")) { anim = new AnimationSet(c, attrs); createAnimationFromXml(c, parser, (AnimationSet) anim, attrs); /** * 如果是alpha标签 则创建AlphaAnimation动画集合 * 参数 c :context * 参数attrs: 属性集代表duration、 startOffset等属性 */ } else if (name.equals("alpha")) { anim = new AlphaAnimation(c, attrs); /** * 如果是scale标签 则创建ScaleAnimation动画集合 * 参数 c :context * 参数attrs: 属性集代表duration、 startOffset等属性 */ } else if (name.equals("scale")) { anim = new ScaleAnimation(c, attrs); /** * 如果是rotate标签 则创建RotateAnimation动画集合 * 参数 c :context * 参数attrs: 属性集代表duration、 startOffset等属性 */ } else if (name.equals("rotate")) { anim = new RotateAnimation(c, attrs); /** * 如果是translate标签 则创建TranslateAnimation动画集合 * 参数 c :context * 参数attrs: 属性集代表duration、 startOffset等属性 */ } else if (name.equals("translate")) { anim = new TranslateAnimation(c, attrs); }else{ try { anim = (Animation) Class.forName(name).getConstructor(Context.class, AttributeSet.class).newInstance(c, attrs); } catch (Exception te) { throw new RuntimeException("Unknown animation name: " + parser.getName() + " error:" + te.getMessage()); } } } if (parent != null) { parent.addAnimation(anim); } return anim; } }
然后修改我们的activity中的代码 只需要将系统的AnimationUtils换成我们自己的MyAnimationUtils就行了。
Animation animation = MyAnimationUtil.loadAnimation(this, R.anim.rotate3d); animation.setFillAfter(true); animation.setDuration(1000); img.startAnimation(animation);
这次才是真正的大功告成!!!
**
源码奉送
**