Span adalah objek markup canggih yang bisa Anda gunakan untuk menata gaya teks pada
pada tingkat karakter atau paragraf. Dengan melampirkan {i>span<i} ke objek teks, Anda bisa mengubah
teks dalam berbagai cara, termasuk menambahkan
warna, membuat teks dapat diklik,
menskalakan ukuran teks, dan menggambar teks dengan cara yang disesuaikan. Span juga dapat
mengubah properti TextPaint
, gambar 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-class tersebut berbeda berdasarkan apakah teks itu sendiri dapat diubah, apakah teks markup dapat berubah, dan struktur data pokok yang berisi data span.
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 tersebut memperluas Spanned
dalam antarmuka berbasis web
yang sederhana. 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 sejumlah kecil {i>span<i} ke satu objek teks dan
teks itu sendiri bersifat hanya baca, gunakan
SpannableString
. - Jika Anda perlu mengubah teks setelah dibuat dan Anda perlu melampirkan {i>span<i} ke
gunakan
SpannableStringBuilder
. - Jika Anda perlu melampirkan banyak span ke objek teks, terlepas dari
untuk menentukan apakah teks itu sendiri bersifat hanya baca, gunakan
SpannableStringBuilder
.
Untuk menerapkan span, panggil setSpan(Object _what_, int _start_, int _end_, int
_flags_)
pada objek Spannable
. Parameter what mengacu pada span yang Anda
berlaku pada teks, serta parameter start dan end menunjukkan bagian
teks tempat Anda menerapkan bentang.
Jika Anda menyisipkan teks di dalam batas span, span secara otomatis akan diperluas
menyertakan teks yang disisipkan. Saat menyisipkan teks pada span
batas—yaitu di indeks awal atau akhir—tanda
menentukan apakah span diperluas untuk menyertakan teks yang disisipkan. Gunakan
tindakan
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
penanda untuk menyertakan teks yang disisipkan, dan menggunakan
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
untuk mengecualikan teks yang disisipkan.
Contoh berikut menunjukkan cara melampirkan
ForegroundColorSpan
ke
{i>string<i}:
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 );
Karena span disetel menggunakan Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, span
meluas untuk memasukkan 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)");
Anda dapat melampirkan beberapa span pada teks yang sama. Contoh berikut menunjukkan bagaimana untuk 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 );
Jenis span Android
Android menyediakan lebih dari 20 jenis span dalam paket android.text.style. Android mengategorikan span dalam dua cara utama:
- Bagaimana span memengaruhi teks: span dapat memengaruhi tampilan teks atau teks metrik.
- Cakupan span: beberapa span dapat diterapkan ke karakter individual, sementara yang lainnya harus diterapkan ke seluruh paragraf.
Bagian berikut menjelaskan kategori ini secara lebih detail.
Span yang memengaruhi tampilan teks
Beberapa span yang berlaku pada level karakter akan memengaruhi tampilan teks, seperti
mengubah warna teks atau latar belakang dan
menambahkan garis bawah atau coretan. Ini
mencakup
Class CharacterStyle
.
Contoh kode berikut menunjukkan cara menerapkan UnderlineSpan
untuk menggarisbawahi
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);
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 ke
perbarui TextPaint
.
Span yang memengaruhi metrik teks
Span lain yang berlaku pada tingkat karakter memengaruhi metrik teks, seperti baris
tinggi dan ukuran teks. Area ini memperluas
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);
Menerapkan span yang memengaruhi metrik teks akan menyebabkan objek pengamatan mengukur ulang teks untuk tata letak dan rendering yang benar—misalnya, mengubah ukuran teks dapat menyebabkan kata muncul pada baris yang berbeda. Menerapkan pendekatan sebelumnya span memicu pengukuran ulang, penghitungan ulang tata letak teks, dan penggambaran ulang teks.
Span yang memengaruhi metrik teks akan memperluas class MetricAffectingSpan
,
class abstrak yang memungkinkan subclass menentukan cara span memengaruhi pengukuran teks
dengan memberikan akses ke TextPaint
. Karena MetricAffectingSpan
diperluas
CharacterSpan
, subclass memengaruhi tampilan teks pada karakter
level organisasi.
Span yang memengaruhi paragraf
Span juga bisa memengaruhi teks pada level paragraf, seperti mengubah
perataan atau margin blok teks. Span yang memengaruhi seluruh paragraf
menerapkan ParagraphStyle
. Kepada
menggunakan span ini, Anda melampirkannya ke seluruh paragraf, tidak termasuk bagian akhir
karakter baris baru. Jika Anda mencoba menerapkan span paragraf ke sesuatu selain
seluruh paragraf, Android tidak menerapkan rentang sama sekali.
Gambar 8 menunjukkan cara Android memisahkan paragraf dalam teks.
Contoh kode berikut menerapkan
QuoteSpan
ke paragraf. Perlu diketahui bahwa
jika Anda melampirkan bentang ke posisi mana pun selain awal atau akhir
Android tidak menerapkan gaya itu 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);
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, tentukan apakah span Anda mempengaruhi teks pada tingkat karakter atau tingkat paragraf dan juga apakah itu mempengaruhi tata letak atau tampilan teks. Hal ini membantu Anda menentukan class dasar yang dapat diperluas dan antarmuka mana yang mungkin diperlukan untuk diimplementasikan. Gunakan tabel berikut sebagai 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 khusus
yang mengubah ukuran teks dan
warna, perluas RelativeSizeSpan
. Melalui pewarisan, RelativeSizeSpan
memperluas CharacterStyle
dan mengimplementasikan dua antarmuka Update
. Karena langkah ini
sudah menyediakan callback untuk updateDrawState
dan updateMeasureState
,
Anda dapat mengganti callback ini untuk menerapkan perilaku kustom. Tujuan
kode berikut membuat span khusus yang memperluas RelativeSizeSpan
dan
mengganti callback updateDrawState
untuk menetapkan 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 mencapai hal yang sama
dengan menerapkan RelativeSizeSpan
dan ForegroundColorSpan
ke teks.
Menguji penggunaan span
Antarmuka Spanned
memungkinkan Anda menyetel span dan juga mengambil span dari
teks. Saat menguji, implementasikan Android JUnit
uji untuk memverifikasi bahwa span yang benar telah ditambahkan
di lokasi yang tepat. Contoh Gaya Visual Teks
aplikasi
berisi rentang yang menerapkan markup ke poin-poin dengan melampirkan
BulletPointSpan
ke teks. Contoh kode berikut menunjukkan cara menguji
apakah poin-poin 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 span kustom
Saat menguji span, pastikan TextPaint
berisi span yang diharapkan
modifikasi dan elemen yang benar muncul di Canvas
. Misalnya,
perhatikan penerapan span kustom yang menambahkan poin berbutir ke
beberapa teks. Poin-poin memiliki ukuran dan warna tertentu, serta terdapat celah
antara margin kiri area drawable dan poin butir.
Anda dapat menguji perilaku class ini dengan menerapkan pengujian AndroidJUnit, yang memeriksa hal berikut:
- Jika Anda menerapkan span dengan benar, butir dengan ukuran yang ditentukan dan muncul di kanvas, dan ada ruang yang sesuai di antara sisi kiri {i>margin<i} dan poin-poin.
- Jika Anda tidak menerapkan span, tidak satu pun perilaku kustom yang akan muncul.
Anda dapat melihat implementasi pengujian ini di bagian TextStyle contoh di GitHub.
Anda dapat menguji interaksi Canvas dengan meniru kanvas, lalu meneruskan
objek ke
drawLeadingMargin()
verifikasi, dan memverifikasi bahwa metode yang benar dipanggil dengan
parameter.
Anda dapat menemukan lebih banyak sampel pengujian span di BulletPointSpanTest.
Praktik terbaik untuk menggunakan span
Ada beberapa cara yang hemat memori untuk menetapkan teks dalam TextView
, bergantung
sesuai kebutuhan Anda.
Melampirkan atau melepaskan span tanpa mengubah teks yang mendasari
TextView.setText()
berisi beberapa overload yang menangani span secara berbeda. Sebagai contoh, Anda dapat
tetapkan 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 span Anda tidak dapat diubah, jadi saat Anda perlu
mengupdate teks atau span, membuat objek Spannable
baru dan memanggil
setText()
lagi, yang juga memicu pengukuran ulang dan penggambaran ulang objek
tata letak.
Untuk menunjukkan bahwa span harus dapat diubah, Anda dapat menggunakan
setText(CharSequence text, TextView.BufferType
type)
,
seperti yang ditunjukkan dalam 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,
BufferType.SPANNABLE
menyebabkan TextView
membuat SpannableString
, dan
Objek CharSequence
yang disimpan oleh TextView
sekarang memiliki markup yang dapat berubah dan
teks yang tidak dapat diubah. Untuk mengupdate span, ambil teks sebagai Spannable
, lalu
mengupdate span sesuai kebutuhan.
Jika Anda melampirkan, melepaskan, atau mengubah posisi span, TextView
akan otomatis
diupdate untuk merefleksikan perubahan pada teks. Jika Anda mengubah atribut internal
dari span yang 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. Menurut
default, terlepas dari apakah Anda menyetel BufferType
atau tidak, TextView
akan membuat
salinan objek CharSequence
dan menyimpannya di memori. Ini membuat semua
TextView
memperbarui secara sengaja—Anda tidak dapat mengupdate yang asli
CharSequence
untuk memperbarui teks. Ini berarti setiap kali Anda menetapkan
, TextView
akan membuat objek baru.
Jika Anda ingin mengambil kontrol lebih besar atas proses ini dan menghindari objek ekstra
Anda sendiri, Anda dapat menerapkan
Spannable.Factory
dan ganti
newSpannable()
.
Daripada membuat objek teks baru, Anda dapat mentransmisikan dan menampilkan objek teks yang sudah ada
CharSequence
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
mengatur teks. Jika tidak, CharSequence
sumber akan dibuat sebagai Spanned
instance dan tidak dapat ditransmisikan ke Spannable
, sehingga newSpannable()
menampilkan
ClassCastException
.
Setelah mengganti newSpannable()
, beri tahu TextView
untuk menggunakan Factory
yang baru:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
Setel objek Spannable.Factory
sekali, tepat setelah Anda mendapatkan referensi ke
TextView
. Jika Anda menggunakan RecyclerView
, tetapkan objek Factory
saat Anda
meng-inflate penayangan Anda terlebih dahulu. Hal ini untuk menghindari pembuatan objek tambahan
RecyclerView
mengikat item baru ke ViewHolder
Anda.
Mengubah atribut span internal
Jika Anda hanya perlu mengubah atribut internal span yang dapat diubah, seperti
warna butir dalam rentang butir khusus, Anda dapat menghindari overhead memanggil
setText()
beberapa kali dengan menyimpan referensi ke span seperti yang dibuatnya.
Bila perlu mengubah span, Anda bisa mengubah referensi, lalu memanggil
invalidate()
atau requestLayout()
di TextView
, bergantung pada jenis
yang Anda ubah.
Dalam contoh kode berikut, implementasi poin-poin 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 memudahkan penggunaan span semuanya. Untuk mempelajari lebih lanjut, lihat dokumentasi paket androidx.core.text.