スパン

Compose を試す
Jetpack Compose は、Android に推奨される UI ツールキットです。Compose でテキストを使用する方法について学習します。
<ph type="x-smartling-placeholder"></ph> テキスト内の複数のスタイル →

スパンは強力なマークアップ オブジェクトで、 適用できます。テキスト オブジェクトにスパンをアタッチすると、 色を追加する、テキストをクリック可能にする、 テキストサイズのスケーリング、カスタマイズされた方法でのテキストの描画などが可能です。スパンは TextPaint プロパティを変更するため、 Canvas、テキストのレイアウトを変更します。

Android には、さまざまなタイプのスパンが用意されており、一般的なテキスト スタイル パターンを幅広くカバーします。また、独自のスパンを作成して、カスタム スタイルを適用することもできます。

スパンを作成して適用する

スパンを作成するには、次の表に示すクラスのいずれかを使用します。 クラスは、テキスト自体が可変であるかどうか、テキストが可変であるか、 マークアップ、およびスパンデータを格納する基になるデータ構造などを定義します。

クラス テキストは変更可能か? マークアップは変更可能か? どのようなデータ構造か?
SpannedString × × リニア配列
SpannableString × リニア配列
SpannableStringBuilder 区間ツリー

3 つのクラスはすべて Spanned を拡張します。 行うことができます。また、SpannableStringSpannableStringBuilder の場合は、Spannable インターフェースも拡張します。

どちらを使用するかを決める方法は次のとおりです。

  • テキストやマークアップを作成後に変更しない場合は、SpannedString を使用します。
  • 1 つのテキスト オブジェクトに少数のスパンを接続する必要がある場合や、 テキスト自体は読み取り専用です。SpannableString を使用してください。
  • 作成後にテキストを変更する必要があり、スパンをアタッチする必要がある場合は、 SpannableStringBuilder を使用します。
  • テキスト オブジェクトに多数のスパンをアタッチする必要がある場合は、 テキスト自体が読み取り専用かどうかを示すには、SpannableStringBuilder を使用します。

スパンを適用するには、Spannable オブジェクトに対して setSpan(Object _what_, int _start_, int _end_, int _flags_) を呼び出します。what パラメータは対象のスパンを参照します。 start パラメータと end パラメータはテキストに適用される部分を示します。 そのスパンを適用するテキストを指定します

スパンの境界内にテキストを挿入すると、スパンは自動的に 挿入したテキストを含めます。スパンにテキストを挿入するとき つまり、start インデックスまたは end インデックスにある フラグ パラメータは、挿入したテキストを含むようにスパンを拡張するかどうかを決定します。使用 Spannable.SPAN_EXCLUSIVE_INCLUSIVE 挿入するテキストを含めるようにフラグを設定し、 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 挿入したテキストを除外します

次の例は、Cloud Storage バケットを ForegroundColorSpan を 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
);
グレーのテキストの一部に赤色のテキストが表示されている画像。
図 1. テキストのスタイルが ForegroundColorSpan

スパンは Spannable.SPAN_EXCLUSIVE_INCLUSIVE を使用して設定されるため、スパンは 次のスライドに示すように、スパン境界に挿入されたテキストが含まれるように展開されます。 次の例をご覧ください。

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)");
SPAN_EXCLUSIVE_INCLUSIVE を使用するとスパンにテキストが追加される様子を示す画像。
図 2. スパンは拡張され、 テキストを追加する際に Spannable.SPAN_EXCLUSIVE_INCLUSIVE

1 つのテキストに複数のスパンをアタッチできます。次の例は 太字で赤色のテキストを作成します。

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)` と `StyleSpan(bold)`)を持つテキストを示す画像
図 3. 複数のスパンを持つテキスト: ForegroundColorSpan(Color.RED)StyleSpan(BOLD)

Android のスパンタイプ

Android は、android.text.style パッケージ内に 20 種以上のスパンタイプを用意しています。Android では、次の 2 つの基準に基づいて、スパンを大まかに分類しています。

  • スパンがテキストに与える影響: スパンはテキストの外観やテキストに与える影響 できます。
  • スパンスコープ: 個々の文字に適用できるスパンと 段落全体に適用する必要があります。
で確認できます。 <ph type="x-smartling-placeholder">
</ph> さまざまなスパンカテゴリを示す画像 <ph type="x-smartling-placeholder">
</ph> 図 4.Android スパンのカテゴリ。

以降のセクションでは、これらのカテゴリについて詳しく説明します。

テキストの外観に影響するスパン

文字レベルで適用される一部のスパンは、次のようにテキストの外観に影響します。 テキストや背景色の変更、下線や取り消し線の追加などを行えます。これらの スパンは、 CharacterStyle クラス。

次のコード例は、下線に UnderlineSpan を適用する方法を示しています。 説明します。

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` を使用してテキストに下線を付ける方法を示す画像
図 5. 下線付きのテキスト: UnderlineSpan

テキストの外観だけに影響するスパンは、レイアウトの再計算をトリガーせずに、テキストの再描画をトリガーします。これらのスパンは UpdateAppearance と拡張 CharacterStyleCharacterStyle のサブクラスは、テキストの描画方法を定義するため、 TextPaint を更新します。

テキストのサイズに影響するスパン

文字レベルで適用されるその他のスパンは、行などのテキスト指標に影響します。 テキストサイズです。これらのスパンは、 MetricAffectingSpan クラスです。

次のコード例は、 RelativeSizeSpan。 テキストサイズを 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 の使用を示す画像
図 6. テキストを拡大しました: RelativeSizeSpan

テキスト指標に影響するスパンを適用すると、モニタリング対象オブジェクトが 正しいレイアウトとレンダリングが行われるようにテキストを再測定します。たとえば、 異なる行に表示されることがあるため、前述の テキスト レイアウトの再測定、テキスト レイアウトの再計算、 表示されます。

テキスト指標に影響するスパンは、MetricAffectingSpan クラスを拡張します。 スパンがテキスト測定に与える影響をサブクラスで定義できる抽象クラスです。 TextPaint へのアクセス権を付与します。MetricAffectingSpan の拡張 CharacterSpan - サブクラスは文字位置のテキストの外観に影響 できます。

段落に影響するスパン

スパンは、段落レベルのテキストにも影響します。たとえば、 テキスト ブロックの配置や余白を指定します。段落全体に影響するスパンは、ParagraphStyle を実装します。宛先 使用する場合は、末尾を除く段落全体に適用します 使用します。段落スパンを Android ではスパンがまったく適用されません。

Android がテキスト内で段落を分離する方法を図 8 に示します。

図 7. Android では、段落の末尾は 改行文字(\n)。

次のコード例は、 QuoteSpan を段落に変更します。注: スペースの最初と最後以外の位置にスパンを Android ではスタイルがまったく適用されません。

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 の例を示す画像
図 8. QuoteSpan 適用しました。

カスタムスパンを作成する

Android の既存のスパンで実現できる機能よりも高度な機能が必要な場合は、カスタムスパンを実装します。独自のスパンを実装する場合は スパンが文字レベルでのテキストと段落レベルのテキストのどちらに影響するかを指定します。 テキストのレイアウトや外観に影響するかどうかも確認します。これにより、 拡張できる基本クラスと必要になる可能性があるインターフェースを決定する 説明します。次の表を参考にしてください。

シナリオ クラスまたはインターフェース
文字単位でテキストに影響するスパンの場合。 CharacterStyle
テキストの外観に影響するスパンの場合。 UpdateAppearance
テキストのサイズに影響するスパンの場合。 UpdateLayout
段落単位でテキストに影響するスパンの場合。 ParagraphStyle

たとえば、テキストサイズを変更するカスタムスパンを実装し、 色、RelativeSizeSpan を拡張します。継承により、RelativeSizeSpan CharacterStyle を拡張し、2 つの Update インターフェースを実装します。これが クラスはすでに updateDrawStateupdateMeasureState のコールバックを提供しているため、 これらのコールバックをオーバーライドして、カスタム動作を実装できます。「 次のコードは、RelativeSizeSpan を拡張するカスタムスパンを作成し、 updateDrawState コールバックをオーバーライドして、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);
    }
}

この例では、カスタムスパンを作成する方法を示します。「新規顧客の獲得」目標を テキストに RelativeSizeSpanForegroundColorSpan を適用して、効果を適用します。

スパンの使用方法をテストする

Spanned インターフェースを使用すると、スパンの設定と取得の両方を行うことができます。 あります。テストの際には、Android JUnit test で、正しいスパンが追加されていることを確認します。 確認します。テキストのスタイル設定のサンプル アプリ には、 BulletPointSpan を追加します。次のコード例は、 期待どおりに表示されるかどうか:

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));
}

その他のテスト例については、以下をご覧ください。 GitHub の MarkdownBuilderTest

カスタムスパンをテストする

スパンをテストする際に、想定する構成が TextPaint に含まれていることを確認する Canvas に正しい要素が表示されることを確認します。たとえば、一部のテキストの先頭に箇条書きを付加するカスタムスパン実装があるとします。箇条書きのサイズと色が指定されていて、空白がある ドローアブル領域の左余白と箇条書きの間に配置します。

このクラスの動作をテストするには、AndroidJUnit テストを実装して、以下の点をチェックします。

  • スパンを正しく適用すると、指定したサイズの箇条書き キャンバスに色が表示され、左側と右側に適切なスペースが 箇条書きにするとよいでしょう。
  • スパンを適用しない場合、カスタム動作は表示されません。

これらのテストの実装は、TextStyling サンプル(GitHub)

キャンバスをモックし、モックされた オブジェクトを drawLeadingMargin() メソッドが呼び出され、正しいメソッドが適切な あります。

その他のスパンテストのサンプルについては、 BulletPointSpanTest

スパンの使用方法に関するベスト プラクティス

TextView にテキストを設定するメモリ効率の高い方法はいくつかあり、 選択できます。

基盤テキストを変更せずにスパンのアタッチやデタッチを行う

TextView.setText() スパンを異なる方法で処理する複数のオーバーロードが含まれています。たとえば 次のコードを使用して Spannable テキスト オブジェクトを設定します。

Kotlin

textView.setText(spannableObject)

Java

textView.setText(spannableObject);

この setText() のオーバーロードを呼び出すと、TextView は、Spannable のコピーを SpannedString として作成し、メモリ内に CharSequence として保持します。つまり、テキストとスパンは不変であるため、必要に応じて テキストまたはスパンを更新します。新しい Spannable オブジェクトを作成して、 setText() を再度呼び出します。これにより、画像の再測定と再描画もトリガーされます。 できます。

スパンを変更可能にする必要があることを示すには、代わりに次のコマンドを使用します。 setText(CharSequence text, TextView.BufferType type) 次のように指定します。

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);

この例では、 BufferType.SPANNABLE パラメータを使用すると、TextView によって SpannableString が作成され、 TextView で保持されている CharSequence オブジェクトに可変マークアップが追加され、 記述します。スパンを更新するには、テキストを Spannable として取得してから、 必要に応じてスパンを更新します。

スパンのアタッチやデタッチ、再配置を行うと、TextView が自動的に更新され、テキストへの変更が反映されます。内部属性を変更すると、 既存のスパンの要素がある場合は、invalidate() を呼び出して外観関連の変更を行うか、 requestLayout(): 指標に関連する変更を行います。

TextView 内でテキストを複数回設定する

RecyclerView.ViewHolder を使用する場合など、TextView を再利用してテキストを複数回設定したいことがあります。方法 BufferType を設定したかどうかに関係なく、TextView によってデフォルト値が作成されます。 CharSequence オブジェクトのコピーを作成してメモリに保持します。これにより、 TextView の更新が意図的なものであり、元のものは更新できません CharSequence オブジェクトを使用してテキストを更新します。つまり、 TextView により新しいオブジェクトが作成されます。

このプロセスをより細かく制御し、余分なオブジェクトが 独自のモジュールを作成して実装できます。 Spannable.Factory とオーバーライド newSpannable()。 新しいテキスト オブジェクトを作成する代わりに、既存のテキストをキャストして返すことができます。 次の例に示すように、Spannable としての CharSequence:

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;
    }
};

次の場合は textView.setText(spannableObject, BufferType.SPANNABLE) を使用する必要があります。 行います。それ以外の場合、ソース CharSequenceSpanned として作成されます。 Spannable にキャストできないため、newSpannable()ClassCastException

newSpannable() をオーバーライドした後、新しい Factory を使用するように TextView に指示します。

Kotlin

textView.setSpannableFactory(spannableFactory)

Java

textView.setSpannableFactory(spannableFactory);

参照を取得したら、次は Spannable.Factory オブジェクトを 1 回設定します。 TextViewRecyclerView を使用している場合は、実行時に Factory オブジェクトを設定します。 まずビューをインフレートしますそうすれば、プロジェクト作成時に RecyclerView は、新しいアイテムを ViewHolder にバインドします。

内部スパン属性を変更する

変更可能なスパンの内部属性( カスタム箇条書きのスパンで箇条書きにすることで、 作成中のスパンへの参照を保持することにより、setText() を複数回作成します。 スパンを変更する必要がある場合は、参照を変更してから タイプに応じて、TextView に対する invalidate() または requestLayout() 属性で識別されます。

次のコード例では、カスタムの箇条書き実装に ボタンがタップされたときにグレーに変わるデフォルトの赤色:

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();
            }
        });
    }
}

Android KTX 拡張関数を使用する

Android KTX には、スパンを操作する拡張関数も含まれています。 簡単になります。詳細については、androidx.core.text パッケージのドキュメントをご覧ください。