スパンは強力なマークアップ オブジェクトで、
適用できます。テキスト オブジェクトにスパンをアタッチすると、
色を追加する、テキストをクリック可能にする、
テキストサイズのスケーリング、カスタマイズされた方法でのテキストの描画などが可能です。スパンは
TextPaint
プロパティを変更するため、
Canvas
、テキストのレイアウトを変更します。
Android には、さまざまなタイプのスパンが用意されており、一般的なテキスト スタイル パターンを幅広くカバーします。また、独自のスパンを作成して、カスタム スタイルを適用することもできます。
スパンを作成して適用する
スパンを作成するには、次の表に示すクラスのいずれかを使用します。 クラスは、テキスト自体が可変であるかどうか、テキストが可変であるか、 マークアップ、およびスパンデータを格納する基になるデータ構造などを定義します。
クラス | テキストは変更可能か? | マークアップは変更可能か? | どのようなデータ構造か? |
---|---|---|---|
SpannedString |
× | × | リニア配列 |
SpannableString |
× | ○ | リニア配列 |
SpannableStringBuilder |
○ | ○ | 区間ツリー |
3 つのクラスはすべて Spanned
を拡張します。
行うことができます。また、SpannableString
と SpannableStringBuilder
の場合は、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:
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE )
SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE );

ForegroundColorSpan
。
スパンは Spannable.SPAN_EXCLUSIVE_INCLUSIVE
を使用して設定されるため、スパンは
次のスライドに示すように、スパン境界に挿入されたテキストが含まれるように展開されます。
次の例をご覧ください。
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) spannable.insert(12, "(& fon)")
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
。
1 つのテキストに複数のスパンをアタッチできます。次の例は 太字で赤色のテキストを作成します。
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 )
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)
。
Android のスパンタイプ
Android は、android.text.style パッケージ内に 20 種以上のスパンタイプを用意しています。Android では、次の 2 つの基準に基づいて、スパンを大まかに分類しています。
- スパンがテキストに与える影響: スパンはテキストの外観やテキストに与える影響 できます。
- スパンスコープ: 個々の文字に適用できるスパンと 段落全体に適用する必要があります。

以降のセクションでは、これらのカテゴリについて詳しく説明します。
テキストの外観に影響するスパン
文字レベルで適用される一部のスパンは、次のようにテキストの外観に影響します。
テキストや背景色の変更、下線や取り消し線の追加などを行えます。これらの
スパンは、
CharacterStyle
クラス。
次のコード例は、下線に UnderlineSpan
を適用する方法を示しています。
説明します。
val string = SpannableString("Text with underline span") string.setSpan(UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
SpannableString string = new SpannableString("Text with underline span"); string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

UnderlineSpan
。
テキストの外観だけに影響するスパンは、レイアウトの再計算をトリガーせずに、テキストの再描画をトリガーします。これらのスパンは
UpdateAppearance
と拡張
CharacterStyle
。
CharacterStyle
のサブクラスは、テキストの描画方法を定義するため、
TextPaint
を更新します。
テキストのサイズに影響するスパン
文字レベルで適用されるその他のスパンは、行などのテキスト指標に影響します。
テキストサイズです。これらのスパンは、
MetricAffectingSpan
クラスです。
次のコード例は、
RelativeSizeSpan
。
テキストサイズを 50% 拡大:
val string = SpannableString("Text with relative size span") string.setSpan(RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
SpannableString string = new SpannableString("Text with relative size span"); string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

RelativeSizeSpan
。
テキスト指標に影響するスパンを適用すると、モニタリング対象オブジェクトが 正しいレイアウトとレンダリングが行われるようにテキストを再測定します。たとえば、 異なる行に表示されることがあるため、前述の テキスト レイアウトの再測定、テキスト レイアウトの再計算、 表示されます。
テキスト指標に影響するスパンは、MetricAffectingSpan
クラスを拡張します。
スパンがテキスト測定に与える影響をサブクラスで定義できる抽象クラスです。
TextPaint
へのアクセス権を付与します。MetricAffectingSpan
の拡張
CharacterSpan
- サブクラスは文字位置のテキストの外観に影響
できます。
段落に影響するスパン
スパンは、段落レベルのテキストにも影響します。たとえば、
テキスト ブロックの配置や余白を指定します。段落全体に影響するスパンは、ParagraphStyle
を実装します。宛先
使用する場合は、末尾を除く段落全体に適用します
使用します。段落スパンを
Android ではスパンがまったく適用されません。
Android がテキスト内で段落を分離する方法を図 8 に示します。

\n
)。
次のコード例は、
QuoteSpan
を段落に変更します。注:
スペースの最初と最後以外の位置にスパンを
Android ではスタイルがまったく適用されません。
spannable.setSpan(QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(new QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

QuoteSpan
適用しました。
カスタムスパンを作成する
Android の既存のスパンで実現できる機能よりも高度な機能が必要な場合は、カスタムスパンを実装します。独自のスパンを実装する場合は スパンが文字レベルでのテキストと段落レベルのテキストのどちらに影響するかを指定します。 テキストのレイアウトや外観に影響するかどうかも確認します。これにより、 拡張できる基本クラスと必要になる可能性があるインターフェースを決定する 説明します。次の表を参考にしてください。
シナリオ | クラスまたはインターフェース |
---|---|
文字単位でテキストに影響するスパンの場合。 | CharacterStyle |
テキストの外観に影響するスパンの場合。 | UpdateAppearance |
テキストのサイズに影響するスパンの場合。 | UpdateLayout |
段落単位でテキストに影響するスパンの場合。 | ParagraphStyle |
たとえば、テキストサイズを変更するカスタムスパンを実装し、
色、RelativeSizeSpan
を拡張します。継承により、RelativeSizeSpan
CharacterStyle
を拡張し、2 つの Update
インターフェースを実装します。これが
クラスはすでに updateDrawState
と updateMeasureState
のコールバックを提供しているため、
これらのコールバックをオーバーライドして、カスタム動作を実装できます。「
次のコードは、RelativeSizeSpan
を拡張するカスタムスパンを作成し、
updateDrawState
コールバックをオーバーライドして、TextPaint
の色を設定します。
class RelativeSizeColorSpan( size: Float, @ColorInt private val color: Int ) : RelativeSizeSpan(size) { override fun updateDrawState(textPaint: TextPaint) { super.updateDrawState(textPaint) textPaint.color = color } }
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); } }
この例では、カスタムスパンを作成する方法を示します。「新規顧客の獲得」目標を
テキストに RelativeSizeSpan
と ForegroundColorSpan
を適用して、効果を適用します。
スパンの使用方法をテストする
Spanned
インターフェースを使用すると、スパンの設定と取得の両方を行うことができます。
あります。テストの際には、Android JUnit
test で、正しいスパンが追加されていることを確認します。
確認します。テキストのスタイル設定のサンプル
アプリ
には、
BulletPointSpan
を追加します。次のコード例は、
期待どおりに表示されるかどうか:
@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()) }
@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
テキスト オブジェクトを設定します。
この setText()
のオーバーロードを呼び出すと、TextView
は、Spannable
のコピーを SpannedString
として作成し、メモリ内に CharSequence
として保持します。つまり、テキストとスパンは不変であるため、必要に応じて
テキストまたはスパンを更新します。新しい Spannable
オブジェクトを作成して、
setText()
を再度呼び出します。これにより、画像の再測定と再描画もトリガーされます。
できます。
スパンを変更可能にする必要があることを示すには、代わりに次のコマンドを使用します。
setText(CharSequence text, TextView.BufferType
type)
次のように指定します。
textView.setText(spannable, BufferType.SPANNABLE) val spannableText = textView.text as Spannable spannableText.setSpan( ForegroundColorSpan(color), 8, spannableText.length, SPAN_INCLUSIVE_INCLUSIVE )
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
:
val spannableFactory = object : Spannable.Factory() { override fun newSpannable(source: CharSequence?): Spannable { return source as Spannable } }
Spannable.Factory spannableFactory = new Spannable.Factory(){
@Override
public Spannable newSpannable(CharSequence source) {
return (Spannable) source;
}
};
次の場合は textView.setText(spannableObject, BufferType.SPANNABLE)
を使用する必要があります。
行います。それ以外の場合、ソース CharSequence
は Spanned
として作成されます。
Spannable
にキャストできないため、newSpannable()
が
ClassCastException
。
newSpannable()
をオーバーライドした後、新しい Factory
を使用するように TextView
に指示します。
textView.setSpannableFactory(spannableFactory)
textView.setSpannableFactory(spannableFactory);
参照を取得したら、次は Spannable.Factory
オブジェクトを 1 回設定します。
TextView
。RecyclerView
を使用している場合は、実行時に Factory
オブジェクトを設定します。
まずビューをインフレートしますそうすれば、プロジェクト作成時に
RecyclerView
は、新しいアイテムを ViewHolder
にバインドします。
内部スパン属性を変更する
変更可能なスパンの内部属性(
カスタム箇条書きのスパンで箇条書きにすることで、
作成中のスパンへの参照を保持することにより、setText()
を複数回作成します。
スパンを変更する必要がある場合は、参照を変更してから
タイプに応じて、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 the mutable span. bulletSpan.color = Color.GRAY // Color doesn't change until invalidate is called. styledText.invalidate() } } }
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 パッケージのドキュメントをご覧ください。