1.先来看下效果图
2.代码实现与讲解
2.1 首先将坐标设置到屏幕中间
canvas.translate(width / 2f, height / 2f)
2.2 绘制内圆
canvas.drawCircle(0f, 0f, (width / 2f - progressWidth / 2f), paintBgProgress)
2.3 绘制外圆弧
if (progress > 0) {
if (circleFrontRect == null) {
circleFrontRect = RectF(
-width / 2f + progressWidth / 2f, -width / 2f + progressWidth / 2f,
width / 2f - progressWidth / 2f, width / 2f - progressWidth / 2f
)
}
canvas.drawArc(circleFrontRect!!, -90f, (progress / 100) * 360, false, paintProgress)
}
2.4 绘制百分比文案
val fontMetrics = paintText.fontMetrics
val basTop = fontMetrics.top
val baseBottom = fontMetrics.bottom
val baseLineY = textRect.centerY() - basTop / 2 - baseBottom / 2
textRect.set((-width / 2f).toInt(), (-height / 2f).toInt(), (width / 2f).toInt(), (height / 2f).toInt())
canvas.drawText("${progress.toInt()}%", textRect.centerX().toFloat(), baseLineY, paintText)
2.5 提供更新进度的方法
fun updateProgress(progress: Float) {
this.progress = progress
invalidate()
}
3.大概思路就是如此,顺便提供下整个完整的类吧
class CircleBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
View(context, attrs, defStyleAttr) {
private val paintBgProgress: Paint
private val paintProgress: Paint
private val paintText: Paint
private var circleFrontRect: RectF? = null
private var progress = 0F
private val progressWidth: Float
private val textRect = Rect()
init {
val ta = context.obtainStyledAttributes(attrs, R.styleable.CircleBar)
val backgroundProgressColor = ta.getColor(
R.styleable.CircleBar_circle_background_progress_color,
Color.parseColor("#666666")
)
val foregroundProgressColor = ta.getColor(
R.styleable.CircleBar_circle_foreground_progress_color,
Color.parseColor("#FFFFFF")
)
progressWidth = ta.getDimensionPixelSize(
R.styleable.CircleBar_circle_progress_width,
2F.dp2px()
).toFloat()
val circleTextColor = ta.getColor(
R.styleable.CircleBar_circle_text_color,
Color.parseColor("#FFFFFF")
)
val circleTextSize = ta.getDimensionPixelSize(
R.styleable.CircleBar_circle_text_size,
14F.sp2px()
).toFloat()
ta.recycle()
paintBgProgress = Paint().apply {
color = backgroundProgressColor
style = Paint.Style.STROKE
strokeWidth = progressWidth
isAntiAlias = true//启用抗锯齿
}
paintProgress = Paint().apply {
color = foregroundProgressColor
style = Paint.Style.STROKE
strokeWidth = progressWidth
isAntiAlias = true//启用抗锯齿
strokeCap = Paint.Cap.ROUND//设置画笔为椭圆
}
paintText = Paint().apply {
color = circleTextColor
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
isAntiAlias = true//启用抗锯齿
textSize = circleTextSize
}
}
override fun onDraw(canvas: Canvas) {
canvas.translate(width / 2f, height / 2f)
canvas.drawCircle(0f, 0f, (width / 2f - progressWidth / 2f), paintBgProgress)
if (progress > 0) {
if (circleFrontRect == null) {
circleFrontRect = RectF(
-width / 2f + progressWidth / 2f, -width / 2f + progressWidth / 2f,
width / 2f - progressWidth / 2f, width / 2f - progressWidth / 2f
)
}
canvas.drawArc(circleFrontRect!!, -90f, (progress / 100) * 360, false, paintProgress)
}
val fontMetrics = paintText.fontMetrics
val basTop = fontMetrics.top
val baseBottom = fontMetrics.bottom
val baseLineY = textRect.centerY() - basTop / 2 - baseBottom / 2
textRect.set((-width / 2f).toInt(), (-height / 2f).toInt(), (width / 2f).toInt(), (height / 2f).toInt())
canvas.drawText("${progress.toInt()}%", textRect.centerX().toFloat(), baseLineY, paintText)
}
/**
* @param progress 值 0-100
*/
fun updateProgress(progress: Float) {
this.progress = progress
invalidate()
}
}
<declare-styleable name="CircleBar">
<!-- 圆形背景颜色 -->
<attr name="circle_background_progress_color" format="color" />
<!-- 圆形前景颜色 -->
<attr name="circle_foreground_progress_color" format="color" />
<!-- 圆形宽度 -->
<attr name="circle_progress_width" format="dimension" />
<!-- 字体颜色 -->
<attr name="circle_text_color" format="color" />
<!-- 字体颜色 -->
<attr name="circle_text_size" format="dimension" />
</declare-styleable>
4.将CirCleBar依附在PopWindow上
internal class ProgressDialog(context: Context) : BasePopupWindow(context) {
private var tvText: AppCompatTextView? = null
private var circleBar: NxinCircleBar? = null
init {
setBackgroundColor(0)
setContentView(R.layout.dialog_common_progress)
tvText = findViewById(R.id.tv_text)
circleBar = findViewById(R.id.progressbar)
}
/**
* @param onTouchOutside 点击空白区域
* @param backCanceled 返回键
*/
private fun setCancel(onTouchOutside: Boolean, backCanceled: Boolean) {
setOutSideDismiss(onTouchOutside)
setBackPressEnable(backCanceled)
}
fun showProgress(text: CharSequence) {
showProgress(true, text)
}
fun showProgress(isCancel: Boolean, text: CharSequence) {
setCancel(isCancel, isCancel)
showPopupWindow()
circleBar?.updateProgress(0F)
tvText?.text = text
}
fun updateProgress(progress: Float) {
circleBar?.updateProgress(progress)
}
}
5.最后模拟下调用方式
class DemoActivity : BaseActivity<ActivityDemoBinding, DefaultViewModel>() {
override fun initView(rootView: View?, savedInstanceState: Bundle?) {
binding.btnProgressDialog.setOnClickListener {
//模拟数据
showProgressDialog(false, "loading...")
lifecycleScope.launch {
for (item in 1..100) {
delay(50)
updateProgress(item.toFloat())
if (item == 100) {
hideProgressDialog()
}
}
}
}
}
}