前人未踏の領域へ Androidアプリ開発編

Androidアプリ開発に関する調査メモ置き場。古い記事にはアプリ以外も含まれます。

Jetpack Compose:TextのmaxLines指定時に表示した文字列と隠れた文字列を知るには

内容

コンテンツをリスト表示する際に、記事などの長文を部分表示したい場合に maxLinesパラメータを使って表示を制限するが、現在どこまで文字列が表示されているかを知りたい。どうすればよいか。

対応

TextViewの場合にはTextUtils.ellipsize() という関数に文字列と画面幅を渡してゴニョゴニョとやっていたが、Jetpack Composeの場合にはTextLayoutResultが利用できる。

Text(text = longText, overflow = TextOverflow.Ellipsis, maxLines = 3,
    onTextLayout = { textLayoutResult ->
        if (textLayoutResult.hasVisualOverflow) {
            overflowOffset = it.getLineEnd(2, true)
        }
    })

onTextLayout にCompose後のレイアウト結果を表すTextLayoutResultインスタンスが渡されてくるので、hasVisualOverflow で表示されなかった部分があるかどうかを確認する。getLineEnd(line:Int)でその行の最後の文字位置が得られる。第2パラメータは結果を目に見えるもののみに限定するかどうか。overflow = TextOverflow.Ellipsis にした場合にここがfalseだと全テキストが最後の行に含まれてしまい、期待した値が得られない。また、hasVisualOverflow の代わりに isLineEllipsized()を使うこともできるが、その場合はoverflow = TextOverflow.Clip の場合に結果がfalseになってしまうので注意

/**
   3行以上の文章を上下に分割する例
**/
@Composable
fun OverflowText(longText: String) {
    var overflowOffset: Int by remember { mutableStateOf(0) }

    Column {
        // 上から3行
        Text(text = longText, overflow = TextOverflow.Clip, maxLines = 3,
            onTextLayout = {
                if (it.hasVisualOverflow) {
                    overflowOffset = it.getLineEnd(2, true)
                }
            })
        Spacer(modifier = Modifier.height(32.dp))
        if (overflowOffset > 0) {
            // 溢れた部分
            Text(text = longText.substring(overflowOffset))
        }
    }
}

結果の例

前よりも楽になってるんだけど、getLineEndで目的が達成できることを見つけるまでに時間がかかった。