随着APP的开发周期演进,APP再也不知足基础的功能保障,须要有较好视觉体验和交互操做。那么动画效果是必不可少的,动画有帧动画,补间动画,属性动画等等。android
本文经过一些简单常见的动画效果,和你们重温属性动画的相关知识点。旨在经过全文,全面掌握属性动画~若是看完本文,还须要查阅其余文章,说明本文总结得还不够好,欢迎留言补充。算法
顾名思义,经过控制对象的属性,来实现动画效果。官方定义:定义一个随着时间 (注:停个顿)更改任何对象属性的动画,不管其是否绘制到屏幕上。数组
能够控制对象什么属性呢?什么属性均可以,理论是经过set和get某个属性来达到动画效果。例如经常使用下面一些属性来实现View对象的一些动画效果。bash
translationX
、translationY
、translationZ
alpha
,透明度全透明到不透明:0f
->1f
rotation
,旋转一圈:0f
->360f
scaleX
,垂直缩放scaleY
简单的效果图:app
简单介绍View对象几个属性动画的使用。less
效果图: ide
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<LinearLayout
android:id="@+id/llAddAccount"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_alignParentRight="true"
android:layout_marginTop="100dp"
android:layout_marginRight="-70dp"//将现有视图藏在屏幕的右边
android:background="@drawable/bg_10_10_fff">
<ImageView
android:id="@+id/ivMakeNote"
android:layout_width="35dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:paddingLeft="2dp"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:src="@mipmap/ic_account_add" />
<TextView
android:id="@+id/tvAddAccount"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:paddingRight="12dp"
android:text="添加帐户"
android:textColor="@color/colorPrimary"
android:textSize="14sp" />
</LinearLayout>
</RelativeLayout>
复制代码
上面只是简单实现了布局,下面看看属性动画代码的实现:函数
llAddAccount.setOnClickListener {
val objectAnimation =ObjectAnimator.ofFloat(llAddAccount, "translationX", 0f, -70f)
objectAnimation.start()
}
复制代码
到这里,咱们才真正看到属性动画的影子。经过ObjectAnimator
的工厂方法ofFloat
咱们获得一个ObjectAnimator
对象,并经过该对象的start()
方法,开启动画效果。布局
ofFloat()
方法的第一个参数为要实现动画效果的View,例如这里总体效果的LinearLayout
;第二个参数为属性名,也就是前面所说的:translationX
,translationY
,alpha
,rotation
,scaleX
,scaleY
等,这里要实现的是水平平移效果,因此咱们采用了translationX
;第三参数为可变长参数,第一个值为动画开始的位置,第二个值为结束值得位置,若是数组大于3位数,那么前者将是后者的起始位置。动画
注意事项:若是可变长参数只有一个值,那么ObjectAnimator
的工厂方法会将值做为动画结束值,此时属性必须拥有初始化值和getXXX
方法。
translationX
和translationY
这里涉及到的位移都是相对自身位置而言。例如 View
在点A(x,y)要移动到点B(x1,y1),那么ofFloat()
方法的可变长参数,第一个值应该0f
,第二个值应该x1-x
。
XML布局实现:
在res/animator文件夹下,建立animator_translation.xml文件,内容以下:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="translationX"
android:valueFrom="0dp"
android:valueTo="-70dp"
android:valueType="floatType"
/>
复制代码
在代码上调用:
llAddAccount.setOnClickListener {
val objectAnimation =AnimatorInflater.loadAnimator(this,R.animator.animator_translation)
objectAnimation.setTarget(llAddAccount)
objectAnimation.start()
}
复制代码
透明度属性动画比较简单,即控制View的可见度实现视觉差动画效果。这里展现效果是从不透明到透明,再到不透明。
tvText.setOnClickListener {
val objectAnimation =ObjectAnimator.ofFloat(tvText, "alpha", 1f,0f,1f)
objectAnimation.duration=3000
objectAnimation.start()
}
复制代码
ofFloat()
方法将属性名换成了透明度alpha
,而且可变长参数增长到了3个。给ObjectAnimator
对象的duration
属性设置了动画展现时间3秒,默认状况下300毫秒。
缩放能够经过控制scaleX
和scaleY
分别在X轴和Y轴上进行缩放,以下图在X轴中进行两次两倍缩放。
tvText.setOnClickListener {
val objectAnimation =ObjectAnimator.ofFloat(tvText, "scaleX", 1f,2f)
objectAnimation.duration=3000
objectAnimation.repeatCount=2
objectAnimation.repeatMode=ValueAnimator.REVERSE
objectAnimation.start()
}
复制代码
ofFloat()
方法传入参数属性为scaleX
和scaleY
时,动态参数表示缩放的倍数。设置ObjectAnimator
对象的repeatCount
属性来控制动画执行的次数,设置为ValueAnimator.INFINITE
表示无限循环播放动画;经过repeatMode
属性设置动画重复执行的效果,取值为:ValueAnimator.RESTART
和ValueAnimator.REVERSE
。
ValueAnimator.RESTART
效果:(即每次都重头开始)
ValueAnimator.REVERSE
效果:(即和上一次效果反着来)
旋转动画也比较简单,将一个View进行顺时针或逆时针旋转。
tvText.setOnClickListener {
val objectAnimation =
ObjectAnimator.ofFloat(tvText, "rotation", 0f,180f,0f)
objectAnimation.duration=3000
objectAnimation.start()
}
复制代码
ofFloat()
方法的可变长参数,若是后者的值大于前者,那么顺时针旋转,小于前者,则逆时针旋转。
若是想要一个动画结束后播放另一个动画,或者同时播放,能够经过AnimatorSet
来编排。
val aAnimator=ObjectAnimator.ofInt(1)
val bAnimator=ObjectAnimator.ofInt(1)
val cAnimator=ObjectAnimator.ofInt(1)
val dAnimator=ObjectAnimator.ofInt(1)
AnimatorSet().apply {
play(aAnimator).before(bAnimator)//a 在b以前播放
play(bAnimator).with(cAnimator)//b和c同时播放动画效果
play(dAnimator).after(cAnimator)//d 在c播放结束以后播放
start()
}
复制代码
或者
AnimatorSet().apply {
playSequentially(aAnimator,bAnimator,cAnimator,dAnimator) //顺序播放
start()
}
AnimatorSet().apply {
playTogether(animator,bAnimator,cAnimator,dAnimator) //同时播放
start()
}
复制代码
另有:
AnimatorSet ().apply {
play(aAnimator).after(1000) //1秒后播放a动画
start()
}
复制代码
若是只是针对View对象的特定属性同时播放动画,咱们也能够采用ViewPropertyAnimator
。
例如:
tvText.animate().translationX(100f).translationY(100f).start()
复制代码
支持属性:
注意到ViewPropertyAnimator
对象具备property(Float)
和propertyBy(Float)
方法,其中property(Float)
是指属性变化多少(能够理解一次有效),而propertyBy(Float)
每次变化多少(能够理解屡次有效)。
举例说明:
translationX
tvText.setOnClickListener {
val animator = tvText.animate()
animator.duration=1000
animator.translationX(100f)//点击一次会向右偏移,再点击没效果
animator.start()
}
复制代码
tvText.setOnClickListener {
val animator = tvText.animate()
animator.duration=1000
animator.translationXBy(100f)//每次点击都会向右偏移
animator.start()
}
复制代码
ValueAnimator
做为ObjectAnimator
的父类,主要动态计算目标对象属性的值,而后设置给对象属性,达到动画效果,而ObjectAnimator
则在ValueAnimator
的基础上极大地简化对目标对象的属性值的计算和添加效果,融合了 ValueAnimator 的计时引擎和值计算以及为目标对象的命名属性添加动画效果这一功能。
举个栗子,经过ValueAnimator
的工厂方法ofFloat
、ofInt
、ofArgb
、ofObject
来实现动画效果:
//ValueAnimator实现
tvText.setOnClickListener {
val valueAnimator = ValueAnimator.ofFloat(0f, 180f)
valueAnimator.addUpdateListener {
tvText.rotationY = it.animatedValue as Float //手动赋值
}
valueAnimator.start()
}
//ObjectAnimator实现
ObjectAnimator.ofFloat(tvText, "rotationY", 0f, 180f).apply { start() }
复制代码
从上面代码能够看出,使用ValueAnimator
实现动画,须要手动赋值给目标对象tvText
的rotationY
,而ObjectAnimator
则是自动赋值,不须要手动赋值就能够达到效果。
动画过程能够经过AnimatorUpdateListener
和AnimatorListener
来监听。
ObjectAnimator.ofFloat(tvText, "translationX", 0f, 780f, 0f).apply {
duration=3000//完成动画所须要时间
repeatCount=ValueAnimator.INFINITE //重复次数:无限循环
repeatMode=ValueAnimator.RESTART //重复模式:重头开始
addUpdateListener { //监听值变化
tvText.translationX= it.animatedValue as Float
}
addListener(object:Animator.AnimatorListener{
override fun onAnimationRepeat(animation: Animator?) {
//动画重复
}
override fun onAnimationEnd(animation: Animator?) {
//动画结束
}
override fun onAnimationCancel(animation: Animator?) {
//动画取消
}
override fun onAnimationStart(animation: Animator?) {
//动画开始
}
})
}
复制代码
动画可调用start()
方法开始,也可调用cancel()
方法取消。
那么,要正确使属性动画实现动画效果,那么目标对象应该注意什么?
set<PropertyName>()
形式的 setter
函数(采用驼峰式大小写形式),例如,若是属性名称为 text
,则须要使用 setText()
方法。ObjectAnimator
的一个工厂方法中仅为 values...
参数指定了一个值,那么该参数须要提供初始值和getPropertyName()
方法。UpdateListener
对象中调用invalidate()
方法,来刷新属性做用后的效果。本文一开始介绍位移属性动画时,有提到经过XML文件来实现动画效果,在这里进一步细讲。
在res/animator文件夹下,建立animator_translation.xml文件。XML文件有四个标签可用,要注意到propertyValuesHolder标签的Android 版本适配。
<?xml version="1.0" encoding="utf-8"?>
<set> =>AnimatorSet
<animator/> =>ValueAnimator
<objectAnimator> =>ObjectAnimator
<propertyValuesHolder/> =>PropertyValuesHolder
</objectAnimator>
</set>
复制代码
set
标签对应代码的AnimatorSet
,只有一个属性能够设置:android:ordering
,取值:同时播放together
、顺序播放sequentially
。
animator
标签对应代码的ValueAnimator
,能够设置以下属性:
android:duration
:动画时长android:valueType
:属性类型,intType
、floatType
、colorType
android:valueFrom
:属性初始值android:valueTo
:属性结束值android:repeatCount
:重复次数android:repeatMode
:重复模式android:interpolator
:插值器,可看下一节默认插值器。android:startOffset
:延迟,对应startOffset()
延迟多少毫秒执行示例:
<animator
android:duration="1000"
android:valueType="floatType"
android:valueFrom="0f"
android:valueTo="100f"
android:repeatCount="infinite"
android:repeatMode="restart"
android:interpolator="@android:interpolator/linear"
android:startOffset="100"
/>
复制代码
objectAnimator
属性对应代码ObjectAnimator
,因为继承自ValueAnimator
,因此属性相对多了` android:propertyName。
看到这里,不知道小伙伴们有没有这个疑问,属性动画是如何计算属性的值的?
这份工做由类型估值器TypeEvaluator
与时间插值器TimeInterpolator
来完成的。
插值器:根据时间流逝的百分比计算出当前属性值改变的百分比。
估值器:根据当前属性改变的百分比来计算改变后的属性值。
从它两的已定义,也能够看出它们之间的协调关系,先由插值器根据时间流逝的百分比计算出目标对象的属性改变的百分比,再由估值器根据插值器计算出来的属性改变的百分比计算出目标对象属性对应类型的值。
从估值器和插值器能够看出属性动画的工做原理,下面看看官方对工做原理的解析:
SDK中默认带有的估值器有: IntEvaluator
、FloatEvaluator
、ArgbEvaluator
,他们分别对应前面咱们调用 ValueAnimator
对象全部对应的ofInt
、ofFloat
、ofArgb
函数的估值器,分别用在Int类型,Float,颜色值类型之间计算。而ofObject
函数则对应咱们自定义类型的属性计算。
当估值器的类型不知足需求,就须要自定义类型估值器。例如咱们要实现下面效果:
data class Point(var x: Float, var y: Float)
复制代码
PointEvaluator
估值器,继承自TypeEvaluator
,泛型参数为Point
类型。经过实现evaluate()
方法,能够实现不少复制的动画效果,咱们这里实现上面简单算法。class PointEvaluator : TypeEvaluator<Point> {
/**
* 根据插值器计算出当前对象的属性的百分比fraction,估算去属性当前具体的值
* @param fraction 属性改变的百分比
* @param startValue 对象开始的位置,例如这里点坐标开始位置:屏幕左上角位置
* @param endValue 对象结束的位置,例如这里点坐标结束的位置:屏幕右下角位置
*/
override fun evaluate(fraction: Float, startValue: Point?, endValue: Point?): Point {
if (startValue == null || endValue == null) {
return Point(0f, 0f)
}
return Point(
fraction * (endValue.x - startValue.x),
fraction * (endValue.y - startValue.y)
)
}
}
复制代码
val animator= ValueAnimator.ofObject(
PointEvaluator(),
Point(0f, 0f),//动画开始属性值
Point(
ScreenUtils.getScreenWidth(this).toFloat(),
ScreenUtils.getScreenHeight(this).toFloat()
)//动画结束值
)
animator.addUpdateListener {//手动更新TextView的x和y 属性
val point = it.animatedValue as Point
tvText.x = point.x
tvText.y = point.y
logError("point:${point}")
}
animator.duration = 5000
btnStart.setOnClickListener {
animator.start()
}
复制代码
一个简单的自定义估值器就算完成了。数学学的好,任何复杂效果都不是问题。
TypeEvaluator
对象的evaluate()
方法的fraction
参数就是插值器计算得来,SDK中默认的时间插值器有:
看看效果:
LinearInterpolator
TimeInterpolator
接口的
getInterpolation()
自定义的。
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
复制代码
要控制动画速率的变化,就得去自定义插值器或估值器,对我这种数学渣渣来讲,简直比上天同样难的。
因此渣渣们能够考虑用关键帧Keyframe
对象来实现。Keyframe
让咱们能够指定某个属性百分比时对象的属性值。
tvText.setOnClickListener {
val start = Keyframe.ofFloat(0f, 0f)
val middle = Keyframe.ofFloat(0.3f, 400f)
val end = Keyframe.ofFloat(1f, 700f)
val holder=PropertyValuesHolder.ofKeyframe("translationX",start,middle,end)
ObjectAnimator.ofPropertyValuesHolder(tvText,holder).apply {
duration=2000
start()
}
}
复制代码
上面代码分别定义了三个关键帧,分别在属性百分比为0f
、0.3f
、1f
对应的translationX
的值。
动画效果:
Keyframe
一样支持ofFloat
、ofInt
、ofObject
。使用关键帧,至少须要有两个关键帧,否则坐等奔溃吧。PropertyValuesHolder
对象是用来保存动画过程所操做的属性和对应的值。
经过ObjectAnimator
的工厂方法能够快速实现一个属性动画,但默认的属性动画不知足本身的需求是,能够经过ValueAnimator
对象来定义本身的属性,注意属性的要求。能够经过AnimatorSet
来实现属性组合播放效果。
动画的原理是经过时间插值器与类型估值器配置使用,控制对象的属性来实现动画效果。