Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
複数行のTextで中間省略(…)を実現する
Search
kobaken
February 19, 2026
Programming
110
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
DroidKaigi初登壇の舞台裏
kobaken0029
0
42
AIがレビューする時代に、 Androidエンジニアは何をレビューするのか 〜 土台づくりとレビュー再設計 〜
kobaken0029
3
1.8k
In the Age of AI Code Review, What Should Android Engineers Review? - Building the Foundation & Redesigning Code Review -
kobaken0029
0
50
Jetpack Compose Preview実践ガイド
kobaken0029
0
200
Serializable / Parcelableとの上手な付き合い方
kobaken0029
0
300
Kotlinの好きなところ
kobaken0029
0
1.5k
Compose駆動開発のためのマルチモジュール化
kobaken0029
0
290
DataStoreを導入してみた
kobaken0029
1
410
Epoxyを用いたレイアウト構築術
kobaken0029
1
270
Other Decks in Programming
See All in Programming
느린 네트워크와 저사양 디바이스 사용자를 위한 웹뷰 로딩 시간 개선기
kmkim
1
220
AIが実装を担う時代の「技術設計」と「チームマネジメント」
kamina_zzz
2
250
Secure Your Coding Agent Like It’s Malware (v1.5) 🇬🇧 @Code.talks 2026
richargh
0
120
高橋メソッドでCVE解説シリーズ 〜CVE-2026-112816〜
hakkadaikon
0
250
선언형 햅틱 라이브러리 진동을 소개합니다
l2hyunwoo
1
310
Materialが消えた日 〜真っ白なFlutterキャンバスにRenderObjectで絵を描く〜
umigishiaoi
0
160
AI駆動開発4年目の現在地(『あなたはFDEになるべきか?』イベント資料)
kumaiu
0
580
学びを記事・スライド・Xへ高速展開する― AI-assisted Learning Loopで回すコンテンツ生成術
ochtum
0
200
Claude Code ハーネスDeep Dive – 君はどこまで制御する? -
takateeen_knj
6
5.2k
Railsのためのパスキー実践入門 〜マルチテナントSaaSでの意思決定〜
utakaha
4
3.8k
flutter_test のその先へ — Flutter アプリの LLM 機能に「評価」という品質保証を
kosukesaigusa
0
200
React Native에 현대적인 빌드 도구 더하기: Rollipop 구현기
leegeunhyeok
0
180
Featured
See All Featured
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
12k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.6k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
560
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
35
2.9k
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
2
980
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
50
10k
Designing for Timeless Needs
cassininazir
1
560
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
60k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1.1k
The World Runs on Bad Software
bkeepers
PRO
72
12k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
840
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