Android自定義LoadingView
好久沒更新博客了,過年回來沒什麼事,把之前的寫的東西記錄一下吧~
之前因爲公司項目需求只要自定義一個loading,效果如下–
沒錯,就是一個由幾個小圓組成的一個轉動的大圓,小圓會根據轉動不斷變小。
好了不多說廢話,接下來我們看下怎麼實現改效果~
- 首先先思考,這個view是由若干個小圓構成的,每個小圓的直徑跟變化都不同步,所以我們要定義一個圓形內部類,方便把每個小圓的對象保存起來
/**
* 內部類,小圓的參數
*/
private inner class CircleWrapper {
var diameter: Int = 0//圓的直徑
var dynamicDiameter: Float = 0.toFloat()//動態直徑
}
CircleWrapper 類裏面有個屬性,一個圓的初始化直徑diameter,一個是該圓的在某個變化時刻的直徑dynamicDiameter
- 接下來是初始化,首先要初始化畫筆,以及小圓每次減少的直徑大小
paint = Paint()
paint!!.isAntiAlias = true
paint!!.style = Paint.Style.FILL
paint!!.color = color
//一圈週期circleTime初始值爲2000(2秒),根據頻率計算出來增量的次數
number = (circleTime / 2 * 1.0 / 1000 * 83f).toInt().toFloat()
this.increment = this.smallD / number
//每次的直徑增量
this.increment = if (this.increment <= 0) 1.0F else this.increment
還有初始化小圓,默認是10個小圓
var wrappers = ArrayList() //存儲小圓的容器
val diameter = this.smallD//直徑,默認10dp
for (i in 10 downTo 1) {
var wrapper = CircleWrapper()
wrapper.diameter = diameter
wrapper.dynamicDiameter = (diameter * i / circleNum).toFloat() //動態直徑遞減
wrappers!!.add(wrapper)
}
- 初始化工作完畢,接下來就是測量該view的大小,重寫onMeasure()方法
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)
val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)
val heightSize = View.MeasureSpec.getSize(heightMeasureSpec)
val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
if (widthMode == View.MeasureSpec.EXACTLY) {
mWidth = widthSize
} else {
//大圓的直徑,加左右兩邊兩個小圓的半徑
mWidth = bigD + smallD
}
if (heightMode == View.MeasureSpec.EXACTLY) {
mHeight = heightSize
} else {
mHeight = bigD + smallD
}
setMeasuredDimension(mWidth, mHeight)
}
-由於不是ViewGroup所有不用layout,所以接下來就是onDraw繪畫了
先貼代碼
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawCircle(canvas)
invalidate()
}
private fun drawCircle(canvas: Canvas) {
val angle = 360f / circleNum
for (i in 0 until circleNum) {
if (wrappers!![i].dynamicDiameter >= 0) {
wrappers!![i].dynamicDiameter = wrappers!![i].dynamicDiameter - increment
}
if (wrappers!![i].dynamicDiameter < 0) {
wrappers!![i].dynamicDiameter = wrappers!![i].diameter.toFloat()
}
//根據角度旋轉畫布,並且繪製新的圓
canvas.rotate(-angle, (mWidth / 2).toFloat(), (mHeight / 2).toFloat())
canvas.drawCircle((mWidth / 2).toFloat(), (mHeight / 2 - bigD / 2).toFloat(), wrappers!![i].dynamicDiameter / 2, paint!!)
}
}
代碼不多,主要看drawCircle方法,首先根據小圓的數量獲得每個小圓之間的角度,這個角度angle下面會用到。
然後遍歷10次(有10個小圓),主要到這行沒有
//根據角度旋轉畫布,並且繪製新的圓
canvas.rotate(-angle, (mWidth / 2).toFloat(), (mHeight / 2).toFloat())
這步是重點,通過旋轉畫筆進行繪畫,旋轉的角度是前面計算出來的角度,然後繪製對應座標的小圓,這樣旋轉一圈就能把10個小圓根據他們對應的直徑繪製出來了!
可是這時怎麼讓他動起來呢? 加個直徑動態縮減就行了
if (wrappers!![i].dynamicDiameter >= 0) {
wrappers!![i].dynamicDiameter = wrappers!![i].dynamicDiameter - increment}
if (wrappers!![i].dynamicDiameter < 0) {
wrappers!![i].dynamicDiameter = wrappers!![i].diameter.toFloat()
}
這幾句代碼的作用就是當該校園的動態直徑大於0時,則直徑減少,減少的值前面已經在初始化計算出來了。如果減少到比0小,則恢復到初始化直徑大小。
最後調用invalidate() 就能執行動態變化了~
是不是很簡單,主要是利用到Canvas對象的rotate方法。然後我們可以自己是加以優化,加一些set屬性的方法,這樣就能更好的實現動態化拉
fun setSmallDiameter(d: Int) {
var d = d
if (d == 0) d = 40
this.smallD = d
initParams()
invalidate()
}
fun setBigDiameter(d: Int) {
var d = d
if (d == 0) d = 140
this.bigD = d
initParams()
invalidate()
}
fun setCircleTime(time: Int) {
var time = time
if (time == 0) time = 2000
this.circleTime = time
initParams()
invalidate()
}
fun setCircleNum(num: Int) {
var num = num
if (num == 0) num = 10
this.circleNum = num
initParams()
invalidate()
}
fun setCircleColor(color: Int) {
var color = color
if (color == 0) color = Color.BLUE
this.color = color
initParams()
invalidate()
}