Index
Flutterの公式チュートリアルのまとめ記事です。
この記事を読む上で必要な知識を以下の記事にもまとめていますので、是非ご覧ください。
[Flutter]ボタンを実装する方法をサンプルコード付き解説(基礎)
- stateを管理する方法にはいくつかのアプローチ法がある
- widget作成者はアプローチ方法を選択する必要がある
- もし良いアプローチ方法がわからなければ、親widgetで管理するアプローチから始めましょう
基本的には以下の三つの中から選ぶことになります。
- 自分自身で管理する
- 親widgetで管理する
- 自分自身と親で分けて管理する
どのアプローチ方法を使うかは以下のように決めることができます。
- checkboxのオンオフ、スライダーのポジションのようなものは親widgetが管理するのが良いでしょう
- アニメーションのようなものの場合自分自身で管理するのが良いでしょう
もし見当もつかない場合はとりあえず親widgetで管理しておくのが良いでしょう。
サンプルコードでは以下の動画のように、ほぼ同じような動きをするwidgetを3つの管理方法を使って実装していきます。
まずは1番簡単な方法から解説していきます。
ポイントは以下の4点
- TapboxAのstateを管理している
- _activeがtapboxAの色をきめている
- _handleTap()メソッドがsetState()メソッドを読んでUIを更新している
- 全ての実装が自分自身()tapboxAに実装されている
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class TapboxA extends StatefulWidget { const TapboxA({super.key}); @override _TapboxAState 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: 110, height: 110, decoration: BoxDecoration( color: _active ? Colors.lightBlue[500] : Colors.grey[600]), child: Center(child: Text(_active ? 'active' : 'Inactive')), )); } } |
ポイントは以下の三つ
- Tapboxの_activeを管理する
- tapboxがタップされた時_handleTapboxChanged()が呼ばれる
- _activeが変化するとき、setState()が呼ばれ、UIが更新される
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
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 = !_active; }); } @override Widget build(BuildContext context) { return SizedBox( child: TapboxB( active: _active, onChanged: _handleTapboxChanged, ), ); } } 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: 110, height: 110, decoration: BoxDecoration( color: active ? Colors.lightGreen[200] : Colors.grey[600], ), child: Center( child: Text(active ? 'Active' : 'Inactive', style: const TextStyle(fontSize: 20.0, color: Colors.white)), ), ), ); } } |
ポイントは以下の6点
- _parentWidgetState2は_activeを管理する
- _parentWidgetState2はboxがタップされた時_handleTapboxChanged()を呼ぶ
- _parentWidgetState2はboxがタップされた時、setStateを呼びUIを更新し_activeを変化させる
- _TapboxCStateは_highlightを管理する
- GestureDetectorはタップイベントを感知します
- _TapboxCStateはsetStateが呼ばれるとUIを更新し_highlightを変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
lass ParentWidget2 extends StatefulWidget { const ParentWidget2({super.key}); @override State<ParentWidget2> createState() => _ParentWidgetState2(); } class _ParentWidgetState2 extends State<ParentWidget2> { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return SizedBox( child: TapBoxC( active: _active, onChanged: _handleTapboxChanged, ), ); } } 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; 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: 110, height: 110, 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: 20.0, color: Colors.white)), ), ), ); } } |
以下のコードをmain.dartにコピペすれば、動きを確認できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: const [ TapboxA(), ParentWidget(), ParentWidget2(), ]), ) // This trailing comma makes auto-formatting nicer for build methods. ); } } class TapboxA extends StatefulWidget { const TapboxA({super.key}); @override _TapboxAState 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: 110, height: 110, decoration: BoxDecoration( color: _active ? Colors.lightBlue[500] : Colors.grey[600]), child: Center(child: Text(_active ? 'active' : 'Inactive')), )); } } 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 = !_active; }); } @override Widget build(BuildContext context) { return SizedBox( child: TapboxB( active: _active, onChanged: _handleTapboxChanged, ), ); } } 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: 110, height: 110, decoration: BoxDecoration( color: active ? Colors.lightGreen[200] : Colors.grey[600], ), child: Center( child: Text(active ? 'Active' : 'Inactive', style: const TextStyle(fontSize: 20.0, color: Colors.white)), ), ), ); } } class ParentWidget2 extends StatefulWidget { const ParentWidget2({super.key}); @override State<ParentWidget2> createState() => _ParentWidgetState2(); } class _ParentWidgetState2 extends State<ParentWidget2> { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return SizedBox( child: TapBoxC( active: _active, onChanged: _handleTapboxChanged, ), ); } } 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; 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: 110, height: 110, 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: 20.0, color: Colors.white)), ), ), ); } } |