分享一個Kotlin 寫的超級 簡單的自定義View,圓環統計

晴天大帥逼發表於2019-06-25

效果圖

最近專案中需要用到一個圓環統計,如下圖所示,於是手擼了一個超級簡單的分享給大家。 就當做給萌新自定義View的入門了,有需要的也可以直接拷貝過去就能用,先看效果圖

分享一個Kotlin 寫的超級 簡單的自定義View,圓環統計

日常分析一波

先構思一波,這個東西應該怎麼實現,因為本身沒有能直接畫出圓環的api,所以我們需要換一個角度來, 可以用扇形來表示圓,中間加個小圓形覆蓋在上面,這樣就成了我們看到的圓環了。有了思路再一看這個,就很簡單了。 剩下的就是控制幾段,所佔比例,以及顏色了。 所以只需要用到 drawCircle 和 drawArc 畫圓和扇形

public void drawArc(@RecentlyNonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @RecentlyNonNull Paint paint) {
     throw new RuntimeException("Stub!");
}

public void drawCircle(float cx, float cy, float radius, @RecentlyNonNull Paint paint) {
     throw new RuntimeException("Stub!");
}

複製程式碼

擼程式碼

1.先準備資料元素

首先我們們要確定有幾段資料,每段的顏色用什麼標識,同時所佔比例是多少。這些都不確定,需要可以手動設定,所以把他們放在一個實體類裡面

    fun setElementList(elements: MutableList<CircularRingElement>) {
        this.elements = elements
        postInvalidate()
    }
    data class CircularRingElement(@ColorInt val color: Int, val value: Float)
複製程式碼

2.計算View的大小

同時將圓心座標,半徑,以及扇形所在區域的矩形一併計算出來了

 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mWidth = MeasureSpec.getSize(widthMeasureSpec)
        mHeight = MeasureSpec.getSize(heightMeasureSpec)
        if (layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            mWidth = dp2px(200f) //設定預設寬高
        }
        if (layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            mHeight = dp2px(200f)
        }
        //外圈扇形所在矩形區域
        rectF.run {
            left = 0f + paddingLeft
            top = 0f + paddingTop
            right = mWidth.toFloat() - paddingRight
            bottom = mHeight.toFloat() - paddingBottom
        }
        innerCircleX = mWidth * 0.5f
        innerCircleY = mHeight * 0.5f
        innerCircleRadius = mWidth * 0.5f - mStrokeWidth - (paddingStart + paddingEnd)
        //儲存測量結果
        setMeasuredDimension(mWidth, mHeight)
    }
複製程式碼

3.Draw

最後再根據資料畫出對應的扇形和內圈圓就OK了

override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        var startAngle = 0f
        var valueAll = 0f
        elements?.run {
            all {
                valueAll += it.value
                true
            }
            for (entry in this) {
                val sweepAngle = entry.value / valueAll * 360f
                canvas?.drawArc(rectF, startAngle, sweepAngle, true, mPaint.apply { color = entry.color })
                startAngle += sweepAngle
            }
        }
        canvas?.drawCircle(innerCircleX, innerCircleY, innerCircleRadius, mPaint.apply { color = Color.WHITE })
    }
複製程式碼

全部程式碼

方便需要的朋友直接拷貝了

fun View.dp2px(dp: Float): Int {
    val scale = this.resources.displayMetrics.density
    return (dp * scale + 0.5f).toInt()
}

fun View.px2dp(px: Float): Int {
    val scale = this.resources.displayMetrics.density
    return (px / scale + 0.5f).toInt()
}
/**
 * actor 晴天 create 2019/6/24
 */
class CircularRingView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private var elements: MutableList<CircularRingElement>? = null
    //內圈小圓的圓心
    private var innerCircleX = 0f
    private var innerCircleY = 0f
    //內圈小圓的半徑
    private var innerCircleRadius = 0f
    //View的寬高
    private var mWidth = 0
    private var mHeight = 0
    //圓環寬度
    private var mStrokeWidth = dp2px(10f)
    //畫筆
    private val mPaint by lazy {
        Paint().apply {
            isAntiAlias = true
            color = Color.RED
            style = Paint.Style.FILL
            strokeWidth = 10f
        }
    }
    //扇形所在的矩形區域
    private val rectF = RectF()
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mWidth = MeasureSpec.getSize(widthMeasureSpec)
        mHeight = MeasureSpec.getSize(heightMeasureSpec)
        if (layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            mWidth = dp2px(200f) //設定預設寬高
        }
        if (layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            mHeight = dp2px(200f)
        }
        //外圈扇形所在矩形區域
        rectF.run {
            left = 0f + paddingLeft
            top = 0f + paddingTop
            right = mWidth.toFloat() - paddingRight
            bottom = mHeight.toFloat() - paddingBottom
        }
        innerCircleX = mWidth * 0.5f
        innerCircleY = mHeight * 0.5f
        innerCircleRadius = mWidth * 0.5f - mStrokeWidth - (paddingStart + paddingEnd)
        //儲存測量結果
        setMeasuredDimension(mWidth, mHeight)
    }
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        var startAngle = 0f
        var valueAll = 0f
        elements?.run {
            all {
                valueAll += it.value
                true
            }
            for (entry in this) {
                val sweepAngle = entry.value / valueAll * 360f
                canvas?.drawArc(rectF, startAngle, sweepAngle, true, mPaint.apply { color = entry.color })
                startAngle += sweepAngle
            }
        }
        canvas?.drawCircle(innerCircleX, innerCircleY, innerCircleRadius, mPaint.apply { color = Color.WHITE })
    }
    /**
     * 設定圓環厚度
     */
    fun setRingThickness(value: Int) {
        this.mStrokeWidth = value
    }
    /**
     * 設定資料元素
     */
    fun setElementList(elements: MutableList<CircularRingElement>) {
        this.elements = elements
        postInvalidate()
    }
    /**
     * 資料元素
     */
    data class CircularRingElement(@ColorInt val color: Int, val value: Float)
}
複製程式碼

總體來說,很簡單,本來想著不傳上來的,但是想著以後可能又會遇到其他各種各樣的統計圖, 於是乎專門新建了這個工程,到時候可以直接歸個總,避免重複造輪子了。同時有朋友要是有別的需求可以留言出來, 我抽空可以幫忙研究研究,就當作提高自己了

專案連結

[git] github.com/qingtian521…

相關文章