0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutter レイアウト

Posted at

元記事

https://docs.flutter.dev/get-started/fundamentals/layout
意訳しています.

はじめに

FlutterがUIツールキットを与えられたら,Flutterウィジェットを使ってレイアウトを作ることに時間を費やすだろう.
このセクションでは,いくつかの一般的なレイアウトウィジェットを使ってレイアウトの組み立て方を学ぶ.
Flutterがどのようにレイアウトを作るのか理解するために,Flutter DevToolsを使うだろう.
最後に,Flutterの最も一般的なレイアウトエラーの1つである,恐ろしい「unbounded constraints」エラーに遭遇し,デバッグするだろう.

Flutterのレイアウトを理解する

Flutterレイアウト機構の中心部分はウィジェットである.
Flutterにおいて,たとえレイアウトモデルがウィジェットであったとしても,ほとんどすべてのものはウィジェットである.
Flutterアプリで見れるImageIconTextはすべてウィジェットである.
見ることもできないものもウィジェットである.
例えば,RowsColumnsgridsは見えるウィジェットを配置し,制約し,整列させる.

複雑なウィジェットを構築するためにウィジェットを作ることによってレイアウトを作成する.
例えば,下の図は,3つのアイコンとそれぞれの下にあるラベル,そして対応するウィジェット・ツリーを示している.

この例では,アイコンとラベルを含む3つの列の行がある.
すべてのレイアウトでは,複雑ではなく,これらのウィジェットを組み合わせたものである.

制約条件

Flutterの制約条件を理解することはレイアウトがFlutterでどのように動作するのか理解する重要なことである.

一般的な意味で,レイアウトはウィジェットのサイズとスクリーンにおける位置を参照する.
すべてのウィジェットのサイズと位置は親ウィジェットによって制約される.
そのウィジェットが欲しい任意のサイズになることはできず,スクリーンでどの位置にいるのか決めることができない.
その代わり,大きさと位置はウィジェットとその親ウィジェットとのやり取りから決定される.

最も単純な例では,レイアウトのやり取りは以下のようになる.

  1. ウィジェットは親ウィジェットから制約を受け取る
  2. 制約条件は最小幅,最大幅,最小高さ,最大高さの4つの組み合わせのみである
  3. ウィジェットは制約条件を満たすように幅と高さを決定し,親ウィジェットに幅と高さを返す
  4. 親ウィジェットは,ウィジェットのサイズとアライメントを確認し,ウィジェットの位置を設定する.整列は,Centerのような様々なウィジェットや,RowとColumnの整列プロパティを使用して,明示的に設定することができる

Flutterでは,レイアウトのやり取りは以下のように要約される.

  1. サイズ条件を渡すと
  2. サイズが返ってきて
  3. 親ウィジェットが位置を設定する

ボックスタイプ

Flutterでは,RenderBoxオブジェクトを通してウィジェットが描かれる.
そのオブジェクトは制約条件のやりとりの仕方を決定する.

一般的に,3種類のボックスがある.

  • できる限り大きくする.例えば,CenterListViewが使われたとき
  • 子ウィジェットと同じ大きさにする.例えば,TransformOpacityが使われたとき
  • できる限り特定のサイズにする.例えば,ImageTextが使われたとき

例えばContainerのような,いくつかのウィジェットはコンストラクタの引数や型によって異なる.
Containerコンストラクタはできる限り大きくするようにデフォルト設定するが,もし幅を与えられたら,その幅を尊重し,その幅に収める.

RowColumnなど他のウィジェットは与えられた条件によって異なる.
フレックスボックスと制約条件について以下の記事をもっと読んだ方がよい.
https://docs.flutter.dev/ui/layout/constraints

単一ウィジェットのレイアウト

Flutterで1つのウィジェットをレイアウトするには,TextImageのような可視ウィジェットを,Centerウィジェットのような画面上の位置を変更できるウィジェットで包む.

Widget build(BuildContext context) {
  return Center(
    child: BorderedImage(),
  );
}

以下の図について,左側は整列されておらず,右側は中心に置かれている.

すべてのウィジェットは以下のどちらかのメンバ変数を持っている.

  • CenterContainerPaddingなど一つの子を持つ場合,childメンバ変数を持つ
  • RowColumnListViewStackなど複数の子を持つ場合,childrenメンバ変数を持つ

Container

Containerはレイアウト,ペイント,位置決め,サイジングのためにいくつかの便利なウィジェットを組み合わせたものである.
それはウィジェットにパディングとマージンを追加することもできる.
Paddingウィジェットを使って同じ効果を得ることができる.
以下はCoontainerの例である.

Widget build(BuildContext context) {
  return Container(
    padding: EdgeInsets.all(16.0),
    child: BorderedImage(),
  );
}

以下の図において,左側はパディングなしで,右側はパディングありである.

Flutterでもっと複雑なレイアウトを作成するために,多くのウィジェットを組み合わせることができる.
例えば,ContainerCenterを組み合わせることができる.

Widget build(BuildContext context) {
  return Center(
    Container(
      padding: EdgeInsets.all(16.0),
      child: BorderedImage(),
    ),
  );
}

垂直や水平のいくつかのウィジェットのレイアウト

最も一般的なレイアウトパターンは垂直方向や水平方向にウィジェットを整列することである.
水平方向に整列するためにRowウィジェットを使うことができる.
垂直方向に整列するためにColumnウィジェットを使うことができる.
このページの始めの画像を両方とも使っている.

これはもっとも単純なRowウィジェットの例である.

Widget build(BuildContext context) {
  return Row(
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}

複雑なレイアウトを作成するために,RowColumnの子ウィジェットはさらにRowColumnを入れ子にすることができる.
例えば,Columnを使うことでそれぞれの画像にラベルを付けることができる.

Widget build(BuildContext context) {
  return Row(
    children: [
      Column(
        children: [
          BorderedImage(),
          Text('Dash 1'),
        ],
      ),
      Column(
        children: [
          BorderedImage(),
          Text('Dash 2'),
        ],
      ),
      Column(
        children: [
          BorderedImage(),
          Text('Dash 3'),
        ],
      ),
    ],
  );
}

RowとColumnの中でウィジェットを整列する

以下の例では,それぞれのウィジェットは200ピクセルの幅があり,表示領域は700ピクセルある.
それぞれのウィジェットは左側から順に並び,余った領域は右側にある.

mainAxisAlignmentcrossAxisAlignmentのメンバ変数を使って,行または列の子プロパティの整列方法を制御する.
Rowの場合,主軸は水平に,交差軸は垂直に走る.
Columnの場合,主軸は垂直に,交差軸は水平に走る.

主軸のアラインメントをspaceEvenlyに設定することは,各画像の間,前後の自由な水平スペースを均等に分割する.

Widget build(BuildContext context) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}

ColumnRowと同時に動作する.
以下の例は3つの画像を縦に並べたものであり,画像の高さは100ピクセルである.
レンダーボックスの高さが300ピクセル以上あるため,spaceEvenlyを設定することは,各画像の間,前後の自由な垂直スペースを均等に分割する.

mainAxisAlignmentcrossAxisAlignmentのEnumはアラインメントのコントロールを行う.

RowやColumnに収まるようにサイジングする

Expandedウィジェットを使うことによって,RowColumnに収まるようにウィジェットのサイズを変える.
以前の例にExpandedウィジェットを適用すると以下のようになる.

Widget build(BuildContext context) {
  return const Row(
    children: [
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
    ],
  );
}

Expandedウィジェットは,兄弟ウィジェットに対してどれだけのスペースを取るかを指定することもできる.
例えば,その兄弟ウィジェットに対して2倍の大きさを占めるようなウィジェットを作ることを考える.
このためには,Expandedウィジェットのflexメンバ変数を使う.
これに設定する整数値はウィジェットのflex係数を決定する.
デフォルト係数は1である.
以下は中央の画像のflex係数を2,他を1とした例である.

Widget build(BuildContext context) {
  return const Row(
    children: [
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        flex: 2,
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
    ],
  );
}

DevToolsとデバッグレイアウト

場合によって,ボックスの制約条件は境界なしで無限になることがある.
これは,最大幅と最大高さがdouble.infinityに設定されている.
可能な限り大きくしようとするボックスは,束縛されない制約が与えられたときに有用に機能せず,デバッグモードでは例外を投げる.

レンダーボックスが非束縛制約で終わる最も一般的なケースは、フレックスボックス(RowまたはColumn)内と、スクロール可能な領域(ListViewや他のScrollViewサブクラスなど)内である.
例えば,ListViewは交差軸で可能な限りスペースに合うように拡張しようとする.
もし,水平方向にスクロール可能なListViewの中に,垂直方向にスクロール可能なListViewがあると,中のListViewは可能な限り水平方向に広がろうとする.
外側のListViewは水平方向にスクロール可能なため,無限の幅を持っている.

Flutterアプリをビルドする間に遭遇する一般的なエラーは,レイアウトウィジェットを正しく使わずに発生する非束縛制約に関するエラーである.

Flutterアプリを作り始めたときに直面する覚悟が必要なエラーが1つだけあるとすれば,それはこれだろう.
https://www.youtube.com/watch/jckqXR5CrPI

スクロール可能なウィジェット

Flutterはスクロール可能な多くの内蔵ウィジェットを持っており,特定のスクロールする動作を作成するためのカスタマイズできる多くのウィジェットがある.
このページでは,スクロール可能なリストを作成するためのウィジェットと同様の,スクロール可能な一般的なウィジェットを作成する.

ListView

ListViewはコンテンツがレンダーボックスよりはみ出た場合,自動的にスクロール可能にするColumnのようなウィジェットである.
ListViewを使う最も基本的な方法は,ColumnRowと同様に使うことである.
ColumnRowと違って,ListViewはその子ウィジェットが横軸の利用可能なスペースをすべて占有する必要がある.
例を以下に示す.

Widget build(BuildContext context) {
  return ListView(
    children: const [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}

とても長いコンテンツを表示したいときにListViewを使う.
この場合,ListView.builderコンストラクタを使うのが一般的である.
builderコンストラクタは画面上で見ることのできる子ウィジェットのbuildメソッドを一度のみ呼び出す.

以下の例では,ListViewはto-doアイテムを表示している.
このtodoアイテムはリポジトリからフェッチするため,todoの数は分からない.

final List<ToDo> items = Repository.fetchTodos();

Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, idx) {
      var item = items[idx];
      return Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(item.description),
            Text(item.isComplete),
          ],
        ),
      );
    },
  );
}

適応的なレイアウト

Flutterはモバイル・タブレット・デスクトップ・ウェブアプリに使われるため,スクリーンサイズや入力デバイスのように環境が異なる中でアプリケーションを適合させる必要がある.
これは適合可能でレスポンシブなアプリを作るのに必要である.

適合可能なウィジェットを作るためによく使われるウィジェットはLayoutBuilderウィジェットである.
LayoutBuilderウィジェットはFlutterでbuilderパターンを使うウィジェットである.

Builderパターン

Flutterでは,builderと名前のつくコンストラクタを見つけるだろう.
以下のリストがすべてではない.

  • ListView.builder
  • GridView.builder
  • Builder
  • LayoutBuilder
  • FutureBuilder

これらの異なるBuilderは異なる問題を解決するために使われる.
例えば,ListView.builderコンストラクタは主にリスト内のアイテムを遅延レンダリングするために使われ,Builderウィジェットは深いウィジェットコードでBuildContextにアクセスするのに使われる.

異なるユースケースであるにもかかわらず,これらのbuilderはどのような動作をするかで統一されている.
Builderウィジェットとbuilderコンストラクタすべてはbuilderと呼ばれる引数を持つ.
例えば,ListView.builderの場合,itemBuilderになる場合もある.
また,builder引数はコールバックも受付可能である.
このコールバックはbuilder関数と呼ばれる.
builder関数は親ウィジェットにデータを渡すウィジェットであり,子ウィジェットを作成し返すための引数を親ウィジェットが使う.
builder関数はいつも,少なくとも1つの引数,通常は2つの引数を渡す.

例えば,LayoutBuilderウィジェットは画面サイズを基にレスポンシブなレイアウトを作成するために使われる.
builderコールバックの本体は,親から受け取ったBoxConstraintsと,ウィジェットのBuildContextが渡される.
この制約条件のため,利用可能なスペースを基に異なるウィジェットを返すことができる.
https://www.youtube.com/watch/IYDVcriKjsw

以下の例では,画面の幅が600ピクセルかどうかでウィジェットを変更するLayoutWidgetとなっている.

Widget build(BuildContext context) {
  return LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
      if (constraints.maxWidth <= 600) {
        return _MobileLayout();
      } else {
        return _DesktopLayout();
      }
    },
  );
}

一方で,ListView.builderコンストラクタのitemBuilderコールバックはbuild内容とintが渡される.
このコールバックはリスト内のすべての要素に対して一度だけ呼ばれる.
そして,int引数はリストのどの要素なのか指定する.
flutterがUIを作成するときに初めてitemBuilderコールバックが呼ばれ,intは0から順に渡される.

インデックスを基にした特殊な設定を行うことができる.
上記のListView.builderコンストラクタを使用した例を示す.

final List<ToDo> items = Repository.fetchTodos();

Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, idx) {
      var item = items[idx];
      return Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(item.description),
            Text(item.isComplete),
          ],
        ),
      );
    },
  );
}

このコード例は,ビルダーに渡されたインデックスを使用して,アイテムのリストから正しいToDoを取得し,ビルダーから返されたウィジェットにそのToDoのデータを表示する.

これを例示するために,以下のコード例は,背景の色を変えるようにしている.

final List<ToDo> items = Repository.fetchTodos();

Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, idx) {
      var item = items[idx];
      return Container(
        color: idx % 2 == 0 ? Colors.lightBlue : Colors.transparent,
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(item.description),
            Text(item.isComplete),
          ],
        ),
      );
    },
  );
}

追加の項目

ここを見て.
https://docs.flutter.dev/get-started/fundamentals/layout#additional-resources

APIリファレンス

ここを見て.
https://docs.flutter.dev/get-started/fundamentals/layout#api-reference

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?