通过自定义View实现加载进度条

267 阅读1分钟

1.先来看下效果图

1664250417566_448x495.gif

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()
                    }
                }
            }
        }
    }

}