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
69
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
複数行のTextで中間省略(…)を実現する
2026年2月12日(木)開催のpixiv App Talkで発表した資料です。
kobaken
February 19, 2026
More Decks by kobaken
See All by kobaken
Jetpack Compose Preview実践ガイド
kobaken0029
0
140
Serializable / Parcelableとの上手な付き合い方
kobaken0029
0
150
Kotlinの好きなところ
kobaken0029
0
1.3k
Compose駆動開発のためのマルチモジュール化
kobaken0029
0
260
DataStoreを導入してみた
kobaken0029
1
390
Epoxyを用いたレイアウト構築術
kobaken0029
1
260
Androidエンジニアが1週間でiOSアプリ開発を学び、1ヶ月で大規模アプリ開発にJOINした話
kobaken0029
0
3.8k
Modern REST Communicate for Android
kobaken0029
0
1.6k
AndroidでモダンREST通信してみたった
kobaken0029
0
270
Other Decks in Programming
See All in Programming
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
2
620
Lessons from Spec-Driven Development
simas
PRO
0
180
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
780
AI時代の仕事技芸論 — ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ
kuranuki
2
670
エンジニアと一緒にテストコードの設計と実装を改善した話
mototakatsu
0
130
Contextとはなにか
chiroruxx
1
310
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
340
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
330
AIとASP.NET Coreで雑Webアプリを作った話
mayuki
0
520
Agentic UI
manfredsteyer
PRO
0
150
AI時代のUIはどこへ行く?その2!
yusukebe
21
7.1k
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
330
Featured
See All Featured
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
170
Music & Morning Musume
bryan
47
7.2k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
390
Unsuck your backbone
ammeep
672
58k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
1.7k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
470
How to make the Groovebox
asonas
2
2.2k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2k
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
200
Fireside Chat
paigeccino
42
3.9k
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
2k
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