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
を使っています。
コメント