You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android带随动进度文本的水平ProgressBar实现方案咨询

实现带同步移动进度文本的动画ProgressBar

我来给你两个实用的方案,帮你轻松实现「进度文本跟着进度条右侧同步移动」的效果,两种方案各有侧重,你可以根据需求选择:

方案一:自定义ViewGroup封装(推荐,灵活性更高)

这种方式把ProgressBar和TextView封装在一个自定义容器里,耦合度低,你可以单独给TextView设置样式、点击事件,甚至替换成其他View,复用性很强。

实现步骤:

  1. 先写一个布局文件,用FrameLayout作为容器,把ProgressBar和TextView放进去(TextView要覆盖在ProgressBar上方);
  2. 自定义ViewGroup继承FrameLayout,初始化时加载布局,监听ProgressBar的进度变化;
  3. 进度变化时,计算TextView应该移动到的位置,用属性动画让它平滑移动,同时更新文本内容。

代码示例:

自定义ViewGroup类

class ProgressWithTextLayout @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private lateinit var progressBar: ProgressBar
    private lateinit var progressText: TextView

    init {
        // 加载内部布局
        inflate(context, R.layout.layout_progress_with_text, this)
        progressBar = findViewById(R.id.progress_bar)
        progressText = findViewById(R.id.progress_text)

        // 监听进度变化,同步更新文本位置和内容
        progressBar.setOnProgressChangeListener { progress, _ ->
            updateTextPosition(progress)
            progressText.text = "$progress%"
        }
    }

    private fun updateTextPosition(progress: Int) {
        // 计算进度条的可绘制宽度(总宽度减去左右padding)
        val usableProgressWidth = progressBar.width - progressBar.paddingLeft - progressBar.paddingRight
        // 计算当前进度对应的右侧x坐标:左padding + 进度比例 * 可绘制宽度
        val progressRightX = progressBar.paddingLeft + (progress / 100f) * usableProgressWidth
        // 让TextView的中心对齐进度条右侧,所以要减去自身宽度的一半
        val targetX = progressRightX - progressText.width / 2f

        // 用属性动画同步进度条的动画时长,保证移动平滑一致
        progressText.animate()
            .translationX(targetX)
            .setDuration(progressBar.progressDrawable?.intrinsicAnimationDuration ?: 300)
            .start()
    }

    // 暴露给外部设置进度的方法
    fun setProgress(progress: Int) {
        progressBar.setProgress(progress, true)
    }
}

内部布局文件 layout_progress_with_text.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:progress="0"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"/>

    <TextView
        android:id="@+id/progress_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:background="@drawable/rounded_corner_bg" <!-- 可选,给文本加个背景 -->
        android:paddingHorizontal="8dp"
        android:paddingVertical="4dp"
        android:textColor="@android:color/white"
        android:textSize="12sp"/>
</merge>

方案二:自定义ProgressBar直接绘制文本(更简洁)

如果你的需求只是简单展示进度文本,不需要对文本做复杂操作,直接自定义ProgressBar,在onDraw里绘制文本是更简洁的选择,少了一个View层级,性能也略好。

实现步骤:

  1. 自定义ProgressBar继承AppCompatProgressBar;
  2. 初始化时配置文本的画笔样式;
  3. 重写onDraw方法,先绘制原生进度条,再计算文本位置,直接绘制文本和可选的背景。

代码示例:

class ProgressWithTextBar @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatProgressBar(context, attrs, defStyleAttr) {

    // 文本画笔
    private val textPaint = Paint().apply {
        color = Color.WHITE
        textSize = resources.getDimension(R.dimen.progress_text_size)
        textAlign = Paint.Align.CENTER
        isAntiAlias = true
    }
    // 文本背景画笔(可选)
    private val bgPaint = Paint().apply {
        color = Color.parseColor("#FF6200EE")
        isAntiAlias = true
    }
    private val textPadding = resources.getDimensionPixelSize(R.dimen.progress_text_padding)
    private var textWidth = 0f
    private var textHeight = 0f

    init {
        // 提前计算文本的最大宽高,避免每次onDraw重复计算
        val textBounds = Rect()
        textPaint.getTextBounds("100%", 0, "100%".length, textBounds)
        textWidth = textBounds.width() + 2 * textPadding
        textHeight = textBounds.height() + 2 * textPadding
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val currentProgress = progress
        // 计算进度条的可绘制区域
        val progressLeft = paddingLeft.toFloat()
        val progressRight = progressLeft + (currentProgress / 100f) * (width - paddingLeft - paddingRight)
        val centerY = height / 2f

        // 绘制文本背景(可选,提升可读性)
        val bgLeft = progressRight - textWidth / 2f
        val bgRight = progressRight + textWidth / 2f
        val bgTop = centerY - textHeight / 2f
        val bgBottom = centerY + textHeight / 2f
        canvas.drawRoundRect(bgLeft, bgTop, bgRight, bgBottom, textHeight / 2, textHeight / 2, bgPaint)

        // 绘制进度文本,垂直居中计算:centerY + (descent + ascent)/-2
        canvas.drawText(
            "$currentProgress%",
            progressRight,
            centerY + (textPaint.descent() + textPaint.ascent()) / -2,
            textPaint
        )
    }

    override fun setProgress(progress: Int, animate: Boolean) {
        super.setProgress(progress, animate)
        // 进度变化时触发重绘
        invalidate()
    }
}

方案对比与注意事项

  • 方案一优势:灵活性高,TextView可以单独设置样式、点击事件,甚至替换成其他控件;缺点是多了一个View层级。
  • 方案二优势:代码简洁,少一个View层级,性能略优;缺点是文本的自定义能力弱,无法单独添加点击事件。

通用注意事项:

  1. Padding处理:计算位置时一定要考虑ProgressBar的左右padding,不然文本会偏移;
  2. 动画同步:方案一中用了ProgressBar自身的动画时长来设置TextView的动画,保证两者移动节奏一致;
  3. 文本居中:不管哪种方案,都要确保文本的中心对齐进度条的右侧,避免偏移;
  4. 适配问题:如果文本长度可能变化(比如多语言),方案一中要在文本更新后重新计算宽度,方案二中可以在onSizeChanged里重新计算文本宽高。

内容的提问来源于stack exchange,提问作者Maurice Gaynor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 07:59:29