# Flutter
# User Interface
# Widget の基本
# 最小構成
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text('Hello World'),
))));
}
StatelessWidget
又はStatefulWidget
を継承したウィジェットを組み合わせて画面を作っていく。- 上記の
MaterialApp
,Scaffold
などは Stateful、Text
は Stateless
- 上記の
- Widget の主たる役割は
build()
メソッドを実装すること。
# 基本ウィジェット
Text
--- テキストColumn
|Row
--- 縦・横方向に要素を並べるExpanded
--- 余白を埋めきるまで拡張する。または収まるように縮小する。flex
により拡張率を設定できる。Stack
--- 複数の要素を絶対配置する。出現順に z 方向に重ねて表示する。Container
--- web でいう div。BoxDecoration
で装飾する。
# マテリアルコンポーネントの利用
MaterialApp
ウィジェット- テーマや
Navigator
、routes
など、マテリアルデザインに必須の機能を提供する。 - マテリアルデザインのウィジェットを使うときには必ず先祖に必要。
- 特に理由がなければ
MaterialApp
+Scaffold
ウィジェットを使うのがグッドプラクティス。
- テーマや
void main() {
runApp(MaterialApp(
title: 'Flutter Tutorial',
home: TutorialHome(),
));
}
class TutorialHome extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null,
),
title: Text('Example title'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
body: Center(
child: Text('Hello, world!'),
),
floatingActionButton: FloatingActionButton(
tooltip: 'Add',
child: Icon(Icons.add),
onPressed: null,
),
);
}
}
# 子ウィジェットへの引数の渡し方
class MyText extends StatelessWidget {
MyText({this.firstName, this.lastName});
final String firstName, lastName;
build(context) {
return Text(firstName + ' ' + lastName);
}
}
// 親側
MyText(
firstName: 'John',
lastName: 'Doe',
)
# ジェスチャーへの反応
GestureDetector
を使う。
class MyButton extends StatelessWidget {
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: Text('tap me'));
}
}
# StatefulWidget
class Counter extends StatefulWidget {
Counter({this.name});
final String name;
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
// setStateを呼ぶことでbuildが再実行されて画面が更新される
_counter++;
});
}
build(context) {
return Column(
children: <Widget>[
RaisedButton(
onPressed: _increment,
child: Text('Increment'),
),
// `StatefulWidget`のプロパティにアクセスするには
// `widget.名前`を使う
Text('Name: ' + widget.name),
// インスタンス変数にアクセスするには`$名前`を使う
Text('Count: $_counter'),
],
);
}
}
// 使う時
Counter(name: 'num of click');
StatefulWidget
とState
が別オブジェクトである理由:- 関心を分離して再利用性を高めるため?いまひとつよくわからない
- StatefulWidget は表示を担当する。親の
build()
が実行されるたびに再生成される一時的なもの。 - State は状態の保持を担当する。親の
build()
が実行されても同じものが使い回される。
# widget の変更検知
widget(StatefulWidget のプロパティのこと。React の props のようなもの)の変更を検知したいときはdidUpdateWidget
をオーバーライドする。
class _CounterState extends State<Counter> {
void didUpdateWidget(Counter oldWidget) {
super.didUpdateWidget(oldWidget);
// 処理
}
}
# State のライフサイクル
ライフサイクルメソッドはinitState
やdispose
をオーバーライドして記述する。
class _CounterState extends State<Counter> {
// マウント時に1度だけ行いたい処理
void initState() {
super.initState();
// 処理
}
// アンマウント時に行いたい処理
void dispose() {
// 処理
super.dispose();
}
}
# key
key を指定しない場合、差分判定はコンポーネントの位置で行われる。
[
if (showFirst) MyWidget(), // 1
MyWidget(), // 2
]
このため例えば上記の showFirst が true から false になった時、削除されるのは 2 番である。これを防ぐには key を使う。
ValueKey
--- 数値などの値で区別するObjectKey
--- オブジェクトの id で区別するUniqueKey
--- 絶対に重複しない。これを指定したコンポーネントは必ず毎回作り直される。
// 使用例
MyTextField(key: ValueKey(2))
MyTextField(key: ObjectKey(someObject))
MyTextField(key: Uniquekey())
# レイアウト
Flutter においてはほぼ全てのものがウィジェットである。
- レイアウトのためのウィジェット ---
Row
やCenter
など - UI エレメントを作るためのウィジェット(目に見えるウィジェット) ---
Text
やRaisedButton
など
Container
--- パディング、マージン、ボーダー、背景色を使いたい時に利用する
# レイアウトの手順
レイアウトウィジェット (opens new window)を選ぶ
Center(child:null)
目に見えるウィジェットを作成する。例えばText
やIcon
など。
Text('hello')
目に見えるウィジェットをレイアウトウィジェットに追加する。レイアウトウィジェットのchild
又はchildren
に記載することで行う。
Center(child:Text('hello'))
レイアウトウィジェットをページに配置する。親ウィジェットのbuild
メソッドや、マテリアルアプリであればScaffold
のbody
に追加することで行う。
class MyComponent extends StatelessWidget {
Widget build(BuildContext context) {
return Center(child: Text('hello'));
}
}
# ハイレベルなウィジェット
下記のようなハイレベルなウィジェットが用意されているので積極的に活用すること。
- Row の代わりに使える
ListTile
(opens new window) - Column の代わりに使える
ListView
(opens new window)
# 配置
Row
やColumn
は配置の設定を行うことができる。
- main axis は flex でいう
justify-content
- cross axis は flex でいう
align-items
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: []
)
# Row や Column をはみ出す場合
- 画像等が大きすぎて画面内に要素が収まらない場合、黄色と黒色のボーダーが警告として画面上に表示される。
- 画面内に収めたい場合は
Expanded
でRow
等の各子要素を囲む。flex
を指定することで拡大率を設定できる。
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Image.asset('images/pic1.jpg'),
),
Expanded(
flex: 2,
child: Image.asset('images/pic2.jpg'),
),
Expanded(
child: Image.asset('images/pic3.jpg'),
),
],
);
# 詰めて表示する
デフォルトではRow
やColumn
は main axis の方向に最大限拡大する。拡大せずに詰めて表示したい場合はmainAxisSize
を設定する。
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star),
Icon(Icons.star),
Icon(Icons.star),
],
);
# 読みやすいコードにする
Flutter のコードは、ネストが深くなるとすぐに読みづらくなる。変数や関数に切り出してネストが深くならないように心がけること。
# 画像
ネットワークから
Image.network('http://lorempixel.com/400/200/');
ローカルから
# pubspec.yaml
flutter:
assets:
- images/pic1.jpg
- images/pic2.jpg
- images/pic3.jpg
Image.asset('images/pic1.jpg');
# 基本的なウィジェット
# Container
- パディング、マージン、ボーダーを使う時
- 背景色や背景画像を変えたい時
# GridView
- ウィジェットを格子状に配置したい時
- はみ出した部分は自動でスクロール可能になる
GridView.count
指定した数で縦横を分割するときGridView.extent
アイテムごとの最大幅を指定して分割するとき
# ListView
- リスト形式で項目を並べるときに使う
- 縦方向、横方向のどちらでも使用できる
- はみ出す場合は自動的にスクロール可能になる
Column
より設定できる項目は少ないが、使いやすく、スクロールも簡単にできる。Column
では自動的にスクロールはできない点に注意
# Stack
- ウィジェット(多くの場合は画像)に重ねてウィジェットを配置するときに使う
- 1 つ目に指定したウィジェットがベースウィジェットとなる。2 つ目以降に指定したウィジェットがその上に重ねて表示される。
- スクロールは不可
- はみだした部分を表示するかどうかは選択できる
# Card
- マテリアルデザインのカードを使いたいとき
- 関連する情報をまとまりにしたいとき
- 丸角と影がつく
- スクロールは不可
# ListTile
- マテリアルデザインのリストタイル (opens new window)を使いたいとき
Row
より設定項目は少ないが、簡単に使うことができる