こんにちは、海岸蒼です。
表題の通り、Widgetをくるくる回してみました。
今回使ったのはRiverpodです。
「Riverpodって難しそう、、、」
と、思われるかもしれません。
ただ、使い方さえ覚えれば簡単ですよ。
本記事ではこのコードの解説と、サンプルコードの紹介を行います。
開発環境は、Flutter 2.2.3、Riverpod 1.0.0-dev.7です。
それでは早速参りましょう!
作成のきっかけ
きっかけはこんぶさん(@pressedkonbu)の次のツイートでした。
このツイートを見て、「Widgetって動かせるんだ、なんでもできるじゃん!」
と感動したのを覚えています。
仕組みはindexを状態として持ち、1つずつ増やしてPositionを変更する、というものです。
これを理解した時、次に思い浮かんだのが以下の数式でした。
$$ x = sin t $$
$$ y = cos t $$
「???」かもしれません。
これは点の円運動を表す式になっています。
$t$をどんどん増やすと、点の$x$座標、$y$座標がそれによって変わります。
この点の座標変化をプロットしていくと、円を描くわけです。
(こちらのページの紹介がわかりやすいです。)
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を使わなくても更新管理ができるわけですね。
この状態を、点の円運動の式の$t$としてどんどん増やしていけば、
Widgetが円運動するわけです。
今回はボタンが押されたら$t$を増加させる無限ループを開始、
もう一度押されると無限ループを停止させる、という考え方でコードを書きます。
サンプルコードと解説
今回作成したコードを記載します。
pubspec.yamlのdependenciesに、flutter_riverpod: ^1.0.0-dev.7を追加しておいてください。
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^1.0.0-dev.7
サンプルコード main.dart
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を、
$t$の状態の管理としてanimationParamaterProviderを定義しています。
9行目
ProviderScopeで全体を囲っています。
23,26行目
Stateless WidgetをConsumerWidgetに書き換えます。
この際、buildメソッドがWidgetRef を変数に持つため、これを追記します。
32~34行目
今回のWidgetが動き回る範囲をSized Boxで定義しておきます。
ちなみに、動き回っている間はこの範囲でしかWidgetは描画されません。
37行目
位置を管理するWidgetとしてPositionedを用います。
状態変化を細かく行うので、AnimatedPositionedにしなくてもアニメーションのように表現できます。
40,41行目
ここが今回のポイントです。
上で説明した点の円運動の式をleftを$x$、topを$y$として記載しています。
状態が1変化すると1度変化する、という形にしたかったので、
状態に$π/180$をかけています(ラジアン変換)
前の100は移動半径、後ろの200は初期位置の調整です。
53行目
ボタンが押された時の動作の定義部分です。
無限ループのフラグをOn Offしています。
54~57行目
ここが、もう一つのポイントの無限ループ部分です。
フラグによって、無限ループするか止まるかが決まります。
無限ループ内では、$t$の増加を行っています。
無限ループの速度をコントロールするため、Future.delayedでディレイをかけています。
以上が、サンプルコードの解説です。
まとめ
今回はWidgetをくるくる回す方法について解説しました。
実は、$t$の値を片方だけ2倍すると、動きが変わります。
こんな感じです。
HotReloadのおかげで一瞬で変わったように見えて面白いですよね。
じゃあ、3倍にすると、、、?などいろいろ試してみるとさらに興味深いですよ。
今回のコードが実際何に使えるのか、ですが、
ルーレットアプリの作成等に使えないかな、と思っています。
もし作られる方がいて、参考にしてくだされば嬉しいです。
本記事がお役に立てば幸いです。
コメント