如上圖所示:android動畫分類大體有兩種一種是
View動畫
一種是轉場動畫
。android幀動畫:將圖片一張一張按順序播放,展示出動畫效果。canvas
補間動畫:實現動畫alpha(淡入淡出),translate(位移),scale(縮放大小),rotate(旋轉)等效果,通常採用xml文件形式。api
屬性動畫:(重點)它是對於對象屬性的動畫。補間動畫的內容,均可以經過屬性動畫實現。bash
這裏咱們就不講幀動畫
跟補間動畫
,這兩個你們能夠本身百度一下用法。(另外這篇文章中的動畫都是在代碼中實現的,若是要看xml的使用方法,能夠看看Android 動畫使用 scale、alpha、translate、rotate、set 這篇其餘人寫的這篇文章。app
imag_view.animate().run {
translationX(400f) //設置左移
duration = 1000//設置動畫運行的時間
setInterpolator(LinearInterpolator()) //設置線性插值器
}
複製代碼
上面是最基本的使用,api提供的有
移動
、
旋轉
、
縮放
、
透明
,看下面的api:
上面的api中能夠看到 都存在
絕對
跟相對
(方法後面-by
)的方法,其中絕對的方法以上面的代碼爲例子,區別是:translationX(400f)
表明將translationX
變成400translationXBy(400f)
表明將translationX
增長400ide
這麼多api就不作逐一展現了。oop
- 用 ObjectAnimator.ofXXX() 建立 ObjectAnimator 對象;
- 添加時長、差值器等各類參數
- 用 start() 方法執行動畫。
ObjectAnimator.ofFloat(imag_view,View.ROTATION,0f,180f).run {
duration = 1000
interpolator = LinearInterpolator()
start()
}
複製代碼
上面是對一個系統提供的
View
進行動畫展現,主要方法是
ObjectAnimator.ofFloat(imag_view,View.ROTATION,0f,180f)
第一個參數:傳入要進行屬性動畫的
view
第二個參數:要變化的屬性值,這裏是傳入
View.ROTATION
,也就是
"rotation"
第三個參數:屬性起始值 第四個參數:屬性結束值
後面還能夠加入多個參數值,從第三個參數開始到最後第n個參數,表示屬性開始 ->中間值->中間值.... ->結束值
注意一點:並不是全部的屬性都是能夠有
set
get
方法,能夠進行屬性動畫。
自定義View屬性動畫以及使用的步驟:動畫
- 爲要改變的屬性添加setter/getter方法
- setter方法中調用
invalidate()
使其從新繪畫- 在onDraw()中根據改變的屬性繪畫出你要的效果
- 使用的時候跟ObjectAnimator基本使用方式一致
大體的模板代碼ui
class CircleView : View {
//提供給外面改變的屬性值 這裏使用kotlin語法 實際上它默認已經實現了setter/getter方法
// 可是這裏個set方法咱們要改寫一下 記得調用 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)
}
}
複製代碼
真正的實現與調用this
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
//畫弧形進度條
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)
//畫百分比的數值
paint.run {
color = Color.BLACK
style = Paint.Style.FILL
}
canvas.drawText("${progress.toInt()}%", centerX, centerY+40, paint)
}
}
複製代碼
//調用代碼
ObjectAnimator.ofFloat(circle_view,"progress",0f,80f).run {
duration = 2000
interpolator = OvershootInterpolator() //插值器 超過結束值後再回彈
repeatCount = INFINITE // 重複次數-無限循環
repeatMode = RESTART //重複模式-從新開始
start()
}
複製代碼
這裏設置了
OvershootInterpolator
插值器,使得它能夠超事後回彈回來。這裏可使用PropertyValuesHolders.ofKeyframe()
作到相似效果,能夠翻到下面對應內容看一下 #####ValueAnimator 這個是ObjectAnimator
的父類,這個我並沒怎麼接觸,後期再補充吧
ObjectAnimator
的監聽器//添加中止的監聽器
addPauseListener( object : Animator.AnimatorPauseListener{
override fun onAnimationPause(animation: Animator?) {
Log.i("MainActivity","addPauseListener -- onAnimationPause ------------------------------------")
}
override fun onAnimationResume(animation: Animator?) {
Log.i("MainActivity","addPauseListener -- onAnimationResume ------------------------------------")
}
})
//添加更新的監聽器
addUpdateListener(object :ValueAnimator.AnimatorUpdateListener{
override fun onAnimationUpdate(animation: ValueAnimator?) {
// Log.i("MainActivity","addUpdateListener -- onAnimationUpdate ------------------------------------")
}
})
//添加監聽器
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------------------------------------")
}
})
}
複製代碼
設置點擊事件監聽
R.id.start_object_animator ->{
省略...
mObjectAnim.start()
Log.i("MainActivity","點擊開始狀態 --${!mObjectAnim.isStarted}")
}
R.id.end_object_animator ->{
mObjectAnim.end()
Log.i("MainActivity","點擊結束狀態 --${!mObjectAnim.isRunning}")
}
R.id.cancel_object_animator ->{
mObjectAnim.cancel()
Log.i("MainActivity","點擊取消狀態 -- ")
}
R.id.pause_object_animator->{
if (mObjectAnim.isRunning) {
mObjectAnim.pause()
Log.i("MainActivity","點擊暫停狀態 --${mObjectAnim.isPaused}")
}
}
R.id.reverse_object_animator->{
Log.i("MainActivity","點擊反向狀態 --")
mObjectAnim.reverse()
}
R.id.resume_object_animator->{
mObjectAnim.resume()
Log.i("MainActivity","點擊繼續執行 --")
}
複製代碼
點擊順序 (這裏點擊開始按鈕是從新初始化動畫,請你們不要誤解) 開始start -> 暫停pause ->繼續執行resume->結束end 開始start->取消cancel 開始start->結束end
MainActivity: AnimatorListener -- onAnimationStart------------------------------------
MainActivity: 點擊開始狀態 --false
MainActivity: addPauseListener -- onAnimationPause ------------------------------------
MainActivity: 點擊暫停狀態 --true
MainActivity: addPauseListener -- onAnimationResume ------------------------------------
MainActivity: 點擊繼續執行 --
MainActivity: AnimatorListener -- onAnimationEnd------------------------------------
MainActivity: 點擊結束狀態 --true
MainActivity: AnimatorListener -- onAnimationStart------------------------------------
MainActivity: 點擊開始狀態 --false
MainActivity: AnimatorListener -- onAnimationCancel------------------------------------
MainActivity: AnimatorListener -- onAnimationEnd------------------------------------
MainActivity: 點擊取消狀態 --
MainActivity: AnimatorListener -- onAnimationStart------------------------------------
MainActivity: 點擊開始狀態 --false
MainActivity: AnimatorListener -- onAnimationEnd------------------------------------
MainActivity: 點擊結束狀態 --true
複製代碼
上面是點擊產生的log
日誌,其中有個addUpdateListener
監聽我沒有打印信息,由於動畫運行中就會不停打印出來,因此就沒有打印出來了。 能夠看到全部的狀態都是能夠有回調方法監聽的。
這裏有個方法要注意一下 cancel()
跟end(
)這個兩個方法。
若是動畫是已經結束了
end()
的時候,就不會有回調onAnimationCancel
跟onAnimationEnd
兩個監聽方法了,看一下ValueAnimator
源碼中有體現了
@Override
public void cancel() {
關鍵代碼1
if (mAnimationEndRequested) {
return;
}
if ((mStarted || mRunning) && mListeners != null) {
if (!mRunning) {
notifyStartListeners();
}
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
for (AnimatorListener listener : tmpListeners) {
關鍵代碼2
listener.onAnimationCancel(this);
}
}
關鍵代碼3
endAnimation();
}
private void endAnimation() {
關鍵代碼4
mAnimationEndRequested = true;
省略......
for (int i = 0; i < numListeners; ++i) {
關鍵代碼5
tmpListeners.get(i).onAnimationEnd(this, mReversing);
}
}
省略......
}
複製代碼
關鍵代碼1:mAnimationEndRequested == true
則不走下面的邏輯,設置true
是在關鍵代碼4
中設置的,也就是說當動畫結束調用了endAnimation()
就不會調用到兩個回調onAnimationCancel
跟onAnimationEnd
。 關鍵代碼2跟3跟5:就是動畫未結束,因此調用了回調onAnimationCancel()
再調用回調onAnimationEnd()
小結:當調用cancel()的時候,動畫未結束時則回調
onAnimationCancel()
跟onAnimationEnd()
,當動畫結束時,則不會調用任何監聽回調方法
end
方法有時候會回調兩個回調 分別是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();
}
複製代碼
從上面的邏輯能夠看到,當調用到!mRunning == true
的時候,會調用startAnimation()
致使回調多一個onAnimationStart
方法。
小結: 當調用
end()
的時候,若是動畫處於運行中,則回調onAnimationEnd
,若是不處於運行中,則回調onAnimationStart
跟onAnimationEnd
。
ViewPropertyAnimator
的監聽器ObjectAnimator
的監聽器,這裏的
ViewPropertyAnimator
多了兩個回調方法
withStartAction
跟withEndAction
分別在開始跟結束調用,可是隻會被調用一次。setListener
中的onAnimationRepeat
回調不會被調用,由於ViewPropertyAnimator
不支持重複 其它的回調跟ObjectAnimator
一致
改變尺寸同時改變透明度,下面這種寫法是幾種動畫一塊運行的
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
}
複製代碼
ObjectAnimator
使用時則是要經過PropertyValuesHolder
來實現 上面的代碼能夠用
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
}
複製代碼
ofKeyframe
(關鍵幀),能夠把同一個動畫屬性拆分紅多個階段
var keyframe = Keyframe.ofFloat(0f,0f)//關鍵幀 0剛纔開時的時候
var keyframe1 = Keyframe.ofFloat(0.5f,95f)//關鍵幀 0.5進行到一半的時候
var keyFrame2 = Keyframe.ofFloat(1f,80f)//關鍵幀1最後的時候
var holder = PropertyValuesHolder.ofKeyframe("progress", keyframe, keyframe1, keyFrame2)
ObjectAnimator.ofPropertyValuesHolder(circle_view,holder).setDuration(3000).start()
複製代碼
實現相似
interpolator = OvershootInterpolator()
插值器超過結束值後再回彈的效果
也是組合動畫的,可讓多個動畫配合執行,不過它可讓動畫前後有序執行~~ playTogether
(同時執行)、playSequentially
(順序執行) 精確配置順序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()來實現
AnimatorSet().apply {
play(animator1).with(animator2).with(animator3).before(animator4)
duration = 2000
start()
}
複製代碼
注意:每一個傳入給
AnimatorSet
的animator
能夠本身定義本身的動畫運行時間、差值器等,可是若是在AnimatorSet
設置了運行時間的話則以在AnimatorSet
設置的爲準。