天天看点

Android开发艺术探索笔记 第七章 动画深入分析

android动画分为三种 View动画 帧动画  属性动画  其中帧动画也属于View动画 不过和常见的View旋转、平移、缩放、透明度的表现形式不同

7.1.1View动画的种类

Android开发艺术探索笔记 第七章 动画深入分析

使用View动画首先需要创建xml文件这个文件的路径为res/anim/filename.xml ,描述文件有固定的语法

<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android.com/apk/res/android"

    android:shareInterpolator="true">

    <alpha

        android:fromAlpha="10.0"

        android:toAlpha="10.0" />

    <scale

        android:fromXScale="10dp"

        android:fromYScale="10dp"

        android:pivotX="10"

        android:pivotY="10"

        android:toXScale="10dp"

        android:toYScale="10dp" />

    <translate

        android:fromXDelta="10"

        android:fromYDelta="10"

        android:toXDelta="10"

        android:toYDelta="10" />

    <rotate

        android:fromDegrees="10"

        android:pivotX="10"

        android:pivotY="10"

        android:toDegrees="10" />

</set>

从上边的语法来看,View的动画既可以是单个动画也可以由一系列的动画组成

<set>标签表示动画集合,对应AnimationSet类它包含很多个类有两个属性:

android:interpolator :表示动画集合所使用的差值器,差值器影响动画的速度,比如非匀速动画就需要差值器来制作动画的过程,这个属性可以不指定,默认为加速减速差值器

android:shareInterpolator:表示集合中的动画是否合计和使用一个差值器,如果集合不指定差值器,那么子动画就需要单独的指定所需要的差值器了

< translate>标签表示平移动画:

  • android:fromXDelta

表示x的起始值,比如0

  • android:fromYDelta

表示y的结束值,比如100

  • android:toXDelta

表示y的起始值

  • android:toYDelta

表示y的结束值

< scale>标签表示的是缩放动画

android:fromXScale

水平方向缩放的起始值,比如0.5

android:fromYScale

竖直方向缩放的起始值

android:pivotX

缩放轴点的x坐标,它会影响缩放的效果

android:pivotY

缩放轴点的y坐标,它会影响缩放的效果

android:toXScale

水平方向缩放的结束值,比如1.2

android:toYScale

竖直方向缩放的起始值

<routate>表示动画的旋转,对应RotateAnimation,它可以使View具有旋转的动画效果

android:fromDegrees

旋转开始的角度,比如0

android:toDegrees

旋转结束的角度,比如180

android:pivotX

旋转轴点的x

android:pivotY

旋转轴点的y

在旋转中也有轴的概念,他也会影响到旋转的效果,轴点扮演者旋转轴的角色,view围绕着轴点进行旋转,默认情况下在view的中心,考虑到一种情况,view围绕自己的中心,和围绕左上角进行90度显然是不同的轨迹

< alpha>表示透明动画。对应的AlphaAnimation

  • android:fromAlpha

表示透明度的起始值,比如0.1

  • android:toAlpha

上面都只是很简单的介绍了XM格式,具体的使用方法还是看文档,我们还有一些常用的属性如下

  • android:duration

动画的时间

  • android:fillAfter

动画结束之后是否停留在结束的位置

除了在xml中创建动画 还可以通过代码实现 但是对于View动画来说建议采用XML来定义动画,这是因为XML的可读性更好,淡然在实际开发中使用xml也是第一选择

7.1.2自定义View动画

除了系统提供的四种View动画外,我们还可以自定义View动画自定义动画,在实际开发过程中基本上用不到

7.1.3帧动画

帧动画就是顺序播放的一组图片,系统提供了一个AnimationDrawable类来实现帧动画

在xml中定义

7.2View动画的使用场景

view动画还可以在一些特殊的场景使用 比如在ViewGroup中可以控制子元素的出场效果,实现不同activity之间的切换效果

7.2.1 LayoutAnimation 作用于ViewGroup为ViewGroup制定一个动画这样他的子元素出场的时候就具有这种动画了,这种效果常被用于listview中

7.2.2 Activity的切换效果 

启动一个

startActivity(new Intent(MainActivity.this,OneActivity.class));

//这是activity的跳转动画

overridePendingTransition(R.anim.animation,R.anim.anim_layout);

退出一个

@Override

    public void onBackPressed() {

        super.onBackPressed();

        overridePendingTransition(R.anim.animation, R.anim.anim_layout);

    }

7.3属性动画

属性动画可以对任何队形做动画们甚至没有对象也可以,属性动画效果得到加强,不再像View动画支持4种属性动画有ValueAnimator,ObjectAnimator,AnimatorSet; ObjectAnimator继承自ValueAnimator,而AnimatorSet可以定义一组动画一般我们都用ObjectAnimator来声明一个ValueAnimator在ObjectAnimator声明属性变化值。

属性动画可以对任意对象的属性进行动画,而不仅仅是View 动画的默认时间是300 默认帧率是10

7.3.1使用属性动画

(1)改变一个对象的translationY属性,让其沿着Y轴向上平移一个时间,该动画在默认的时间完成,

ObjectAnimator.ofFloat(iv_icon, "translationY", -iv_icon.getHeight()).start();

(2)改变一个对象的背景颜色值,典型的就是改变view的背景,下面的动画是让view的背景从0xffff8080到0xff8080ff,动画会无限循环和反转

ValueAnimator valueAnimator =

        ObjectAnimator.ofInt(ll_content, "backgroundColor", 0xFFFF8080, 0xFF8080FF);

valueAnimator.setDuration(3000);

valueAnimator.setEvaluator(new ArgbEvaluator());

valueAnimator.setRepeatCount(ValueAnimator.INFINITE);

valueAnimator.setRepeatMode(ValueAnimator.REVERSE);

valueAnimator.start();

(3)动画集合,5s内对view的旋转,平移,缩放和透明度进行改变

实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:

  • after(Animator anim)   将现有动画插入到传入的动画之后执行
  • after(long delay)   将现有动画延迟指定毫秒后执行
  • before(Animator anim)   将现有动画插入到传入的动画之前执行
  • with(Animator anim)   将现有动画和传入的动画同时执行

好的,有了这四个方法,我们就可以完成组合动画的逻辑了,那么比如说我们想要让TextView先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作,就可以这样写:

1

2

3

4

5

6

7

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, 

"translationX"

, -500f, 0f); 

ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, 

"rotation"

, 0f, 360f); 

ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, 

"alpha"

, 1f, 0f, 1f); 

AnimatorSet animSet = 

new

AnimatorSet(); 

animSet.play(rotate).with(fadeInOut).after(moveIn); 

animSet.setDuration(

5000

); 

animSet.start(); 

 可以看到,这里我们先是把三个动画的对象全部创建出来,然后new出一个AnimatorSet对象之后将这三个动画对象进行播放排序,让旋转和淡入淡出动画同时进行,并把它们插入到了平移动画的后面,最后是设置动画时长以及启动动画。

当然也可以直接使用playTogether方法将所有的动画操作放入一个集合中

AnimatorSet set = new AnimatorSet();

set.playTogether(

        ObjectAnimator.ofFloat(iv_icon, "rotationX", 0, 360),  中心x轴进行3d旋转

        ObjectAnimator.ofFloat(iv_icon, "rotationY", 0, 180),中心y轴进行3d旋转

        ObjectAnimator.ofFloat(iv_icon, "rotation", 0, -90),中心进行平面旋转

        ObjectAnimator.ofFloat(iv_icon, "translationX", 0, 90),x轴进行平移

        ObjectAnimator.ofFloat(iv_icon, "translationY", 0, 90),y轴进行平移

        ObjectAnimator.ofFloat(iv_icon, "scaleX", 0, 1.5f),,x轴进行缩放

        ObjectAnimator.ofFloat(iv_icon, "scaleY", 0, 0.5f),y轴进行缩放

        ObjectAnimator.ofFloat(iv_icon, "alpha", 0, 2.5f, 1) 透明度变化

);

set.setDuration(3000).start();

属性动画的各个参数是比较好理解的,我们简单来说下他们之间的含义

propertyName:表示属性动画作用对象的属性的名称

duration:表示动画的时长

valueFrom:表示属性的起始值

valueTo:表示属性的结束值

startOffset:表示动画的延迟时间,当动画开始后,需要延迟多少毫秒才会真正的播放

repeatCount:表示动画的重复次数

repeatMode:表示动画的重复模式

valueType:表示propertyName有两个属性有int和float两个可选项,分别表示属性的类型,和浮点型,另外,如果所制定的是颜色类型,那么就不需要指定propertyName,系统会自动对颜色类型进行处理

实际开发中一般使用代码动态创建属性动画

ValueAnimator、 ObjectAnimator、 AnimatorSet都可以使用addListener添加监听事件,生成四个方法,如果你觉得每次实现四种方法,太磨叽,那么AnimatorListenerAdapter也可以实现你的逻辑,他可以实现你想实现的某一个方法。      

7.3.4 对任意的属性做动画

这里最一个需求,就是给buttion设置一个动画,让他的宽度从当前的变成500px,这个可以用view动画来搞定,但是你仔细想想,view不能对宽高变化,所有我么可以使用属性动画,

我们会发现button确实拉伸了

但是书中却说不能改变

看下大佬的分享

下面对属性动画的原理:属性动画要求动画作用在对象提供的get/set方法,属性动画根据外界传递的该属性的初始值和最终值,以动画效果多次去set,每次传递的set方法的值都不一样,确切来说是随着时间的时间推移,所传递的值越来越接近最终值,总结一下,我们对object的属性abc做动画,如果想让动画生效,要同时满足两个条件:

(1)object必须要提供set方法,如果动画的时候没有传递初始值,那么我们还要提供get方法,因为系统要去取abc的属性(如果这条不满意,程序直接Crash)

(2)object的set方法对abc所做的改变必须通过某种方法反应,比如带来UI的改变(如果这条不满足,动画无效果但是不会Crash)

以上条件缺一不可,那么为什么我们对button的width属性做动画没有效果,这是因为button内部虽然提供了get/set方法,但是这个set方法并不是改变视图大小,他是textview新添加的方法,view是没有这个setWidth方法的,由于button继承了textview,所有button也就有了set方法,下面看一下这个get/get的源码

    @android.view.RemotableViewMethod

    public void setWidth(int pixels) {

        mMaxWidth = mMinWidth = pixels;

        mMaxWidthMode = mMinWidthMode = PIXELS;

        requestLayout();

        invalidate();

    }

    @ViewDebug.ExportedProperty(category = "layout")

    public final int getWidth() {

        return mRight - mLeft;

    }

从上述的源码可以看出,get的确是获取view的宽度,而set是textview的专属方法,他的作用不是设置view的宽度,而是设置textview的最大宽度和最小宽度,这个和textview的宽度不死一个东西,具体来说,textview的宽度对应XML中的android:layout_width,而textview还有一个属性android:width,这个就对应了setwidth,总之textview和button的set/get干的不是同一件事,通过set无法改变控件的宽度,所以对width做属性动画没有效果,对于属性动画的两个条件来说,本例中的动画只满足了第一个条件,我们有三个解决办法:

给你的对象增加set/get方法,前提是你有权限的话

用这个类来包装原始对象,间接提供get/set方法

采用ValueAnimator,监听动画过程自己去实现

我们来具体的实现下这三个解决办法

1.给你的对象增加set/get方法,前提是你有权限的话

这个的意思很好理解,如果你有权限的话,加个set/get方法就搞定了,但是很多时候我们没有权限去这么做,比如本文开头所提到的问题,你无法给button加上一个合乎要求的setwidth方法,因为这个是Android SDK内部实现的,这个方法很简单,但是往往是不可行的,这里就不对其进行更多的分析了

2.用这个类来包装原始对象,间接提供get/set方法

    private void performAnimate() {

        ViewWrapper viewWrapper = new ViewWrapper(btn);

        ObjectAnimator.ofInt(viewWrapper, "width", 500).setDuration(1000).start();

    }

    private static class ViewWrapper {

        private View mTarget;

        public ViewWrapper(View mTarget) {

            this.mTarget = mTarget;

        }

        public int getWidth() {

            return mTarget.getLayoutParams().width;

        }

        public void setWidth(int width) {

            mTarget.getLayoutParams().width = width;

            mTarget.requestLayout();

        }

    }

上述代码在1000ms中宽度增加到500,为了达到这个效果我们写了一个包装类去提供方法,这样也就完美的实现了

3.采用ValueAnimator,监听动画过程自己去实现

首先说下什么是ValueAnimator,ValueAnimator本身不作用于任何对象,也就是说直接使用它没有任何的效果,他可以对一个值做动画,然后我们监听这个过程,在过程中修改我们对象的属性值,这样就相当于我们的对象做了动画,下面我们用例子来说明

    private void performAnimator(final View target, final int start, final int end) {

        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            //持有一个IntEvaluator对象,方便下面估值的时候使用

            private IntEvaluator mEvaluator = new IntEvaluator();

            @Override

            public void onAnimationUpdate(ValueAnimator animation) {

                //获得当前动画的进度值,整形1-100之间

                int currentValue = (int) animation.getAnimatedValue();

                //获得当前进度占整个动画之间的比例,浮点0-1之间

                float fraction = animation.getAnimatedFraction();

                //直接使用整形估值器,通过比例计算宽度,然后再设置给按钮

                target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);

                target.requestLayout();

            }

        });

        valueAnimator.setDuration(5000).start();

    }

上面的代码的效果和刚才的viewwrapper是一样的,关于ValueAnimator还要再说一下,拿上来的例子来说,他会在5s内将一个数1变成100,然后动画的每一帧会回调的每一帧onAnimationUpdate方法,在这个方法里,我们可以获取当前的值和占用的比例我们可以计算出宽度是多少,比如时间过去了一半,当前值是50,比例是0.5,假设起始值为100,最终是500px,那么500-100=400,所有这个时候乘以0.5=200,这些都是内部实现,我们不用自己写,直接用。

7.3.5 属性动画的工作原理

继续阅读