【Flutter】Widgetをくるくる回してみた【Positioned】【Riverpod】

Flutter基礎

こんにちは、海岸蒼です。

表題の通り、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倍にすると、、、?などいろいろ試してみるとさらに興味深いですよ。

今回のコードが実際何に使えるのか、ですが、
ルーレットアプリの作成等に使えないかな、と思っています。

もし作られる方がいて、参考にしてくだされば嬉しいです。

本記事がお役に立てば幸いです。

コメント

タイトルとURLをコピーしました