- ジェスチャーの検知は
GestureDetector
で行う - 以下の感じで子要素に対して
onTap
の挙動を決めたりする
GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: Container(
height: 50.0,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Colors.lightGreen[500],
),
child: const Center(
child: Text('Engage'),
),
),
);
- StatefulなWidgetを利用すれば可能
- 以下の形になる
class Counter extends StatefulWidget {
...constructor...
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extend State<Counter> {
int _counter = 0 // State
void _increment() { // Mutation
setState(() { // これが必須
_counter++;
});
}
@override
Widget build(BuildContext context) { // buildはState側に記述
return ...
}
- 基本的に以下のように分かれている
- Widget…コンストラクタと
createState
- State…State関連とbuild
- このように分かれているのは2つのオブジェクトでライフサイクルが違うため。
- Widget…一時的なオブジェクト
- State…情報を永続的にもつ
- 親WidgetにはCallbackで、子WidgetにはStateで状態が共有される
- 以下の感じ
// CounterDisplay 受け取ったcounterを表示するのみ
class CounterDisplay extends StatelessWidget {
const CounterDisplay({required this.count, super.key});
final int count;
@override
Widget build(BuildContext context) {
return Text('Count: $count');
}
}
// CounterIncrementor onPressedというCallbackを受け取り、ボタンが押されたらそれを実行する
class CounterIncrementor extends StatelessWidget {
const CounterIncrementor({required this.onPressed, super.key});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: const Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CounterIncrementor(onPressed: _increment), // ボタンタップ時に_increment()が実行され_counterが+1される
const SizedBox(width: 16),
CounterDisplay(count: _counter), // _counterの表示
],
);
}
}
- こんな感じで状態を持つコンポーネント(
Counter
)と持たないで表示 or 変更処理をするコンポーネント(CounterDisplay
, CounterIncrementor
)に分ける
class Product {
const Product({required this.name});
final String name;
}
typedef CartChangedCallback = Function(Product product, bool inCart);
// これはItemの1つの要素
class ShoppingListItem extends StatelessWidget {
ShoppingListItem({
required this.product,
required this.inCart,
required this.onCartChanged,
}) : super(key: ObjectKey(product));
final Product product;
final bool inCart;
final CartChangedCallback onCartChanged;
// inCartフラグの値次第で、表示が変わる
Color _getColor(BuildContext context) {
// The theme depends on the BuildContext because different
// parts of the tree can have different themes.
// The BuildContext indicates where the build is
// taking place and therefore which theme to use.
return inCart //
? Colors.black54
: Theme.of(context).primaryColor;
}
// inCartフラグの値次第で、表示が変わる
TextStyle? _getTextStyle(BuildContext context) {
if (!inCart) return null;
return const TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
@override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
onCartChanged(product, inCart);
},
leading: CircleAvatar(
backgroundColor: _getColor(context),
child: Text(product.name[0]),
),
title: Text(
product.name,
style: _getTextStyle(context),
),
);
}
}
// 状態を持つコンポーネント
class ShoppingList extends StatefulWidget {
const ShoppingList({required this.products, super.key});
final List<Product> products;
// The framework calls createState the first time
// a widget appears at a given location in the tree.
// If the parent rebuilds and uses the same type of
// widget (with the same key), the framework re-uses
// the State object instead of creating a new State object.
@override
State<ShoppingList> createState() => _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
final _shoppingCart = <Product>{};
// タップされたItemをCartに追加・削除する
void _handleCartChanged(Product product, bool inCart) {
setState(() {
// When a user changes what's in the cart, you need
// to change _shoppingCart inside a setState call to
// trigger a rebuild.
// The framework then calls build, below,
// which updates the visual appearance of the app.
if (!inCart) {
_shoppingCart.add(product);
} else {
_shoppingCart.remove(product);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Shopping List'),
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 8.0),
children: widget.products.map((product) {
return ShoppingListItem(
product: product,
inCart: _shoppingCart.contains(product), // inCartはcontainsの結果を渡している
onCartChanged: _handleCartChanged,
);
}).toList(),
),
);
}
}