Flutter レイアウトの基本 ——Column 縦レイアウト#
Column
は縦方向に子ビューをレイアウトするウィジェットで、Row
に似ています。子ビューを満たしたい場合は、Expanded
を使用して子ビューを包みます。
Column
はスクロールできません(通常、Column
を使用する際、子ビューの内容は親ビューの高さを超えることはできません)。もし多くの子ビューがあり、スクロールが必要な場合は、ListView
の使用をお勧めします。
横方向のレイアウトが必要な場合は、Row
を使用します。
要素が 1 つだけの場合は、Align
またはCenter
を使用してレイアウトを考慮できます。
基本的な使用法#
Column の一般的なプロパティは以下の通りです:
- Column の一般的なプロパティ
- children: 子ビュー
- textDirection: 子ビューの水平方向のレイアウト方向
- TextDirection.ltr: 左から右
- TextDirection.rtl: 右から左
- verticalDirection: 子ビューの縦方向のレイアウト方向
- VerticalDirection.down: 上から下、デフォルトはこれ
- VerticalDirection.up: 下から上
- mainAxisSize: 子ビューが親ビューの縦方向に占めるサイズ
- MainAxisSize.min: 最小、これを設定した後に mainAxisAlignment を設定すると、表示効果はすべて start の効果になります
- MainAxisSize.max: 最大、デフォルトはこれ、親ビューのサイズに従います
- mainAxisAlignment: 子ビューが親ビューに対してどのようにレイアウトされるか、縦方向のレイアウト
- MainAxisAlignment.spaceAround: 子ビュー間と子ビューから親ビューまでの距離に間隔があります
- MainAxisAlignment.center: すべての子ビューが中央に配置されます
- MainAxisAlignment.end: すべての子ビューが末尾に配置されます
- MainAxisAlignment.spaceBetween: 子ビュー間に等しい間隔があり、親ビューとの間隔はありません
- MainAxisAlignment.spaceEvenly: 子ビュー間と子ビューから親ビューまでの距離に間隔があり、かつ間隔が等しい
- MainAxisAlignment.start: すべての子ビューが最初に配置されます
- crossAxisAlignment: 子ビューの水平方向のレイアウト方法
- CrossAxisAlignment.start: 水平に左寄せ
- CrossAxisAlignment.end: 水平に右寄せ
- CrossAxisAlignment.center: 水平に中央寄せ、デフォルトはこれ
- CrossAxisAlignment.stretch
- CrossAxisAlignment.baseline
それでは一つずつ見ていきましょう:
textDirection の効果:#
コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: new AppBar(
title: new Text('Column Learn'),
),
body: Column(
textDirection: TextDirection.ltr,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAß'),
Text('TextDirection rtl'),
Text('CrossAxisAlignment start'),
FlutterLogo()
],
),
),
);
}
}
効果は以下の通りです:
効果からわかるように、textDirection
は子ビューの水平方向のレイアウトの方向ですが、ここでcrossAxisAlignment
も同時に設定されていることに注意が必要です。設定しない場合、表示効果はCrossAxisAlignment.center
と一致し、textDirection
のみを設定しても効果はありません。興味がある方は自分で検証してみてください。
Ps:CrossAxisAlignment.center
の効果は、想像しているように画面全体の幅が中央に揃うわけではありません。実際には、最も長い子ビューの幅に基づいて中央に揃います。
verticalDirection の効果#
コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: new AppBar(
title: new Text('Column Learn'),
),
body: Column(
verticalDirection: VerticalDirection.down,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAß'),
Text('VerticalDirection up'),
Text('CrossAxisAlignment start'),
FlutterLogo()
],
),
),
);
}
}
効果は以下の通りです:
mainAxisSize && mainAxisAlignment の効果#
mainAxisSize の効果:
コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: new AppBar(
title: new Text('Column Learn'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAß'),
Text('MainAxisAlignment spaceAround'),
Text('MainAxisSize max'),
Text('CrossAxisAlignment start'),
FlutterLogo()
],
),
),
);
}
}
効果は以下の通りです:
同様に、mainAxisAlignment
をspaceAround
に設定した場合、mainAxisSize
をmin
とmax
に設定した違いが見られます。max
を設定すると全画面に適応され、min
を設定すると効果はありません。
mainAxisAlignment
(mainAxisSize
がmax
の場合)の効果:
コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: new AppBar(
title: new Text('Column Learn'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAß'),
Text('MainAxisAlignment spaceEvenly'),
Text('MainAxisSize max'),
Text('CrossAxisAlignment start'),
FlutterLogo()
],
),
),
);
}
}
効果は以下の通りです:
図からわかるように、mainAxisSize
がmax
の場合、mainAxisAlignment
の各異なる値の表示効果が見られます。
crossAxisAlignment の効果#
コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: new AppBar(
title: new Text('Column Learn'),
),
body: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAß'),
Text('MainAxisSize max'),
Text('CrossAxisAlignment start'),
FlutterLogo()
],
),
),
);
}
}
表示効果は以下の通りです:
上記から、crossAxisAlignment
の各異なる値の表示効果がわかります。
ただし、値が baseline の場合、エラーが発生します。エラーは
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ 次のアサーションがMyApp(dirty)のビルド中にスローされました: crossAxisAlignmentをCrossAxisAlignment.baselineで指定した場合、textBaselineが必要です 'package:flutter/src/widgets/basic.dart': アサーションに失敗しました:行4369 pos 15: 'crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null'
なぜでしょうか?エラーメッセージから推測すると、crossAxisAlignment.baseline
を設定した場合、textBaseline
プロパティを設定する必要があります。したがって、textBaseline
プロパティを追加すれば解決できます。
コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: new AppBar(
title: new Text('Column Learn'),
),
body: Column(
textBaseline: TextBaseline.alphabetic,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
Text('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAß'),
Text('TextBaseline alphabetic'),
Text('MainAxisSize max'),
Text('CrossAxisAlignment baseline'),
FlutterLogo()
],
),
),
);
}
}
効果は以下の通りです:
textBaseline の異なる値にはどのような違いがあるのでしょうか?参考にしてくださいFlutter の TextBaseline enum における alphabetic と ideographic の違いは何ですか
スクリーンショットは以下の通りです:
公式の注意点#
注意点 1#
Column
の子ビューにExpanded
またはFlexible
の子ビューがあり、さらにこのColumn
ウィジェットが別のColumn
ウィジェットやListView
、または他の不定高さのウィジェットに配置されている場合、エラーが発生します。
検証コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: new AppBar(
title: new Text('Column Learn'),
),
body: Column(
textBaseline: TextBaseline.ideographic,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
Column(
children: [Expanded(child: FlutterLogo())],
),
],
),
),
);
}
}
エラーは以下の通りです:
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ レイアウト中に次のアサーションがスローされました: RenderFlexの子は非ゼロのフレックスを持っていますが、受信した高さの制約は無制限です。
エラーの原因は:
Expanded
を使用する場合、親ビューの高さが固定されている必要があります。Expanded
で包まれた子ビューは親ビューの領域を埋める必要があります。しかし、Column
やListView
などの可動ビューにネストされている場合、親ビューの高さは固定されていないため、Expanded
は埋めることができません。
解決策:
外側がColumn
の場合、内側のColumn
をExpanded
で包むことができます。これにより、内側のColumn
ウィジェットに外側のColumn
ウィジェットの高さを埋めるように指示することになり、高さが確定します。
コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: new AppBar(
title: new Text('Column Learn'),
),
body: Column(
textBaseline: TextBaseline.ideographic,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
Expanded(
child: Column(
children: [Expanded(child: FlutterLogo())],
),
),
],
),
),
);
}
}
外側がListView
や他の可動ビューの場合、可動ビューの内容の高さは不確定であるため、なぜ外側に不定高さのビューがあるのかを考慮する必要があります。この場合、Column
ウィジェットの子ビューにExpanded
やFlexible
を使用することは通常避けるべきです。
注意点 2#
Row
と同様に、子ビューの内容が親ビューの領域を超えた場合、Flutter はデバッグモードで黄色の警告を表示します。効果は以下の通りです:
解決策:
Column
の代わりにListView
を使用して、子ビューの内容をスクロール可能にします。