«

[置顶] Android自定义动画类——实现3D旋转动画

时间:2024-3-2 16:56     作者:韩俊     分类: Android


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);

这次才是真正的大功告成!!!

**

源码奉送

**

标签: android

热门推荐