【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進数やビット演算を理解しておくと、さまざまな実装で応用することができます。是非使ってみてください。
この記事が気に入ったらサポートをしてみませんか?