Android当中的动画3—自定义Tween Animation


前言

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

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

}
最后来看我们的自定义动画TestAnimation.java

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当中定义。

<?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>
2、在/res/anim/animation.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>
3在我们的Activity当中使用我们在xml当中定义的
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了

else if(name.equals("com.suansuan.tweenanimation.animation.TestAnimation")){
                    anim = new TestAnimation(c,attrs);
                }
然后试试,OK  ,效果和上面一样。完成,好了,谢谢大家观看,希望如果有错的话,大家可以指出 我们一块进步,一块快乐的码代码。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值