サンプルアプリケーションの概要
今回作成するアプリは図1のようなもので、こちらを作成するサンプルを通じてレイアウトの紹介をしていきます。
このような一覧表示とその詳細という画面構成は、スマホアプリでよくあるのではないかと思います。実際には細かい部分の装飾をより行うわけですが、構成の基本はよく使われているはずです。
一覧画面の作成
一覧画面を作成するためにレイアウト構成を決め、その構成に合わせてコードを作成していきます。
一覧画面のレイアウト構成
一覧画面での各レイアウト構成を記したものが図2です。一覧として表示される部品は、今回はあくまでレイアウトのサンプルということで固定で作成しています。しかし、多くの場合ではデータをネットワーク上からロードし表示するようなことになる想定です。
従って、実際には、表示するアイテム数が何個になるのかはプログラム作成時にはわからないということになります。その点が、前回紹介したColumnレイアウトと違うところですが、それ以外はColumn/Rowレイアウトが利用可能な配置方法になります。
ListViewを使ったレイアウト
一覧画面を作成する際に、よく使うレイアウトの1つがListViewというレイアウトウィジェットです。このListViewウィジェットは一般的にスクロールが必要なレイアウトに対して使用します。
このウィジェットを使う利点は、画面表示に必要な部分のみの描画が可能になるため全体の表示件数が多くても、表示されている部分のみの描画ですみます。
リスト1が実際にListViewを使ったコード例です。
class CouponListView extends StatelessWidget{ Function onPressed; CouponListView(this.onPressed); @override Widget build(BuildContext context) { return Container( color: Colors.white, // (1) ListViewを配置する child: listViewBuilder() ); } // (2) 固定数で表示する Widget buildListView(){ return ListView( children: [ CouponListItem(onPressed), CouponListItem(onPressed), CouponListItem(onPressed), ], ); } // (3) データの個数に従って、表示する場合 final items = [0,1,2,3,4,5,6]; // (4) ListView.builder()を代わりに使う Widget listViewBuilder(){ return ListView.builder( // (5) データ個数 itemCount: items.length, // (6) 表示するウィジェット itemBuilder: (BuildContext context, int index) { return CouponListItem(onPressed); } ); } }
(1)でListViewウィジェットContainerウィジェットの子ウィジェットとして配置しています。(2)はColumnウィジェットと同じようにあらかじめ表示する子ウィジェットが作成できる場合に使う方法です。
しかし、本来やりたいことは、表示するデータを外部から取得することです。そこで、データを(3)のように疑似的に用意しています。
そして、このデータに応じて表示するには、(4)のようにListView.builder()コンストラクタを使います。(4)itemCountでデータ個数を設定し、(5)itemBuilderで表示するウィジェットを作成方法を設定します。
コードを実行した結果が図3のようになります。
親ウィジェットのサイズに応じて表示サイズを調整する
続いて、一覧内の表示する中身であるCouponListItemについて作成していきます。この自作ウィジェットでは、画像とその右側に表示される領域に分けてレイアウトをします。その際に、図4のような課題があります。
画像は100x100の正方形として表示する前提ですが、用意する画像が必ずしも正方形とは限らないケースがあります。そのようなケースを想定し、はみ出した部分は自動的に表示しないようにしたいと思います。そして、右側の領域は、画面サイズに応じて広がる領域として作成します。この例のように、固定サイズと自動で広がる領域の組み合わせというケースはよく見るレイアウト形式です。
このレイアウトを実装したコードがリスト2です。
Widget build(BuildContext context) { return Container( margin: EdgeInsets.all(10), padding: EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: Colors.white, boxShadow: [BoxShadow(color: Colors.grey, blurRadius: 3)]), // (1) 縦方向に並べる child: Row( children: [ Container( // (1)画像を配置 width: 100, height: 100, child: imageWidget(), // : (省略) ), // (2) 自動的に広がる Expanded( child: Container( height: 100, child: MainContent(onPressed) ) ) ])); } // (3) 画像を表示 Widget imageWidget(){ return ClipRect( child: FittedBox( child: Image.asset('assets/images/c_img3.jpg'), fit: BoxFit.cover, ) ); }
(1)では、Rowウィジェットを使って横に並べます。(1)の場所に100x100サイズで画像を配置します。ただし、詳細については後述します。(2)ではサイズを広げるExpandedレイアウトを使います。
そして、画像ですが、そのまま画像(Imageウィジェット)を配置すると自動でリサイズされすべてが表示されるように縮小されていまいます。
この振る舞いを変えるにはFittedBoxを使います。このウィジェットは、子ウィジェットが親ウィジェットとサイズが異なる場合にどのようにサイズを調整するかを指定できます。
画像を目的のサイズに調整する方法は、fitプロパティで指定可能で、表1の値が指定可能です。実際に画像がどのような振る舞いになるかはリファレンスを見ると分かります。
HTML/CSSを知っている方であれば、background-image-sizeのようなことができるとイメージしていただければと思います。注意すべき点は、親サイズからはみ出して表示されてしまうということです。その場合にはみ出した部分を消してくれるのがClipRectです。
プロパティ値 | 説明 |
---|---|
contain |
親ウィジェットのサイズに合わせてすべて収まるように表示します。
|
cover |
親ウィジェットの縦、もしくは横のサイズに合わせて隙間がないようにリサイズします。ただし、はみ出した部分も表示されてしまいます。
|
fill |
親ウィジェットの縦と横の双方のサイズに合わせて隙間がないようにリサイズします。
|
fitWidth |
親ウィジェットの横のサイズにリサイズします。ただし、はみ出した部分も表示されてしまいます。
|
fitHeight | 親ウィジェットの縦のサイズにリサイズします。ただし、はみ出した部分も表示されてしまいます。 |
none |
親ウィジェットにかかわらずサイズを変更せずに表示します。ただし、はみ出した部分も表示されてしまいます。
|
scaleDown | containとほぼ同様ですが、親ウィジェットのサイズより小さいときには拡大されることはありません。 |
親ウィジェットのサイズに応じてフォントサイズを調整する
そして、画像の右領域を表示するウィジェットはMainContentというウィジェットを自作しています。このウィジェットは基本的にColumn/Rowレイアウトで可能です。
実際には装飾などがあり長いコードとなりますが、このウィジェットは基本的にColumn/Rowレイアウトで可能です。そのため、詳細は割愛します。
しかし、先ほどと同様にサイズに合わせてフォントサイズを変更する箇所があります。
その際には、先ほども使ったFittedBoxを使うと可能です。このようにFittedBoxはサイズ調整する際に非常に便利なレイアウトです。
FittedBox( fit: BoxFit.contain, child: Text("詳細を見る"), ),