一新蟆、簡介
如上圖所示:android動畫分類大致有兩種一種是
View動畫
一種是轉(zhuǎn)場動畫
织鲸。幀動畫:將圖片一張一張按順序播放千扔,展現(xiàn)出動畫效果砸烦。
補間動畫:實現(xiàn)動畫alpha(淡入淡出)冗尤,translate(位移)听盖,scale(縮放大小)裂七,rotate(旋轉(zhuǎn))等效果皆看,一般采用xml文件形式。
屬性動畫:(重點)它是對于對象屬性的動畫背零。補間動畫的內(nèi)容腰吟,都可以通過屬性動畫實現(xiàn)。
這里我們就不講幀動畫
跟補間動畫
,這兩個大家可以自己百度一下用法毛雇。(另外這篇文章中的動畫都是在代碼中實現(xiàn)的嫉称,如果要看xml的使用方法,可以看看Android 動畫使用 scale灵疮、alpha织阅、translate、rotate震捣、set
這篇其他人寫的這篇文章荔棉。
二、屬性動畫
基本使用 (ViewPropertyAnimator)
imag_view.animate().run {
translationX(400f) //設(shè)置左移
duration = 1000//設(shè)置動畫運行的時間
setInterpolator(LinearInterpolator()) //設(shè)置線性插值器
}
上面是最基本的使用蒿赢,api提供的有
移動
润樱、旋轉(zhuǎn)
、縮放
诉植、透明
祥国,看下面的api:上面的api中可以看到 都存在
絕對
跟相對
(方法后面-by
)的方法,其中絕對的方法以上面的代碼為例子晾腔,區(qū)別是:
translationX(400f)
代表將translationX
變成400
translationXBy(400f)
代表將translationX
增加400
這么多api就不做逐一展示了舌稀。
ObjectAnimator
1. 基本的使用方式:
- 用 ObjectAnimator.ofXXX() 創(chuàng)建 ObjectAnimator 對象;
- 添加時長灼擂、差值器等各種參數(shù)
- 用 start() 方法執(zhí)行動畫壁查。
ObjectAnimator.ofFloat(imag_view,View.ROTATION,0f,180f).run {
duration = 1000
interpolator = LinearInterpolator()
start()
}
上面是對一個系統(tǒng)提供的
View
進(jìn)行動畫展示,主要方法是ObjectAnimator.ofFloat(imag_view,View.ROTATION,0f,180f)
第一個參數(shù):傳入要進(jìn)行屬性動畫的
view
第二個參數(shù):要變化的屬性值剔应,這里是傳入
View.ROTATION
睡腿,也就是"rotation"
第三個參數(shù):屬性起始值
第四個參數(shù):屬性結(jié)束值
后面還可以加入多個參數(shù)值,從第三個參數(shù)開始到最后第n個參數(shù)峻贮,表示屬性開始 ->中間值->中間值.... ->結(jié)束值
注意一點:并非所有的屬性都是可以有
set
get
方法席怪,可以進(jìn)行屬性動畫。
2. 自定義View動畫
自定義View屬性動畫以及使用的步驟:
- 為要改變的屬性添加setter/getter方法
- setter方法中調(diào)用
invalidate()
使其重新繪畫- 在onDraw()中根據(jù)改變的屬性繪畫出你要的效果
- 使用的時候跟ObjectAnimator基本使用方式一致
大致的模板代碼
class CircleView : View {
//提供給外面改變的屬性值 這里使用kotlin語法 實際上它默認(rèn)已經(jīng)實現(xiàn)了setter/getter方法
// 但是這里個set方法我們要改寫一下 記得調(diào)用 invalidate()
var progress: Float = 0f
set(value) {
field = value
invalidate()
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//省略......
canvas!!.drawArc(rectF, 135f, progress * 2.7f, false, paint)
//省略......
canvas.drawText("${progress.toInt()}%", centerX, centerY+40, paint)
}
}
真正的實現(xiàn)與調(diào)用
class CircleView : View {
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
val rectF = RectF()
init {
paint.run {
textSize = dpToPixel(50f)
textAlign = Paint.Align.CENTER
}
}
var radius = dpToPixel(120f)
var progress: Float = 0f
set(value) {
field = value
invalidate()
}
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet)
@RequiresApi(Build.VERSION_CODES.M)
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
var centerX = width / 2f
var centerY = height / 2f
//畫弧形進(jìn)度條
paint.run {
color = context.getColor(R.color.colorAccent)
style = Paint.Style.STROKE
strokeCap = Paint.Cap.ROUND
strokeWidth = dpToPixel(20f)
}
rectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius)
canvas!!.drawArc(rectF, 135f, progress * 2.7f, false, paint)
//畫百分比的數(shù)值
paint.run {
color = Color.BLACK
style = Paint.Style.FILL
}
canvas.drawText("${progress.toInt()}%", centerX, centerY+40, paint)
}
}
//調(diào)用代碼
ObjectAnimator.ofFloat(circle_view,"progress",0f,80f).run {
duration = 2000
interpolator = OvershootInterpolator() //插值器 超過結(jié)束值后再回彈
repeatCount = INFINITE // 重復(fù)次數(shù)-無限循環(huán)
repeatMode = RESTART //重復(fù)模式-重新開始
start()
}
這里設(shè)置了
OvershootInterpolator
插值器纤控,使得它可以超過后回彈回來挂捻。這里可以使用PropertyValuesHolders.ofKeyframe()
做到類似效果,可以翻到下面對應(yīng)內(nèi)容看一下
ValueAnimator
這個是ObjectAnimator
的父類船万,這個我并沒怎么接觸刻撒,后期再補充吧
2. 設(shè)置監(jiān)聽器
-
設(shè)置
ObjectAnimator
的監(jiān)聽器
//添加停止的監(jiān)聽器
addPauseListener( object : Animator.AnimatorPauseListener{
override fun onAnimationPause(animation: Animator?) {
Log.i("MainActivity","addPauseListener -- onAnimationPause ------------------------------------")
}
override fun onAnimationResume(animation: Animator?) {
Log.i("MainActivity","addPauseListener -- onAnimationResume ------------------------------------")
}
})
//添加更新的監(jiān)聽器
addUpdateListener(object :ValueAnimator.AnimatorUpdateListener{
override fun onAnimationUpdate(animation: ValueAnimator?) {
// Log.i("MainActivity","addUpdateListener -- onAnimationUpdate ------------------------------------")
}
})
//添加監(jiān)聽器
addListener(object : Animator.AnimatorListener{
override fun onAnimationRepeat(animation: Animator?) {
Log.i("MainActivity","AnimatorListener -- onAnimationRepeat------------------------------------")
}
override fun onAnimationEnd(animation: Animator?) {
Log.i("MainActivity","AnimatorListener -- onAnimationEnd------------------------------------")
}
override fun onAnimationCancel(animation: Animator?) {
Log.i("MainActivity","AnimatorListener -- onAnimationCancel------------------------------------")
}
override fun onAnimationStart(animation: Animator?) {
Log.i("MainActivity","AnimatorListener -- onAnimationStart------------------------------------")
}
})
}
設(shè)置點擊事件監(jiān)聽
R.id.start_object_animator ->{
省略...
mObjectAnim.start()
Log.i("MainActivity","點擊開始狀態(tài) --${!mObjectAnim.isStarted}")
}
R.id.end_object_animator ->{
mObjectAnim.end()
Log.i("MainActivity","點擊結(jié)束狀態(tài) --${!mObjectAnim.isRunning}")
}
R.id.cancel_object_animator ->{
mObjectAnim.cancel()
Log.i("MainActivity","點擊取消狀態(tài) -- ")
}
R.id.pause_object_animator->{
if (mObjectAnim.isRunning) {
mObjectAnim.pause()
Log.i("MainActivity","點擊暫停狀態(tài) --${mObjectAnim.isPaused}")
}
}
R.id.reverse_object_animator->{
Log.i("MainActivity","點擊反向狀態(tài) --")
mObjectAnim.reverse()
}
R.id.resume_object_animator->{
mObjectAnim.resume()
Log.i("MainActivity","點擊繼續(xù)執(zhí)行 --")
}
點擊順序 (這里點擊開始按鈕是重新初始化動畫,請大家不要誤解)
開始start -> 暫停pause ->繼續(xù)執(zhí)行resume->結(jié)束end
開始start->取消cancel
開始start->結(jié)束end
MainActivity: AnimatorListener -- onAnimationStart------------------------------------
MainActivity: 點擊開始狀態(tài) --false
MainActivity: addPauseListener -- onAnimationPause ------------------------------------
MainActivity: 點擊暫停狀態(tài) --true
MainActivity: addPauseListener -- onAnimationResume ------------------------------------
MainActivity: 點擊繼續(xù)執(zhí)行 --
MainActivity: AnimatorListener -- onAnimationEnd------------------------------------
MainActivity: 點擊結(jié)束狀態(tài) --true
MainActivity: AnimatorListener -- onAnimationStart------------------------------------
MainActivity: 點擊開始狀態(tài) --false
MainActivity: AnimatorListener -- onAnimationCancel------------------------------------
MainActivity: AnimatorListener -- onAnimationEnd------------------------------------
MainActivity: 點擊取消狀態(tài) --
MainActivity: AnimatorListener -- onAnimationStart------------------------------------
MainActivity: 點擊開始狀態(tài) --false
MainActivity: AnimatorListener -- onAnimationEnd------------------------------------
MainActivity: 點擊結(jié)束狀態(tài) --true
上面是點擊產(chǎn)生的log
日志耿导,其中有個addUpdateListener
監(jiān)聽我沒有打印信息声怔,因為動畫運行中就會不停打印出來,所以就沒有打印出來了舱呻。
可以看到所有的狀態(tài)都是可以有回調(diào)方法監(jiān)聽的醋火。
這里有個方法要注意一下 cancel()
跟end(
)這個兩個方法。
如果動畫是已經(jīng)結(jié)束了
end()
的時候,就不會有回調(diào)onAnimationCancel
跟onAnimationEnd
兩個監(jiān)聽方法了,看一下ValueAnimator
源碼中有體現(xiàn)了
@Override
public void cancel() {
關(guān)鍵代碼1
if (mAnimationEndRequested) {
return;
}
if ((mStarted || mRunning) && mListeners != null) {
if (!mRunning) {
notifyStartListeners();
}
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
for (AnimatorListener listener : tmpListeners) {
關(guān)鍵代碼2
listener.onAnimationCancel(this);
}
}
關(guān)鍵代碼3
endAnimation();
}
private void endAnimation() {
關(guān)鍵代碼4
mAnimationEndRequested = true;
省略......
for (int i = 0; i < numListeners; ++i) {
關(guān)鍵代碼5
tmpListeners.get(i).onAnimationEnd(this, mReversing);
}
}
省略......
}
關(guān)鍵代碼1:mAnimationEndRequested == true
則不走下面的邏輯胎撇,設(shè)置true
是在關(guān)鍵代碼4
中設(shè)置的,也就是說當(dāng)動畫結(jié)束調(diào)用了endAnimation()
就不會調(diào)用到兩個回調(diào)onAnimationCancel
跟onAnimationEnd
介粘。
關(guān)鍵代碼2跟3跟5:就是動畫未結(jié)束,所以調(diào)用了回調(diào)onAnimationCancel()
再調(diào)用回調(diào)onAnimationEnd()
小結(jié):當(dāng)調(diào)用cancel()的時候晚树,動畫未結(jié)束時則回調(diào)
onAnimationCancel()
跟onAnimationEnd()
,當(dāng)動畫結(jié)束時雅采,則不會調(diào)用任何監(jiān)聽回調(diào)方法
end
方法有時候會回調(diào)兩個回調(diào) 分別是onAnimationStart
跟onAnimationEnd
,看一下ValueAnimator
源碼中的邏輯
public void end() {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
if (!mRunning) {
// Special case if the animation has not yet started; get it ready for ending
startAnimation();
mStarted = true;
} else if (!mInitialized) {
initAnimation();
}
animateValue(shouldPlayBackward(mRepeatCount, mReversing) ? 0f : 1f);
endAnimation();
}
從上面的邏輯可以看到爵憎,當(dāng)調(diào)用到!mRunning == true
的時候,會調(diào)用startAnimation()
導(dǎo)致回調(diào)多一個onAnimationStart
方法婚瓜。
小結(jié): 當(dāng)調(diào)用
end()
的時候宝鼓,如果動畫處于運行中,則回調(diào)onAnimationEnd
巴刻,如果不處于運行中愚铡,則回調(diào)onAnimationStart
跟onAnimationEnd
。
-
設(shè)置
ViewPropertyAnimator
的監(jiān)聽器
相比
ObjectAnimator
的監(jiān)聽器胡陪,這里的ViewPropertyAnimator
多了兩個回調(diào)方法
withStartAction
跟withEndAction
分別在開始跟結(jié)束調(diào)用沥寥,但是只會被調(diào)用一次。
setListener
中的onAnimationRepeat
回調(diào)不會被調(diào)用柠座,因為ViewPropertyAnimator
不支持重復(fù)
其它的回調(diào)跟ObjectAnimator
一致
三. 屬性動畫組合
-
ViewPropertyAnimator
改變尺寸同時改變透明度邑雅,下面這種寫法是幾種動畫一塊運行的
R.id.btn_viewProperty_mulity->{
if (isSelect) {
imag_view.animate().scaleX(1.5f).scaleY(1.5f).alpha(0f).duration =2000
}else{
imag_view.animate().scaleX(1.0f).scaleY(1.0f).alpha(1f).duration =2000
}
isSelect = !isSelect
}
-
PropertyValuesHolder
ObjectAnimator
使用時則是要通過PropertyValuesHolder
來實現(xiàn)
上面的代碼可以用
R.id.btn_viewProperty_mulity->{
if (isSelect) {
var propertyValueHolder1 = PropertyValuesHolder.ofFloat("scaleX",1f,1.5f)
var propertyValueHolder2 = PropertyValuesHolder.ofFloat("scaleY",1f,1.5f)
var propertyValueHolder3 = PropertyValuesHolder.ofFloat("alpha",1f,0f)
ObjectAnimator.ofPropertyValuesHolder(imag_view,propertyValueHolder1,propertyValueHolder2,propertyValueHolder3)
.setDuration(2000).start()
}else{
var propertyValueHolder1 = PropertyValuesHolder.ofFloat("scaleX",1.5f,1f)
var propertyValueHolder2 = PropertyValuesHolder.ofFloat("scaleY",1.5f,1f)
var propertyValueHolder3 = PropertyValuesHolder.ofFloat("alpha",0f,1f)
ObjectAnimator.ofPropertyValuesHolder(imag_view,propertyValueHolder1,propertyValueHolder2,propertyValueHolder3)
.setDuration(2000).start()
}
isSelect = !isSelect
}
-
PropertyValuesHolders.ofKeyframe()同一個屬性拆分
ofKeyframe
(關(guān)鍵幀),可以把同一個動畫屬性拆分成多個階段
var keyframe = Keyframe.ofFloat(0f,0f)//關(guān)鍵幀 0剛才開時的時候
var keyframe1 = Keyframe.ofFloat(0.5f,95f)//關(guān)鍵幀 0.5進(jìn)行到一半的時候
var keyFrame2 = Keyframe.ofFloat(1f,80f)//關(guān)鍵幀1最后的時候
var holder = PropertyValuesHolder.ofKeyframe("progress", keyframe, keyframe1, keyFrame2)
ObjectAnimator.ofPropertyValuesHolder(circle_view,holder).setDuration(3000).start()
實現(xiàn)類似
interpolator = OvershootInterpolator()
插值器超過結(jié)束值后再回彈的效果
-
AnimatorSet
也是組合動畫的妈经,可以讓多個動畫配合執(zhí)行淮野,不過它可以讓動畫先后有序執(zhí)行~~
playTogether
(同時執(zhí)行)、playSequentially
(順序執(zhí)行)
精確配置順序with()
,before()
,after()
示例代碼:
var animator1 = ObjectAnimator.ofFloat(imag_view,"alpha",0f,1f)
animator1.interpolator = AccelerateDecelerateInterpolator()
var animator3 = ObjectAnimator.ofFloat(imag_view,"scaleX",0f,1f)
var animator2 = ObjectAnimator.ofFloat(imag_view,"scaleY",0f,1f)
var animator4 = ObjectAnimator.ofFloat(imag_view,"translationX",0f,200f)
animator4.interpolator = LinearInterpolator()
AnimatorSet().apply {
playTogether(animator2,animator3)
playSequentially(animator1,animator4)
duration = 2000
start()
}
解釋一下上面的代碼
上面使用
AnimatorSet
將多個ObjectAnimator
放在一塊運行吹泡,并且playTogether(animator2,animator3)
代表一起運行playSequentially(animator1,animator4)
代表先后順序運行所以就有了下面的效果圖 (x軸骤星、y軸方向放大跟透明度由0-1)是一起的,然后在x軸方向移動爆哑。
上面的播放邏輯也可以使用精確配置順序with(),before(),after()來實現(xiàn)
AnimatorSet().apply {
play(animator1).with(animator2).with(animator3).before(animator4)
duration = 2000
start()
}
注意:每個傳入給
AnimatorSet
的animator
可以自己定義自己的動畫運行時間洞难、差值器等,但是如果在AnimatorSet
設(shè)置了運行時間的話則以在AnimatorSet
設(shè)置的為準(zhǔn)泪漂。