setText(CharSequence text, BufferType type)

TextView.setText() 有两个重载方法用于设置文本内容:

//TextView.java
private BufferType mBufferType = BufferType.NORMAL;
//1
public final void setText(CharSequence text) {
   setText(text, mBufferType); //mBufferType默许是BufferType.NORMAL类型
 }
//2
public void setText(CharSequence text, BufferType type) {
   .......
}
public enum BufferType {
  NORMAL, SPANNABLE, EDITABLE
}

setText(CharSequence text) 最终也是调用了setText(CharSequence text, BufferType type) 方法将 text 设置为 TextView 的文本内容,并运用指定的 BufferType 类型来处理文本。

依据不同的类型,TextView 会采取不同的方法处理文本内容。两个参数的意义:

  • textCharSequence类型,可所以字符串或其他 CharSequence 的完成类。
  • type:文本的处理类型,类型为 BufferType 枚举,可选值为NORMALSPANNABLEEDITABLE

BufferType 枚举类型包括以下三个常量:

  • NORMAL:表明普通文本类型,不支撑任何款式或作用,默许设置。
  • SPANNABLE:表明可运用插入 Span 的文本类型,能够运用 Spannable 或其子类(如 SpannableStringSpannableStringBuilder)来设置文本的款式和作用,例如色彩、字体、点击事情等。
  • EDITABLE:表明可修正的文本类型,用于 EditTextView 中。

示例:

TextView textView = findViewById(R.id.textView);
// 1、运用默许的 BufferType.NORMAL 处理文本
textView.setText("Hello, World!");
// 2、运用 BufferType.SPANNABLE 处理文本,支撑设置款式和作用
SpannableString spannableString = new SpannableString("Hello, World!");
spannableString.setSpan(new ForegroundColorSpan(Color.RED), 0, 5, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString, TextView.BufferType.SPANNABLE);
// 假如setText被以BufferType.SPANNABLE方法调用,那么文字可被转为 Spannable:
val spannableText = textView.text as Spannable
// 现在咱们能够继续设置或删除 span
spannableText.setSpan(
     ForegroundColorSpan(color), 
     0, 5, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
// 3、运用 BufferType.EDITABLE 处理文本,支撑修正功能
Editable editable = Editable.Factory.getInstance().newEditable("Editable text");
textView.setText(editable, TextView.BufferType.EDITABLE);

下面主要介绍BufferType.SPANNABLE在设置Span时的一些优化建议。

官方推荐运用Span的最佳做法

已知SpannedString 完成的 Spanned接口,SpannableString、SpannableStringBuilder 完成的 Spannable接口,而他们又都承继了CharSequence接口,如下:

超能力文本:TextView.setText(CharSequence, BufferType)中的秘密
TextView 中设置文本时能够依据不同需求选用多种节省内存的方法。

1、不更改文本,只附加或别离 Span

TextView.setText() 包括能够以不同方法处理 Span 的多种重载。例如,能够运用以下代码设置 Spannable 文本目标:

textView.setText(spannableObject)

调用此 setText() 重载时,TextView 会创立 Spannable 的副本作为 SpannedString,并将其作为 CharSequence 保存在内存中。这意味着文本和 Span 都不可变,因此当需求更新文本或 Span 时,需求创立一个新的 Spannable 目标并再次调用 setText(),而这也会触发从头丈量和从头绘制布局。

如需表明这些 Span 可变,您能够改为运用 setText(CharSequence text, TextView.BufferType type),如下例所示:

textView.setText(spannable, BufferType.SPANNABLE)
//将textView.text从头转变为Spannable
val spannableText = textView.text as Spannable
spannableText.setSpan(
     ForegroundColorSpan(color),
     8, spannableText.length,
     SPAN_INCLUSIVE_INCLUSIVE
)

在该示例中,由于运用了 BufferType.SPANNABLE 参数,TextView 创立了 SpannableString(留意这里不是SpannedString哟),而由 TextView 保存的 CharSequence 目标现在具有可变Span标记和不可变文本。如需更新 Span,咱们能够将该文本作为 Spannable 进行检索,然后依据需求更新 Span

当附加、别离或从头定位 Span 时,TextView 会自动更新以反映对文本的更改。不过请留意:假如更改现有 Span 的内部特点,还需求调用 invalidate()(假如进行与外观相关的更改)或 requestLayout()(假如进行与丈量相关的更改)

2、在TextView 中多次设置文本

在某些情况下(例如运用 RecyclerView.ViewHolder 时),可能想要重复运用 TextView 并多次设置文本。默许情况下,不管是否设置 BufferTypeTextView 都会创立 CharSequence 目标的副本并将其保存在内存中,这意味着每次设置新的文本时,TextView 都会创立一个新目标。

假如希望更好地操控此进程并避免创立额外的目标,能够完成自己的 Spannable.Factory 并替换 newSpannable()。能够不用创立新的文本目标,而直接对现有 CharSequence 进行类型转化并将其作为 Spannable 返回,如下所示:

//默许的Spannable.Factory
public static class Factory {
        private static Spannable.Factory sInstance = new Spannable.Factory();
        /**
         * Returns the standard Spannable Factory.
         */
        public static Spannable.Factory getInstance() {
            return sInstance;
        }
        /**
         * Returns a new SpannableString from the specified CharSequence.
         * You can override this to provide a different kind of Spannable.
         */
        public Spannable newSpannable(CharSequence source) {
            return new SpannableString(source);
        }
    }
//自定义Spannable.Factory
val spannableFactory = object : Spannable.Factory() {
    override fun newSpannable(source: CharSequence?): Spannable {
        return source as Spannable
    }
}

请留意,在设置文本时,必须运用 textView.setText(spannableObject, BufferType.SPANNABLE)。不然,源 CharSequence 将作为 Spanned 实例进行创立,并且无法转化为 Spannable,然后导致 newSpannable() 抛出 ClassCastException

在替换 newSpannable() 之后,需求告知 TextView 运用新的 Factory

textView.setSpannableFactory(spannableFactory)

请必须在取得对 TextView 的引用后立即设置 Spannable.Factory 目标。假如运用的是 RecyclerView,请在初次创立视图时设置 Factory 目标。这可避免 RecyclerView 在将新的项绑定到 ViewHolder 时创立额外的目标。

3、更改内部 Span 特点

假如只需更改可变 Span 的内部特点(例如,自定义项目符号 Span 中的项目符号色彩),能够经过在创立 Span 时保存对该 Span 的引用来避免多次调用 setText() 所产生的开支。当需求修正 Span 时,能够修正引用,然后依据更改的特点类型,对 TextView 调用 invalidate()requestLayout()

在下面的代码示例中,自定义项目符号完成的默许色彩为红色,在点击按钮时会变为灰色:

class MainActivity : AppCompatActivity() {
    // keeping the span as a field
    val bulletSpan = BulletPointSpan(color = Color.RED)
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val spannable = SpannableString("Text is spantastic")
        // setting the span to the bulletSpan field
        spannable.setSpan(
            bulletSpan,
            0, 4,
            Spanned.SPAN_INCLUSIVE_INCLUSIVE
        )
        styledText.setText(spannable)
        button.setOnClickListener {
            // change the color of our mutable span
            bulletSpan.color = Color.GRAY
            // color won’t be changed until invalidate is called
            styledText.invalidate()
        }
    }
}

材料

【1】https://developer.android.com/guide/topics/text/spans?hl=zh-cn#best-practices