Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
複数行のTextで中間省略(…)を実現する
Search
kobaken
February 19, 2026
Programming
64
0
Share
複数行のTextで中間省略(…)を実現する
2026年2月12日(木)開催のpixiv App Talkで発表した資料です。
kobaken
February 19, 2026
More Decks by kobaken
See All by kobaken
Jetpack Compose Preview実践ガイド
kobaken0029
0
130
Serializable / Parcelableとの上手な付き合い方
kobaken0029
0
130
Kotlinの好きなところ
kobaken0029
0
1.3k
Compose駆動開発のためのマルチモジュール化
kobaken0029
0
250
DataStoreを導入してみた
kobaken0029
1
380
Epoxyを用いたレイアウト構築術
kobaken0029
1
250
Androidエンジニアが1週間でiOSアプリ開発を学び、1ヶ月で大規模アプリ開発にJOINした話
kobaken0029
0
3.7k
Modern REST Communicate for Android
kobaken0029
0
1.6k
AndroidでモダンREST通信してみたった
kobaken0029
0
270
Other Decks in Programming
See All in Programming
Java × distroless で 軽量なコンテナイメージを / Java on Distroless
contour_gara
0
290
開発とはなにか、Essenceカーネルで見えるもの
ukin0k0
0
210
The Arts and Crafts of Work in the AI Era — Toward Mastery in Software Development
kuranuki
0
460
Hive Metastoreを通して学ぶIceberg REST Catalog ― 仕様から実装まで
okumin
0
280
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
350
AIとRubyの静的型付け
ukin0k0
0
130
関係性から理解する"同一性"の型用語たち
pvcresin
2
560
タクシーアプリ『GO』の バックエンド開発のおける AI利活用と若者のすべて
pyama86
3
1.7k
1人1案件のプロダクトエンジニア時代に、"プロセス監督"としてチャレンジしたこと
non0113
0
330
Signal Forms: Beyond the Basics @ngBaguette 2026 in Paris
manfredsteyer
PRO
0
120
ふつうのFeature Flag実践入門
irof
6
2.9k
TypeScriptだけでAIエージェントを作る フロント・エージェント・インフラのフルスタック実践
har1101
6
1k
Featured
See All Featured
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
250
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.5k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
30k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.7k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
2.9k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
820
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
580
Making Projects Easy
brettharned
120
6.6k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
Into the Great Unknown - MozCon
thekraken
41
2.5k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
A Modern Web Designer's Workflow
chriscoyier
698
190k
Transcript
複数行のTextで中間省略(...)を実現する @kobaken
自己紹介 • kobaken / こばけん / @koba_dog_ ◦ 入社9年目 ◦
Comic Division / pixivコミックSection / プロダクト開発Unit / 開発Team • Android / Kotlin / マンガ / ゲーム / YouTube 2
本日のお品書き • なぜ中間省略(...)が必要なのか? • 実装アプローチ • 考慮すべきポイント • アルゴリズムと実装 •
まとめ 3
なぜ中間省略(...)が必要なのか? 4
なぜ中間省略(...)が必要なのか? • 「最初」と「最後」に重要な情報がある場合 ◦ 例1:長いファイルパス(/data/user/0/com.example.../files/config.json) ◦ 例2:マンガのタイトル( 悪役令嬢の中の人~断罪された転生...します~:3【イラスト特典付】 ) •
これを末尾省略(...)にすると一番知りたい情報が見えなくなる ◦ 例1:長いファイルパス(/data/user/0/com.example.android/files/con...) ▪ 🤦ファイル名がわからない ◦ 例2:マンガのタイトル( 悪役令嬢の中の人~断罪された転生者のため嘘つきヒロイ...) ▪ 🤦巻数がわからない 5
要件を定義する ~今回やりたいこと~ • Text の中間付近を省略する • 複数行表示に対応 • 省略記号の挿入位置は最終行の1行前の末尾 6
「赤毛の役立たず」とクビになっ た魔力なしの魔女ですが、「薬草 の知識がハンパない!」と王立研 究所に即採用されました。【電子 限定おまけ付き】5巻 長いテキスト 中間省略の表示(最大2行) 「赤毛の役立たず」とクビにな 限定おまけ付き】5巻 中間省略を適用 … 最終行の手前で省略 末尾にある巻数情 報
しかし、標準機能にはとある制限が... 7
標準機能では「複数行+中間省略」はできない • TextView の android:ellipsize="middle" は maxLines="1" が必須 • Compose
の TextOverflow.MiddleEllipsis も同様 • 複数行(maxLines > 1)に設定した瞬間、中間省略は無視されてしまう 8 🚨今回の要件を満たすための実装が必要🚨
実装アプローチ 9
TextLayoutResult の紹介 • テキストの計測や配置計算が完了した後のレイアウト情報を保持するクラス ◦ 全体のサイズ、行数、各行の座標やベースラインなど • 文字のオフセットや座標、行の境界といった詳細な情報にもアクセスできる ◦ getLineStart,
getLineEnd, getBoundingBox など 利用用途 • カスタム描画やテキスト選択、クリック位置の判定などの制御に利用される ◦ 今回は主にカスタム描画と中間省略のロジックで使います 取得方法 • Textコンポーザブルの onTextLayout コールバックや TextMeasurer.measure の戻り値 10
どう実現する?実現アプローチを検討してみる • TextMeasurer から TextLayoutResult を取得してテキスト情報を得る ◦ 描画される前にCanvasへの描画をシミュレートする ◦ lineCount
や getLineStart / getLineEnd を駆使して中間省略ロジックを実装する ◦ 中間処理後の TextLayoutResult を使って Canvas へテキストを描画する • 省略ロジックでは TextUtils.ellipsize を使わない ◦ TextUtils.ellipsize は TextView 時代から活用されているUtil関数 ◦ これまでpixivコミックでは TextView による複数行の中間省略では TextUtils.ellipsize が採用されていた ▪ (最大行数) * (1行あたりの横幅) を合計幅とした擬似的な1行のテキストと見立てていた ◦ 改行位置が考慮されないため稀に意図しない挙動になる場合がある ◦ TextLayoutResult は行ごとに情報が取得できるため、今回は自前で省略ロジックを組み立ていく 11
考慮すべきポイント 12
注意点とハマりどころ(パフォーマンス) • フェーズを理解して無駄な計算や描画が発生しないようにする 13
Jetpack Compose のフェーズ • Compose がデータをUIに変換するまで、3つのフェーズが存在する 1. Compositionフェーズ 2. Layoutフェーズ
3. Drawingフェーズ • 今回は Layoutフェーズと Drawingフェーズに注目していく ◦ どのくらいのサイズでどこに配置するのかを決定する → Layoutフェーズ ◦ 前フェーズで決定したUIを画面に描画 → Drawingフェーズ 14
注意点とハマりどころ(パフォーマンス) • フェーズを理解して無駄な計算や描画が発生しないようにする ◦ 2つのフェーズをまたいで必要なものは再計算させないように意識する 15
注意点とハマりどころ(パフォーマンス) • フェーズを理解して無駄な計算や描画が発生しないようにする ◦ 2つのフェーズをまたいで必要なものは再計算させないように意識する • SubcomposeLayout (BoxWithConstraints) は利用しない ◦
Layoutフェーズで Composition する特殊なレイアウト ▪ 普通のレイアウトと比べるとオーバーヘッドあり ◦ 子要素をもとに他の要素を動的に配置したり変更したりする場合に便利 ◦ Text の描画領域を決定するためのサイズ情報 (Constraints) が欲しい ▪ BoxWithConstraints を使うと Constraints 生成に必要な maxWidth などが簡単に取得できるが... ◦ Layout コンポーザブルで代替可能 16
注意点とハマりどころ(アクセシビリティ) • スクリーンリーダーが省略(...)された文字列を読んでしまう ◦ pixivコミックでは文字+表紙でどの作品の何巻なのかを判別できる ◦ 仮に省略されたタイトルに巻数が表記されていないパターンがあっても、表紙から情報を得られる ◦ 視覚情報なしで判別できるように、省略前の全文を読ませるようにするべき 17
アルゴリズムと実装 18
実装アルゴリズム(1/2) 19 1. テキストを TextMeasurer で計測 して TextLayoutResult を取得 2.
省略が必要かどうか判定する 3. 中間省略されたテキストを生成 (※2/2を参照) 4. 省略後のテキストで再計測
実装アルゴリズム(2/2) 20 • 省略する行のindexを算出 • 省略行より前の文字列①を取得する • 省略行を加工して文字列②を取得する ◦ 任意の文字を(...)に置換する
◦ 今回は末尾の1文字を対象にする • 最終行の文字列③を取得する • ①+②+③
パフォーマンスのその先へ • Layoutコンポーザブルを使用する • Modifier で Canvas へ描画する • MeasurePolicy
で Layoutフェーズに実施される処理を実装 ◦ 中間省略のための TextLayoutResult の計算はここで実行する 21
コンポーネント定義と準備 22 • rememberTextMeasurer から TextMeasurer のインスタンスを取得 • 計測時にズレが生じないように文字間 隔を0にしておく
• TextLayoutResult をキャッシュ機構 を準備 ◦ Layoutフェーズで行われる計算結果を保持し ておき Drawingフェーズで再計算されないよ うにしておく
MeasurePolicy を実装する 23 • Layoutフェーズで中間省略を実行 ◦ キャッシュがあればそのまま返却 ◦ 計測結果をキャッシュしておく •
呼び出し元からもレイアウト結果を確 認できるようにする ◦ Text コンポーネントを踏襲 • layout でサイズを決定
Layoutコンポーネントによる描画 24 • TextLayoutResult から Canvas に文 字列を描画 ◦ canvas
に対して multiParagraph.paint する ことで複数行テキストの描画が実現 • Modifier.semantics に加工前の全文 を渡す ◦ 省略後の文字列が TalkBack などで読まれない ように対策する
実際に実行してみる 25
26 デフォルト実装の場合(maxLines=2) 😢
27 複数行に対応した実装の場合(maxLines=2)
まとめ 28
まとめ • 長い文章の先頭と末尾に重要な情報がある場合は中間省略を検討してみよう ◦ ファイルパス、単行本のタイトルなど • 標準機能では複数行の中間省略は未サポートなので自前実装が必要 ◦ TextLayoutResult を駆使することで実現可能
• いくつか考慮すべきポイントを押さえておこう ◦ 文字列の描画や再計算にかかるコスト ▪ SubcomposeLayout の利用を避けて、 Layout コンポーネントでパフォーマンスを意識 ▪ キャッシュを活用してフェーズをまたいだ再計算が走らないようにする ◦ アクセシビリティの考慮 ▪ Modifier.semantics で省略前の文字列を渡しておく ▪ 全文表示されるUIも用意する 29
参考文献 • (APIリファレンス)TextLayoutResult ◦ https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextLayoutResult • Jetpack Composeのフェーズ ◦ https://developer.android.com/develop/ui/compose/phases
• Jetpack Composeで真ん中省略のテキストを実装する ◦ https://qiita.com/Nabe1216/items/d6434507d38cd642efcd • Compose でパフォーマンスの高いレイアウトを作る ◦ https://qiita.com/mx_albert/items/7332a7c2236ef44a8b2e 30 Supported by Gemini 3 Pro