PR

[Flutter]Flutter Widget of the Week「#28 Dismissible」、「#29 SizedBox」、「#30 ValueListenableBuilder」

Flutter

はじめに

前回はFlutter Widget of the Weekの「#25 Align」、「#26 Positioned」、「#27 AnimatedBuilder」を紹介しました。

今回はその続きで「#28 Dismissible」、「#29 SizedBox」、「#30 ValueListenableBuilder」の3つです。

前回の記事はこちら

Flutter Widget of the Week

環境

  • Flutter 2.10.3

記事にした時点でのバージョンです。GitHubに公開しているのは常に最新の安定版(stable)を使う予定です。

#28 Dismissible

Dismissibleとは

スマートフォンの操作で一覧表示されている項目を左右のどちらかにスワイプして削除する動きを実現する際に使います。

注意点として、keyに指定する内容はindex(配列の何番目)等の情報は使わないようにしてください。

以下のようなエラーが出やすくなります。必ず一意となるkeyを設定してください。

════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown building Dismissible-[<2>](dirty, dependencies: [Directionality], state: _DismissibleState#dd76d(tickers: tracking 2 tickers)):
A dismissed Dismissible widget is still part of the tree.
2

Make sure to implement the onDismissed handler and to immediately remove the Dismissible widget from the application once that handler has fired.

The relevant error-causing widget was
Dismissible-[<2>]

サンプルコード

import 'package:flutter/material.dart';

class SamplePage028 extends StatefulWidget {
  const SamplePage028({
    Key? key,
  }) : super(key: key);

  @override
  State<SamplePage028> createState() => _SamplePage028State();
}

class _SamplePage028State extends State<SamplePage028> {
  final _list = <String>[
    'A',
    'B',
    'C',
    'D',
    'E',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dismissible'),
        centerTitle: true,
      ),
      body: SafeArea(
        child: ListView.builder(
          itemBuilder: (context, index) {
            final str = _list[index];
            return Dismissible(
              key: ValueKey(str),
              background: Container(
                color: Colors.green,
                padding: const EdgeInsets.symmetric(horizontal: 20),
                alignment: Alignment.centerLeft,
                child: const Icon(
                  Icons.notifications,
                  color: Colors.white,
                ),
              ),
              secondaryBackground: Container(
                color: Colors.red,
                padding: const EdgeInsets.symmetric(horizontal: 20),
                alignment: Alignment.centerRight,
                child: const Icon(
                  Icons.delete,
                  color: Colors.white,
                ),
              ),
              child: ListTile(
                title: Text(str),
              ),
              onDismissed: (direction) {
                setState(() {
                  _list.removeAt(index);
                  if (direction == DismissDirection.startToEnd) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('$strを削除しました'),
                      ),
                    );
                  }
                });
              },
            );
          },
          itemCount: _list.length,
        ),
      ),
    );
  }
}

結果

左へスワイプすると削除

右へスワイプすると削除しつつ通知を出します。

動画

公式リファレンス

Dismissible class - widgets library - Dart API
API docs for the Dismissible class from the widgets library, for the Dart programming language.

#29 SizedBox

SizedBoxとは

サイズを指定したウィジェットを作成する際に利用します。また子ウィジェットを必要としないのでスペースを埋めるのに利用したり、widthやheightにinfinityを指定したりSizedBox.expandを使うことで親の許容限界まで子ウィジェットを広げることができます。

サンプルコード

import 'package:flutter/material.dart';

class SamplePage029 extends StatelessWidget {
  const SamplePage029({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dismissible'),
        centerTitle: true,
      ),
      body: SafeArea(
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              SizedBox(
                width: double.infinity,
                height: 60,
                child: ElevatedButton(
                  onPressed: () {},
                  child: const Text('infinity指定だと画面サイズまで広げる'),
                ),
              ),
              const SizedBox(
                height: 100,
              ),
              SizedBox(
                width: 200,
                height: 60,
                child: ElevatedButton(
                  onPressed: () {},
                  child: const Text('↑スペース作る'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

結果

使うとこんな感じ。

動画

公式リファレンス

SizedBox class - widgets library - Dart API
API docs for the SizedBox class from the widgets library, for the Dart programming language.

#30 ValueListenableBuilder

ValueListenableBuilderとは

ValueNotifierを継承したクラスを使う状態管理の方法の1つです。昨今ではStateNotifierを使ったほうが便利なのでValueNotifier使っている人は減ってると思います。

ただValueNotifierを状態変数が1つなので状態管理クラスがシンプルに保てます。

アニメーションやInheritedWidgetと組み合わせて使うことが多いようです。

サンプルコード

import 'dart:math';

import 'package:flutter/material.dart';

class SamplePage030 extends StatefulWidget {
  const SamplePage030({
    Key? key,
  }) : super(key: key);

  @override
  State<SamplePage030> createState() => _SamplePage030State();
}

class _SamplePage030State extends State<SamplePage030>
    with SingleTickerProviderStateMixin {
  final _notifier = _Sample030Notifier();
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      duration: const Duration(seconds: 4),
      vsync: this,
    )..repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    debugPrint('_SamplePage030State#build');

    final animation = Tween(
      begin: 0,
      end: 2 * pi,
    ).animate(_controller);

    return Scaffold(
      appBar: AppBar(
        title: const Text('ValueListenableBuilder'),
        centerTitle: true,
      ),
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.max,
            children: [
              ValueListenableBuilder<int>(
                valueListenable: _notifier,
                builder: (context, value, child) {
                  return Text(value.toString());
                },
              ),
              const SizedBox(height: 200),
              ValueListenableBuilder<num>(
                valueListenable: animation,
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.green,
                ),
                builder: (context, value, child) {
                  return Transform.rotate(
                    angle: animation.value.toDouble(),
                    child: child,
                  );
                },
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _notifier.change(_notifier.value + 1);
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

class _Sample030Notifier extends ValueNotifier<int> {
  _Sample030Notifier() : super(0);

  void change(int i) {
    value = i;
    notifyListeners();
  }
}

結果

ボタンを押しても不要なbuildが走らないのがわかります。

動画

公式リファレンス

ValueListenableBuilder class - widgets library - Dart API
API docs for the ValueListenableBuilder class from the widgets library, for the Dart programming language.

さいごに

DismissibleはとSizedBoxは頻繁に使いそうなのでしっかり覚えておきたいですね。

ValueListenableBuilderは使い所がよくわかりません。

おすすめ参考書

リンク

GitHub - nobushiueshi/flutter_widget_of_the_week
Contribute to nobushiueshi/flutter_widget_of_the_week development by creating an account on GitHub.

コメント

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