Dartには色々なコンストラクタがありますが、あまりFlutterで使いこなせていない感がありました。
同僚と話していて、こういった使い方なら汎用性があるのでは?というのがあったので、「名前付きコンストラクタ」と「リダイレクトコンストラクタ」を使って説明したいと思います。
完成のイメージ
- メンバー一覧用のカードを用意したい
- 退会したメンバーは背景をグレーにして、「退会済み」の文言を表示したい
元となるカードのウィジェットを作成する
メンバー個々のウィジェットを作成する際に下記のデータを送ってもらうこととします。
- メンバーの名前
- アバター画像のファイル名
- 在籍状況
- 背景色
ウィジェットのコードはこんな感じになると思います。
Widget build(BuildContext context) {
return Card(
child: ListTile(
//アバター画像
leading: ClipRRect(
borderRadius: BorderRadius.circular(25),
child: Image.asset(
'images/${widget.imgFileName}',
width:
height: 50,
),
),
title: Text(widget.name), //名前
trailing: widget.statusIcon, //在籍状況
),
color: widget.backgroundColor, //背景色
margin: EdgeInsets.symmetric(vertical: 20),
);
}
名前付きコンストラクタ で呼び出す
通常のメンバーカードであるnormal
と、退会済みのメンバーカードのdisabled
をそれぞれ指定することでカードの作り分けを行いたいので、コードは以下のようにしたいです。
Column(
children: <Widget>[
MemberCard.normal(name: '鈴木 たかし', imgFileName: 'avator1.png'),
MemberCard.disabled(name: '高橋 ゆうさく', imgFileName: 'avator2.png'),
MemberCard.normal(name: 'Sarah Adams', imgFileName: 'avator3.jpeg'),
MemberCard.normal(name: 'Richard Mason', imgFileName: 'avator4.jpeg'),
]
)
このnormal
と、disabled
の部分が名前付きコンストラクタになります。
名前付きコンストラクタ を作成する
実際のコンストラクタは以下のようになります。
class MemberCard extends StatefulWidget {
final String name; //名前
final String imgFileName; //アバター画像
// 通常のユーザー
MemberCard.normal({name, imgFileName})
: this._init(
name: name,
imgFileName: imgFileName
);
// 退会したユーザー
MemberCard.disabled({name, imgFileName})
: this._init(
name: name,
imgFileName: imgFileName
);
MemberCard._init({Key key,
this.name = '',
this.imgFileName = ''
}) : super(key: key);
@override
_MemberCardState createState() => _MemberCardState();
}
コンストラクタの引数は宣言時と同じものです。
その後「:」(コロン)をつけて、this._init
に追加のコンストラクタ処理を渡しています。
これが、リダイレクトコンストラクタになります。
実際のthis._init
を見てみます。
key
は自動的に振り当てられたもので、name
とimgFileName
は引数として渡されたものです。
そしてまたそれを親クラスにsuper
として処理を渡しています。
しかし、これだけではnormal
とdisabled
の違いがなく、あまり恩恵を感じられませんので、ひと手間加えてみます。
明確に目的を分けて作成する
class MemberCard extends StatefulWidget {
final String name; //名前
final String imgFileName; //アバター画像
final Color backgroundColor; //背景色(追加部分)
final statusIcon; //メンバーのステータス(追加部分)
// 通常のユーザー
MemberCard.normal({name, imgFileName})
: this._init(
name: name,
imgFileName: imgFileName,
statusIcon: Icon(Icons.check_circle, color: Colors.green,) //(追加部分)
);
// 退会したユーザー
MemberCard.disabled({name, imgFileName})
: this._init(
name: name,
imgFileName: imgFileName,
backgroundColor: Colors.black12, //(追加部分)
statusIcon: Text('退会済み') //(追加部分)
);
MemberCard._init({Key key,
this.name = '',
this.imgFileName = '',
this.backgroundColor = Colors.white, //(追加部分)
this.statusIcon = null //(追加部分)
}) : super(key: key);
@override
_MemberCardState createState() => _MemberCardState();
}
「//(追加部分)
」とコメントしている部分が追加したコードの差分です。
ポイントとなる部分を説明します。
backgroundColor
新しいインスタンス変数としてbackgroundColor
を追加しました。
それぞれのコンストラクタで初期化されていたり、されていなかたりします。
-
normal
: 初期化していない -
disabled
: 初期化している -
init
: 初期化している
つまり、normal
で初期化していないbackgroundColor
をinit
で初期化していることになります。
statusIcon
インスタンス変数statusIcon
には型が付いておらず、各コンストラクタでは、下記のように初期化しております。
-
normal
:Icon()
-
disabled
:Text()
-
init
:null
あまり綺麗なやり方ではないと思いますが、型を指定しないことによって自由に初期化にウィジェットを当てることができます。
プログラム全文
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Constructor Demo',
home: Scaffold(
appBar: AppBar(
title: Text('メンバーリスト'),
),
body: Column(
children: <Widget>[
MemberCard.normal(name: '鈴木 たかし', imgFileName: 'avator1.png'),
MemberCard.disabled(name: '高橋 ゆうさく', imgFileName: 'avator2.png'),
MemberCard.normal(name: 'Sarah Adams', imgFileName: 'avator3.jpeg'),
MemberCard.normal(name: 'Richard Mason', imgFileName: 'avator4.jpeg'),
],
)
),
);
}
}
class MemberCard extends StatefulWidget {
final String name; //名前
final String imgFileName; //アバター画像
final Color backgroundColor; //背景色
final statusIcon; //メンバーのステータス
// 通常のユーザー
MemberCard.normal({name, imgFileName})
: this._init(
name: name,
imgFileName: imgFileName,
statusIcon: Icon(Icons.check_circle, color: Colors.green,)
);
// 退会したユーザー
MemberCard.disabled({name, imgFileName})
: this._init(
name: name,
imgFileName: imgFileName,
backgroundColor: Colors.black12,
statusIcon: Text('退会済み')
);
MemberCard._init({Key key,
this.name = '',
this.imgFileName = '',
this.backgroundColor = Colors.white,
this.statusIcon = null
}) : super(key: key);
@override
_MemberCardState createState() => _MemberCardState();
}
class _MemberCardState extends State<MemberCard> {
@override
Widget build(BuildContext context) {
return Card(
color: widget.backgroundColor,
margin: EdgeInsets.symmetric(vertical: 20),
child: ListTile(
leading: ClipRRect(
borderRadius: BorderRadius.circular(25),
child: Image.asset(
'images/${widget.imgFileName}',
width: 50,
height: 50,
),
),
title: Text(widget.name),
trailing: widget.statusIcon,
),
);
}
}
プログラム全文(github)
https://github.com/naoaki-kaito/flutter_constructor_example