在 Android 中,基本的动画共有三种类型:android
android.view.animation
包下面的一些类,只能被用来设置给 View,缺点是好比当控件移动以后,接收点击的控件的位置不会跟随移动,而且可以实现的效果只有移动、缩放、旋转和淡入淡出操做四种及其组合。android.animation
包下面的一些类,只对 API 11
以上版本的Android 系统才有效,但咱们能够经过兼容库作低版本兼容。这种动画能够设置给任何 Object,包括那些尚未渲染到屏幕上的对象。这种动画是可扩展的,可让你自定义任何类型和属性的动画。文末有免费福利哦git
在这里,咱们先对 Drawable 动画进行讲解,由于它相对于后面的两种动画比较简单。在示例程序咱们准备了一系列图片资源,并在 drawable 文件夹下面定义了动画资源 record_anim.xml:github
<?xml version="1.0" encoding="utf-8"?> <animation-list android:oneshot="false" xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/record0" android:duration="500"/> <item android:drawable="@drawable/record1" android:duration="500"/> <item android:drawable="@drawable/record2" android:duration="450"/> <item android:drawable="@drawable/record3" android:duration="400"/> <item android:drawable="@drawable/record4" android:duration="350"/> <item android:drawable="@drawable/record5" android:duration="400"/> <item android:drawable="@drawable/record6" android:duration="400"/> </animation-list> 复制代码
而后,咱们在代码中使用该资源,并将其赋值给 ImageView。而后,咱们从该控件中获取该 Drawable 并将其转换成 AnimationDrawable,随后咱们调用它的 start()
方法就开启了 Drawable 动画:面试
getBinding().ivRecord.setImageResource(R.drawable.record_anim); animDraw = (AnimationDrawable) getBinding().ivRecord.getDrawable(); animDraw.start(); 复制代码
此外,咱们能够调用该 Drawable 的 stop()
方法中止动画。小程序
使用帧动画的时候要注意设置的图片不宜过多、过大,以防止由于内存不够而出现 OOM。性能优化
该动画的资源处在 android.view.animation
包下,主要有如下几个类,它们都继承自 Animation
,咱们可使用它们来实现复杂的动画。这些动画类分别有对应的 xml 标签,因此,咱们能够在 xml 中定义动画,也能够在代码中实现动画效果。这里的 AnimationSet
能够用来将多个动画效果进行组合,各预约义动画的对照能够参考下面这张图表:架构
固然,要实现一种动画效果会有许多属性须要指定,在 xml 中,咱们用标签的属性指定,在代码中咱们用对象的 setter 方法指定。因而,咱们能够获得下面这个对应关系:app
上面的对应关系是全部的 View 动画共用的,对各个具体的动画类型还有其独有的属性。你能够在各个动画的构造方法中,经过它们从 AttributeSet
中获取了哪些字段来了解它们都定义了哪些属性,这里咱们不对其一一进行说明。各预约义的属性动画分别按照不一样的方式实现了 Animation
的 applyTransformation
方法,具体的这些属性如何使用以及 View 动画的效果是如何实现的,均可经过阅读该方法的定义得知。框架
对于 AnimationSet
,它内部维护了一个 Animation
列表,而且其自己也是一个 Animation
,因此,AnimationSet
内部能够添加子 AnimationSet
。ide
文末有免费福利哦
上文中咱们提到过,View 动画的具体实现是经过覆写 Animation
的 applyTransformation
方法来完成的。这里咱们以 AlphaAnimation
为例来看它是如何做用的,同时你应该注意插值器的做用原理。该方法会在 Animation
中被循环调用,调用的时候会根据插值器计算出一个时间,并将其传递到 applyTransformation
方法中。
Animation
的 getTransformation
方法片断:
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { if (!mStarted) { fireAnimationStart(); mStarted = true; if (NoImagePreloadHolder.USE_CLOSEGUARD) { guard.open("cancel or detach or getTransformation"); } } if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); if (mCycleFlip) { normalizedTime = 1.0f - normalizedTime; } final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); applyTransformation(interpolatedTime, outTransformation); } 复制代码
AlphaAnimation
的 applyTransformation
方法:
protected void applyTransformation(float interpolatedTime, Transformation t) { final float alpha = mFromAlpha; t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime)); } 复制代码
显然,这里的 interpolatedTime
的是一个比例。好比,假如一个透明动画须要持续 10s
,透明度须要从 0.5f
到 1.0f
,而插值的规则是一个二次函数。那么第 t (0<t<10)
秒的时候控件的透明度应该是:
alpha = 0.5f + (1.0f - 0.5f) * t^2 / 100 复制代码
以上就是插值器的做用原理,你也能够按照本身的需求实现本身的插值器,从而实现期待的动画效果。
做为一个例子,这里咱们实现一个让控件抖动的动画。在 anim 文件夹下面,咱们定义一个平移的动画,并使用插值器使其重复:
anim/shake.xml
的定义:
<?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="700" android:fromXDelta="0.0" android:interpolator="@anim/cycle_7" android:toXDelta="15.0" /> 复制代码
插值器 anim/cicle_7.xml
的定义:
<?xml version="1.0" encoding="utf-8"?> <cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android" android:cycles="4.0" /> 复制代码
而后,咱们在代码中加载 Animation
并调用控件的 startAnimation()
方法开启动画:
getBinding().v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.shake)); 复制代码
对于 View
,咱们有 startAnimation()
用来对 View
开始动画;有 clearAnimation()
用来取消 View
在执行的动画。
不使用 xml,仅使用代码咱们同样能够实现上述的效果,这里咱们再也不进行说明。
LayoutAnimation
做用于 ViewGroup
,可使其子元素出场时都均有某种动画效果,一般用于 ListView
。咱们能够像下面这样定义布局动画:
<?xml version="1.0" encoding="utf-8"?> <layoutAnimation android:delay="500" android:animation="@anim/shake" xmlns:android="http://schemas.android.com/apk/res/android" /> 复制代码
显然,这里咱们须要引用一个其余的动画。而后,咱们能够在 ListView
的 layoutAnimation
属性中指定布局动画。或者调用 ListView
的 setLayoutAnimation()
方法应用上述动画。
咱们能够经过在 Activity
中调用 overridePendingTransition(R.anim.shake, R.anim.shake);
方法来重写 Activity
的切换动画。注意这个方法应该在 startActivity(Intent)
或者 finish()
以后当即调用。
咱们能够对比 View 动画来学习属性动画。
android.animation
包下面的一些类。Animator
;属性动画也为咱们提供了几个预约义的类:AnimatorSet
, ObjectAnimator
, TimeAnimator
和 ValueAnimator
;这几个预约义类之间的继承关系是,AnimatorSet
和 ValueAnimator
直接继承自 Animator
,而 ObjectAnimator
和 TimeAnimator
继承自 ValueAnimator
。setter
方法。xml
和代码两种定义方式,它的 xml
一般定义在 animator
文件夹下面,而 View 动画定义在 anim
文件夹下面。AnimationUtils
的方法用来从布局文件夹中加载属性动画:AnimatorInflater
类的 loadAnimator()
方法。TimeInterpolator
,而且也提供了几个预约义的插值器。View
的方法来使用属性动画,咱们能够经过 View 的 animate()
方法获取一个 ViewPropertyAnimator
,而后调用 ViewPropertyAnimator
的其余方法进行链式调用以实现复杂的属性动画效果。下面是属性动画的代码实现和 xml
实现两种方式的对比:
上文中,咱们总结了属性动画的一些知识,并将其与 View 动画进行了对比。这里是一个简单的梳理,在下文中咱们会对属性动画进行更加详细的介绍。
上面说过 ValueAnimator
是 ObjectAnimator
和 TimeAnimator
的基类,咱们能够这样使用它:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setDuration(300); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float currentValue = (float) animation.getAnimatedValue(); Log.d("TAG", "cuurent value is " + currentValue); } }); anim.start(); 复制代码
这里咱们使用 log
输出了值渐变的过程,从日志中能够看出它的效果是值从 0
不断递增直到 1
。若是咱们在这个监听方法中根据值修改控件的属性同样能够实现动画效果。除了 ofFloat()
还有 ofInt()
等方法,它们的效果类似。
上面,若是咱们想要实现动画效果,须要在 ValueAnimator
的监听事件中修改对象的属性,这里的 ObjectAnimator
,咱们只须要传入对象实例和属性的字符串名称,修改对象属性的操做就能够自动完成。好比下面的程序的效果是控件 textview
的透明度会在 5s
以内从 1 变成 0 再变回 1.
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f); animator.setDuration(5000); animator.start(); 复制代码
注意这里咱们传入的是 alpha
,这个字段自己并不存在于控件中,而是有一个 setAlpha()
的方法。也就是说,ObjectAnimator
做用的原理是经过反射触发 setter
方法而不是修改属性来实现的。你能够在类 PropertyValuesHolder
中更详细地了解这方面的内容。
PropertyValuesHolder
包装了咱们要修改的属性的对象和方法等信息,而后会使用反射触发指定对象的方法来完成对对象属性的修改。其中
void setupSetter(Class targetClass) { Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType(); mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType); } 复制代码
会去寻找咱们要修改属性的 setter
方法,而后
void setAnimatedValue(Object target) { if (mProperty != null) { mProperty.set(target, getAnimatedValue()); } if (mSetter != null) { try { mTmpValueArray[0] = getAnimatedValue(); mSetter.invoke(target, mTmpValueArray); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } } 复制代码
会去触发 setter
方法,以修改对象的属性。
文末有免费福利哦
AnimatorSet
内部提供了一个构建者 AnimatorSet.Builder
来帮助咱们构建组合动画,AnimatorSet.Builder
提供了下面四种方法:
after(Animator anim)
:将现有动画插入到传入的动画以后执行after(long delay)
:将现有动画延迟指定毫秒后执行before(Animator anim)
:将现有动画插入到传入的动画以前执行with(Animator anim)
:将现有动画和传入的动画同时执行当咱们调用 AnimatorSet
的 play()
方法的时候就能获取一个 AnimatorSet.Builder
实例,而后咱们就可使用构建者的方法进行链式调用了:
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(); 复制代码
正如前文所述,属性动画也有本身的插值器,咱们能够经过插值函数指定在某个时间段内属性改变的速率。插值函数获得的是一个比例,是没有意义的。在 View 动画的 AlphaAnimation
中,若是咱们指定了起止的透明度,那么咱们能够经过透明度的计算规则获得某个时刻的透明度。可是对于属性动画,由于它能够应用于任何属性,这个属性又多是任何类型的,那么这个属性将采用什么样的计算规则呢?这就须要咱们使用 TypeEvaluator
来指定一个计算规则。也就是说,TypeEvaluator
是属性动画的属性的计算规则。
下面是 TypeEvaluator
的定义,这里的三个参数的含义分别是,fraction
是当前的比例,能够经过插值器计算获得;startValue
和 endValue
分别是属性变化的起止值。它的返回结果就是在某个时刻某个属性的值。
public interface TypeEvaluator<T> { public T evaluate(float fraction, T startValue, T endValue); } 复制代码
属性动画中已经为咱们提供了几个预约义的 TypeEvaluator
,好比 FloatEvaluator
:
public class FloatEvaluator implements TypeEvaluator<Number> { public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); } } 复制代码
在属性动画的 PropertyValuesHolder
中会根据属性的类型选择预约义的 TypeEvaluator
。可是若是咱们的属性的类型不在预约义的范围以内就须要本身实现一个 TypeEvaluator
。下面咱们以日期类型为例来实现一个 TypeEvaluator
。
当咱们使用 ValueAnimator
的 ofObject()
方法获取 ValueAnimator
实例的时候,要求咱们传入一个 TypeEvaluator
,因而咱们能够像下面这样定义:
private static class DateEvaluator implements TypeEvaluator<Date> { @Override public Date evaluate(float fraction, Date startValue, Date endValue) { long startTime = startValue.getTime(); return new Date((long) (startTime + fraction * (endValue.getTime() - startTime))); } } 复制代码
而后,咱们能够这样使用它:
ValueAnimator animator = ValueAnimator.ofObject(new DateEvaluator(), new Date(0), new Date()); animator.setDuration(5000); animator.addUpdateListener(animation -> { Date date = (Date) animation.getAnimatedValue(); LogUtils.d(date); }); animator.start(); 复制代码
这样就能够获得在 5s 以内输出的从时间戳为0,到当前时刻的全部的日期变化。
就像 View 动画同样,咱们能够为属性动画指定一个插值器。插值器的做用是用来设置指定时间段内数值的变化的速率。在属性动画中,插值器是 TimeInterpolator
,一样也有几个默认的实现:
AccelerateDecelerateInterolator
:先加速后减速。AccelerateInterpolator
:加速。DecelerateInterpolator
:减速。AnticipateInterpolator
:先向相反方向改变一段再加速播放。AnticipateOvershootInterpolator
:先向相反方向改变,再加速播放,会超出目标值而后缓慢移动至目标值,相似于弹簧回弹。BounceInterpolator
:快到目标值时值会跳跃。CycleIinterpolator
:动画循环必定次数,值的改变为一正弦函数:Math.sin(2 * mCycles * Math.PI * input)。LinearInterpolator
:线性均匀改变。OvershottInterpolator
:最后超出目标值而后缓慢改变到目标值。咱们能够像下面这样定义一个属性动画,
<set android:ordering=["together" | "sequentially"]> <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=["repeat" | "reverse"] android:valueType=["intType" | "floatType"]/> <animator android:duration="int" android:valueFrom="float | int | color" android:valueTo="float | int | color" android:startOffset="int" android:repeatCount="int" android:repeatMode=["repeat" | "reverse"] android:valueType=["intType" | "floatType"]/> <set> ... </set> </set> 复制代码
这里的android:ordering
用于控制子动画启动方式是前后有序的仍是同时进行,两个可选参数: sequentially
表示动画按照前后顺序;together
(默认)表示动画同时启动。
这里的 <objectAnimator>
标签的含义以下:
这样在 XML 中定义了属性动画以后,咱们能够在代码中经过工具类获取到动画实例并使用:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.animtor.property_animator); set.setTarget(myObject); set.start(); 复制代码
View
动画并无真正改变 View
的属性,即 View
动画执行以后并未改变 View
的真实布局属性值。譬如咱们在布局中有一个 Button
在屏幕上方,咱们设置了平移动画移动到屏幕下方而后保持动画最后执行状态呆在屏幕下方,这时若是点击屏幕下方动画执行以后的 Button
是没有任何反应的,而点击原来屏幕上方没有 Button
的地方却响应的是点击 Button
的事件。px
在不一样设备上面的兼容问题,使用动画的时候尽可能使用 dp
做为单位。你能够在Github获取以上程序的源代码: Android-references。
想学习更多Android知识,请加入Android技术开发企鹅交流 7520 16839
进群与大牛们一块儿讨论,还可获取Android高级架构资料、源码、笔记、视频
包括 高级UI、Gradle、RxJava、小程序、Hybrid、移动架构、React Native、性能优化等全面的Android高级实践技术讲解性能优化架构思惟导图,和BATJ面试题及答案!
群里免费分享给有须要的朋友,但愿可以帮助一些在这个行业发展迷茫的,或者想系统深刻提高以及困于瓶颈的朋友,在网上博客论坛等地方少花些时间找资料,把有限的时间,真正花在学习上,因此我在这免费分享一些架构资料及给你们。但愿在这些资料中都有你须要的内容。