android覆盖扩散动画,[Android]多层波纹扩散动画——自定义View绘制
之前整理過一些屬性動畫的基本操作,這一段時間的動畫相關需求都安然度過了。直到這次……
一、另一種動畫需求
多數交互中的動畫都是讓單個頁面元素動起來,這種就很適合用屬性動畫實現。但是對于 多個元素、非頁面內元素 的動畫需求,就不方便用View+屬性動畫實現了。
舉個例子,也就是這次做的:
波紋效果需要同時繪制 多個 同心圓,而且這些圓 不是頁面內的元素,未觸發之前不需要顯示。如果用屬性動畫實現,至少需要在xml布局文件中添加多個ImageView畫圓,效率低還不易復用。
二、WaveView分析
使用自定義View繪制動畫的主要思路是這樣的:
分解動畫成幀,考慮如何在onDraw中繪制每一幀
提取出繪制所需參數,分為隨時間變化和不可變兩種,不可變參數可以暴露出去(setter方法/attribute設置)
總結隨時間變化的參數變化規律,實現時間軸
按需求提供出播放,暫停,停止,重置等等方法
按照這個思路,一步步實現一下WaveView吧。
第一步:
在onDraw中畫圓需要用到canvas.drawCircle方法,四個參數:圓心x、y坐標,半徑和Paint。WaveView繪制的每一幀都是圓,區別是圓的半徑,數量,透明度。還需要設置圓的最小半徑和最大半徑,以及擴散的時候兩個圓的半徑差。
每次繪制只需要把所有的圓畫出來:
第二步:
擴散過程中隨時間變化的參數只有半徑和透明度。圓的數量,最小最大半徑和半徑差則是不可變參數。所有的都加了默認值,防止
第三步:
最艱難的一步,時間軸。我們需要一個定時觸發的機制去改變mWaveList中的每一個值,還要同時修改paint的alpha值。這個變量嘛,還是越少越方便,波紋的效果是半徑越大alpha越小,只要控制半徑變化就可以計算出alpha。然后便是循環的問題,當波紋半徑大于最大半徑波紋就會消失,此時便可循環使用,再從最小半徑擴散一次。
我這里的時間軸用了CountDownTimer,應該不是一種很好的選擇,只是個人習慣寫起來順手了…
第四步:
最后這個就簡單了,直接控制CountDownTimer的start和cancel就可以了。
三、再完善一下?
內部功能已經實現了,還要提取參數的設置方法,方便其他地方使用呀。java中需要添加setter/getter方法,用模版代碼生成就好,kotlin的話直接把需要暴露的field改為public即可。
如果需要在xml布局文件中設置默認參數,還需要添加對應的attribute,現在這樣已經夠用了,我就不加了?
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.CountDownTimer
import android.util.AttributeSet
import android.view.View
class WaveView@JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint:Paint = Paint()
public var mBgColor = Color.TRANSPARENT
public var mWaveColor = Color.WHITE
public var mRadiusMin = 0f
public var mRadiusMax = 1080f
public var mWaveInterval = 240f
//用每次擴散半徑增加的值作為速度參數
public var speed = 10f
private val mWaveList = ArrayList()
private val timeline = object:CountDownTimer(300000,16){
override fun onTick(millisUntilFinished: Long) {
// 每個時間間隔都把正在擴散的波紋半徑增加
for (i in 0 until mWaveList.size){
mWaveList[i] = mWaveList[i] + speed
if (mWaveList[i] < mRadiusMin + mWaveInterval){
break
}
}
//最外層波紋超過最大值時,重新把它添加到波紋隊列末尾
if (mWaveList[0] > mRadiusMax){
mWaveList[0] = mRadiusMin
val newList = transList(mWaveList)
mWaveList.clear()
mWaveList.addAll(newList)
}
invalidate()
}
override fun onFinish() {
//盡量保證手動調用waveStop,就不會執行到這里
reset()
}
}
init {
initWave(mWaveList)
paint.color = mWaveColor
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas!!.drawColor(mBgColor)
val centerX = canvas.width.div(2).toFloat()
val centerY = canvas.height.div(2).toFloat()
for (i in 0 until mWaveList.size){
paint.alpha = calcAlpha(mWaveList[i])
canvas.drawCircle(centerX, centerY, mWaveList[i], paint)
}
}
fun wave(){
timeline.start()
}
fun stopWave(){
timeline.cancel()
}
fun reset(){
timeline.cancel()
mWaveList.clear()
initWave(mWaveList)
}
private fun initWave(waveList: ArrayList){
val waveNum = ((mRadiusMax-mRadiusMin)/mWaveInterval).toInt() + 2
for (i in 1..waveNum){
waveList.add(mRadiusMin)
}
}
private fun transList(list: ArrayList):ArrayList{
val newList = ArrayList()
(1 until list.size).mapTo(newList) { list[it] }
newList.add(list[0])
return newList
}
// 通過半徑計算透明度,趨勢是半徑越大越透明,直到看不見
private fun calcAlpha(r:Float):Int = ((mRadiusMax - r)/ mRadiusMax * 120).toInt()
}
復制代碼
最終得到的就是這樣的一個View啦,使用方法大概是這樣的:
在xml中放一個WaveView
findViewById之后,set基本參數
在需要的時機執行wave()方法
四、后記
做成這樣應該可以滿足設計師dalao們的需求了,呼~
自定義View是一個涵蓋內容很廣泛的課題,除了完全實現一個可展示、可交互的控件,還能優化布局的繪制效率,實現迷之交互,以及這特別的動畫效果,需要深入學習的內容還有很多呀。
近期在惡補Kotlin,打算進入實踐了,所以寫Demo的時候都用的Kotlin。但是目前的項目都是Java的,所以又翻譯了一版Java的。完整代碼見Github吧。
前往github
如有問題或是建議,歡迎留言評論。
總結
以上是生活随笔為你收集整理的android覆盖扩散动画,[Android]多层波纹扩散动画——自定义View绘制的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 3度电超大容量!电小二旗舰户外电源300
- 下一篇: DSCC 研究:三星 S23 / Ult
