Android带随动进度文本的水平ProgressBar实现方案咨询
实现带同步移动进度文本的动画ProgressBar
我来给你两个实用的方案,帮你轻松实现「进度文本跟着进度条右侧同步移动」的效果,两种方案各有侧重,你可以根据需求选择:
方案一:自定义ViewGroup封装(推荐,灵活性更高)
这种方式把ProgressBar和TextView封装在一个自定义容器里,耦合度低,你可以单独给TextView设置样式、点击事件,甚至替换成其他View,复用性很强。
实现步骤:
- 先写一个布局文件,用FrameLayout作为容器,把ProgressBar和TextView放进去(TextView要覆盖在ProgressBar上方);
- 自定义ViewGroup继承FrameLayout,初始化时加载布局,监听ProgressBar的进度变化;
- 进度变化时,计算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层级,性能也略好。
实现步骤:
- 自定义ProgressBar继承AppCompatProgressBar;
- 初始化时配置文本的画笔样式;
- 重写
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层级,性能略优;缺点是文本的自定义能力弱,无法单独添加点击事件。
通用注意事项:
- Padding处理:计算位置时一定要考虑ProgressBar的左右padding,不然文本会偏移;
- 动画同步:方案一中用了ProgressBar自身的动画时长来设置TextView的动画,保证两者移动节奏一致;
- 文本居中:不管哪种方案,都要确保文本的中心对齐进度条的右侧,避免偏移;
- 适配问题:如果文本长度可能变化(比如多语言),方案一中要在文本更新后重新计算宽度,方案二中可以在
onSizeChanged里重新计算文本宽高。
内容的提问来源于stack exchange,提问作者Maurice Gaynor
相关产品推荐
相关产品推荐

