Flutterで、複数の選択肢の中から1つだけ選択するボタンを作るには、ChoiceChip(チョイスチップ)を使うと簡単におしゃれなボタンが作成できます。
この記事では、ChoiceChipの基本的な使い方と、実践的な応用例を紹介します。
具体的には、ChoiceChipをカスタムウィジェットとして作成し、その中でList.generateを使ってボタンに表示するテキストをリストから自動で取得したり、ボタンの数が多い場合には、スマホの画面幅で自動折り返しする方法などを解説します。
- Flutter : 3.27.1
- Dart : 3.6.0
基本のChoiceChip
FlutterでChoiceChipを作る際にまず必要な引数は、以下の3つです。
label:ボタンの中に表示する文字やアイコンselected:どういうときに「ボタンが選ばれた」と認識するのかonSelected:ボタンが選ばれたときの処理
下記のコードが、上記3つの引数を指定したChoiceChipの書き方です。
ChoiceChip(
label: Text("1個目のボタン"),
selected: selectValue == 1,
onSelected: (bool isSelected) {
setState(() {
selectValue = isSelected ? 1 : 0;
});
},
)行番号6のselectValue = isSelected ? 1 : 0は、「三項演算子」(または「三項条件演算子」)を使った構文で、isSelectedがtrueなら1、そうでなければ0をselectValueに代入しています。
この代入処理をsetStateで実行して、ボタンが選択された状態に描画を更新します。
基本のChoiceChipで3つのボタンをつくる

2つ目のボタンが押されたところ.
ChoiceChipはsetStateで画面更新をしたいので、StatefulWidgetを使います。
下記のコードでは、StatefulWidgetを継承した_MyHomePageStateクラスの中で3つのChoiceChipを直接作っています。
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectValue = 0;
String result = "";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('チョイスチップ'),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
SizedBox(height: 24.0),
ChoiceChip(
label: Text("1個目のボタン"),
selected: selectValue == 1,
onSelected: (bool isSelected) {
setState(() {
selectValue = isSelected ? 1 : 0;
result = selectValue.toString();
});
},
),
ChoiceChip(
label: Text("2個目のボタン"),
selected: selectValue == 2,
onSelected: (bool isSelected) {
setState(() {
selectValue = isSelected ? 2 : 0;
result = selectValue.toString();
});
},
),
ChoiceChip(
label: Text("3個目のボタン"),
selected: selectValue == 3,
onSelected: (bool isSelected) {
setState(() {
selectValue = isSelected ? 3 : 0;
result = selectValue.toString();
});
},
),
Text("押された選択ボタン:$result"),
],
),
),
);
}
}行番号23-32のChoiceChipひとつで、ひとつのボタンを作っています。
行番号19でColumnにしているので、ボタンは縦に並びます。
行番号8のint selectValue = 0;で初期値に代入している0は、どのボタンにも割り当てられていない番号なので、スマホ画面の初期状態でもどのボタンも選択されていない状態になります。

ChoiceChipの実践例
上記のコードでは汎用性がないので、より実践的に使えるようChoiceChipをカスタマイズしておくと良いです。
ここでのカスタマイズのポイントは、次の4点です。
- ボタンの作成は、
Listから必要な個数を自動生成する(List.generate()の使用) - ボタン内の値は、
Listの値を使う(カスタムウィジェットの作成) - ボタンを横並びにしたときに、スマホの幅1行に入りきらない場合は自動で折り返す(
Wrap()の使用) - 表示色などの設定
ChoiceChipをより実践的に使うには、List.generate()とWrap()を使ったカスタムウィジェットを作ります。
そのためには、まずボタンに表示する内容の入ったリストから作っていきます。
リストの作成
ここでは、下記のようなItemクラスを作成(行番号3-13)してitemsというリスト(行番号15-31)を作成しました。
Itemクラスは、item.dartという名前のDartファイルにしています。
この中で、各Itemごとに「ボタンに表示するテキスト」「色」「アイコン」を定義しました。
import 'package:flutter/material.dart';
class Item {
final String itemName;
final Color itemColor;
final Icon itemIcon;
Item({
required this.itemName,// ボタンに表示するテキスト
required this.itemColor,// 色
required this.itemIcon,// アイコン
});
}
final List<Item> items = [
Item(
itemName: "1個目のボタン",
itemColor: Colors.lightBlueAccent,
itemIcon: Icon(Icons.account_circle),
),
Item(
itemName: "2個目のボタン",
itemColor: Colors.amberAccent,
itemIcon: Icon(Icons.adb),
),
Item(
itemName: "3個目のボタン",
itemColor: Colors.pinkAccent,
itemIcon: Icon(Icons.accessibility_new),
),
];
カスタムウィジェットの作成

リストを作成したら、自分のニーズに合わせてChoiceChipをカスタムウィジェットとして作成します。
ここでは、[choice_button.dart]というファイル名でChoiceButtonというウィジェットを作りました。
import 'package:flutter/material.dart';
class ChoiceButton extends StatefulWidget {
final List items;
final ValueChanged onItemSelected;
const ChoiceButton({super.key, required this.items, required this.onItemSelected});
@override
State<ChoiceButton> createState() => _ChoiceButtonState();
}
class _ChoiceButtonState extends State<ChoiceButton> {
int? selectValue;
@override
Widget build(BuildContext context) {
return Wrap(
children: List<Widget>.generate(widget.items.length, (int index) {
return ChoiceChip(
label: widget.items[index].itemIcon,
selected: selectValue == index,
backgroundColor: Colors.blueGrey,
selectedColor: widget.items[index].itemColor,
onSelected: (bool isSelected) {
setState(() {
selectValue = isSelected ? index : null;
widget.onItemSelected(widget.items[index]);
});
},
);
}).toList(),
);
}
}StatefulWidgetで作成(行番号3)
class ChoiceButton extends StatefulWidget {ChoiceChip()が継承するのはStatefulWidgetです。Statefulじゃないと、ボタンの選択状態を更新表示するsetStateが使えません。
コンストラクタの作成(行番号6)
class ChoiceButton extends StatefulWidget {
final List items;
final ValueChanged onItemSelected;
const ChoiceButton({super.key, required this.items, required this.onItemSelected});
@override
State<ChoiceButton> createState() => _ChoiceButtonState();
}
class _ChoiceButtonState extends State<ChoiceButton> {
int? selectValue;
@override
Widget build(BuildContext context) {
return Wrap(
children: List<Widget>.generate(widget.items.length, (int index) {
return ChoiceChip(
label: widget.items[index].itemIcon,
...行番号4が、引数として受け取るリスト。
行番号5は、ボタンが選択されたときに呼び出しもとが実行する関数です。
行番号6のコンストラクタで、行番号4, 5の引数を必須プロパティとして指定します。
このコンストラクタの値をbuild()メソッドの中で使うには、widget.itemsというように、変数名の前にwidget.をつけます。(行番号17, 19など )
onItemSelectedのVoidCallbackという型については、こちらの関連記事でも紹介しています。

Wrap()で自動改行する(行番号16)

Wrap()を使うと、スマホ幅に入りきらない場合でもエラーにならない.
@override
Widget build(BuildContext context) {
return Wrap(
children: ...build()メソッドでは、まずWrap()を使います。Wrap()を使うことで、スマホの画面からはみ出してしまったボタンは、自動で次の行に移動します。
ChoiceChipの基本の使い方ではボタンを縦に並べていましたが、Wrap()での並び方向は横方向がデフォルトです。
縦並びにするときは、direction プロパティをdirection: Axis.verticalとします。
return Wrap(
direction: Axis.vertical,
children: ...List.generateでボタンを自動生成する(行番号17)
@override
Widget build(BuildContext context) {
return Wrap(
children: List<Widget>.generate(widget.items.length, (int index) {
return Wrap()のchildren:に、List.generate()を指定して、必要な数のボタンを自動生成します。
widget.items.length:自動生成する項目の数(int index):自動生成するボタンの位置
自動生成するボタンの数は「リスト項目の数」なので、引数として受け取ったリストの項目数を指定しています。
次に、「生成したボタンアイテムが何番目なのか」を格納するためにindexという変数の宣言で、(int index)としています。
ChoiceChip()の表示設定(行番号18-25)
あとは、ChoiceChip()のプロパティを使って、自分好みの体裁にします。
ここでは、ボタン内にはアイコンを表示し、ボタンの色もリストの定義を使って設定しました。
return ChoiceChip(
label: widget.items[index].itemIcon,
selected: selectValue == index,
backgroundColor: Colors.blueGrey,
selectedColor: widget.items[index].itemColor,
onSelected: (bool isSelected) {
setState(() {
selectValue = isSelected ? index : null;
widget.onItemSelected(widget.items[index]);
});
},
);label:ウィジェットが指定できるので、アイコンを表示selected:数字を直接入力せずにindexを使用して、リストの順番と連動backgroundColor:選択されていないときのボタンの色を、リストの定義から指定selectedColor:選択されたときのボタンの色を、リストの定義から使用onSelected:ボタンが選択されたときの値(true)を、変数(isSelected)に格納して…setState:selected:に指定したselectValueの更新と、ValueChanged型として指定したonItemSelected関数を実行

「label」にはアイコン表示もできる.
.toList()(行番号30)
return Wrap(
children: List<Widget>.generate(widget.items.length, (int index) {
return ChoiceChip(
label: widget.items[index].itemIcon,
selected: selectValue == index,
backgroundColor: Colors.blueGrey,
selectedColor: widget.items[index].itemColor,
onSelected: (bool isSelected) {
setState(() {
selectValue = isSelected ? index : null;
widget.onItemSelected(widget.items[index]);
});
},
);
}).toList(),
);最後の行番号30で、List.generate()に.toList()をつけて、リストの自動生成を完成させます。
カスタムウィジェットの呼び出し
いよいよ、作成したChoiceButtonウィジェットを使って選択ボタンを自動生成します。
ここでは、[main.dart]の中から呼び出しています。
import 'package:flutter/material.dart';
import 'package:choicechip_try01/choice_button.dart';
import 'package:choicechip_try01/item.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ChoiceChip as Custom',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var result = "";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('チョイスチップ(カスタムWidget)'),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
SizedBox(height: 24.0),
ChoiceButton(
items: items,
onItemSelected: (item) {
setState(() {
result = item.itemName;
});
},
),
Text("押されたボタン:$result"),
],
),
),
);
}
}まずは冒頭の行番号2-3で、[item.dart(リスト)]と、[choice_button.dart(ChoiceButtonのカスタムウィジェット)]をインポートします。
行番号43からが、実際にChoiceButtonウィジェットを表示しているコードです。
行番号44のitems: itemsでリストを渡し、
行番号45のonItemSelected: (item)とすることで、カスタムウィジェットから戻された値をitemとして使用できます。
行番号46で、ボタンの下に表示するText()ウィジェットを再描画するために、setStateを使っています。


コメント