【问题标题】:How to underline text in TextView with some different color than that of text?如何在 TextView 中使用与文本颜色不同的颜色为文本添加下划线?
【发布时间】:2013-09-27 08:52:14
【问题描述】:

我知道如何在文本视图中为文本添加下划线。但是如何用一些不同的颜色给文本加下划线呢? 下划线可以用:

TextView t = (TextView) findViewById(R.id.textview);
t.setPaintFlags(t.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
t.setText("Underline Text");

假设我的文本颜色是黑色,我想用蓝色下划线,怎么做? 提前致谢。

【问题讨论】:

  • 恐怕你需要两个文本视图!
  • 你可以这样使用 Email.setText(Html.fromHtml("W : "+""+Email1+""));使用你想要的颜色代码。
  • ~Vipul J,你克服了这个问题吗?

标签: android android-layout textview


【解决方案1】:

我遇到了同样的问题,我在阅读 EditText 上的其他一些帖子时偶然发现了 Layout 课程。它通过在画布上手动绘制下划线,为您提供了实现这一目标所需的一切。

首先我定义了自定义属性,以便在 XML 布局文件中轻松自定义

<declare-styleable name="UnderlinedTextView" >
    <attr name="underlineHeight" format="dimension" />
    <attr name="underlineOffset" format="dimension" />
    <attr name="underlineColor" format="color" />
    <attr name="underLinePosition" format="enum">
        <enum name="baseline" value="0" />
        <enum name="below" value="1" />
    </attr>
</declare-styleable>

还有一个自定义的TextView

   class UnderlinedTextView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {

    @Retention(AnnotationRetention.SOURCE)
    @IntDef(POSITION_BASELINE, POSITION_BELOW)
    annotation class UnderLinePosition {
        companion object {
            const val POSITION_BASELINE = 0
            const val POSITION_BELOW = 1
        }
    }

    private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }

    var lineColor: Int
        get() = linePaint.color
        set(value) {
            if (linePaint.color != value) {
                linePaint.color = value
                invalidate()
            }
        }

    var lineHeight: Float
        get() = linePaint.strokeWidth
        set(value) {
            if (linePaint.strokeWidth != value) {
                linePaint.strokeWidth = value
                updateSpacing()
            }
        }

    var lineTopOffset = 0F
        set(value) {
            if (field != value) {
                field = value
                updateSpacing()
            }
        }

    @UnderLinePosition
    var linePosition = POSITION_BASELINE

    private val rect = Rect()

    private var internalAdd: Float = lineSpacingExtra

    private inline val extraSpace
        get() = lineTopOffset + lineHeight

    init {
        val density = context.resources.displayMetrics.density

        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.UnderlinedTextView, defStyleAttr, 0)
        lineColor = typedArray.getColor(R.styleable.UnderlinedTextView_underlineColor, currentTextColor)
        lineTopOffset = typedArray.getDimension(R.styleable.UnderlinedTextView_underlineOffset, 0f)
        lineHeight = typedArray.getDimension(R.styleable.UnderlinedTextView_underlineHeight, density * 1)
        linePosition = typedArray.getInt(R.styleable.UnderlinedTextView_underLinePosition, POSITION_BASELINE)
        typedArray.recycle()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(measuredWidth, measuredHeight + (extraSpace + 0.5f).toInt())
    }

    override fun onDraw(canvas: Canvas?) {
        canvas?.takeIf { !text.isNullOrEmpty() }?.let {
            val count = lineCount
            val layout = layout
            var xStart: Float
            var xStop: Float
            var yStart: Float
            var firstCharInLine: Int
            var lastCharInLine: Int
            var lastLine: Boolean
            var offset: Int
            val lineSpacing = lineSpacingExtra * lineSpacingMultiplier
            for (i in 0 until count) {
                val baseline = getLineBounds(i, rect)
                lastLine = i == count - 1
                offset = if (lastLine) 0 else 1
                firstCharInLine = layout.getLineStart(i)
                lastCharInLine = layout.getLineEnd(i)
                xStart = layout.getPrimaryHorizontal(firstCharInLine)
                xStop = layout.getPrimaryHorizontal(lastCharInLine - offset)
                yStart = when (linePosition) {
                    POSITION_BASELINE -> baseline + lineTopOffset
                    POSITION_BELOW -> (rect.bottom + lineTopOffset) - if (lastLine) 0F else lineSpacing
                    else -> throw NotImplementedError("")
                }
                canvas.drawRect(xStart, yStart, xStop, yStart + lineHeight, linePaint)
            }
        }
        super.onDraw(canvas)
    }

    private fun updateSpacing() {
        setLineSpacing(internalAdd, 1f)
    }

    override fun setLineSpacing(add: Float, mult: Float) {
        internalAdd = add
        super.setLineSpacing(add + extraSpace, 1f)
    }
}

那么它的用法就很简单了

<some.package.UnderlinedTextView
    android:id="@+id/tvTest"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_marginBottom="10dp"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:gravity="center"
    android:text="This is a demo text"
    android:textSize="16sp"
    app:underlineColor="#ffc112ef"
    app:underlineHeight="3dp"/>

最终结果

  • 多线

  • 单行

【讨论】:

  • 我怎样才能在这个自定义视图中只给特定文本下划线,没有 spannable 和 else....
  • 我认为你在这个 UnderlinedTextView 类上会崩溃,以防文本为空。作为一种解决方法,我认为可以在 onDraw 上添加对文本的检查:if (text.isNullOrEmpty()) { super.onDraw(canvas) return }
  • 当我们的设计师给我一些自定义字体时,这些字体在使用 UnderlineSpan 时只会呈现极细的线条,从而拯救了我的 a**。谢谢!
【解决方案2】:

您可以尝试如下:

  String styledText = "<u><font color='red'>Underline Text</font></u>.";
  textView.setText(Html.fromHtml(styledText), TextView.BufferType.SPANNABLE);

【讨论】:

【解决方案3】:

另一种解决方案,这次不扩展TextView(基于我很久以前写的一个问题,here):

有一个drawable显示为下划线,并有一个文本本身的跨度:

text_underline.xml

<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="line">
    <padding android:bottom="10dp"/>
    <stroke
        android:width="1dp"
        android:color="#3792e5"/>
</shape>

DrawableSpan.kt

class DrawableSpan(private val drawable: Drawable) : ReplacementSpan() {
    private val padding: Rect = Rect()

    init {
        drawable.getPadding(padding)
    }

    override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
        val rect = RectF(x, top.toFloat(), x + measureText(paint, text, start, end), bottom.toFloat())
        drawable.setBounds(rect.left.toInt() - padding.left, rect.top.toInt() - padding.top, rect.right.toInt() + padding.right, rect.bottom.toInt() + padding.bottom)
        canvas.drawText(text, start, end, x, y.toFloat(), paint)
        drawable.draw(canvas)
    }

    override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int = Math.round(paint.measureText(text, start, end))

    private fun measureText(paint: Paint, text: CharSequence, start: Int, end: Int): Float = paint.measureText(text, start, end)

}

用法:

    val text = getString(R.string.large_text)
    val spannable = SpannableString(text)
    spannable.setSpan(DrawableSpan(resources.getDrawable(R.drawable.text_underline)), 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    textView.setText(spannable, TextView.BufferType.SPANNABLE)

结果:

【讨论】:

    【解决方案4】:

    我还不能添加评论,所以我将发布作为答案。

    我只想说 Bojan Kseneman 的回答 (https://stackoverflow.com/a/30717100/2771087) 太棒了。不过,我想纠正一个问题。

    它不是查找一行中最后一个字符的结束位置,而是抓取倒数第二个字符的结尾,然后添加该行中第一个字符的宽度。这两行在这里:

    x_diff = layout.getPrimaryHorizontal(firstCharInLine + 1) - x_start;
    x_stop = layout.getPrimaryHorizontal(lastCharInLine - 1) + x_diff;
    

    除此之外,getSecondaryHorizo​​ntal() 可用于抓取角色的另一侧,如下所示:

    x_stop = layout.getSecondaryHorizontal(lastCharInLine);
    

    但是,对于多行文本区域,这也会在每行末尾的空格处加下划线。因此,要解决这个问题,请在计算 x_stop 之前使用如下代码跳过它:

    while (lastCharInLine != firstCharInLine &&
           Character.isWhitespace(getText().charAt(lastCharInLine - 1))) {
        lastCharInLine--;
    }
    

    【讨论】:

    • 非常好!毕竟没那么“粗”!
    【解决方案5】:
    imee.setText(Html.fromHtml("<font color=#9e4c4e>  <u>" + ime + "</u>  </font>"));
    avtorr.setText(Html.fromHtml("<font color=#90494f>  <u>" + avtorr + "</u>  </font>"));
    

    最好的解决方案是使用 Html。

    【讨论】:

      【解决方案6】:

      如果您是 XML 的粉丝。看看我的解决方案:

      在drawable文件夹中创建一个选择器selector_edittext_white.xml

      <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:bottom="-15dp">
          <rotate xmlns:android="http://schemas.android.com/apk/res/android"
              android:fromDegrees="0"
              android:pivotX="0.5"
              android:pivotY="0.5"
              android:toDegrees="0">
              <shape android:shape="line">
                  <stroke
                      android:width="0.5dp"
                      android:color="@android:color/white" />
              </shape>
          </rotate>
        </item>
      </layer-list>
      

      然后,设置你的 EditText

      android:background="@drawable/selector_edittext_white"
      

      在上面的设置中,下划线的颜色是white,你可以通过改变android:bottom来移动它,上面是“-15dp”。如果它消失了,请尝试像这样设置您的 EditText 下边距

      android:layout_marginBottom="5dp"
      

      【讨论】:

      • 这对于单行文本来说是一个相当不错的解决方案,但是如果文本是多行的话就不行了。
      【解决方案7】:

      另一种解决方案,也有跨度

      ColoredUnderlineSpan.kt

      /**
       * A span which draw a colored line below the text
       *
       * @property color The color of the underline
       * @property thickness The thickness of the line in pixels
       */
      @RequiresApi(Build.VERSION_CODES.Q)
      class ColoredUnderlineSpan(@ColorInt private val color: Int, @Px private val thickness: Float) : CharacterStyle(), UpdateAppearance {
          override fun updateDrawState(tp: TextPaint) {
              tp.underlineColor = color
              tp.underlineThickness = thickness
          }
      }
      
      /**
       * A span which draw a colored line below the text
       *
       * @param color The color of the underline
       * @property thickness The thickness of the line in pixels
       */
      class ColoredUnderlineSpanPreQ(@ColorInt color: Int, @Px private val thickness: Float) : ReplacementSpan() {
      
          private val linePaint = Paint().apply {
              this.color = color
          }
      
          override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
              canvas.drawText(text, start, end, x, y.toFloat(), paint)
              canvas.drawRect(x, bottom - thickness, (x + paint.measureText(text, start, end)), bottom.toFloat(), linePaint)
          }
      
          override fun getSize(
              paint: Paint,
              text: CharSequence,
              start: Int,
              end: Int,
              fm: Paint.FontMetricsInt?
          ): Int = paint.measureText(text, start, end).roundToInt()
      }
      

      【讨论】:

        【解决方案8】:

        这对我很有用:

        terms.setText(Html.fromHtml("I have read and agree to the <u><font color='red'>Terms and Conditions</u>"));
        

        【讨论】:

          【解决方案9】:

          如果是一行(短)文本,则可以在 XML 文件中以非常简单的方式解决它 此示例是白色背景上带有橙色下划线的灰色文本

          {
              <LinearLayout
                  android:id="@+id/textUnderLineLayout"
                  android:layout_height="wrap_content"
                  android:layout_width="wrap_content"
                  android:background="@color/color_orange">
          
                  <TextView
                          android:id="@+id/underlined_text"
                          android:layout_width="wrap_content"
                          android:layout_height="wrap_content"
                          android:layout_marginTop="0dp"
                          android:layout_marginStart="0dp"
                          android:layout_marginEnd="0dp"
                          android:layout_marginBottom="1dp"
                          android:text="@string/some_text_string"
                          android:textColor="@color/color_grey"
                          android:background="@color/color_white"/>
              </LinearLayout>
          
          }
          

          【讨论】:

            【解决方案10】:

            android developer's answer 的启发,我想出了另一个解决方案,以便更好地理解下划线的边界和位置。

            text_underline.xml

            <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
                <item
                    android:height="1dp"
                    android:gravity="bottom|center_horizontal"
                    android:left="1dp"
                    android:right="1dp">
            
                    <shape android:shape="rectangle">
                        <!--<corners android:radius="2dp" /> -->
                        <solid android:color="#D9FFFFFF" />
                        <!--background color of box-->
                    </shape>
                </item>
            </layer-list>
            

            在 TextView 或 Button 中的使用

            SpannableString(text).apply {
                setSpan(object : ReplacementSpan() {
                    @SuppressLint("UseCompatLoadingForDrawables")
                    private val drawable: Drawable = resources.getDrawable(R.drawable.text_underline, null)
            
                    override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
                        drawable.setBounds(x.toInt(),
                            top,
                            (x + measureText(paint, text, start, end)).toInt(),
                            (bottom + (buttonHeight - (bottom - top)) / 2.0f).roundToInt())
            
                        canvas.drawText(text, start, end, x, y.toFloat(), paint)
                        drawable.draw(canvas)
                    }
            
                    override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int =
                        paint.measureText(text, start, end).roundToInt()
            
                    private fun measureText(paint: Paint, text: CharSequence, start: Int, end: Int): Float = paint.measureText(text, start, end)
            
                },
                    0,
                    text.length,
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
            
                setText(this, BufferType.SPANNABLE)
            }
            

            buttonHeight 是要应用下划线样式的 View 的高度。

            结果如下:

            【讨论】:

              【解决方案11】:
                  Paint p = new Paint();
                  p.setColor(Color.RED);
              
                  TextView t = (TextView) findViewById(R.id.textview);
                  t.setPaintFlags(p.getColor());
                  t.setPaintFlags(Paint.UNDERLINE_TEXT_FLAG);
                  t.setText("Underline Text");    
              

              制作新的油漆颜色。并将绘制分配给 textview。

              【讨论】:

              • Color.RED 用于选择油漆的颜色。
              • 告诉我你得到/看到了什么
              • 还是不行,“UNDERLINE_TEXT_FLAG”是静态字段,所以'p'是什么都没关系。
              • 这是一个警告。当您运行您的应用程序时,您会看到/发生什么?
              • 什么都没有,下划线颜色和之前一样。这个警告意味着它不会起作用。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2013-06-03
              • 2011-11-18
              • 2012-08-17
              • 2016-10-22
              • 2013-02-20
              • 1970-01-01
              • 2021-02-09
              相关资源
              最近更新 更多