Android 自定義帶圓角的 Span
阿新 • • 發佈:2020-12-10
技術標籤: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
}
}