今日は、自作アプリのイマイチなUIを向上させる作業をしていきます。
ChatGPTは禁止
今まではChatGPTに指示して
コードを修正してもらってコピペするだけでしたが
勉強の成果を試すため、自力で頑張ってみたいと思います。
お題は、かなり前にテキトーに作ってほったらかしていた
Flutter_UI_Catalogです。
新しい画面を追加する作業をしております。
このように
Widgetごとにカードが表示されているのですが
右上のアイコンが小さいし
上じゃなくて真ん中に表示したい。
ちなみにこのアイコンは、そのWidgetの詳細ページから
Touchモードに移行できることを示すためにつけようと思っているものです。
まず、現在のコード(一部)はこちら
@override
Widget build(BuildContext context) {
if (data == null) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
final keys = data!.keys.toList();
return Scaffold(
appBar: AppBar(title: const Text('Learn')),
body: ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: keys.length,
itemBuilder: (context, index) {
final key = keys[index];
// _meta を一覧から除外
if (key == '_meta') {
return const SizedBox.shrink();
}
final widgetData = data![key];
final category =
(widgetData['category'] ?? '').toString();
// Touch対応
final touch =
widgetData['touch'] ?? false;
// Param数
final paramCount =
(widgetData['params'] as List?)?.length ?? 0;
return Card(
margin: const EdgeInsets.symmetric(vertical: 6),
child: ListTile(
leading: Icon(
_getCategoryIcon(category),
color: Colors.black54,
),
title: Row(
children: [
Expanded(
child: Text(
key,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
// Touchアイコン
if (touch)
const Padding(
padding: EdgeInsets.only(right: 8),
child: Icon(
Icons.touch_app,
size: 20,
color: Colors.orange,
),
),
],
),
// Param数バッジ
subtitle: Padding(
padding: const EdgeInsets.only(top: 6),
child: Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.7),
borderRadius:
BorderRadius.circular(999),
),
child: Text(
'$paramCount params',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => WidgetDetailPage(
name: key,
data: widgetData,
),
),
);
},
),
);
},
),
);
}
修正箇所を特定する
さて、脱初心者を目指し日々勉強している私ですが
画面の見た目をいじりたかったら
Scaffold()の中を変更すれば良いのかな?
ということは分かってきました。
そんなわけでScaffold()で検索してみます。
なんと2か所該当してしまいました。8行目と15行目です。
どちらを直せばよいのだろう?
上のScaffold()はIf(){}で囲まれているし
中身を見るとCircularProgressIndicator()しか無い。
これって確か、読み込み中を示すぐるぐるを表示させるやつだったはず。
テストでこの画面を何回も起動しており
ここで読み込み中になったことはなかったので、一瞬戸惑いましたが
どうやら本丸はここではなく、その下のScaffold()のようです。
というわけで、2つ目のScaffold()の中を見ていきます。
私が今理解できる範囲で、Widgetと、名前付き引数を
整理してみました。
(本来のツリーの使い方とは違うかもしれません。間違っているところもあるかも)
Scaffold
├ appBar:AppBar
│ └ title:Text()
└ body:ListView.builder()
├ padding:EdgeInsets.all()
├ itemCount:keys.length
└ itemBuilder:(){}
一部省略しましたが
構造としては意外とシンプル。
そして、このitemBuilder:(){}がやっかいですね。
これはおそらく、Widgetを返す関数というやつではないでしょうか。
そしてどうやら、画面を作っている部品の大半はここに入っている模様。
頑張ってさらに中を読み解いてみます。
最初のIfやfinal群は、構造には関係なさそうなので無視。
すると、return Card()を発見!
修正箇所はここですね。
下までスクロールしてみても
この後のコードは全て、このCardの中身として書かれている様子。
このCardが、itemBuilder:(){}が返すWidgetに間違いなさそうです。
では、Cardの中身を見ていきましょう。
これは1つづつ辿っていては大変なので
Icon()を狙い撃ちします。
Icon()は全部で3か所。
child: ListTile(
leading: Icon(
_getCategoryIcon(category),
color: Colors.black54,
),
ここは、categoryを渡しているので
カード左端に表示されている、Widgetのカテゴリーを示すアイコンでしょう。
次はここ。
if (touch)
const Padding(
padding: EdgeInsets.only(right: 8),
child: Icon(
Icons.touch_app,
size: 20,
color: Colors.orange,
),
),
どう考えてもここですね。
念のためもう一つも見てみます。
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
),
違いますね。これは右端の>のことでしょう。
こんな感じで、直すべき場所は絞れました。
あとはどう直すか。
サイズは簡単に直せそうだけど、上下真ん中に配置はどうするのかな。
調べて、考えて、やってみる。
普段なら即ChatGPTに聞いてコピペですが
今日は頑張って自力で調べてみます。
どうにかFlutterDocsのLayout widgetsのページにたどり着きました。
この中だと、Centerというのを使えば良さそう。
しかし説明を見てもさっぱりなので(英語は苦手です)
いろいろ実験してみました。
まずはconst Padding(
を、const Center(
に変更してみます。
すると、その下のpaddingのところがエラーになります。
FlutterDocsのCenterのページを見ると
Propertiesの中にpaddingというのはありません。
だからエラーになったのでしょう。
思い切ってこの行は削除してみます。
そしてRun。
なぜか全然変化無し。
そこでアイコンサイズを40に変えてみます。
すると
どうやら、このアイコンの下に見えない壁がある模様。
埒が明かないので元に戻して仕切り直しです。
もう一度コードを見直してみると
まずListTileがあって、その中に
- title:
- subtitle:
があって、今いじっているIconは
title:の中に、Row()で横並びに入っているみたいです。
ListTileのドキュメントを見ると
- leading:
- title:
- subtitle:
- trailing:
このあたりの引数で、表示位置が決まっているみたいです。
こんな感じですね。

titleやsubtitleの入っているブロックを
まとめて分割する方法は思いつきません。
では、trailingに
Row()でIcon()入れたら良いのでは?
こうしてみました。
trailing: Row(
children: [
if (touch)
const Padding(
padding: EdgeInsets.only(right: 8),
child: Icon(
Icons.touch_app,
size: 20,
color: Colors.orange,
),
),
const Icon(
Icons.arrow_forward_ios,
size: 16,
),
],
),
結果、
クラッシュしました
どうやらここまでのようです。
諦めてChatGPTに修正を頼みます。
問題点は2か所
① Expanded
title: Expanded(
child: Text(
key,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
Expanded は
- Row
- Column
- Flex
の子としてしか使えません!
② trailing の Row
trailing: Row(
Row はデフォルトで「できるだけ横幅を広げよう」とします。
でも trailing は
「最小サイズで表示して」
という場所。
なので衝突します。
こうすればOK。
trailing: Row(
mainAxisSize: MainAxisSize.min,
無事、修正できました。
ちょっと勉強しただけで
分かったつもりになって背伸びしたら
あっさり撃沈しました。
まだまだ学ばなければならないことは多いみたいです。
それでも、直すべき箇所は合ってたみたいだから
前よりは成長しているはず。
懲りずにまた挑戦してみたいと思います。