Android 自定義View之八卦圖

Android Custom View: BaGuaView

[toc]java

最近想熟悉一下自定義 view 的內容,因而寫了下面這個八卦圖,記錄一下開發流程。react

bagua

自定義View的通用步驟

這個自定義 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。

github

loading
相關文章
相關標籤/搜索