Span adalah objek markup canggih yang dapat Anda gunakan untuk memberi gaya teks pada level karakter atau paragraf. Dengan melampirkan rentang ke objek teks, Anda dapat mengubah teks dengan berbagai cara, termasuk menambahkan warna, menjadikan teks dapat diklik, menskalakan ukuran teks, dan menggambar teks dengan cara yang disesuaikan. Span juga dapat mengubah properti TextPaint
, menggambar pada Canvas
, dan mengubah tata letak teks.
Android menyediakan beberapa jenis span yang mencakup berbagai pola gaya teks umum. Anda juga dapat membuat span sendiri untuk menerapkan gaya kustom.
Membuat dan menerapkan span
Untuk membuat span, Anda dapat menggunakan salah satu class yang tercantum dalam tabel berikut. Class berbeda berdasarkan apakah teksnya dapat berubah, apakah markup teksnya dapat berubah, dan struktur data dasar yang berisi data rentang.
Class | Teks yang dapat berubah | Markup yang dapat berubah | Struktur data |
---|---|---|---|
SpannedString |
Tidak | Tidak | Array linear |
SpannableString |
Tidak | Ya | Array linear |
SpannableStringBuilder |
Ya | Ya | Hierarki interval |
Ketiga class ini memperluas antarmuka Spanned
. SpannableString
dan SpannableStringBuilder
juga memperluas
antarmuka Spannable
.
Berikut cara memutuskan mana yang akan digunakan:
- Jika Anda tidak mengubah teks atau markup setelah dibuat, gunakan
SpannedString
. - Jika Anda perlu melampirkan sedikit span ke satu objek teks dan
teks tersebut bersifat hanya baca, gunakan
SpannableString
. - Jika Anda perlu mengubah teks setelah membuatnya dan Anda harus melampirkan rentang ke teks, gunakan
SpannableStringBuilder
. - Jika Anda perlu melampirkan banyak span ke objek teks, terlepas dari
apakah teks tersebut bersifat hanya baca, gunakan
SpannableStringBuilder
.
Untuk menerapkan span, panggil setSpan(Object _what_, int _start_, int _end_, int
_flags_)
pada objek Spannable
. Parameter what merujuk pada span yang Anda
terapkan ke teks, dan parameter start dan end menunjukkan bagian
teks yang Anda terapkan span.
Jika Anda menyisipkan teks di dalam batas span, span akan otomatis diperluas untuk
mencakup teks yang disisipkan. Saat menyisipkan teks di batas span, yaitu pada indeks start atau end, parameter flags akan menentukan apakah span diperluas untuk mengakomodasi teks yang disisipkan. Gunakan
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
untuk menyertakan teks yang disisipkan, dan gunakan
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
untuk mengecualikan teks yang disisipkan.
Contoh berikut menunjukkan cara melampirkan
ForegroundColorSpan
ke
string:
Kotlin
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE )
Java
SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE );

ForegroundColorSpan
.
Karena span disetel menggunakan Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, span
akan diperluas untuk mengakomodasi teks yang disisipkan pada batas span, seperti yang ditunjukkan dalam
contoh berikut:
Kotlin
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) spannable.insert(12, "(& fon)")
Java
SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE ); spannable.insert(12, "(& fon)");

Spannable.SPAN_EXCLUSIVE_INCLUSIVE
.
Anda dapat melampirkan beberapa span pada teks yang sama. Contoh berikut menunjukkan cara membuat teks yang tebal dan merah:
Kotlin
val spannable = SpannableString("Text is spantastic!") spannable.setSpan(ForegroundColorSpan(Color.RED), 8, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan( StyleSpan(Typeface.BOLD), 8, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE )
Java
SpannableString spannable = new SpannableString("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); spannable.setSpan( new StyleSpan(Typeface.BOLD), 8, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );

ForegroundColorSpan(Color.RED)
dan
StyleSpan(BOLD)
.
Jenis span Android
Android menyediakan lebih dari 20 jenis span dalam paket android.text.style. Android mengategorikan span dalam dua cara utama:
- Cara span memengaruhi teks: span dapat memengaruhi tampilan teks atau metrik teks.
- Cakupan span: beberapa span dapat diterapkan ke karakter individual, sementara yang lain harus diterapkan ke seluruh paragraf.

Bagian berikut menjelaskan kategori ini secara lebih mendetail.
Span yang memengaruhi tampilan teks
Beberapa rentang yang diterapkan pada tingkat karakter memengaruhi tampilan teks, seperti mengubah warna teks atau latar belakang dan menambahkan garis bawah atau coretan. Span
ini memperluas
class CharacterStyle
.
Contoh kode berikut menunjukkan cara menerapkan UnderlineSpan
untuk memberi garis bawah pada teks:
Kotlin
val string = SpannableString("Text with underline span") string.setSpan(UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
Java
SpannableString string = new SpannableString("Text with underline span"); string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

UnderlineSpan
.
Span yang hanya memengaruhi tampilan teks memicu penggambaran ulang teks tanpa
memicu penghitungan ulang tata letak. Span ini menerapkan
UpdateAppearance
dan memperluas
CharacterStyle
.
Subclass CharacterStyle
menentukan cara menggambar teks dengan memberikan akses untuk mengupdate TextPaint
.
Span yang memengaruhi metrik teks
Span lain yang diterapkan pada level karakter memengaruhi metrik teks, seperti tinggi
baris dan ukuran teks. Span ini memperluas
class MetricAffectingSpan
.
Contoh kode berikut membuat
RelativeSizeSpan
yang
meningkatkan ukuran teks sebesar 50%:
Kotlin
val string = SpannableString("Text with relative size span") string.setSpan(RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
Java
SpannableString string = new SpannableString("Text with relative size span"); string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

RelativeSizeSpan
.
Menerapkan span yang memengaruhi metrik teks akan menyebabkan objek pengamatan mengukur ulang teks untuk mendapatkan tata letak dan rendering yang benar. Misalnya, mengubah ukuran teks dapat menyebabkan munculnya kata pada baris yang berbeda. Menerapkan span sebelumnya akan memicu pengukuran ulang, penghitungan ulang tata letak teks, dan penggambaran ulang teks.
Span yang memengaruhi metrik teks memperluas class MetricAffectingSpan
, class abstrak yang memungkinkan subclass menentukan pengaruh span terhadap pengukuran teks dengan memberikan akses ke TextPaint
. Karena MetricAffectingSpan
memperluas
CharacterStyle
, subclass akan memengaruhi tampilan teks pada level karakter.
Span yang memengaruhi paragraf
Span juga dapat memengaruhi teks pada level paragraf, seperti mengubah perataan atau margin blok teks. Span yang memengaruhi seluruh paragraf
menerapkan ParagraphStyle
. Untuk
menggunakan span ini, Anda melampirkannya ke seluruh paragraf, kecuali karakter
baris baru akhir. Jika Anda mencoba menerapkan span paragraf ke hal lain selain
seluruh paragraf, Android tidak akan menerapkan span sama sekali.
Gambar 8 menunjukkan cara Android memisahkan paragraf dalam teks.

\n
).
Contoh kode berikut menerapkan
QuoteSpan
ke paragraf. Perhatikan bahwa
jika Anda melampirkan span ke posisi selain awal atau akhir
paragraf, Android tidak akan menerapkan gaya sama sekali.
Kotlin
spannable.setSpan(QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
Java
spannable.setSpan(new QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

QuoteSpan
diterapkan ke paragraf.
Membuat span kustom
Jika Anda membutuhkan fungsi lebih dari yang disediakan dalam span Android yang sudah ada, Anda dapat menerapkan span kustom. Saat menerapkan span Anda sendiri, putuskan apakah span memengaruhi teks pada level karakter atau level paragraf, dan juga apakah span memengaruhi tata letak atau tampilan teks. Cara ini membantu Anda menentukan class dasar yang dapat diperluas dan antarmuka mana yang mungkin perlu Anda terapkan. Gunakan tabel berikut untuk referensi:
Skenario | Class atau antarmuka |
---|---|
Span Anda memengaruhi teks pada level karakter. | CharacterStyle |
Span Anda memengaruhi tampilan teks. | UpdateAppearance |
Span Anda memengaruhi metrik teks. | UpdateLayout |
Span Anda memengaruhi teks pada level paragraf. | ParagraphStyle |
Misalnya, jika Anda perlu menerapkan span kustom yang mengubah ukuran dan warna teks, perluas RelativeSizeSpan
. Melalui pewarisan, RelativeSizeSpan
memperluas CharacterStyle
dan mengimplementasikan dua antarmuka Update
. Karena class ini
sudah menyediakan callback untuk updateDrawState
dan updateMeasureState
,
Anda dapat mengganti callback ini untuk menerapkan perilaku kustom Anda. Kode
berikut membuat span kustom yang memperluas RelativeSizeSpan
dan
menggantikan callback updateDrawState
untuk menyetel warna TextPaint
:
Kotlin
class RelativeSizeColorSpan( size: Float, @ColorInt private val color: Int ) : RelativeSizeSpan(size) { override fun updateDrawState(textPaint: TextPaint) { super.updateDrawState(textPaint) textPaint.color = color } }
Java
public class RelativeSizeColorSpan extends RelativeSizeSpan { private int color; public RelativeSizeColorSpan(float spanSize, int spanColor) { super(spanSize); color = spanColor; } @Override public void updateDrawState(TextPaint textPaint) { super.updateDrawState(textPaint); textPaint.setColor(color); } }
Contoh ini menggambarkan cara membuat span kustom. Anda dapat memperoleh efek yang sama dengan menerapkan RelativeSizeSpan
dan ForegroundColorSpan
pada teks.
Menguji penggunaan span
Antarmuka Spanned
memungkinkan Anda menyetel span dan mengambil span dari
teks. Saat menguji, terapkan pengujian JUnit Android untuk memverifikasi bahwa span yang benar telah ditambahkan di lokasi yang benar. Aplikasi contoh Gaya Teks
berisi span yang menerapkan markup ke poin berbutir dengan melampirkan
BulletPointSpan
ke teks. Contoh kode berikut menunjukkan cara menguji
apakah poin berbutir muncul seperti yang diharapkan:
Kotlin
@Test fun textWithBulletPoints() { val result = builder.markdownToSpans("Points\n* one\n+ two") // Check whether the markup tags are removed. assertEquals("Points\none\ntwo", result.toString()) // Get all the spans attached to the SpannedString. val spans = result.getSpans<Any>(0, result.length, Any::class.java) // Check whether the correct number of spans are created. assertEquals(2, spans.size.toLong()) // Check whether the spans are instances of BulletPointSpan. val bulletSpan1 = spans[0] as BulletPointSpan val bulletSpan2 = spans[1] as BulletPointSpan // Check whether the start and end indices are the expected ones. assertEquals(7, result.getSpanStart(bulletSpan1).toLong()) assertEquals(11, result.getSpanEnd(bulletSpan1).toLong()) assertEquals(11, result.getSpanStart(bulletSpan2).toLong()) assertEquals(14, result.getSpanEnd(bulletSpan2).toLong()) }
Java
@Test public void textWithBulletPoints() { SpannedString result = builder.markdownToSpans("Points\n* one\n+ two"); // Check whether the markup tags are removed. assertEquals("Points\none\ntwo", result.toString()); // Get all the spans attached to the SpannedString. Object[] spans = result.getSpans(0, result.length(), Object.class); // Check whether the correct number of spans are created. assertEquals(2, spans.length); // Check whether the spans are instances of BulletPointSpan. BulletPointSpan bulletSpan1 = (BulletPointSpan) spans[0]; BulletPointSpan bulletSpan2 = (BulletPointSpan) spans[1]; // Check whether the start and end indices are the expected ones. assertEquals(7, result.getSpanStart(bulletSpan1)); assertEquals(11, result.getSpanEnd(bulletSpan1)); assertEquals(11, result.getSpanStart(bulletSpan2)); assertEquals(14, result.getSpanEnd(bulletSpan2)); }
Untuk contoh pengujian lainnya, lihat MarkdownBuilderTest di GitHub.
Menguji rentang kustom
Saat menguji rentang, verifikasi bahwa TextPaint
berisi modifikasi yang diharapkan dan elemen yang benar muncul di Canvas
Anda. Misalnya,
perhatikan penerapan span kustom yang menambahkan poin berbutir ke
beberapa teks. Poin berbutir memiliki ukuran dan warna yang ditentukan, dan ada jarak
antara margin kiri area yang dapat digambar dan poin berbutir.
Anda dapat menguji perilaku class ini dengan menerapkan pengujian AndroidJUnit, yang memeriksa hal berikut:
- Jika Anda menerapkan span dengan benar, poin berbutir untuk ukuran dan warna yang ditentukan muncul di kanvas, dan terdapat ruang yang sesuai di antara margin kiri dan poin berbutir.
- Jika Anda tidak menerapkan span, tidak satu pun perilaku kustom yang akan muncul.
Anda dapat melihat penerapan pengujian ini dalam contoh TextStylingdi GitHub.
Anda dapat menguji interaksi Canvas dengan meniru canvas, meneruskan objek yang ditiru ke metode drawLeadingMargin()
, dan memverifikasi bahwa metode yang benar telah dipanggil dengan parameter yang benar.
Anda dapat menemukan lebih banyak contoh pengujian span dalam BulletPointSpanTest.
Praktik terbaik untuk menggunakan span
Ada beberapa cara hemat memori untuk menyetel teks dalam TextView
, bergantung pada kebutuhan Anda.
Melampirkan atau melepaskan span tanpa mengubah teks yang mendasari
TextView.setText()
berisi beberapa overload yang menangani span secara berbeda. Misalnya, Anda dapat
menyetel objek teks Spannable
dengan kode berikut:
Kotlin
textView.setText(spannableObject)
Java
textView.setText(spannableObject);
Saat memanggil overload setText()
ini, TextView
akan membuat salinan file
Spannable
sebagai SpannedString
dan menyimpannya dalam memori sebagai CharSequence
.
Artinya, teks dan rentang Anda tidak dapat diubah, sehingga saat perlu mengupdate teks atau rentang, buat objek Spannable
baru dan panggil kembali setText()
, yang juga akan memicu pengukuran ulang dan menggambar ulang tata letak.
Untuk menunjukkan bahwa rentang harus dapat diubah, Anda dapat menggunakan
setText(CharSequence text, TextView.BufferType
type)
,
seperti yang ditunjukkan pada contoh berikut:
Kotlin
textView.setText(spannable, BufferType.SPANNABLE) val spannableText = textView.text as Spannable spannableText.setSpan( ForegroundColorSpan(color), 8, spannableText.length, SPAN_INCLUSIVE_INCLUSIVE )
Java
textView.setText(spannable, BufferType.SPANNABLE); Spannable spannableText = (Spannable) textView.getText(); spannableText.setSpan( new ForegroundColorSpan(color), 8, spannableText.getLength(), SPAN_INCLUSIVE_INCLUSIVE);
Dalam contoh ini, parameter
BufferType.SPANNABLE
menyebabkan TextView
membuat SpannableString
, dan objek
CharSequence
yang disimpan oleh TextView
sekarang memiliki markup yang dapat diubah dan
teks yang tidak dapat diubah. Untuk mengupdate rentang, ambil teks sebagai Spannable
, lalu
perbarui rentang sesuai kebutuhan.
Jika Anda melampirkan, melepaskan, atau mengubah posisi span, TextView
akan otomatis
diupdate untuk merefleksikan perubahan pada teks. Jika Anda mengubah atribut internal
span yang sudah ada, panggil invalidate()
untuk membuat perubahan terkait tampilan atau
requestLayout()
untuk membuat perubahan terkait metrik.
Menyetel teks di TextView beberapa kali
Dalam beberapa kasus, seperti saat menggunakan
RecyclerView.ViewHolder
,
Anda mungkin perlu menggunakan kembali TextView
dan menyetel teks beberapa kali. Secara default, terlepas dari apakah Anda menyetel BufferType
atau tidak, TextView
akan membuat salinan objek CharSequence
dan menyimpannya di memori. Hal ini membuat semua update TextView
bersifat intensional. Anda tidak dapat mengupdate objek CharSequence
yang asli untuk mengupdate teks. Ini berarti setiap kali Anda menyetel teks baru, TextView
akan membuat objek baru.
Jika ingin lebih mengontrol proses ini dan menghindari pembuatan objek tambahan, Anda dapat menerapkan Spannable.Factory
Anda sendiri dan menggantikan newSpannable()
.
Bukan dengan membuat objek teks baru, Anda dapat mentransmisikan dan mengembalikan
CharSequence
yang sudah ada sebagai Spannable
, seperti yang ditunjukkan dalam contoh berikut:
Kotlin
val spannableFactory = object : Spannable.Factory() { override fun newSpannable(source: CharSequence?): Spannable { return source as Spannable } }
Java
Spannable.Factory spannableFactory = new Spannable.Factory(){ @Override public Spannable newSpannable(CharSequence source) { return (Spannable) source; } };
Anda harus menggunakan textView.setText(spannableObject, BufferType.SPANNABLE)
saat
menyetel teks. Jika tidak, CharSequence
sumber akan dibuat sebagai instance Spanned
dan tidak dapat ditransmisikan ke Spannable
, yang menyebabkan newSpannable()
memunculkan
ClassCastException
.
Setelah menggantikan newSpannable()
, beri tahu TextView
untuk menggunakan Factory
baru:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
Tetapkan objek Spannable.Factory
satu kali, tepat setelah Anda mendapatkan referensi ke
TextView
Anda. Jika Anda menggunakan RecyclerView
, setel objek Factory
saat Anda
pertama kali memperbesar tampilan. Hal ini dilakukan untuk menghindari pembuatan objek tambahan saat
RecyclerView
Anda mengikat item baru ke ViewHolder
.
Mengubah atribut span internal
Jika Anda hanya perlu mengubah atribut internal span yang dapat diubah, seperti
warna butir dalam span butir kustom, Anda dapat menghindari overhead memanggil
setText()
beberapa kali dengan menyimpan referensi ke span seperti saat dibuat.
Jika perlu mengubah span, Anda dapat mengubah referensi, lalu memanggil invalidate()
atau requestLayout()
pada TextView
, bergantung pada jenis atribut yang Anda ubah.
Pada contoh kode berikut, penerapan poin berbutir kustom memiliki warna default merah yang berubah menjadi abu-abu saat tombol diketuk:
Kotlin
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 the mutable span. bulletSpan.color = Color.GRAY // Color doesn't change until invalidate is called. styledText.invalidate() } } }
Java
public class MainActivity extends AppCompatActivity { private BulletPointSpan bulletSpan = new BulletPointSpan(Color.RED); @Override protected void onCreate(Bundle savedInstanceState) { ... SpannableString spannable = new 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(new View.OnClickListener() { @Override public void onClick(View view) { // Change the color of the mutable span. bulletSpan.setColor(Color.GRAY); // Color doesn't change until invalidate is called. styledText.invalidate(); } }); } }
Menggunakan fungsi ekstensi Android KTX
Android KTX juga berisi fungsi ekstensi yang mempermudah kita saat bekerja dengan span. Untuk mempelajari lebih lanjut, lihat dokumentasi paket androidx.core.text.