こんにちは、海岸蒼です。
表題の通り、Widgetをくるくる回してみました。
今回使ったのはRiverpodです。
「Riverpodって難しそう、、、」
と、思われるかもしれません。
ただ、使い方さえ覚えれば簡単ですよ。
本記事ではこのコードの解説と、サンプルコードの紹介を行います。
開発環境は、Flutter 2.2.3、Riverpod 1.0.0-dev.7です。
それでは早速参りましょう!
作成のきっかけ
きっかけはこんぶさん(@pressedkonbu)の次のツイートでした。
このツイートを見て、「Widgetって動かせるんだ、なんでもできるじゃん!」
と感動したのを覚えています。
仕組みはindexを状態として持ち、1つずつ増やしてPositionを変更する、というものです。
これを理解した時、次に思い浮かんだのが以下の数式でした。
「???」かもしれません。
これは点の円運動を表す式になっています。
この点の座標変化をプロットしていくと、円を描くわけです。
(こちらのページの紹介がわかりやすいです。)
http://www.geisya.or.jp/~mwm48961/kou2/parameter0.htm
「この式を利用すれば一つの状態を扱うことで円が描けるのでは?」
そう思い、コードを書き始めました。
概要

状態管理
今回状態管理に使ったのはRiverpodです。
Riverpodは状態管理に適したパッケージです。
これを使うと、Stateless WidgetをStateful Widgetのように扱えます。
書くコードも少なくて済むので非常に有用なパッケージです。
覚えることは4つだけ。
- 全体をProviderScopeで囲む
- 状態を変化させるStateless WidgetをConsumerWidgetに変える
- 状態を管理する入れ物としてStateProviderをグローバル領域に定義する
- 状態を参照するときは ref.watch(provider名).state,
更新するときはref.read(provider名).stateを使う
状態が更新されると、ref.watch(provider名).stateを含むWidgetがリビルドされます。
これにより、setStateを使わなくても更新管理ができるわけですね。
この状態を、点の円運動の式の
Widgetが円運動するわけです。
今回はボタンが押されたら
もう一度押されると無限ループを停止させる、という考え方でコードを書きます。
サンプルコードと解説

今回作成したコードを記載します。
pubspec.yamlのdependenciesに、flutter_riverpod: ^1.0.0-dev.7を追加しておいてください。
1 2 3 4 | dependencies: flutter: sdk: flutter flutter_riverpod: ^1.0.0-dev.7 |
サンプルコード main.dart
01 02 03 04 05 06 07 08 09 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 | import 'dart:math' ; import 'package:flutter/material.dart' ; import 'package:flutter_riverpod/flutter_riverpod.dart' ; final animationFlagProvider = StateProvider((ref) => false ); final animationParamaterProvider = StateProvider((ref) => 0); void main() { runApp(ProviderScope(child: MyApp())); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Animation Test' , theme: ThemeData( primarySwatch: Colors.blue, ), home: AnimationTest(), ); } } class AnimationTest extends ConsumerWidget { const AnimationTest({Key? key}) : super (key: key); @override Widget build(BuildContext context, WidgetRef ref) { return Material( child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( width: 400, height: 400, child: Stack( children: <Widget>[ Positioned( width: 50.0, height: 50.0, left:100 * cos(ref.watch(animationParamaterProvider).state * pi / 180)+200, top:100 * sin(2*ref.watch(animationParamaterProvider).state * pi / 180)+200, child: Container( color: Colors.blue, ), ), ], ), ), Container( margin: EdgeInsets.only(bottom: 200), child: ElevatedButton( onPressed: () async { ref.read(animationFlagProvider).state = !ref.watch(animationFlagProvider).state; while (ref.watch(animationFlagProvider).state) { ref.read(animationParamaterProvider).state ++; await Future.delayed(Duration(milliseconds: 1)); } }, child: Container( color: Colors.blue, child: const Center(child: Text( 'Tap me' )), ), ), ) ], ), ); } } |
サンプルコードの解説
5,6行目
状態を管理するプロバイダーの定義です。
今回は無限ループのOn Off の状態の管理としてanimationFlagProviderを、
9行目
ProviderScopeで全体を囲っています。
23,26行目
Stateless WidgetをConsumerWidgetに書き換えます。
この際、buildメソッドがWidgetRef を変数に持つため、これを追記します。
32~34行目
今回のWidgetが動き回る範囲をSized Boxで定義しておきます。
ちなみに、動き回っている間はこの範囲でしかWidgetは描画されません。
37行目
位置を管理するWidgetとしてPositionedを用います。
状態変化を細かく行うので、AnimatedPositionedにしなくてもアニメーションのように表現できます。
40,41行目
ここが今回のポイントです。
上で説明した点の円運動の式をleftを
状態が1変化すると1度変化する、という形にしたかったので、
状態に
前の100は移動半径、後ろの200は初期位置の調整です。
53行目
ボタンが押された時の動作の定義部分です。
無限ループのフラグをOn Offしています。
54~57行目
ここが、もう一つのポイントの無限ループ部分です。
フラグによって、無限ループするか止まるかが決まります。
無限ループ内では、
無限ループの速度をコントロールするため、Future.delayedでディレイをかけています。
以上が、サンプルコードの解説です。
まとめ

今回はWidgetをくるくる回す方法について解説しました。
実は、
こんな感じです。
HotReloadのおかげで一瞬で変わったように見えて面白いですよね。
じゃあ、3倍にすると、、、?などいろいろ試してみるとさらに興味深いですよ。
今回のコードが実際何に使えるのか、ですが、
ルーレットアプリの作成等に使えないかな、と思っています。
もし作られる方がいて、参考にしてくだされば嬉しいです。
本記事がお役に立てば幸いです。
コメント