隨着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
來實現屬性組合播放效果。
動畫的原理是經過時間插值器與類型估值器配置使用,控制對象的屬性來實現動畫效果。