Compose のテキスト

テキストは UI の主要な構成要素です。Jetpack Compose を使用すると、テキストの表示や入力を簡単に行うことができます。Compose では構成要素のコンポジションを利用できるため、特定のコンポーザブルの設計とロジックを意図したとおりに機能させるにあたり、プロパティやメソッドを上書きしたり、大きなクラスを拡張したりする必要はありません。

Compose には、テキストの表示とユーザー入力の処理に最低限必要な BasicTextBasicTextField がベースとして用意されています。その上位レベルでは、マテリアル デザイン ガイドラインに沿ったコンポーザブルである TextTextField を利用できます。Android ユーザーに適切な外観と操作感を提供できるほか、多くのコードを書かなくても簡単にカスタマイズできるオプションも利用できるため、これらのコンポーザブルを使用することをおすすめします。

テキストの表示

テキストを表示するための最も基本的な方法は、引数として String を指定して Text コンポーザブルを使用することです。

@Composable
fun SimpleText() {
    Text("Hello World")
}

黒色のプレーン テキストで「Hello World」という語句を表示

リソースを使用してテキストを表示する

Text の値をハードコードするのではなく、文字列リソースを使用することをおすすめします。これにより、Android ビューと同じ文字列を共有できるだけでなく、アプリの国際化もしやすくなります。

@Composable
fun StringResourceText() {
    Text(stringResource(R.string.hello_world))
}

テキストのスタイル設定

Text コンポーザブルには、コンテンツのスタイル設定を行うためのオプションのパラメータが複数用意されています。以下に、テキストに関する最も一般的なユースケースに対応するパラメータを示します。Text のすべてのパラメータを確認するには、Compose Text ソースコードをご覧ください。

これらのパラメータを設定すると、テキスト値全体にスタイルが適用されます。同じ行や段落内に複数のスタイルを適用する必要がある場合は、複数のインライン スタイルのセクションをご覧ください。

テキストの色を変更する

@Composable
fun BlueText() {
    Text("Hello World", color = Color.Blue)
}

「Hello World」という語句を青色のテキストで表示

文字サイズを変更する

@Composable
fun BigText() {
  Text("Hello World", fontSize = 30.sp)
}

「Hello World」という語句を大きな文字サイズで表示

テキストを斜体にする

テキストを斜体にするには fontStyle パラメータを使用します(または別の FontStyle を設定します)。

@Composable
fun ItalicText() {
  Text("Hello World", fontStyle = FontStyle.Italic)
}

「Hello World」という語句を斜体で表示

テキストを太字にする

テキストを太字にするには、fontWeight パラメータを使用します(または別の FontWeight を設定します)。

@Composable
fun BoldText() {
    Text("Hello World", fontWeight = FontWeight.Bold)
}

「Hello World」という語句を太字で表示

テキストの配置

textAlign パラメータを使用すると、Text コンポーザブルの面積内でのテキストの配置を設定できます。

デフォルトでは、Text はコンテンツの値に応じて自然なテキストの配置を選択します。

  • Text コンテナの左端: ラテン文字、キリル文字、ハングルなど、左から右に記述するアルファベットの場合
  • Text コンテナの右端: アラビア語やヘブライ語など、右から左に記述するアルファベットの場合
@Preview(showBackground = true)
@Composable
fun CenterText() {
    Text("Hello World", textAlign = TextAlign.Center,
                modifier = Modifier.width(150.dp))
}

「Hello World」という語句をコンテナ要素の中央に表示

Text コンポーザブルのテキストの配置を手動で設定する場合は、TextAlign.LeftTextAlign.Right の代わりに TextAlign.StartTextAlign.End を使用します。これにより、使用する言語のテキストの向きに応じて、テキストが Text コンポーザブルの適切な側に解決されます。たとえば、TextAlign.End では、フランス語テキストは右側、アラビア語テキストは左側に配置されます。一方、TextAlign.Right では、使用されているアルファベットの種類にかかわらず、テキストが右側に配置されます。

シャドウ

style パラメータを使用すると、TextStyle 型のオブジェクトを設定し、複数のパラメータ(シャドウなど)を構成できます。Shadow は、シャドウの色、オフセット、Text に対するシャドウの相対位置、ぼかしの程度を表すぼかし半径を受け取ります。

@Preview(showBackground = true)
@Composable
fun TextShadow() {
    val offset = Offset(5.0f, 10.0f)
    Text(
        text = "Hello world!",
        style = TextStyle(
            fontSize = 24.sp,
            shadow = Shadow(
                color = Color.Blue,
                offset = offset,
                blurRadius = 3f
            )
        )
    )
}

「Hello World」という語句を青いシャドウで表示

フォントの操作

Text には、コンポーザブルで使用されるフォントを設定するための fontFamily パラメータを指定できます。デフォルトでは、Serif、Sans Serif、Monospace、Cursive のフォント ファミリーが含まれています

@Composable
fun DifferentFonts() {
    Column {
        Text("Hello World", fontFamily = FontFamily.Serif)
        Text("Hello World", fontFamily = FontFamily.SansSerif)
    }
}

「Hello World」という語句を 2 つのフォント(セリフ体とサンセリフ体)で表示

fontFamily 属性を使用して、res/font フォルダで定義されているカスタムのフォントと書体を操作できます。

開発環境の res > font フォルダの図

次の例は、これらのフォント ファイルに基づき、Font 関数を使用して、fontFamily を定義する方法を示しています。

val firaSansFamily = FontFamily(
        Font(R.font.firasans_light, FontWeight.Light),
        Font(R.font.firasans_regular, FontWeight.Normal),
        Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
        Font(R.font.firasans_medium, FontWeight.Medium),
        Font(R.font.firasans_bold, FontWeight.Bold)
)

最後に、この fontFamilyText コンポーザブルに渡すことができます。fontFamily には、異なる太さを含めることが可能です。その場合は、次のように手動で fontWeight を設定して、テキストに適した太さを選択します。

Column {
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
    Text(
        ..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal,
        fontStyle = FontStyle.Italic
    )
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
    Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
}

「Hello World」という語句をさまざまな太さとスタイルで表示

アプリ全体のタイポグラフィを設定する方法については、テーマのドキュメントをご覧ください。

1 つのテキストに複数のスタイルを設定する

同じ Text コンポーザブルに複数のスタイルを設定するには、AnnotatedString を使用する必要があります。これは、任意のアノテーションのスタイルでアノテーションを付けることができる文字列です。

AnnotatedString は、以下を含むデータクラスです。

  • Text
  • SpanStyleRangeList(テキスト値内の位置範囲を使用したインライン スタイル設定と同等です)
  • ParagraphStyleRangeList(テキストの配置、テキスト方向、行の高さ、テキストのインデント スタイルを指定します)

TextStyleText コンポーザブルで使用され、SpanStyleParagraphStyleAnnotatedString で使用されます。

SpanStyleParagraphStyle の違いは、ParagraphStyle は段落全体に適用できるのに対し、SpanStyle は文字単位で適用できる点にあります。テキストの一部を ParagraphStyle でマークすると、その部分は先頭と末尾にラインフィードがあるものとして残りのテキストから分離されます。

AnnotatedString は、タイプセーフなビルダーを使用して buildAnnotatedString を簡単に作成できます。

@Composable
fun MultipleStylesInText() {
    Text(
        buildAnnotatedString {
            withStyle(style = SpanStyle(color = Color.Blue)) {
                append("H")
            }
            append("ello ")

            withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
                append("W")
            }
            append("orld")
        }
    )
}

「Hello World」という語句を、インラインで複数のスタイル変更(H は青色、W は赤色と太字)を行って表示

ParagraphStyle も同じ方法で設定できます。

@Composable
fun ParagraphStyle() {
    Text(
        buildAnnotatedString {
            withStyle(style = ParagraphStyle(lineHeight = 30.sp)) {
                withStyle(style = SpanStyle(color = Color.Blue)) {
                    append("Hello\n")
                }
                withStyle(
                    style = SpanStyle(
                        fontWeight = FontWeight.Bold,
                        color = Color.Red
                    )
                ) {
                    append("World\n")
                }
                append("Compose")
            }
        }
    )
}

3 つの段落を 3 種類のスタイル(青色、赤色と太字、黒色)で表示

最大行数

Text コンポーザブルに表示する行数を制限するには、maxLines パラメータを設定します。

@Composable
fun LongText() {
    Text("hello ".repeat(50), maxLines = 2)
}

2 行表示された後に切り捨てられた長いテキスト

テキスト オーバーフロー

長いテキストを制限する場合は、TextOverflow を指定することをおすすめします。これは、表示されているテキストが切り捨てられた場合にのみ表示されます。これを行うには、textOverflow パラメータを設定します。

@Composable
fun OverflowedText() {
    Text("Hello Compose ".repeat(50), maxLines = 2, overflow = TextOverflow.Ellipsis)
}

2 行表示された後に切り捨てられた長いテキスト。末尾に省略記号が表示されます

includeFontPadding API と lineHeight API

includeFontPadding は、フォント指標に基づいてテキストの最初の行の上と最後の行の下にパディングを追加するレガシー プロパティです。Compose 1.2.0 では、includeFontPadding はデフォルトで true に設定されています。

Compose 1.2.0 では、試験運用版 / 非推奨 API PlatformTextStyle を使用して includeFontPaddingfalse に設定し(追加のパディングが削除されます)、必要に応じてテキストを調整することをおすすめします。

@Composable
fun AlignedText() {
    Text(
        text = myText,
        style = LocalTextStyle.current.merge(
            TextStyle(
                lineHeight = 2.5.em,
                platformStyle = PlatformTextStyle(
                    includeFontPadding = false
                ),
                lineHeightStyle = LineHeightStyle(
                    alignment = LineHeightStyle.Alignment.Center,
                    trim = LineHeightStyle.Trim.None
                )
            )
        )
    )
}

Compose の今後のバージョンでは、includeFontPadding はデフォルトで false に設定され、PlatformTextStyle API は削除される予定です。

この変更の背景、includeFontPadding がビューシステムでどのように機能していたか、Google が Compose に加えた変更、新しい LineHeightStyle API の詳細については、Compose テキストにおけるフォントのパディングの修正に関するブログ記事をご覧ください。

テーマ設定

テキストのスタイル設定にアプリのテーマを使用する場合は、テーマのドキュメントをご覧ください。

ユーザー操作

Jetpack Compose により、Text でのきめ細かい操作が可能となり、テキストをコンポーザブル レイアウト全体でより柔軟に選択できるようになりました。テキストでのユーザー操作では、他のコンポーザブル レイアウトとは異なり、Text コンポーザブルの一部に修飾子を追加することができません。このセクションでは、ユーザー操作を可能にするさまざまな API について説明します。

テキストの選択

コンポーザブルはデフォルトでは選択不可であるため、ユーザーがアプリからテキストを選択してコピーすることはできません。テキストの選択を有効にするには、テキスト要素を SelectionContainer コンポーザブルでラップする必要があります。

@Composable
fun SelectableText() {
    SelectionContainer {
        Text("This text is selectable")
    }
}

ユーザーが選択した短い文。

選択可能な領域の特定の部分で選択を無効にすることもできます。それには、選択不可にする部分を DisableSelection コンポーザブルでラップする必要があります。

@Composable
fun PartiallySelectableText() {
    SelectionContainer {
        Column {
            Text("This text is selectable")
            Text("This one too")
            Text("This one as well")
            DisableSelection {
                Text("But not this one")
                Text("Neither this one")
            }
            Text("But again, you can select this one")
            Text("And this one too")
        }
    }
}

長い文。ユーザーは文全体を選択しようとしましたが、DisableSelection が適用された 2 つの行は選択されませんでした。

テキストのクリック位置を取得する

Text のクリックをリッスンするには、clickable 修飾子を追加します。ただし、Text コンポーザブル内でクリック位置を取得して、その位置によって異なるアクションを提供する場合は、代わりに ClickableText を使用する必要があります。

@Composable
fun SimpleClickableText() {
    ClickableText(
        text = AnnotatedString("Click Me"),
        onClick = { offset ->
            Log.d("ClickableText", "$offset -th character is clicked.")
        }
    )
}

アノテーション付きのクリック

ユーザーが Text コンポーザブルをクリックしたときに、Text 値の一部に情報を追加することもできます(特定の単語にアタッチされている URL をブラウザで開く、など)。そのためには、タグ(String)、アイテム(String)、テキスト範囲をパラメータとして取るアノテーションを追加する必要があります。AnnotatedString から、これらのアノテーションをタグまたはテキスト範囲でフィルタできます。次に例を示します。

@Composable
fun AnnotatedClickableText() {
    val annotatedText = buildAnnotatedString {
        append("Click ")

        // We attach this *URL* annotation to the following content
        // until `pop()` is called
        pushStringAnnotation(tag = "URL",
                             annotation = "https://developer.android.com")
        withStyle(style = SpanStyle(color = Color.Blue,
                                    fontWeight = FontWeight.Bold)) {
            append("here")
        }

        pop()
    }

    ClickableText(
        text = annotatedText,
        onClick = { offset ->
            // We check if there is an *URL* annotation attached to the text
            // at the clicked position
            annotatedText.getStringAnnotations(tag = "URL", start = offset,
                                                    end = offset)
                .firstOrNull()?.let { annotation ->
                    // If yes, we log its value
                    Log.d("Clicked URL", annotation.item)
                }
        }
    )
}

テキストの入力と変更

TextField を使用すると、ユーザーがテキストの入力や変更を行えるようになります。TextField の実装には次の 2 つのレベルがあります。

  1. TextField はマテリアル デザインの実装であり、次のようなマテリアル デザイン ガイドラインに沿っているため、この実装を選択することをおすすめします。
    • デフォルトのスタイル設定は塗りつぶしです。
    • OutlinedTextField は、枠線のスタイル設定バージョンです。
  2. BasicTextField を使用すると、ユーザーはハードウェア キーボードまたはソフトウェア キーボードを使用してテキストを編集できます。ただし、ヒントやプレースホルダのような装飾は用意されていません。
@Composable
fun SimpleFilledTextFieldSample() {
    var text by remember { mutableStateOf("Hello") }

    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

「Hello」という単語が入力された編集可能なテキスト フィールド。フィールドに編集不可のラベル「Label」が含まれています。

@Composable
fun SimpleOutlinedTextFieldSample() {
    var text by remember { mutableStateOf("") }

    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

紫色の枠線とラベルが付いた編集可能なテキスト フィールド。

TextField のスタイル設定

TextFieldBasicTextField は、カスタマイズ用のパラメータを多数共有しています。TextField の完全なリストについては、TextField ソースコードをご覧ください。有用なパラメータの一部を以下に示します。

  • singleLine
  • maxLines
  • textStyle
@Composable
fun StyledTextField() {
    var value by remember { mutableStateOf("Hello\nWorld\nInvisible") }

    TextField(
        value = value,
        onValueChange = { value = it },
        label = { Text("Enter text") },
        maxLines = 2,
        textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
        modifier = Modifier.padding(20.dp)
    )
}

ラベルと 2 つの編集可能な行がある、複数行の TextField

デザインにマテリアルの TextField または OutlineTextField が必要な場合は、BasicTextField ではなく TextField をおすすめします。ただし、マテリアル仕様の装飾を必要としないデザインを構築する場合には、BasicTextField を使用する必要があります。

キーボード オプション

TextField を使用すると、キーボード レイアウトなどのキーボード構成オプションを設定できます。また、キーボードでサポートされている場合は、自動修正を有効にすることも可能です。ソフトウェア キーボードが次に示すオプションに対応していない場合、一部のオプションは保証されない可能性があります。サポートされているキーボード オプションのリストを次に示します。

  • capitalization
  • autoCorrect
  • keyboardType
  • imeAction

形式

TextField を使用すると、入力値に VisualTransformation を設定できます。たとえば、パスワードの文字を * で置き換えたり、クレジット カード番号の 4 桁の数字ごとにハイフンを挿入したりできます。

@Composable
fun PasswordTextField() {
    var password by rememberSaveable { mutableStateOf("") }

    TextField(
        value = password,
        onValueChange = { password = it },
        label = { Text("Enter password") },
        visualTransformation = PasswordVisualTransformation(),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
    )
}

文字がマスクされたパスワード入力欄

その他の例については、VisualTransformSamples ソースコードをご覧ください。

入力のクリーニング

テキストを編集する場合の一般的なタスクは、先頭の文字列を削除することか、あるいは変更されるたびに入力文字列を変換することです。

一つのモデルとして、onValueChange ごとにキーボードから任意の大幅な編集がなされる場合を考える必要があります。たとえば、ユーザーが自動修正や、単語の絵文字への置き換えなど、高度な編集機能を使用する場合です。これを適切に処理するには、今回 onValueChange に渡されるテキストは、前回または次回 onValueChange に渡される値と無関係であるという前提で、変換ロジックを作成します。

また、先頭のゼロを禁止するテキスト フィールドを実装するには、値が変更されるたびに先頭のゼロをすべて削除します。

@Composable
fun NoLeadingZeroes() {
  var input by rememberSaveable { mutableStateOf("") }
  TextField(
      value = input,
      onValueChange = { newText ->
          input = newText.trimStart { it == '0' }
      }
  )
}

テキストを削除する際にカーソルの位置を制御するには、状態の一部として TextFieldTextFieldValue オーバーロードを使用します。

ダウンロード可能なフォント

Compose 1.2-alpha07 以降では、Compose アプリでダウンロード可能なフォント API を使用して Google Fonts を非同期でダウンロードし、アプリで使用することができます。

カスタム プロバイダが提供するダウンロード可能なフォントは、現時点ではサポートされていません。

プログラムを介してダウンロード可能なフォントを使用する

アプリ内からプログラムを介してフォントをダウンロードするには、次の手順を行います。

  1. 依存関係を追加します。

    Groovy

    dependencies {
        ...
        implementation "androidx.compose.ui:ui-text-google-fonts:1.2.0-beta01"
    }
    

    Kotlin

    dependencies {
        ...
        implementation("androidx.compose.ui:ui-text-google-fonts:1.2.0-beta01")
    }
  2. Google Fonts の認証情報で GoogleFont.Provider を初期化します。
    @OptIn(ExperimentalTextApi::class)
    val provider = GoogleFont.Provider(
       providerAuthority = "com.google.android.gms.fonts",
       providerPackage = "com.google.android.gms",
       certificates = R.array.com_google_android_gms_fonts_certs
    )
    
    プロバイダが受け取るパラメータは次のとおりです。
    • Google Fonts のフォント プロバイダの権限
    • プロバイダの ID を確認するためのフォント プロバイダ パッケージ
    • プロバイダの ID を確認するための、証明書のハッシュセットのリストGoogle Fonts プロバイダに必要なハッシュは、JetChat サンプルアプリの font_certs.xml ファイルで確認できます。
    アプリでダウンロード可能なフォントの API を使用するには、ExperimentalTextApi アノテーションを追加する必要があります。
  3. FontFamily を次のように定義します。
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
       Font(googleFont = GoogleFont(name), fontProvider = provider)
    )
    
    フォントの他のパラメータをクエリすることもできます。たとえば、weight と style には FontWeightFontStyle をそれぞれ使用します。
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
       Font(googleFont = GoogleFont(name), fontProvider = provider,
            weight = FontWeight.Bold, style = FontStyle.Italic)
    )
    
  4. コンポーズ可能な Text 関数で使用できるように FontFamily を設定します。
    Text(
        fontFamily = fontFamily,
        text = "Hello World!"
    )
    
    FontFamily を使用するタイポグラフィを定義することもできます。
    val MyTypography = Typography(
       body1 = TextStyle(
       fontFamily = fontFamily,
       fontWeight = FontWeight.Normal,
       fontSize = ...
    ),
       body2 = TextStyle(
       fontFamily = fontFamily,
       fontWeight = FontWeight.Bold,
       letterSpacing = ...
    ),
       h4 = TextStyle(
       fontFamily = fontFamily,
       fontWeight = FontWeight.SemiBold
       ...
    ),
    ...
    
    次に、タイポグラフィをアプリのテーマに設定します。
    MyAppTheme(
       typography = MyTypography
    ) {
    ...
    

Compose でダウンロード可能なフォントをマテリアル 3 と一緒に実装するアプリの例については、JetChat サンプルアプリをご覧ください。

代替フォント

フォントが適切にダウンロードされなかった場合に備えて、フォントの代替チェーンを指定できます。たとえば、ダウンロード可能なフォントを次のように定義したとします。

import androidx.compose.ui.text.googlefonts.Font

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
   Font(googleFont = fontName, fontProvider = provider),
   Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold)
)

両方のウェイトのデフォルト フォントを次のように定義できます。

import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.googlefonts.Font

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
   Font(googleFont = fontName, fontProvider = provider),
   Font(resId = R.font.my_font_regular),
   Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold),
   Font(resId = R.font.my_font_regular_bold, weight = FontWeight.Bold)
)

正しいインポートを追加していることを確認してください。

このように FontFamily を定義すると、2 つのチェーン(ウェイトごとに 1 つ)を含む FontFamily が作成されます。読み込みメカニズムでは、まずオンライン フォントを解決し、次にローカルの R.font リソース フォルダにあるフォントを解決しようとします。

実装のデバッグ

フォントが正しくダウンロードされていることを確認するには、デバッグ コルーチン ハンドラを定義します。ハンドルを使用すると、フォントが非同期でロードされなかった場合に実行する動作を指定できます。

まず、CoroutineExceptionHandler を作成します。

val handler = CoroutineExceptionHandler { _, throwable ->
   // process the Throwable
   Log.e(TAG, "There has been an issue: ", throwable)
}

次に、それを createFontFamilyResolver メソッドに渡して、リゾルバが新しいハンドラを使用できるようにします。

CompositionLocalProvider(
        LocalFontFamilyResolver provides createFontFamilyResolver(LocalContext.current, handler)
    ) {
        Column {
            Text(
                text = "Hello World!",
                style = MaterialTheme.typography.body1
            )
        }
    }

プロバイダの isAvailableOnDevice API を使用して、プロバイダが利用可能であり、証明書が正しく構成されているかどうかをテストすることもできます。このテストを実行するには isAvailableOnDevice メソッドを呼び出します。プロバイダが正しく構成されていない場合には false が返されます。

val context = LocalContext.current
LaunchedEffect(Unit) {
   if (provider.isAvailableOnDevice(context)) {
       Log.d(TAG, "Success!")
   }
}

注意点

Google Fonts では、Android で新しいフォントを利用できるようになるまでに数か月かかります。フォントが fonts.google.com に追加されてから、ダウンロード可能なフォント API(ビューシステムまたは Compose)で利用できるようになるまでに時間的なずれがあります。新たに追加されたフォントが、IllegalStateException でアプリに読み込めない場合があります。デベロッパーがこのエラーと他の種類のフォント読み込みエラーを見分けることができるよう、Compose に例外についての説明メッセージを追加しました。変更内容については、こちらをご覧ください。