7_Android动画深入分析

Android的动画可以分为三种:View动画、帧动画和属性动画,其实帧动画也属于View动画的一种,只不过它和平

移旋转等常见的View等常见的View动画在表现形式上略有不同而已。View通过对场景里的对象不断做图像变换

(平移、缩放、旋转、透明度)从而产生动画效果,是一种渐进式动画,并且View动画支持自定义。帧动画通过顺

序播放一系列图像从而产生动画效果,可以简单理解为图片切换动画,很显然,如果图片过多过大就会导致

OOM。属性动画通过动态改变对象的属性从而达到动画效果。

7.1.1 View动画

View动画对象作用于View,有四种动画效果,分别是平移动画、缩放动画、旋转动画和透明度动画。

View动画的四种变换效果对应着Animation的四个子类:TranslateAnimation、ScaleAnimation、RotateAnimation和AlphaAnimation 这四种动画即

可以通过xml来定义也可以通过代码来动态创建。文件创建路径res/anim/name.xml

View动画可以是单个动画也可以由一系列动画组成。<set>标签表示动画集合,对应AnimationSet类,它可以包含若干个动画,并且它的内部也是可以嵌套其他动画集合的。

android:interpolator

表示动画集合所采用的插值器,插值器影响动画的速度,比如非非匀速动画就需要通过插值器来控制动画的播放过程。这个属性可以不指定,默认为@android:anim/accelerate_decelerate_interpolator,即加速减速插值器。

android:shareInterpolator

表示集合中的动画是否和集合共享同一个插值器。。如果集合不指定插值器,那么子动画就需要单独指定所需的插值器或者使用默认值。

<translate> 标签表示平移动画,对应TranslateAnimation类,它可以使一个View在水平和竖直方向完成平移的动画效果:

android:fromXDelta----表示x的起始值;

android:toXDelta----表示x的结束值;

android:fromYDelta----表示y的起始值;

android:toYDelta----表示y的结束值;

<scale>标签表示缩放动画,对应ScaleAnimation,它可以使View具有放大或者缩小的动画效果:

andorid:fromXScale---水平方向缩放的起始值;

android:toXScale---水平方向缩放的结束值;

android:fromYScale---竖直方向缩放的起始值;

android:toYScale---竖直方向缩放的起始值;

android:pivotX---缩放的轴点的x坐标;

android:pivotY---缩放的轴点的y坐标;

在<scale>标签中提到了轴点的概念,默认情况下轴点是View的中心点,这个时候在水平方向进行缩放的会导致View想左右两个方向同时进行缩放,但是如果把轴点设为View的右边界,那么View就只会向左边进行缩放,反之则向右边进行缩放。

<rotate>标签表示旋转动画,对于RotateAnimation,它可以使View具有旋转动画效果:

android:fromDegress---旋转开始的角度;

android:toDegress---旋转结束的角度;

android:pivotX---旋转轴点的x坐标;

android:pivotY---旋转轴点的y坐标;

在旋转动画中,轴点就是旋转轴,即View是围绕着轴点进行旋转的,默认情况下轴点为View的中心点。

<alpha>标签表示透明度动画,对应AlphaAnimation,可以改变View的透明度:

android:fromAlpha---表示透明度的起始值;

android:toAlpha---表示透明度的结束值;

上面简单介绍了View动画的xml格式,View还有一些常用的属性:

android:duration---动画的持续时间;

android:fillAfter---动画结束以后View是否停留在结束位置。

7.1.2自定义View动画

除了系统提供的四种View动画外,我们还可以自定义View动画。自定义动画是一件即简单又复杂的事情,说它简单,是因为派生一种新动画只需要继承Animation这个抽象类,然后重写它的initialize和applyTransformation方法,在initialize方法中一些初始化工作,在applyTransformation方中进行相应的矩阵变换即可,很多时候需要采用Camera来简单矩阵变换的过程。说它复杂,是因为自定义View动画的过程主要是矩阵变换的过程,而矩阵变换是数学上的概念。

7.1.3帧动画

帧动画是顺序播放一组预先定义好的图片,不同于View动画,系统提供了另一个类AnimationDrawable来使用帧动画。 View view = findViewByid(xxxx); view.setBackgroundSource(R.drawable.animation); AnimationDrawable drawable = (AnimationDrawable)view.getBackground(); drawable.start();

7.2.1LayoutAnimation

LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,这样当它的子元素出场时都会具有这种动画效果,这种效果常常被用在ListView上,我们时常会看到一种特殊的ListView,它的每个Item都以一定的动画的形式出现,使用的就是LayoutAnimation。LayoutAnimation也是一个View动画。

android:delay

表示子元素开始动画的时间延迟,比如子元素入场动画的时间周期为300ms,那么0.5表示每个子元素都需要延迟150ms才能播放入场动画。

android:animationOrder

表示子元素动画顺序,有三种选项:normal、reverse和random,其中normal表示顺序显示,即排在前面的子元素先开始播放入场动画;reverse表示逆向显示,即排在后面的子元素先开始播放入场动画;random则是随机播放入场动画。

android:animation

为子元素指定具体的入场动画。

为ViewGroup指定android:layoutAnimation属性:android:layoutAnimation="@anim/anim_layout",这种方式适用于所有ViewGroup。

除了在XML中指定LayoutAnimation外,还可以通过LayoutAnimationController来实现:

ViewGroup view = fiindViewById(xx);

Animation animation = AnimationUtils.loadAnimation(this,R.anim.item);

LayoutAnimationController controller = new LayoutAnimationContrroller(animation);

controller.setDelay(0.5f); controller.setorder(LayoutAnimationController.ORDER_NORMAL);

view.setLayoutAnimation(controller);

7.2.2Activity的切换效果

Activity有默认的切换效果,但是这个效果我们是可以自定义的,主要用到overridePendingTransition(int endterAnim, int exitAnim)这个方法,这个方法必须在startActivity(Intent)或者finish()之后调用才能生效。

enterAnim-----Activity被打开时,所需要的动画资源id。

exitAnim----Activity被暂停时,所需要的动画资源id。

当启动一个activity时,可以按照如下方式为其添加自定义的切换效果:

Intent intent = new Intent(this,xxx.class);

startActivity(intent);

overridePendingTransition(int endterAnim,exitAnim);

当Activity退出时,也可以为其指定自己的切换效果 overridePendingTransition(int endterAnim,exitAnim);

需要注意的是,overridePendingTransition这个方法必须位于startActivity或者finish的后面,否则动画效果不起作用。

Fragment也可以添加切换动画,由于Fragment是在API11中新引入的类,因此为了兼容性我们需要使用support-v4兼容包,在这个情况下我们可以通过FragmentTransition中setCustomAnimations()方法来添加切换动画。这个切换动画需要是View动画,之所以不能采用属性动画因为存在兼容性问题,在低版本上无法使用。

7.3.1属性动画可以对任何对象做动画,甚至还可以没有对象。除了作用对象进行了扩展以外,属性动画的效果也得到了加强,不再像View动画那样只能支持四种简单的变换。属性动画中有ValueAnimator、ObjectAnimator、和AnimatorSet等概念,通过它们可以实现绚丽的动画。

动画默认时间间隔300ms,默认帧数 10ms。其可以达到的效果是:在一个时间间隔内完成对象从一个属性值到另一个属性的改变。因此,属性动画几乎是无所不能的,只要对象有这个属性,它都能实现动画效果。可以采用开源动画库nineoldandroids来兼容。网址 link

Nineoldandroids对属性动画做了兼容,在api 11以前的版本其内部是通过代理View动画来实现的,因此在android低版本上,它的本质上还是View动画,尽管使用方法看起来是属性动画。比较常用的动画库:ValueAnimator、ObjectAnimator和AnimatorSet,其中ObjectAnimator继承自ValueAnimator,AnimatorSet是动画集合,可以定义一组动画。

(1)改变一个对象的transitionY属性,让其沿着Y轴向上平移一段距离:它的高度,该动画在默认时间内完成,动画的完成时间是可以定义的。想要更灵活的效果我们还可以定义插值器和估算算法,但是一般来说我们不需要自定义,系统已经预置了一些。

objectAnimmator.ofFloat(myobject,"translationY",-myObject.getHeight()).start();

(2)改变一个对象的背景色属性,典型的情形是改变View的背景色,下面的动画可以让背景在3秒内实现从0xFFFF8080到0xFF8080FF的渐变,动画会无限循环而且会有反转的效果。

ValueAnimator colorAnim = ObjectAnimator.ofInt(this,"backgroundColor",/Red/0xFFFF8080,/Blue/0xFF8080FF); colorAnim.setDuration(3000); colorAnim.setEvaluator(new ArgbEvaluator()); colorAnim.setRepeatCount(ValueAnimator.INFINITE);

(3)动画集合,5秒内对View的旋转、平移、缩放和透明度都进行了改变。

AnimatorSet set = new AnimatorSet();

set.playTogether(ObjectAnimator.ofFloat(myView,"rotationX",0,360), ObjectAnimator.ofFloat(myView,"rotationY",0,180), ObjecAnimator.ofFloat(myView,"rotation",0,-90), ObjectAnimator.ofFloat(myView,"translationX",0,90), ObjectAnimation.ofFloat(myView,"translationY",0,90), ObjectAnimator.ofFloat(myView,"scaleX",1,1.5f), ObjectAnimator.ofFloat(myView,"scaleY",1,1.5f), ObjectAnimator.ofFloat(myView,"alpha",1,0.25f),1);

set.setDuration(5*1000).start();

属性动画除了通过代码实现以外,还可以通过XML来定义。属性动画需要定义在res/animator目录下

属性动画的各种参数都比较好理解,在XML中可以定义Valueanimator、ObjectAnimator以及AnimatorSet,其中<set>标签对应AnimatorSet,<animator>标签对应ValueAnimator ,<ObjectAnimator>对应ObjectAnimator。

7.3.3属性动画的监听器

属性动画提供了监听器用于监听动画的播放过程,主要如下两个接口: AnimatorUPdateListener和AnimatorListener。

从AnimatorListener的定义它可以监听动画的开始,结束,取消,以及重复播放。同时为了方便开放,系统还提供了AnimatorListenerAdapter这个类,它是AnimatorListener的适配器类,这样我们就可以有选择地实现回调方法。

AnimatorUpdateListneer比较特殊,它会监听整个动画过程,动画是由许多帧组成的,每播放一帧,onAnimatorUpdate就会被调用一次。

7.3.4对任意属性做动画

给Button加一个动画,让这个Button的宽度从当前宽度增加到500px。无法用View动画实现,因为View动画根本不支持对宽度进行动画,View动画支持四种类型:平移(Translate)、旋转(Rotate)、缩放(Scale)、透明度(Alpha)。

可以用x方向缩放可以让Button在x方向放大,看起来好像是宽度增加了,实际上不是,只是Button被放大了而已,而且由于x方向被放大,这个时候Button的背景以及上面的文本都被拉伸了,甚至有可能button会超出屏幕。

private void performAnimate(){
    ObjectAnimator.ofInt(mButton,"width",500).setDuration(5000).start();
}

上面代码运行后没有效果,其实没效果是对的,如果随便传递一个属性过去,轻则没动画效果,重则程序直接Crash。

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

(1)object必须要提供setAbc方法,如果动画的时候没有传递初始值,那么还要提供getAbc方法,因为系统要去取abc属性初始值。

(2)object的setAbc对属性abc所做的改变必须能够通过某种方法反映出来,比如会带来UI的改变之类。

以上条件缺一不可以。那么为什么我们对Button的width属性做动画会没有效果?这是因为Button内部虽然提供了getWidth和setWidth方法,但是这个setWidth方法并不是改变视图的大小,它是TextView新添加的方法,View是没有这个setWidth方法的,由于Button继承了TextView,所以Button也就有了setWidth方法。而setWidth是TextView和其子类的专属方法,它的作用不是设置View的宽度,而是设置TextView和其子类的最大宽度和最小宽度的,这个和TextView的宽度不是一个东西。具体来说,TextView的宽度对应于XML中的android:layout_width属性,而TextView还有一个属性android:width,这个android:width属性就对应了TextView的setWidth方法。总之,TextView和Button的setWidth、getWidth干的不是同一个事情,通过setWidth无法改变控件的宽度,所以对Width做属性动画没有效果。对应于属性动画的两个条件来说。

针对上述问题,官方文档上告诉我们有3种解决方法:

(1)给你的对象加上get和set方法,如果你有权限的话。

(2)用一个类来包装原始对象,间接为其提供get和set方法。

(3)采用ValueAnimator,监听动画过程,自己实现属性的改变。

1 给你的对象加上get和set方法,如果你有权限的话

如果你有权限的话,加上get和set就搞定了。但是很多时候我们没权限这么做。你无法给Button加上一个合乎要求的setWidth方法,因为这是android Sdk内部实现的。

2 用一个类包装原始对象,间接为其提供get和set方法

这是一个很有用的解决方法

private void performAnimate(){
    ViewWrapper wrapper = new ViewWrapper(view);
    
    objectAnimator.ofINt(wrapper,"width",500).setDuration(5000).start();
}

@Override
public void onClick(View v){
    if (v == view){
        performAnimate();
    }
}

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

上述代码在5s内让Button的宽度增加到了500px,为了达到这个效果,我们提供了ViewWrapper类专门用于包装View,具体到本例是包装Button。然后我们对ViewWrapper的Width属性做属性动画,并且在setWidth方法中修改其内部的targt的宽度。

3 采用ValueAnimator,监听动画过程,自己实现属性的改变

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

private void performAnimatr(final View target,final end){
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            //持有一个IntEvaluator 对象,方便下面估值时候使用
            IntEvaluator mIntEvaluator = new IntEvaluator();

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获得当前动画的进度值,整型 0~100之间
                int currentValue = (int) animation.getAnimatedValue();
                //获得当前进度占整个动画进程的比例 浮点型 0~1
                float fraction = animation.getAnimatedFraction();
                //直接调用整型估值器,通过比例计算出高度,然后设给Button
                target.getLayoutParams().width = mIntEvaluator.evaluate(fraction, start, end);
                target.requestLayout();
            }
        });

        valueAnimator.setDuration(5000).start();
}

上述代码的效果图和采用ViewWrapper是一样的,关于这个ValueAnimator要再说一下,拿上面的例子,它会在5000ms内将一个数从1变成100,然后动画的每一帧会回调onAnimationUpdate方法。在这个方法里,我们可以获取当前的值(0~100)和当前值所占的比例,我们可以计算出Button现在的宽度应该是多少。比如时间了一半,当前值50,比例0.5,假设Button现在的宽度是100px,最终宽度是500px,那么Buttton增加的宽度也应该占总增加宽度的一半,总增加肯定是500-100=400,所以这个时候Buton应该增加宽度是400x0.5 = 200,那么当前Button的宽度应该为初始宽度+增加宽度(100+200 = 300)。上述计算过程很简单,其实它就是整型估算器的内部实现。

7.3.5属性动画的工作原理

属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次调用set方法。每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,因为系统要去获取属性的初始值。对于属性动画来说,其动画过程中所做的就是这么多。

首先我们要找一个入口,就从ObjectAnimator.ofInt(view,"width",500).setDuration(5000).start()开始,其他动画都是类似的。看ObjectAnimator的start方法,知道首先会判断如果当前动画、等待的动画(Pending)和延迟的动画中有和当前动画相同的动画,那么就把相同的动画给取消掉,再接着就调用了父类的super。start方法,因为ObjectAnimator继承了ValueAnimator。看ValueAnimator的start方法可以知道属性动画需要运行在有Looper的线程中。上述代码最终会调用AnimationHandler的start方法,可以知道属性动画需要运行在Lopper的线程中。

7.4 使用动画的注意事项

主要分为几类:

1 OOM问题 这个问题主要出现在帧动画中,当图片数量过多且图片较大时,就极易出现OOM,这个在实际开发中尤为注意,尽量避免使用帧动画。

2 内存泄露

在属性动画有一类无限循环的动画,这类动画需要在activity退出时及时停止,否则将导致activity无法释放从而造成内存泄露,通过验证发现View动画并存在此问题。

3 兼容性问题

动画在3.0以下的系统有兼容性问题,在某些特殊场景可能无法正常工作,因此需要做好适配工作。

4 View动画的问题

View动画是对View的影像做动画,并不是真正地改变View的状态,因此有时候会出现动画完成后View无法隐藏的现象,即setVisibility(View.GONE)失效了,这个时候只要调用view.clearAnimation()清除View动画即可解决此问题。

5 不要使用px

在进行动画的过程中,要尽量使用dp,使用px会导致在不同的设备上有不同的效果。

6 动画元素的交互

将View移动(平移)后,在android3.0以前的系统上,不管是View动画还是属性动画,新位置均无法触发单击事件,同时,老位置仍然可以触发单击事件。尽管View已经在视觉上不存在了,将View移动到原位置后,原位置的单击事件继续生效。从3.0开始,属性动画的单击事件触发位置为移动后的位置,但是View动画仍然在原位置。

7 硬件加速

使用动画过程中,建议开启硬件加速,这样会提高动画的流畅性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值