Add Interactivity
- 本ページのコード、内容は以下を参考にしています
Stateful WidgetとStateless Widgets
- stateless widgetとはユーザの操作を受けないもの…Icon, Text, IconButtonなど
- stateful widgetとはユーザの操作を受けるCheckbox, Radio, Sliderなどなど
- Stateful WidgetではStateクラス内でsetStateを呼び出すことでフレームワークに再描画を指示できる
Stateの管理について
- 状態をwidgetその者が管理するのか、親が管理するのか、などは状況による
- 以下の基準が参考になる
- チェックボックスのチェック状態・スライダーの位置などユーザデータが関係する場合は親Widget
- アニメーションなどであればWidget自身で管理する
- 迷ったら親で管理することから始める
Widget自身で管理
- ListViewはコンテンツが表示画面を越えると自動的にスクロールになるが、この状態は開発者側で管理すべきではない
- TapboxA内で
_active
を使ってtabされたかどうかを管理する
サンプルコード
import 'package:flutter/material.dart';
// TapboxA manages its own state.
//------------------------- TapboxA ----------------------------------
class TapboxA extends StatefulWidget {
const TapboxA({super.key});
@override
State<TapboxA> createState() => _TapboxAState();
}
class _TapboxAState extends State<TapboxA> {
bool _active = false;
void _handleTap() {
setState(() {
_active = !_active;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: _active ? Colors.lightGreen[700] : Colors.grey[600],
),
child: Center(
child: Text(
_active ? 'Active' : 'Inactive',
style: const TextStyle(fontSize: 32.0, color: Colors.white),
),
),
),
);
}
}
//------------------------- MyApp ----------------------------------
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo'),
),
body: const Center(
child: TapboxA(),
),
),
);
}
}
親Widgetで管理
- 多くの場合はこれが合理的
- 例えばIconButtonはStatelessだが、親が状態を持って渡すことで変化させられる
サンプルコード
import 'package:flutter/material.dart';
// ParentWidget manages the state for TapboxB.
//------------------------ ParentWidget --------------------------------
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false; // 親でstateをもつ
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return SizedBox(
child: TapboxB(
active: _active, // 子に渡す
onChanged: _handleTapboxChanged, // 変更のための処理も渡す、handleTapで実行される
),
);
}
}
//------------------------- TapboxB ----------------------------------
class TapboxB extends StatelessWidget {
const TapboxB({
super.key,
this.active = false,
required this.onChanged,
});
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
child: Center(
child: Text(
active ? 'Active' : 'Inactive',
style: const TextStyle(fontSize: 32.0, color: Colors.white),
),
),
),
);
}
}
混合の方法
- 子Widgetで一部管理し、親で他の状態を管理する
- 以下の例ではonTapした時のactive/inactiveは親Widgetが管理し、onTapUpなどの時のhighlightについては子Widgetで管理している
サンプルコード
import 'package:flutter/material.dart';
//---------------------------- ParentWidget ----------------------------
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return SizedBox(
child: TapboxC(
active: _active, // 親から状態を渡す
onChanged: _handleTapboxChanged,
),
);
}
}
//----------------------------- TapboxC ------------------------------
class TapboxC extends StatefulWidget {
const TapboxC({
super.key,
this.active = false,
required this.onChanged,
});
final bool active;
final ValueChanged<bool> onChanged;
@override
State<TapboxC> createState() => _TapboxCState();
}
class _TapboxCState extends State<TapboxC> {
bool _highlight = false; // 子Widgetでも状態を一部管理している
void _handleTapDown(TapDownDetails details) {
setState(() {
_highlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
setState(() {
_highlight = false;
});
}
void _handleTapCancel() {
setState(() {
_highlight = false;
});
}
void _handleTap() {
widget.onChanged(!widget.active);
}
@override
Widget build(BuildContext context) {
// This example adds a green border on tap down.
// On tap up, the square changes to the opposite state.
return GestureDetector(
onTapDown: _handleTapDown, // Handle the tap events in the order that
onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
onTap: _handleTap,
onTapCancel: _handleTapCancel,
child: Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: _highlight
? Border.all(
color: Colors.teal[700]!,
width: 10.0,
)
: null,
),
child: Center(
child: Text(widget.active ? 'Active' : 'Inactive',
style: const TextStyle(fontSize: 32.0, color: Colors.white)),
),
),
);
}
}