Android开发之漫漫长途 XVII——动画(续)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!
前言
上一篇文章呢,我们说了关于View动画的那些事,这里也在总结一下,使用View动画时需要注意以下4点:(更多详情请参看我的上一篇博客。)
- View动画的坐标系:View动画说到底是View的一系列运动,既然是运动,那么参照物(坐标系)是很重要的。
- View动画的使用场景:View动画的主体是View,更准确的说是View的副本(影子),View动画更改的只是显示,其x,y坐标仍然没有改变,响应事件的位置没有改变,也就是说view本身并没有改变。也因此,不要使用View动画做交互性操作,例如点击。
- 自定义View动画的步骤:如果我们不满足于系统已经定义好的Animation,=,可以自定义自己的Animation,重写initialize和applyTransformation这两个方法即可。
- Matrix:接着上一点说,如果想要掌握高级的酷炫的动画效果,那么理解Android View动画的矩阵变换的实质是必经之路。
那么本章呢是来介绍Android动画的另外一个大类属性动画
属性动画简介
属性动画是API11新加入的特性,和View动画不同,它可以对任何对象做动画,甚至还可以没有对象,动画默认时间间隔300ms,默认帧率10ms/帧。其可以达到的效果是:在一个时间间隔内完成对对象从一个属性值到另一个属性值得改变。常用属性动画类ValueAnimator、ObjectAnimator和AnimationSet,其中ObjectAnimator继承于ValueAnimator.
注:gihub上JakeWharton大神对API11之前做了属性动画的兼容,它的原理其实也很简单,主要就是判断当前sdk版本,如果大于API11,那么就调用官方的API,否则自己实现动画效果。另外,在API使用方面,它与官方的属性动画基本一致。另外,在API使用方面,它与官方的属性动画基本一致。比如ObjectAnimator、ValueAnimator等等。 感兴趣的同学可以访问该项目的github网址NineOldAndroids
属性动画的使用
Java代码实现
例如改变一个对象(obj)的translationY属性,可以写为
ValueAnimator.ofFloat(obj,"translationY",100);
XML实现(属性动画的XML描述语法的固定格式)
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering=["sequentially"|"together"]>
<objectAnimator
android:propertyName="string"
android:duration="int"
android:valueFrom="float|int|color"
android:valueTo="float|int|color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["restart"|"reverse"]
android:valueType=["colorType"|"intType"]>
</objectAnimator>
<animator
android:duration="int"
android:valueFrom="float|int|color"
android:valueTo="float|int|color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["restart"|"reverse"]
android:valueType=["colorType"|"intType"]>
</animator>
...
</set>
很多属性都是见名知义的,下面呢简单介绍一下各属性名称的含义。
属性动画的核心类有3个类,AnimatorSet,ObjectAnimator以及ValueAnimator
- XML文件中的 <set>标签对应AnimatorSet, <set>标签的ordering属性有2个候选值"sequentially"|"together",分别表示 <set>标签内的动画是按照前后顺序播放和同时播放。
- <animatior>对应ValueAnimator,
android:duration:表示动画的时长
android:valueFrom:表示属性的起始值
android:valueTo:表示属性的结束值
android:startOffset:表示动画的延迟时间,动画开始后,需要延迟多少毫秒后才会真正播放该动画
android:repeatCount:表示动画的重复次数,默认值是0,为-1时,表示无限循环。
android:repeatMode:表示动画的重复播放模式,restart表示动画每次都是重新开始播放,reverse表示动画第1 次播放完毕后,第2次会逆向播放,第3次又从头开始播放,以此类推 - <objectAnimator>对应ObjectAnimator,
android:propertyName:表示属性动画作用对象的属性名称
android:valueType:表示android:propertyName的值的类型,分为intType,和floatType,分别代表整型数值和浮点型数值,若android:propertyName指定的属性表示的是颜色,那么无需指定android:valueType,系统会自动适配
其他属性的含义与上面的<animatior>一致。
属性动画的进阶
我们先来看一个需求:要求对一个Button做动画,要求让其宽度从原始宽度增加到500px。这也太简单了,
Button mButton = (Button) findViewById(R.id.button);
ObjectAnimator.ofInt(mButton,"width",500).setDuration(1000).start();
程序运行,但是却没有效果,这是为什么呢,仔细想想没效果也是应该的,因为你随便传递了一个属性名称过去,轻则动画没有效果,重则直接Crash。那么这个号称可以对任意属性做动画的属性动画使用的时候有哪些需要注意的地方呢
- 属性动画要求动画的作用对象提供该属性的get和set方法,
- 属性的改变必须通过某种方法反映出来,比如会带来UI的修改之类的
以上的条件缺一不可
这时又有一个问题如果想要对一个对象的属性做动画,但是属性又没有对应的get和set方法怎么办呢??
概括来讲有如下3种解决办法:
- 给你的对象加上get和set方法,如果你有权限的话。而这个方法对于Android SDK内部实现的类就不可行,这个方法是最简单的,但是往往是不可行的。
- 用一个类包装原始对象,间接为其提供get和set方法
- 采用ValueAnimator,监听动画过程,自己实现属性的改变
下面仍以上面Button的宽度动画作为需求给出方法2,3的解决代码
方法2:
mButton = (Button) findViewById(R.id.button);
ObjectAnimator.ofInt(new ViewWrapper(mButton),"width",500).setDuration(1000).start();
private 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();
}
}
方法3:
使用方法3之前,我们先来看属性动画的监听器AnimatorUpdateListener和AnimatorListener
属性动画的监听器AnimatorUpdateListener和AnimatorListener
AnimatorUpdateListener
public static interface AnimatorListener {
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
}
如上代码所示AnimatorListener监听了动画的开始、结束、取消和重复播放,同时系统提供了AnimatorListenerAdapter适配器方便我们使用,我们可以继承这个类并有选择的实现方法。
AnimatorListener
public static interface AnimatorUpdateListener {
/**
* <p>Notifies the occurrence of another frame of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationUpdate(ValueAnimator animation);
}
如上图所示,AnimatorUpdateListener 监听了动画的整个过程,动画每播放一帧,onAnimationUpdate就被调用一次,
下面就来看一下如何使用上面的属性动画的监听器来实现属性动画
mButton = (Button) findViewById(R.id.button);
performAnimate(mButton, mButton.getWidth(), 500);
private void performAnimate(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 animator) {
// 获得当前动画的进度值,整型,1-100之间
int currentValue = (Integer) animator.getAnimatedValue();
Log.d(TAG, "current value: " + currentValue);
// 获得当前进度占整个动画过程的比例,浮点型,0-1之间
float fraction = animator.getAnimatedFraction();
// 直接调用整型估值器通过比例计算出宽度,然后再设给Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
使用动画的注意事项
- OOM问题,此类问题主要出现在帧动画中,帧动画中如果使用大量图片,则可能会造成OOM问题,避免的方法同理,最好不要在帧动画中使用大量大尺寸的图片
- View动画的问题,View动画的作用主题实际上是View的影子,所以View动画不适合做有交互性点击的动画,另外View动画也不能真正改变View的状态,所以有的时候会出现动画结束后,View.setVisibily(VIEW.GONE)失效,或者其他异常情况,使用View.clearAnimation()清除动画即可
- 内存泄露,在属性动画中有一类无限循环的动画,这类动画要在Activity销毁之前及时停止,否则会造成Activity无法回收导致内存泄漏。
本篇总结
本章呢接着上一篇说了Android动画的另外一个大类属性动画,至此Android动画相关的文章完结,因笔者水平有限,所以有不当之处还请指出
下篇预告
下篇呢,开始Android并发编程系列
参考博文
此致,敬礼