Skip to content

Instantly share code, notes, and snippets.

@take4blue
Created April 5, 2023 09:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save take4blue/3d7ae94154a1d4fd6eb60b908e31a7ab to your computer and use it in GitHub Desktop.
Save take4blue/3d7ae94154a1d4fd6eb60b908e31a7ab to your computer and use it in GitHub Desktop.
Material 3のFilterChipに掲載されていたメニューから項目を選択できる機能を実装したもの
import 'package:flutter/material.dart';
/// FilterChipに選択機能を設けたもの
class SelectableFilterChip<T> extends StatefulWidget {
SelectableFilterChip({
required this.onChanged,
required this.items,
this.hint,
super.key,
this.value,
this.onUnselected,
}) : assert(items == null ||
items.isEmpty ||
value == null ||
items.where((DropdownMenuEntry<T> item) {
return item.value == value;
}).length ==
1),
assert(value != null || (value == null && hint != null));
/// ポップアップで表示するメニュー項目。
///
/// [onChanged]か[items]のどちらかがnullの場合chipボタンをdisabledにする。
final List<DropdownMenuEntry<T>>? items;
/// [value]がnullの場合にChipに表示するヒント情報(プレースホルダ)
final Widget? hint;
/// [SelectableFilterChip]に選択されている値。
///
/// [value]がnullの場合[hint]を表示する。
/// [value]がnull以外の場合[items]の中の選択されている項目の[child]を表示する。
final T? value;
/// 選択時に呼び出されるコールバック。
///
/// [onChanged]か[items]のどちらかがnullの場合chipボタンをdisabledにする。
final ValueChanged<T?>? onChanged;
/// 選択解除を行ったときに呼び出されるコールバック。
final VoidCallback? onUnselected;
@override
State<SelectableFilterChip<T>> createState() =>
_SelectableFilterChipState<T>();
}
class _SelectableFilterChipState<T> extends State<SelectableFilterChip<T>> {
bool popupMenu = false;
/// タップして要素を選択可能かどうか
bool get canOnTap => !(widget.onChanged == null || widget.items == null);
/// itemを選択されているかどうか
bool get isSelected => widget.value != null;
@override
Widget build(BuildContext context) {
final containerKey = GlobalKey();
final label = widget.value == null
? widget.hint!
: Text(widget.items!
.firstWhere(
(element) => element.value == widget.value,
)
.label);
return InputChip(
key: containerKey,
label: Wrap(
children: [
label,
if (canOnTap)
Icon(popupMenu ? Icons.arrow_drop_up : Icons.arrow_drop_down),
],
),
selected: isSelected,
onPressed: canOnTap
? () {
final render = containerKey.currentContext!.findRenderObject()!;
final pos = MatrixUtils.transformRect(
render.getTransformTo(null), render.paintBounds);
setState(() {
popupMenu = true;
});
showMenu<T>(
context: context,
position: RelativeRect.fromLTRB(
pos.left, pos.bottom, pos.right, pos.bottom),
items: widget.items!.map<PopupMenuItem<T>>((e) {
return PopupMenuItem<T>(
value: e.value,
child: ListTile(
dense: true,
title: Text(e.label),
leading: e.leadingIcon,
trailing: e.trailingIcon,
contentPadding: const EdgeInsets.all(0),
horizontalTitleGap: 1,
minVerticalPadding: 0,
),
);
}).toList())
.then((value) {
widget.onChanged!(value);
}).whenComplete(() {
setState(() {
popupMenu = false;
});
});
}
: null,
deleteIcon: const Icon(Icons.close),
onDeleted: isSelected ? widget.onUnselected : null,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment