[toc]java
最近想熟悉一下自定義 view 的內容,因而寫了下面這個八卦圖,記錄一下開發流程。react
這個自定義 view 主要分爲兩步,測量和繪製。git
其中測量部分幾乎是全部自定義 view 的通用步驟。github
新建 BaguaView,並實現 onMeasure() 方法。canvas
class Bagua2View @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
複製代碼
下面主要來看測量方法。app
相關的內容這裏再也不多說,若是 測量模式是 AT_MOST, 表示子View具體大小沒有尺寸限制,爲 wrap_content,可是存在上限,上限通常爲父View大小;那麼設置一個默認值,大小爲屏幕寬度或者高度中的較小值。代碼以下:ide
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// 獲取寬的測量模式
val wSpecMode = MeasureSpec.getMode(widthMeasureSpec)
// 獲取控件提供的 view 寬的最大值
val wSpecSize = MeasureSpec.getSize(widthMeasureSpec)
val hSpecMode = MeasureSpec.getMode(heightMeasureSpec)
val hSpecSize = MeasureSpec.getSize(heightMeasureSpec)
if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_SIZE, DEFAULT_SIZE)
} else if (wSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_SIZE, hSpecSize)
} else if (hSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(wSpecSize, DEFAULT_SIZE)
}
}
val DEFAULT_SIZE = if (getWinWidth() < getWinHeight()) getWinWidth() else getWinHeight()
複製代碼
整個圖像來講,中間的太極圖寬高是view 寬高的一半。動畫
下面開始畫中間的太極部分。ui
// left表示view到父view左邊的距離,所以 xCenter 爲 view 中心的x座標
val xCenter = (right - left) / 2f
val yCenter = (bottom - top) / 2f
paint.color = getColor(R.color.white)
// 先畫一個白色的圓形
canvas?.drawCircle(xCenter, yCenter, (size - STROKE_WIDTH) / 2, paint)
複製代碼
中間的太極圖能夠分爲幾部分,首先是左右對稱的半圓,能夠用畫圓弧的方法畫出來。spa
paint.color = getColor(R.color.black)
canvas?.drawArc(
0f + size * 0.25f,
0f + size * 0.25f,
size.toFloat() * 0.75f,
size.toFloat() * 0.75f,
-90f,
180f,
false,
paint
)
複製代碼
在白色圓的前提下,畫出右邊的黑色半圓。前四個參數定義一個矩形,限制圓弧的大小。
後兩個參數顯示圓弧的長度。從 -90度畫到 180度。
接下來分別畫出上下兩個小一點的圓形,建立出太極圖的基本形狀。
代碼以下:
// 畫中間大小的兩個圓形
canvas?.drawCircle(xCenter, yCenter * 1.25f, size / 8f, paint)
paint.color = getColor(R.color.white)
canvas?.drawCircle(xCenter, yCenter * 0.75f, size / 8f, paint)
canvas?.drawCircle(xCenter, yCenter * 1.25f, size / 32f, paint)
paint.color = getColor(R.color.black)
複製代碼
其中前兩個參數是圓形的圓心座標,第三個參數是圓形的半徑。
下面就是如何讓這個太極圖轉起來。
思路就是經過旋轉畫布,從新繪製整個太極圖,形成一種旋轉太極的效果。
canvas?.rotate(startAngle * direction, xCenter, yCenter)
複製代碼
第一個參數表示旋轉的角度。
private var startAngle: Float = 0f
// 控制順時針和逆時針
private var direction = 1
val anim = ValueAnimator.ofFloat(0f, 360f)
.apply {
duration = 2000
repeatCount = ValueAnimator.INFINITE
interpolator = LinearInterpolator()
addUpdateListener {
startAngle = it.animatedValue as Float
invalidate()
}
}
fun doAnim() {
if (!anim.isStarted) {
anim.start()
return
}
if (anim.isPaused) {
anim.resume()
return
}
anim.pause()
}
複製代碼
經過 ValueAnimator 建立連續的旋轉角度,並不斷調用 invalidate() 進行從新繪製,是太極圖旋轉。
思路:先在正上方畫出三個黑色矩形,並每次旋轉畫布45度。重複執行,畫出8個卦象。
接着經過變量去控制在每一個卦象中間是否再畫一個白色的矩形。
代碼以下:
private val threeLineList = arrayListOf<List<Boolean>>().apply {
add(arrayListOf(false, false, false))
add(arrayListOf(true, false, false))
add(arrayListOf(true, false, true))
add(arrayListOf(true, true, false))
add(arrayListOf(true, true, true))
add(arrayListOf(false, true, true))
add(arrayListOf(false, true, false))
add(arrayListOf(false, false, true))
}
threeLineList.forEachIndexed { index, list ->
canvas?.rotate(if (index == 0) 0f else 45f, xCenter, yCenter)
drawThreeLine(canvas, list)
}
複製代碼
上面的8個列表來控制卦象的形狀,true 表示中間有白色矩形。
fun drawThreeLine( canvas: Canvas?, list: List<Boolean> ) {
val firstTop = size * 0.2f - reactHeight / 2
list.forEachIndexed { index, b ->
paint.color = getColor(R.color.black)
val left = size * 0.5f - reactWidth / 2
val top = firstTop - (index * 1.8f) * reactHeight
val right = left + reactWidth
val bottom = top + reactHeight
canvas?.drawRect(left, top, right, bottom, paint)
if (b) {
// 表示地
paint.color = getColor(R.color.white)
val l = left + reactWidth * 0.4f
val t = top - 2
val r = l + reactWidth * 0.2f
val b = bottom + 2
canvas?.drawRect(l, t, r, b, paint)
}
}
}
複製代碼
這裏矩形的具體位置能夠稍微調整一下。
目前來講,整個圖像均可以一塊兒旋轉。但怎麼作到內外兩部分的旋轉方向不一樣。
最開始的想法是自定義內外兩個 view,放在 viewgroup 裏面,利用屬性動畫去實現不一樣方向的旋轉。
但其實能夠藉助上面太極旋轉的經驗。
首先太極圖順時針旋轉 1度,畫出太極;外層逆時針旋轉2;其實就是在原位置逆時針旋轉1度。
不斷旋轉進行繪製,便可實現一個自定義view裏面,不用部分分別作動畫的效果。
在上面知識的基礎上,也能夠畫出下面甚至更多的自定義 view。