Durasi

Mencoba cara Compose
Jetpack Compose adalah toolkit UI yang direkomendasikan untuk Android. Pelajari cara menggunakan teks di Compose.

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 akhirtanda 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
);
Gambar yang menampilkan teks abu-abu, sebagian berwarna merah.
Gambar 1. Teks diberi gaya dengan ForegroundColorSpan.

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)");
Gambar yang menunjukkan bagaimana span menyertakan lebih banyak teks saat SPAN_EXCLUSIVE_INCLUSIVE digunakan.
Gambar 2. Span akan diperluas untuk mencakup teks tambahan saat menggunakan Spannable.SPAN_EXCLUSIVE_INCLUSIVE.

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
);
Gambar yang menampilkan teks dengan beberapa span: `ForegroundColorSpan(Color.RED)` dan `StyleSpan(BOLD)`
Gambar 3. Teks dengan beberapa span: 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:

  • 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.
Gambar yang menunjukkan kategori span yang berbeda-beda
Gambar 4. Kategori span Android.

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);
Gambar yang menunjukkan cara menggarisbawahi teks menggunakan `UnderlineSpan`
Gambar 5. Teks yang digarisbawahi menggunakan 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 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);
Gambar yang menunjukkan penggunaan RelativeSizeSpan
Gambar 6. Teks dibuat lebih besar menggunakan RelativeSizeSpan.

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.

Gambar 7. Di Android, paragraf diakhiri dengan karakter baris baru (\n).

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);
Gambar yang menunjukkan contoh QuoteSpan
Gambar 8. QuoteSpan yang diterapkan pada 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, 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.