1. 程式人生 > 其它 >Android 自定義帶圓角的 Span

Android 自定義帶圓角的 Span

技術標籤:Android

當 TextView 中部分文字需要新增背景時,我們一般使用 BackgroundColorSpan 實現,效果如下圖:

為了美化 UI,有時需要實現帶圓角的背景,如下圖:

這時需要怎麼實現呢?首先想到的是檢視 BackgroundColorSpan 是否有設定圓角的介面,但不幸的是並沒有該介面。接著想到的是是否可以實現一個繼承 BackgroundColorSpan 的類,來繪製背景。但檢視 BackgroundColorSpan 類,並沒有繪製背景的相關介面。

package android.text.style;

import android.annotation.NonNull;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextPaint;

public class BackgroundColorSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan {
    public BackgroundColorSpan(int color) {
        throw new RuntimeException("Stub!");
    }

    public BackgroundColorSpan(@NonNull Parcel src) {
        throw new RuntimeException("Stub!");
    }

    public int getSpanTypeId() {
        throw new RuntimeException("Stub!");
    }

    public int describeContents() {
        throw new RuntimeException("Stub!");
    }

    public void writeToParcel(@NonNull Parcel dest, int flags) {
        throw new RuntimeException("Stub!");
    }

    public int getBackgroundColor() {
        throw new RuntimeException("Stub!");
    }

    public void updateDrawState(@NonNull TextPaint textPaint) {
        throw new RuntimeException("Stub!");
    }
}

那怎樣實現需要的效果呢?幸好 Android 系統提供了ReplacementSpan,我們可以通過繼承 ReplacementSpan,實現我們需要的效果。

package android.text.style;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.text.TextPaint;

public abstract class ReplacementSpan extends MetricAffectingSpan {
    public ReplacementSpan() {
        throw new RuntimeException("Stub!");
    }

    public abstract int getSize(@NonNull Paint var1, CharSequence var2, int var3, int var4, @Nullable FontMetricsInt var5);

    public abstract void draw(@NonNull Canvas var1, CharSequence var2, int var3, int var4, float var5, int var6, int var7, int var8, @NonNull Paint var9);

    @Nullable
    public CharSequence getContentDescription() {
        throw new RuntimeException("Stub!");
    }

    public void setContentDescription(@Nullable CharSequence contentDescription) {
        throw new RuntimeException("Stub!");
    }

    public void updateMeasureState(TextPaint p) {
        throw new RuntimeException("Stub!");
    }

    public void updateDrawState(TextPaint ds) {
        throw new RuntimeException("Stub!");
    }
}

我們主要做的是實現 ReplacementSpan 中的抽象方法 getSize 和 draw 。

  • getSize:返回Span替換文字後的寬度,也是圓角背景的寬度;
  • draw:先後繪製我們需要的圓角背景和文字。
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.text.style.ReplacementSpan

class RoundCornerBackgroundSpan : ReplacementSpan {

    private var size = 0
    private var bgColor = 0
    private var radius = 0f
    private var bgRect = RectF()

    constructor(color: Int, radius: Float) {
        bgColor = color
        this.radius = radius
    }

    override fun getSize(paint: Paint,
                         text: CharSequence?,
                         start: Int,
                         end: Int,
                         fm: Paint.FontMetricsInt?): Int {
        if (text.isNullOrEmpty()) {
            return 0
        }

        size = (paint.measureText(text, start, end) + 2 * radius).toInt()
        return size
    }

    override fun draw(canvas: Canvas,
                      text: CharSequence?,
                      start: Int,
                      end: Int,
                      x: Float,
                      top: Int,
                      y: Int,
                      bottom: Int,
                      paint: Paint) {
        if (text.isNullOrEmpty()) {
            return
        }

        // 儲存文字顏色
        val color = paint.color
        // 設定背景顏色
        paint.color = bgColor
        paint.isAntiAlias = true
        // 設定文字背景矩形,x為span其實左上角相對整個TextView的x值,y為span左上角相對整個View的y值。paint.ascent()獲得文字上邊緣,paint.descent()獲得文字下邊緣
        bgRect = RectF(x, y + paint.ascent(), x + size, y + paint.descent())
        canvas.drawRoundRect(bgRect, radius, radius, paint)
        // 恢復畫筆的文字顏色
        paint.color = color
        canvas.drawText(text, start, end, x + radius, y.toFloat(), paint)
    }

}

具體使用方式和使用 BackgroundColorSpan 一樣,參考如下程式碼:

import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.SpannableString
import android.text.Spanned
import android.text.style.BackgroundColorSpan
import android.widget.TextView

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val spannableString = SpannableString("Hello, world!")
        //spannableString.setSpan(BackgroundColorSpan(Color.RED), 0, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
        spannableString.setSpan(RoundCornerBackgroundSpan(Color.RED, 10.0f), 0, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
        val textView = findViewById<TextView>(R.id.text_view)
        textView.text = spannableString
    }
}