如何在Android Studio中实现TextView内部嵌套另一个TextView
Android 消息TextView右下角嵌入时间、文本自动绕排实现方案
以下是两种稳定可行的实现方式,均可以实现消息文本不覆盖时间、自动适配换行的效果:
方案一:自定义TextView内置时间绘制(性能最优,无额外布局嵌套)
- 核心逻辑:重写TextView的测量、绘制流程,自动计算文本换行位置预留时间区域,直接在控件右下角绘制时间文本,不需要额外嵌套布局。
- 核心实现代码:
class MessageTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatTextView(context, attrs, defStyleAttr) { // 时间文本样式配置,可根据需求自定义 private val timeTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12f, resources.displayMetrics) color = Color.parseColor("#999999") } private var timeStr = "" private val timeHorizontalPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6f, resources.displayMetrics).toInt() private var timeWidth = 0 private var timeLineHeight = 0 // 对外暴露设置时间的方法 fun setTime(time: String) { timeStr = time timeWidth = timeTextPaint.measureText(timeStr).toInt() timeLineHeight = (timeTextPaint.descent() - timeTextPaint.ascent()).toInt() requestLayout() invalidate() } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val textLayout = layout ?: return // 计算最后一行剩余宽度,不足放下时间则增加控件高度 val lastLineAvailableWidth = measuredWidth - paddingLeft - paddingRight - textLayout.getLineWidth(lineCount - 1) if (lastLineAvailableWidth < timeWidth + timeHorizontalPadding) { setMeasuredDimension(measuredWidth, measuredHeight + timeLineHeight + timeHorizontalPadding / 2) } } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) if (timeStr.isEmpty()) return // 右下角绘制时间文本 val drawX = (measuredWidth - paddingRight - timeWidth).toFloat() val drawY = (measuredHeight - paddingBottom - timeTextPaint.descent()) canvas.drawText(timeStr, drawX, drawY, timeTextPaint) } }
- 使用方式:直接在布局中替换原有的消息TextView,调用
setTime方法传入时间字符串即可。
方案二:Span占位+独立时间TextView(实现简单,适配性强)
- 核心逻辑:计算时间文本的宽度,在消息文本末尾插入对应宽度的占位Span让文本自动绕开时间区域,再通过布局将时间TextView固定在消息TextView的右下角。
- 核心实现代码:
// 1. 测量时间文本所需宽度 val timeStr = "18:25" val timeTextSize = 12f // sp单位 val timePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, timeTextSize, resources.displayMetrics) } val timeTotalWidth = timePaint.measureText(timeStr) + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics) // 2. 给消息文本添加占位Span val messageContent = "需要展示的消息内容,支持任意长度自动换行" val spannableContent = SpannableStringBuilder(messageContent) spannableContent.setSpan( object : ReplacementSpan() { override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?) = timeTotalWidth.toInt() override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {} }, spannableContent.length - 1, spannableContent.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE ) // 3. 赋值给控件,布局中需将时间TextView约束在消息TextView的右下角 messageTv.text = spannableContent timeTv.text = timeStr
- 布局参考:使用ConstraintLayout作为父布局,给时间TextView添加
layout_constraintBottom_toBottomOf="@id/messageTv"、layout_constraintEnd_toEndOf="@id/messageTv"、android:layout_marginEnd="8dp"、android:layout_marginBottom="4dp"属性即可。
内容的提问来源于stack exchange,提问作者Shaurya Goyal
相关产品推荐
相关产品推荐

