見出し画像

【Flutter】ビット演算を利用したトグルスイッチの状態管理

アプリの設定画面でよく使われる、トグルスイッチですが、ビット演算を利用すると、複数のトグルスイッチの状態を一つの変数で管理することができ、よりスマートな実装が可能になります。

また、トグルスイッチ意外にも、一つの要素につき、二つの状態を管理しなければいけない場合に応用できます。

それでは実装方法を紹介していきます。

ViewModel

まず、ロジックとなるViewModelを構築します。

状態管理はproviderパッケージを利用しています。

providerパッケージの利用方法について簡単に知りたい方は、こちらに目を通して見てください。


こちらが全体のコードです↓

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

class Model extends ChangeNotifier {
 int _selected = 0; //1. onになっているスイッチの状態を管理するための変数を定義
 List<int> values = [] //2. それぞれのスイッチの番号を入れるリストの変数を定義


// 3. スイッチそれぞれの番号を生成し、リストに追加する。
 void start() {
   var val = 1;
   for (var i = 1; i <= 5; i++) {
     values.add(val);
     val <<= 1;
   }
 }


// 4. スイッチが切り替わったタイミングで呼び出され、_selectedの値を更新する。
 void change(int val) {
   if (_selected & val == 0) {
     _selected += val;
     notifyListeners();
   } else {
     _selected -= val;
     notifyListeners();
   }
 }

 
 int get selected => _selected;
 set selected(int val) {
   _selected = val;
   notifyListeners();
 }
}

コード上にコメントで全体の流れを書き込みました。これを元に詳しく解説していきます。

1.  onになっているスイッチの状態を管理するための変数を定義

最初に、_selectedという変数を定義しています。

この変数はonになっているスイッチの状態のみを管理するための変数です。

そして型はint型になっていますが、ただの整数としてこの変数を利用するわけではありません。

_selectedに入る数字は、ビット演算をするために、2進法で表した場合の値として利用されます。

2. それぞれのスイッチの番号を入れるリストの変数を定義
 

二つ目に変数valuesが定義されています。valuesには、それぞれのスイッチに振られた数字のリストが入ります。

5つのスイッチがある場合どんな数字が入るかというと、2進数で表した時

[1,10,100,1000, 10000] 

のように1の位置がひと桁ずつ繰り上がっていく数字です。

実際に入る数字は10進数であるため、[1, 2, 4, 8, 16]となります。

3. スイッチそれぞれの番号を生成し、リストに追加する。

View側から、画面が構築される前に呼び出される関数、startメソッドを定義しています。

この関数の中では

var val = 1;

といふうに、変数が定義されています。

このコードは、1つ目のスイッチの番号を表しています。

そして最初はvalに1が入った状態でfor文の中で利用されます。

for (var i = 1; i <= 5; i++) {
   values.add(val);
     val <<= 1;
}

一回目のループでは1がリストのvaluesにaddされます。

そして、このコード「val <<= 1;」(val = val << 1の省略形)によって、valの値が1→2に変わります。

ここでようやくbit演算が出てきました。

「<<」この記号は10進数を2進数で表した数字の桁を左にずらす時に使います。

つまり2進数で表した場合、valの値は

1→10→100→1000

のように変わります。

最終的には、valuesの値は[1,2,4,8,16]となり、2進数で表すと[1,10,100,1000,10000] となります。

4. スイッチが切り替わったタイミングで呼び出され、_selectedの値を更新する。

void change(int val) {
   if (_selected & val == 0) {
     _selected += val;
     notifyListeners();
   } else {
     _selected -= val;
     notifyListeners();
   }
 }

スイッチが切り替わったタイミングで処理を行うchangeメソッドを定義しています。

引数には切り変わったスイッチの番号が入ります。

中では_selectedの状態を参照し、引数「val」の値が_selectedに無い場合は加算、あった場合は 減算するといった処理をしています。

そして「&」という記号ですが、これにより2進数同士で数字を比較し、同じ桁の数字がどちらも1と表示されている部分は1を、それ以外は0にして返されます。

例えば、全てのスイッチがオフになっている状態から、4つ目のスイッチをオンにする場合、最初の変数の状態を2進数で表すと次のようになっています。

_selected   0

val               1000

そして下の式の答えはtrueになります

0 & 1000 == 0

よって、_selectedの値にvalの値が加算され

_selected 1000

というふうに変わります。

この状態から2つ目のスイッチをつけた場合は

_selected 1000

_selected 1010

という風に変わります。

View

続いてView側の実装です。

こちらがView側の全コードです。大まかな流れをコメントで書いておきました。

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

class DemoApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     theme: ThemeData.dark().copyWith(
       scaffoldBackgroundColor: darkBlue,
     ),
     debugShowCheckedModeBanner: false,
     home: Scaffold(
         body: SafeArea(
       child: ToggleSwitches(),
     )),
   );
 }
}


class ToggleSwitches extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   final model = Provider.of<Model>(context, listen: false);
   
   model.start();  // 1. Modelクラスで定義したstart()を呼び出す

   return Selector<Model, List<int>>(
   
     // 2. Modelクラスで定義したvaluesの状態を監視
   
     selector: (_, value) => value.values,
     builder: (_, values, __) => ListView(
       children: [
       
         ...values.map((value) {  
           return Selector<Model, int>(
           
             // 3. Modelクラスで定義したselectedの状態を監視 
              
             selector: (_, value) => value.selected,
             builder: (_, selected, __) {
               return Container(
                 child: ListTile(
                   onTap: () {},
                   trailing: Switch(
                   
                       // 4. スイッチが入れ替わった時にchangeメソッドを呼び出す
                       onChanged: (newValue) => model.change(value),
                       
                       
                       // 5. selectedの中にvalue(ボタンの数字)が含まれていればスイッチがON
                                                           になる 
                       value: (selected & value) != 0),
                       
                   title: const Text(
                     'スイッチ',
                     style: TextStyle(color: Colors.white),
                   ),
                 ),
               );
             },
           );
         })
       ],
     ),
   );
 }
}

トグルスイッチの実装はSwitchというWidgetで簡単に作ることができます。


5. selectedの中にvalue(ボタンの数字)が含まれていればスイッチがON

最後にここのコードについて説明します。

value: (selected & value) != 0)                                                           

Switchウィジェットのプロパティ「value」の型はbooleanですので、trueかfalseのどちらかが返るようにします。

trueであればスイッチがONになります。

下のような場合は、2つ目と5つ目のスイッチがオン、valueの値のボタンはselectedに含まれていないので、4つ目のスイッチはオフです。

selected 10010

value         1000

まとめ

このように2進数やビット演算を理解しておくと、さまざまな実装で応用することができます。是非使ってみてください。







この記事が気に入ったらサポートをしてみませんか?