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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
kobaken
February 19, 2026
Programming
68
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
140
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
Copilot CLI の継戦能力を高める コンテキスト管理
nozomutu
1
1.2k
Datadog × OpenTelemetry 入門と実践のあいだ
kn_to_maxpno
1
140
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
230
関係性から理解する"同一性"の型用語たち
pvcresin
2
640
SPMマルチモジュールで テストカバレッジを取得する技法
yosshi4486
0
140
Spring Security 実践 ─ GraphQL APIで実務に役立つ 認証・認可 を学ぶ
wagyu
0
150
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
540
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
150
TypeSpec で繋ぐ複数プロダクトの型安全
maroon8021
1
380
Signal Forms: Beyond the Basics @ngBaguette 2026 in Paris
manfredsteyer
PRO
0
230
RTSPクライアントを自作してみた話
simotin13
0
490
密結合なバックエンドから TypeScript のコードを生成する
kemuridama
1
740
Featured
See All Featured
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
130
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
520
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.2k
Fashionably flexible responsive web design (full day workshop)
malarkey
408
66k
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
160
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
Designing for Performance
lara
611
70k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.3k
Building the Perfect Custom Keyboard
takai
2
780
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
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