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创建动画的过程有点类似于自定义控件的使用。
attrs.xml
<resources>
<declare-styleable name="Rotate3dAnimation">
<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">
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.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 not 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);
这次才是真正的大功告成!!!
**
**