效果圖
最近專案中需要用到一個圓環統計,如下圖所示,於是手擼了一個超級簡單的分享給大家。 就當做給萌新自定義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…