0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

単一要素の選択が可能リストをListViewとproviderパッケージで実現する方法とその落とし穴

Last updated at Posted at 2023-08-27

Flutter は 4 ヶ月目くらいの自称初心者です。アプリの宣伝も兼ねて、アプリ開発とリリースを経てつまづいた点や学びになった点をたまにアウトプットしようと思います。
https://itunes.apple.com/jp/app/id6460978343?mt=8

まとめ

ListView や Column, Rolw 配下の widget のうちどの要素が選択されているか provider で管理する方法を示す。さらに、要素の indexを保持すると更新のたびに全要素の再ビルドが走るため、各 widget で重たい処理を行う場合必要に応じて各要素ごとに選択されいるかを bool で保持すると良い。

導入

ドット絵エディタの Diorite では、ステータスの管理に provider package を使用しています。
また、エディターでは多数用意された描画用のツールから1つのツールを選択し描画する必要があるため、ListView や Column, Rolw など複数の widget をまとめる widget と provider を使用し、以下のようにアイコンの描画とステータスの管理を行なっていました。

こちらは、最小構成で再現したコードです。
DartPad にコピペすることで簡単に試すことができます。

main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(ChangeNotifierProvider(
      create: (_) => DrawItemProvider(),
      child: const MaterialApp(
        home: MySample(),
      )));
}

class MySample extends StatelessWidget {
  const MySample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: Row(
        children: context
            .watch<DrawItemProvider>()
            .drawIcons
            .asMap()
            .entries
            .map((e) => GestureDetector(
                onTap: () {
                  context.read<DrawItemProvider>().setDrawId(e.key);
                },
                child: DrawItem(e.key)))
            .toList(),
      )),
    );
  }
}

class DrawItem extends StatelessWidget {
  final index;

  const DrawItem(int this.index, {super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
        margin: const EdgeInsets.all(2),
        width: 40,
        height: 40,
        color:
            (context.select(((DrawItemProvider d) => d.drawId)) == this.index)
                ? Colors.black
                : context.read<DrawItemProvider>().drawIcons[index]);
  }
}

class DrawItemProvider extends ChangeNotifier {
  var drawId = 0;
  List<Color> drawIcons = [Colors.red, Colors.blue, Colors.green];

  void setDrawId(int i) {
    drawId = i;
    notifyListeners();
  }
}


以下のようにタップで選択したアイコンに変更を加えることができます。(サンプルでは色を黒に変えています。)
image.png

発生した問題

使用していたアプリでは、アイコン内部でかなり重たい処理を実行し、解像度の高い画像を表示していました。具体的には、メインのキャンバスで描画している画像の特定レイヤーの見切り出して、画像として表示する処理をしていました。そのため、なるべくアイコンの再描画と再ビルドを避けたく、debugPrintRebuildDirtyWidgets フラグをtrueに調べていたところどうやら操作のたびに全アイコンがビルドされているため、この原因調査と修正対応が必要になりました。

原因

気づいてしまえば単純な話ですが、原因は各 DrawItem が同じ drawId を参照していたからでした。

対応

今回は、要素ごとに、選択されたかをステートで持つことで対応しました。
これにより要素数だけ確保する領域は増えるのですが、たかだか Bool が要素数だけ増えるため、要素数に比例して重たい描画処理が増加することに比べると全然許容できるかと思います。

対応済みサンプルコード

変更した DrawItem と DrawItemProvider のみ示します。

class DrawItem extends StatelessWidget {
  final index;

  const DrawItem(int this.index, {super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
        margin: const EdgeInsets.all(2),
        width: 40,
        height: 40,
        color:
            (context.select(((DrawItemProvider d) => d.isSelected[index])))
                ? Colors.black
                : context.read<DrawItemProvider>().drawIcons[index]
    );
  }
}

class DrawItemProvider extends ChangeNotifier {
  var drawId = 0;
  List<Color> drawIcons = [Colors.red, Colors.blue, Colors.green];
  List<bool> isSelected = [true,false,false];

  void setDrawId(int i) {
    isSelected[drawId] = false;
    drawId = i;
    isSelected[i] = true;
    notifyListeners();
  }
}

最後に

Flutter は最初使い始めた時は特に何も考えなくてもかなり楽に使えるという印象でしたが、パフォーマンスを意識し始めると気にしないといけないことが複数出てくるな〜という印象に変わりました。
しかしながら、パフォーマンスを気にする必要がある、ということはある程度形ができてからになると思うので、アイディアやデザインを高速で形にできるという点で非常に良い言語だなと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?