📝
Flutter でボタンを押すと伸縮する Text を作る
はじめに
長いテキスト等を表示する際に、最初は一部だけ表示しておいて、ボタン等を押すと全文を表示するような UI があると思います。
Folio | Udemy | ミュージック |
---|---|---|
Flutter で上記のような UI を作ろうとして、意外と情報がなかったのでまとめてみます。
また、今回作った物は全て以下のリポジトリにアップしてあります 🙇♂️
作るもの
以下の要件を満たすものを作成します。
- 初期状態では数行のみテキストを表示する
- ボタン等を押すと全文を表示するように切り替える
- テキストが短い場合はボタン等を表示しない
実装
それでは実際に作ってみます。
表示したいテキストの行数やサイズは描画後にしかわからないので、方針としては LayoutBuilder
と TextPainer
を使用して描画されるテキストの行数、サイズを取得し、最大行数に達しているかどうかで表示する Widget を切り替えるようにします。
LayoutBuilder
LayoutBuilder
には Widget のサイズを取得してもらい、テキストの描画の際に取得したサイズから幅の情報を取り出して使用します。
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, constraints) {
// `constrains` に入っているサイズの情報を利用してテキストの描画等を行う
});
}
TextPainter
TextPainter
には Text
に適応させる TextStyle
を指定した上でテキストの描画を行ってもらい、描画したテキストが最大行数に達しているかどうかを判断してもらいます。
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, constraints) {
// テキストのスタイル、最大行数等を指定して `TextPainter` を作成
final textStyle = widget.style ?? DefaultTextStyle.of(context).style;
final textSpan = TextSpan(text: widget.data, style: textStyle);
final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr, maxLines: widget.maxLines);
// テキストの描画を行う
textPainter.layout(maxWidth: constraints.maxWidth);
if (textPainter.didExceedMaxLines) {
// 最大行数よりも多いテキストの場合
} else {
// 最大行数よりも少ないテキストの場合
}
});
}
できたもの
上記2つをまとめて1つの StatefulWidget
にしてみました。
見た目、動作としては以下のようになります。
短いテキストにはボタンが表示されず、長いテキストのみボタンが表示されていて、かつボタンを押すと開いたり閉じたりできるようになりました。
ソースコード
expandable_text.dart
import 'package:flutter/material.dart';
class ExpandableText extends StatefulWidget {
const ExpandableText(
this.data, {
Key key,
this.maxLines = 3,
this.textOverflow = TextOverflow.fade,
this.style,
}
): super(key: key);
final String data;
final int maxLines;
final TextOverflow textOverflow;
final TextStyle style;
_ExpandableTextState createState() => _ExpandableTextState();
}
class _ExpandableTextState extends State<ExpandableText> with SingleTickerProviderStateMixin {
bool _isExpanded = false;
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, constraints) {
final textStyle = widget.style ?? DefaultTextStyle.of(context).style;
final textSpan = TextSpan(text: widget.data, style: textStyle);
final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr, maxLines: widget.maxLines);
textPainter.layout(maxWidth: constraints.maxWidth);
if (textPainter.didExceedMaxLines) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
widget.data,
style: textStyle,
overflow: widget.textOverflow,
maxLines: _isExpanded ? null : widget.maxLines,
),
const SizedBox(height: 4.0,),
FlatButton(
onPressed: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
child: Text(
_isExpanded ? '閉じる' : '開く'
),
)
],
);
} else {
return Text(
widget.data,
style: textStyle,
maxLines: widget.maxLines,
);
}
});
}
}
さいごに
Column
の部分を Stack
にしたりすれば iOS 標準のミュージックアプリっぽいものも作れそうです。
あとは伸縮の際にアニメーションさせることができればもっといい感じになりそうですね 🤔
Discussion