您的位置:首页 > 移动开发 > Android开发

7_Android动画深入分析

2018-02-08 15:19 330 查看
Android的动画可以分为三种:View动画、帧动画和属性动画,其实帧动画也属于View动画的一种,只不过它和平移旋转等常见的View等常见的View动画在表现形式上略有不同而已。View通过对场景里的对象不断做图像变换(平移、缩放、旋转、透明度)从而产生动画效果,是一种渐进式动画,并且View动画支持自定义。帧动画通过顺序播放一系列图像从而产生动画效果,可以简单理解为图片切换动画,很显然,如果图片过多过大就会导致OOM。属性动画通过动态改变对象的属性从而达到动画效果。

7.1.1 View动画

View动画对象作用于View,有四种动画效果,分别是平移动画、缩放动画、旋转动画和透明度动画。View动画的四种变换效果对应着Animation的四个子类:TranslateAnimation、ScaleAnimation、RotateAnimation和AlphaAnimation 这四种动画即可以通过xml来定义也可以通过代码来动态创建。文件创建路径res/anim/name.xmlView动画可以是单个动画也可以由一系列动画组成。<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来兼容。网址 linkNineoldandroids对属性动画做了兼容,在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 硬件加速使用动画过程中,建议开启硬件加速,这样会提高动画的流畅性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息