关于Android动画的一点愚见

原创 2016年08月31日 20:38:30

这一段时间在看Activity的工作流程,奈何内容太多,涉及到的东西有点多,暂时放下这一篇,慢慢来。先总结一下自己学过的Android自定义动画与属性动画。

Android的View动画分为两类变换动画(Tweened Animation)以及帧动画(Frame by-frame Animation)。

变换动画又分为四大类:平移(translate),旋转(rotate),缩放(scale),透明度(alpha)。

View动画的使用

View动画的编程有两种方法:一种是通过xml中编写,另一种在Java代码中编程控制。
在系统支持xml的view动画中只支持ttranslate,rotate,scale,alpha四种标签,下面是一个实例:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:zAdjustment="normal">

    <translate
        android:duration="100"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:interpolator="@android:anim/linear_interpolator"
        android:toXDelta="100"
        android:toYDelta="0"/>

    <rotate
        android:duration="400"
        android:fromDegrees="0"
        android:toDegrees="45"/>

</set>

代码则是这么调用:

        Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim1);
        Img.startAnimation(animation);

在这里面,值得一提的是:属性android:interpolator插值器,这是指动画的是按照怎么变化规律变化的,,比如线性变化,加速变化等等,就像初中物理那样,一个物体在做匀速运动还是变速运动。

除了使用xml配置之外,还可以使用代码配置并且控制:

                AlphaAnimation alphaAnimation = new AlphaAnimation(1, (float) 0.1);
                alphaAnimation.setDuration(3000);
                Img.startAnimation(alphaAnimation);

思路都是一样的,不过只是在xml调用的时候,声明的Animation需要调用AnimationUtils.loadAnimation(Context context,int id);代码直接声明相应类型的动画类型即可。

还有一点,那就是Activity切入切出时的动画设置,必须调用overridePendingTransition(int enterAnim,int exitAnim)。

自定义View动画

自定义View动画,我需要继承一个Animation的类,再通过矩阵变换来转换其中的动画效果。有一位大神已经通过矩阵的方式将Animation解读了一遍,让我受益良多。

这里是地址:Android动画的矩阵运用

在自定义View动画的时候,Camera类经常被用到,它相当于是一个摄像机一样从不同的角度来观察对象,其结果可以简化矩阵变换的过程。同时也要重写两个方法:
1.public void initialize(int width,int height,int parentWidth,int parentHeight)是用来初始化一些数值或者类。
2.protected void applyTransformation(float interpolateTime,Transformation t)这是用来编写矩阵的变换的。

让我们编写一个3D旋转的自定义动画吧。

public class Roate3dAnimation extends Animation{

    private final float mFromDegrees;
    private final float mToDegress;
    private final float mCenterX;
    private final float mCenterY;
    private final float mDepthZ;
    private final boolean mReverse;
    private Camera mCamera;

    //构造器获取初始角度,变化都角度,获取中心的点的x,y,z坐标
    public Roate3dAnimation(float fromDegress,float toDegress,float centerX,
            float centerY,float depthZ,boolean reverse){
        mFromDegrees = fromDegress;
        mToDegress = toDegress;
        mCenterX = centerX;
        mCenterY = centerY;
        mDepthZ = depthZ;
        mReverse = reverse;
    }

    //初始化Camera
    @Override
    public void initialize(int width,int height,int parentWidth,int parentHeight){
        super.initialize(width, height, parentWidth, parentHeight);
        mCamera = new Camera();
    }

    @Override
    protected void applyTransformation(float interpolatedTime,Transformation t){
        final float fromDegress = mFromDegrees;
        //角度变化随着时间变化而变化:当前角度=起始角度+((结束角度-起始角度)*角度/每秒)
        float degress = fromDegress + ((mToDegress - fromDegress) * interpolatedTime);

        final float centerX = mCenterX;
        final float centerY = mCenterY;
        final Camera camera = mCamera;
        //获取此时的矩阵
        final Matrix matrix = t.getMatrix();
        //camera保存此时camera状态
        camera.save();
        //mReverse是指是反向旋转还是正向旋转
        if(mReverse){
            camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
        }else {
            camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
        }
        //camera对象额旋转
        camera.rotateY(degress);
        //获取旋转后的矩阵
        camera.getMatrix(matrix);
        camera.restore();

        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);


    }


}

上面就是通过camera简化了自己计算矩阵的过程。调用对象还是和之前一样:

        Roate3dAnimation dAnimation = new Roate3dAnimation(0, 180, 50, 50, 1, true);
        dAnimation.setDuration(3000);
        img.startAnimation(dAnimation);

到这里就说完了自定义View动画了,现在已经和少用这种动画,在API11之后Android加入了强大的动画器:属性动画,来更加简单的的完成更加绚烂多彩的动画了。

属性动画

属性动画,顾名思义,这不是只针对View的动画,而是可以对任意一个对象的进行动画操作,比如说,View动画就很难操作Button中的宽度,而属性动画却能弥补这些缺点。

属性动画中我们常用这么几个类:ValueAnimator,ObjectAnimator,AnimatorSet等。直接让我们看一个例子吧:

        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(img, "rotationX", 0.0f,306.0f),
                ObjectAnimator.ofFloat(img, "rotationY", 0.0f,180.0f),
                ObjectAnimator.ofFloat(img, "rotation", 0.0f,-90.0f),
                ObjectAnimator.ofFloat(img, "translationX", 0.0f,90.0f)
                );
        set.setDuration(5000).start();

从上面这这一段代码,可以清晰的认知到,AnimatorSet是作为一个动画的集合,ObjectAnimator则是通过反射获取对象的属性来变化的。在这里我们值得注意的是第二个参数就是指我们要变化的属性,但是如果对象中没有该属性的get函数和set函数,动画将不会生效。
就会出现如下警告:

Method setX() with type int not found on target class class android.widget.Button

我们同样可以配置xml来处理属性动画:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially" >
    <objectAnimator
        android:propertyName="RotationX"
        android:duration="300"
        android:valueTo="90"
        android:valueType="intType"/>

    <objectAnimator
        android:propertyName="RotationY"
        android:duration="300"
        android:valueTo="90"
        android:valueType="intType"/>

</set>

但是用xml来配置属性动画有个缺点就是,很多身后我们无法知道属性的起始值会导致我们的动画出现一些意外的情况,如果用xml来配置,除非提前知道这些值,我们提倡使用代码控制。
还有一个坑,那就是即使引用了本身View里面存在的属性获取,设置的函数还是有可能获取不到。我暂时在源码里面看不出原因。
所以基于以上种种问题,属性动画还是使用代码控制比较好。

那么我们遇到了,对象没有提供set,get的方法的时候,难道就素手无策了吗?不,下面提供三种方案,来解决:

  1. 我们可以获取更高级的权限来修改对象中源码,给相应的对象加上set,get的方法。这是最简单直接的方法,但是比如说Button,这是SDK中实现的,我们往往不可能因此而重新编译一个Android来。
  2. 用一个类来包装原始对象,间接的提供get和set方法。比如说:
    private static class ViewWrapper{
        private View mTarget;
        public ViewWrapper(View target){
            mTarget = target;
        }

        public int getWidth(){
            return mTarget.getLayoutParams().width;
        }

        public void setWidth(int width){
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }
  1. 我们可以使用ValueAnimator来监听动画的过程,实现自己的属性的修改,ValueAnimator是ObjectAnimator的父类,它并没有指定对象确提供了修改对象属性的抽象类,我们往往可以调用valueAnimator.addUpdateListener(new AnimatorUpdateListner(){})的方法,下面是例子:
    private void performAnimate(final View target,final int start,final int end){
        //valueAnimator动画进度条范围
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            //整形插值器的声明
            private IntEvaluator mEvaluator = new IntEvaluator();
            @Override
            public void onAnimationUpdate(ValueAnimator anim) {
                // TODO Auto-generated method stub
                //获取此时动画的进度条
                int currentvalue = (Integer)anim.getAnimatedValue();
                Log.e("currentvalue", ""+currentvalue);
                //获取此时进度条占总进度条的百分比
                float fraction = anim.getAnimatedFraction();
                //相应属性的变化
                target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
                //刷新对象UI
                target.requestLayout();
            }
        });
        valueAnimator.setDuration(5000).start();
    }

到这里就结束了,属性动画的全部内容,为了检验自己是否明白,自己模仿了知乎的目录动画,做了一个简单的属性动画效果。下面是动画的核心源码:

public class MyFloatMenu extends RelativeLayout{

    private ImageView menu;
    private ImageView bell;
    private ImageView wallet;
    private ImageView shopping;
    private ImageView wait_use;
    private View bellView;
    private View waitView;
    private View walletView;
    private View shopView;
    private boolean isOpen = false;

    public MyFloatMenu(Context context){
        super(context);
        init(context);
    }

    public MyFloatMenu(Context context,AttributeSet attr){
        super(context,attr);
        init(context);
    }

    public MyFloatMenu(Context context,AttributeSet attr,int defStyle){
        super(context, attr, defStyle);
        init(context);
    }

    private void init(final Context context){
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(R.layout.menu, this);
        menu = (ImageView)view.findViewById(R.id.plus_menu);
        bellView = inflater.inflate(R.layout.bell, null);
        waitView = inflater.inflate(R.layout.wait, null);
        walletView = inflater.inflate(R.layout.wallet, null);
        shopView = inflater.inflate(R.layout.shopping, null);
        this.addView(bellView);
        this.addView(waitView);
        this.addView(walletView);
        this.addView(shopView);
        bellView.setVisibility(View.GONE);
        waitView.setVisibility(View.GONE);
        walletView.setVisibility(View.GONE);
        shopView.setVisibility(View.GONE);
        menu.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                setVisible();
                if(isOpen == false){
                    perform();
                    isOpen = true;
                }else if (isOpen == true) {
                    backperform();
                    isOpen = false;
                }

            }
        });

        bellView.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent intent = new Intent(context, testActivity.class);
                context.startActivity(intent);
            }
        });
    }


    private void setVisible(){
        bellView.setVisibility(View.VISIBLE);
        waitView.setVisibility(View.VISIBLE);
        walletView.setVisibility(View.VISIBLE);
        shopView.setVisibility(View.VISIBLE );
    }

    public void perform(){
        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(menu, "rotation", 45.0f),
                ObjectAnimator.ofFloat(bellView, "translationX", 300f),
                ObjectAnimator.ofFloat(waitView, "rotationZ", 30.0f),
                ObjectAnimator.ofFloat(waitView, "translationY", 150.0f),
                ObjectAnimator.ofFloat(waitView, "translationX", 259.8f),
                ObjectAnimator.ofFloat(walletView, "rotationZ", 60.0f),
                ObjectAnimator.ofFloat(walletView, "translationX", 150.0f),
                ObjectAnimator.ofFloat(walletView, "translationY", 259.8f),
                ObjectAnimator.ofFloat(shopView, "translationY", 300f));
        set.setDuration(500).start();
    }

    public void backperform(){
        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(menu, "rotation", 90.0f),
                ObjectAnimator.ofFloat(bellView, "translationX", -300f),
                ObjectAnimator.ofFloat(waitView, "rotationZ", -30.0f),
                ObjectAnimator.ofFloat(waitView, "translationY", -150.0f),
                ObjectAnimator.ofFloat(waitView, "translationX", -259.8f),
                ObjectAnimator.ofFloat(walletView, "rotationZ", -60.0f),
                ObjectAnimator.ofFloat(walletView, "translationX", -150.0f),
                ObjectAnimator.ofFloat(walletView, "translationY", -259.8f),
                ObjectAnimator.ofFloat(shopView, "translationY", -300f));
        set.setDuration(500).start();
    }

    @Override
    protected void onLayout(boolean change, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        super.onLayout(change, l, t, r, b);
    }

}

感谢任玉刚大神的android开发探索艺术以及郭霖大神的博客。

这里附上Github工程的源码:Github

版权声明:本文为博主原创文章,未经博主允许不得转载。

关于Notification的一点愚见(PendingIntent工作流程)

PendingIntent,顾名思义,延迟的Intent。那么肯定会产生一个疑问,那就是PendingIntent究竟和Intent的区别在哪里呢? 其实PendingIntent其实就是对Inte...

关于RemoteView的一点愚见(实现桌面小部件)

RemoteView中的应用除了有通知栏的自定义,当还有桌面小部件也是通过RemoteView来完成的。...

关于Handler消息机制一点愚见与总结

Handler消息机制在android刷新UI中是经常使用到的一个工具,但是Handler并不是专门用于刷新UI,是为了让任务能够轻易的切换到Handler中操作。出现的缘由,主要是因为android...

关于Window和WindowManager的一点愚见(添加,删除,更新)

正文先看看自己要编写一个WindowManager该如何实现。 先看看MainActivity.java @Override protected void onCreate(Bundl...

关于activity启动流程的一点愚见

前言Activity在android开发中作为显示的组件,它在整个Acndroid开发中占有着重要的地位。已经开发了Android一段时间的我对Activity七大生命周期以及如何启动产生了兴趣。然而...

关于Notification通知的一点愚见(自定义通知栏)

今天在复习Notification通知栏的时候,好奇翻了一下源码,终于弄懂了PendingIntent在Notification中的机制以及工作流程,这对我研究Activity的启动流程有了一定的帮助...

关于AsyncTask的一点愚见

在Android开发中,为了避免出现ANR现象,主要是指来自于接触事件响应事件过长来说,我们开发者通常会将耗时长的操作,如网络操作,大图片加载,IO操作等等会放在子线程中去处理。而Android中线程...

关于滚动图片停在浏览器边界上的一点解决办法(个人愚见)。。

   最近开始学习javascript,想写个滚动图片的脚本来试试。   想想这种脚本的模板网上应该有许多,果断百度之,果然一堆,然后复制了一个代码下来试试,大概如下(向上滚动): var spee...
  • CP_Hook
  • CP_Hook
  • 2011年04月27日 22:41
  • 597

关于Window创建流程的一点愚见(Activity启动过程中)

在介绍Window在Activity启动中的创建流程时,我先简单的说明一下Activity的启动流程,Activity的启动是通过函数startActivity(Intent intent)启动的。如...

关于Activity的一点愚见

这一段时,准备研究一下四大组件中Activity的启动过程。在此之前,让我自己总结一下Activity里面一些基础的知识点。Activity 的生命周期Activitty有七大生命周期,也是有七个函数...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:关于Android动画的一点愚见
举报原因:
原因补充:

(最多只允许输入30个字)