Android自定义动画类——实现3D旋转动画

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

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

**

源码奉送

**

时间: 2024-10-12 23:58:46

Android自定义动画类——实现3D旋转动画的相关文章

Android ActionBar中的按钮添加旋转动画

将Menu菜单项显示在ActionBar上,这里显示一个刷新按钮,模拟在刷新动作时的添加刷新动画 菜单布局 menu.xml <menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/action_stop" android:orderInCategory="100" android:showAsAction

Android 3D旋转动画之Camera 和 Matrix

前面两篇博文讲解的都是Android 的2D动画效果,要想做出非常炫酷的3D动画效果怎么办?android 并没有提供3D动画接口给用户,所以我们得自己重写这样一个3D接口动画. 接口如下: /* * @Title: My3dAnimation.java * @Description: TODO<请描述此文件是做什么的> * @author: xjp * @data: 2014年9月15日 上午8:54:10 * @version: V1.0 */ package com.xjp.animat

android自定义View之(四)------一键清除动画

1.前言: 自己也是参考别人的一些自定义view例子,学习了一些基本的自定义view的方法.今天,我参考了一些资料,再结合自已的一些理解,做了一个一键清除的动画.当年,我实现这个是用了几张图片,采用Frame anination的方式来实现,但是这个方法,不灵活,并且占资源,下面,我就采用自定义view的方法来实现这个功能. 2.效果图: 3.具体详细代码 3.1 \res\values\attrs_on_key_clear_circle_view.xml <resources> <de

Android自定义view教程04-------------自定义属性动画

不太会美工,所以随便写了个菜单打开关闭的动画,主要是教会大家如何使用属性动画,可以这么说 学会属性动画 前面的fream动画和tween动画可以不用看了,因为他们2能做的,属性动画也能做, 他们2不能做的,属性动画也能做. 直接上代码吧,注释写的还算详细. 主activity代码 实际上没啥好看的,主要就是使用了dialogfragment,没有用dialog,因为谷歌后来推荐 我们使用这个dialogfragment,而且这个确实比dialog要优秀方便很多. package com.exam

Android自定义view教程01-------------Android的Frame动画详解

本系列博文 最终的目的是能教会大家自己实现比较复杂的android 自定义控件.所以知识点不仅仅局促在自定义view本身上面.实际上现在github上一些做的比较出色的自定义控件 大部分都是由三个部分组成 第一:动画 第二:自定义view 第三:触摸滑动控制.所以我们这个系列也是由动画作为开篇.最终会带着大家分析几个github上比较出色的自定义控件. Android 的frame动画是比较简单基础的内容,在以往的2.x 3.x版本很多人都会去使用这个 来作为loading 图的实现方法.但是最

css3 3d旋转动画

    <!doctype html> <html> <head> <meta charset="utf-8"> <title>css3 3d动画 keyframes</title> </head> <body> <style>/*************** ANIMATIONS ***************/ @-webkit-keyframes spin { from {

Android自定义View绘图实现拖影动画

前几天在"Android绘图之渐隐动画"一文中通过画线实现了渐隐动画,但里面有个问题,画笔较粗(大于1)时线段之间会有裂隙,我又改进了一下.这次效果好多了. 先看效果吧: 然后我们来说说基本的做法: 根据画笔宽度,计算每一条线段两个顶点对应的四个点,四点连线,包围线段,形成一个路径. 后一条线段的路径的前两个点,取(等于)前一条线段的后两点,这样就衔接起来了. 把Path的Style修改为FILL,效果是这样的: 可以看到一个个四边形,连成了路径. 好啦,现在说说怎样根据两点计算出包围

CSS3 3D旋转动画代码实例

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-

android自定义刷新类控件

android虽然定义了种类非常丰富的控件,但是有的时候这些自定义的控件还是不能满足我的要求,为了能够适配更多的需求,我们需要在原有的基础上进行自定义控件. 今天我向大家介绍的就是android中最常见的刷新类控件.因为我们最近正在参加一个项目,在项目组长的带领下,我学到了很多的东西,这对我的android技术的提升非常大,定义一个自定义控件可能不是很难,但是如何让这个自定义控件更加有效.更加快速地运行. 首先我们需要建立一个自定义控件类: package com.example.ui.widg