Android动画深度详解
前言 Android动画也是Android系统中一个很重要的模块, 在平时开发中, 为了做出炫酷的效果, 动画可以说是必不可少的; 本文将总结Android中与动画相关的部分, 文中部分内容整理自文末参考链接, 权作笔记~ 需要声明的是文章不会详细通过源码去讲解各种动画的实现细节, 因为相对来说, 动画的熟练使用更为重要, 所以本文只是提一下关键的动画源码部分 正文 一. 概述 Android中动画分为三大类:View动画,Transition(过渡动画), 属性动画; 下文也将从这三个方面进行总结和讲解 动画的本质实际上就是将作用对象的属性值在一段时间内缓慢的改变, 将每一个小的时间片段对应的属性值改变作用到对象并进行不断重绘, 造成肉眼看起来的的动画效果~ 二. View动画 2.1 基本使用总结 View动画分为四种, 如下表: 名称 标签 子类 效果 平移动画 <translate> TranslateAnimation 移动 缩放动画 <scale> ScaleAnimation 缩放 旋转动画 <rotate> RotateAnimation 旋转 透明度 <alpha> AlphaAnimation 透明度 注: 动画中还有一种叫帧动画, 这里也归为View动画中, 后文单独讲解 View动画可以使用xml描述, 也可以使用代码描述(即使用上表中的四个子类); 使用xml描述的语法格式如下: 注: 位置为res/anim/filename.xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@[package:]anim/interpolator_resource" android:shareInterpolator="[true | false]" android:fillAfter="[true | false]" android:duration="int" android:repeatMode="[reverse | restart]"> <alpha android:fromAlpha="float" android:toAlpha="float" /> <scale android:fromXScale="float" android:toXScale="float" android:fromYScale="float" android:toYScale="float" android:pivotX="float" android:pivotY="float"/> <translate android:fromXDelta="float" android:toXDelta="float" android:fromYDelta="float" android:toYDelta="float"/> <rotate android:fromDegrees="float" android:toDegrees="float" android:pivotX="float" android:pivotY="float"/> <set> ... </set> </set> 解释: <set>代表AnimationSet, 可以包含若干动画 android:shareInterpolator: 表示集合中的动画是否和集合共享同一个插值器; 如果集合不指定插值器, 那么子动画需要单独指定或者使用默认插值器 android:pivotX: 轴点x坐标 android:pivotY: 轴点y坐标 android:fillAfter: 动画结束之后,View是否停留在结束位置,true表示停留在结束位置,false表示不停留 在代码中使用View动画: Button button = findViewById(R.id.button); Animatoin animation = AnimationUtils.loadAnimation(this, R.anim.animation_item); button.startAnimation(animation); 2.2 自定义View动画 TranslateAnimation,ScaleAnimation,RotateAnimation, 和AlphaAnimation都继承自Animation; 如果要自定义View动画的话, 也需要继承Animation, 并重写Animation.initialize()和Animation.applyTransformation()方法;initialize()顾名思义就是进行一些初始化工作, 比如设置属性的初始值等,applyTransformation()就是根据时间的流失量来计算出当前时间片段所对应的属性值, 并设置到对应的作用对象(对于View动画来说该对象就是View); 这里的设置往往是通过Matrix来作用的, 如下以TranslateAnimation.applyTransformation()为例 @Override protected void applyTransformation(float interpolatedTime, Transformation t) { float dx = mFromXDelta; float dy = mFromYDelta; if (mFromXDelta != mToXDelta) { dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime); // 通过时间流失量计算出x的改变量 } if (mFromYDelta != mToYDelta) { dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime); // 通过时间流失量计算出y的改变量 } t.getMatrix().setTranslate(dx, dy); // 通过Matrix作用到对象 } 上述代码其实也可以当做自定义View动画的模板代码, 自定义View动画时, 可以使用Camera来配合计算改变量; 关于Camera和Matrix的使用, 可以参见博客 2.3 帧动画 帧动画就是顺序播放一组预先定义好的图片, 系统提供了AnimationDrawable来使用帧动画 xml定义如下: 注: 位置为/res/drawable/filename.xml <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/item" android:duration="100"/> <item android:drawable="@drawable/item" android:duration="100"/> <item android:drawable="@drawable/item" android:duration="100"/> <item android:drawable="@drawable/item" android:duration="100"/> </animation-list> 使用帧动画时, 当图片较多或者较大时可能引起OOM 三. Transition(过渡动画) 过渡动画其实是用于控制ViewGroup的Item的出场效果, 或者Activity之间的切换效果等 3.1 LayoutAnimation xml实现格式如下: 注: 位置为/res/anim/anim_layout.xml <?xml version="1.0" encoding="utf-8"?> <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" android:delay="0.5" android:animationOrder="[normal | reverse | random]" android:animation="@anim/anim_item"/> 解释: android:delay: 子元素延迟多少时间执行动画; 比如子元素入场动画时间周期为300ms, 那么0.5表示每个每个子元素都需要延迟150ms才能播放入场动画, 总体来说, 第一个子元素延迟150ms开始播放动画, 第二个子元素延迟300ms开始播放动画, 以此类推 android:animationOrder:normal表示顺序显示, 即排在前面的子元素先开始动画;reverse表示逆向显示;random表示随机显示 当定义好layoutAnimation之后, 在布局文件中就可以通过ViewGroup的属性android:layoutAnimation="@anim/anim_layout"来指定入场动画了~ 代码实现, 格式如下: 即通过LayoutAnimationController实现 ListView listView = findViewById(R.id.list); Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item); LayoutAnimationController controller = new LayoutAnimationController(animation); controller.setDelay(0.5f); controller.setOrder(LayoutAnimationController.ORDER_NORMAL); listView.setLayoutAnimation(controller); 3.2 Activity的切换效果 当启动一个Activity的时候, 可以按照如下方式为其添加自定义的切换效果: Intent intent = new Intent(this, AnimActivity.class); startActivity(intent); overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim); 当Activity退出时, 可以如下添加切换效果: @Override public void finish() { super.finish(); overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim); } 可以看出, 都是使用overridePendingTransition()来指定切换动画, 同时需要注意的是这个方法必须在startActivity(intent)或者finish()之后被调用才能生效 为Fragment添加切换动画: 可以通过FragmentTransaction.setCustomAnimations()来添加切换动画; 需要注意的是该动画必须是View动画, 不能用属性动画(因为属性动画在API 11引入,Fragment也是API 11才引入的) 四. 属性动画 属性动画是在Android 3.0(API 11)开始引入的; 属性动画可以用xml实现, 也可以用代码实现, 但是一般都是用代码实现 4.1 基本使用 4.1.1 ViewPropertyAnimator 操作View属性值, 可以通过View.animate()来获取一个ViewPropertyAnimator对象, 然后就可以通过ViewPropertyAnimator来操作该对象的属性值了~ 可以操作的属性值参见下图(图片来源, 参见文末参考链接): 使用示例如View.animate().setDuration(500).alpha(0.5); 4.1.2 ObjectAnimator 也是针对View的特定属性, 同时还要求该属性提供了对应的set和get方法, 基本使用如下; 因为ObjectAnimator是通过属性的set方法来不断改变属性值的, 所以set方法是一定需要的, 至于get方法只是用于获取动画开始的初始值的, 如果明确指定了初始值的话, 也可以提供get方法(如果没有提供get方法, 同时又没有指定初始值的话, 将Crash; 如果没有set方法, 不会Crash, 只是没有效果而已) ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0, 65); animator.start(); 如果一个属性没有set方法的话, 解决方法有以下三种: 如果有权限的话, 自己给对象加上set和get方法 用一个类来包装原始对象, 间接为其提供set和get方法 采用ValueAnimator, 监听动画过程, 自己实现属性改变 4.2 插值器 所谓插值器就是属性改变的速度, 系统提供了如下插值器: AccelerateDecelerateInterpolator LinearInterpolator AccelerateInterpolator DecelerateInterpolator AnticipateInterpolator OvershootInterpolator AnticipateOvershootInterpolator BounceInterpolator CycleInterpolator PathInterpolator FastOutLinearInInterpolator FastOutSlowInInterpolator LinearOutSlowInInterpolator 其中FastOutLinearInInterpolator,FastOutSlowInInterpolator,LinearOutSlowInInterpolator是Android 5.0(API 21)引入的三个新的Interpolator模型, 并把它们加入了support v4包中 关于各种插值器的讲解, 可以参见博客, 该博客讲的比较详细, 此处不再赘述~ 4.3 估值器 估值器即TypeEvaluator, 作用是根据当前属性改变的百分比来计算改变后的属性值, 用于协助插值器实现非线性运动; 系统提供了如下估值器: IntEvaluator IntArrayEvaluator FloatEvaluator FloatArrayEvaluator ArgbEvaluator PointFEvaluator RectEvaluator 如果要对其他类型做动画(非int,float,Color), 那么需要自定义类型估值算法, 即继承TypeEvaluator自己实现其evaluate()方法即可 估值器基本使用如下: ObjectAnimator anim = ObjectAnimator.ofObject(view, "alpha", new FloatEvaluator(), 0, 1); 4.4 监听器 ViewPropertyAnimator和ObjectAnimator设置监听器的方法如下表: ViewPropertyAnimator setListener() setUpdateListener() withStartAction() withEndAction() ObjectAnimator addListener() addUpdateListener() addPauseListener() 注: ViewPropertyAnimator可以通过setListener()和setUpdateListener()来设置监听器, 移除监听器可以通过setListener(null)和setUpdateListener(null)来移除;ViewPropertyAnimator独有的withStartAction()和withEndAction()方法, 可以设置一次性(动画结束后就自动弃掉了, 即一次有效)的动画开始或结束的监听 ObjectAnimator则是用addListener()和addUpdateListener()来添加一个或多个监听器, 移除监听器则是通过removeListener()和removeUpdateListener()来指定移除对象;ObjectAnimator支持使用pause()方法暂停, 所以它还多了一个addPauseListener()和removePauseListener()的支持 ViewPropertyAnimator.withStartAction/EndAction()是ViewPropertyAnimator的独有方法, 它们和set/addListener()中回调的onAnimationStart()和onAnimationEnd()相比起来的不同主要有两点: withStartAction()和withEndAction()是一次性的, 在动画执行结束后就自动弃掉了, 就算之后再重用ViewPropertyAnimator来做别的动画, 用它们设置的回调也不会再被调用; 而set/addListener()所设置的AnimatorListener是持续有效的, 当动画重复执行时, 回调总会被调用 withEndAction()设置的回调只有在动画正常结束时才会被调用, 而在动画被取消时不会被执行; 这点和AnimatorListener.onAnimationEnd()的行为是不一致的 监听器方法有: AnimatorListener onAnimationStart() onAnimationEnd() onAnimationCancel() onAnimationRepeat() 注: 即使动画通过cancle()方法取消,onAnimationEnd()也会被调用; 所以当动画被取消时, 如果设置了AnimatorListener, 那么onAnimationCancel()和onAnimationEnd()都会被调用;onAnimationCancel()会先于onAnimationEnd()被调用; 由于ViewPropertyAnimator不支持重复, 所以这个方法对ViewPropertyAnimator相当于无效 五. 动画注意事项 避免使用帧动画, 易造成OOM 动画需要考虑暂停和取消; 属性动画中有一类无线循环的动画, 这类动画在Activity退出时要及时停止, 否则将导致Activity无法释放从而造成内存泄露; 通过验证后发现View动画无此问题 使用View动画之后可能会出现View无法隐藏的现象, 即setVisibility(View.GONE)失效了, 此时可以使用view.clearAnimation()来清除View动画即可 Android 3.0以前系统, 不管是View动画还是属性动画, 都只是作用于View内容(因为Android 3.0以前, 属性动画底层其实也是通过View动画实现的), 新位置无法触发单击事件; 从3.0开始, 属性动画的单击事件触发位置为移动后的位置, 但是View动画仍然在原位置 END 现在加Android高级开发群;701740775,可免费领取一份最新Android高级架构技术体系大 纲和进阶视频资料,以及这些年年积累整理的所有面试资源笔记。加群请备注csdn领取xx 资料