Created
April 5, 2023 09:24
-
-
Save take4blue/3d7ae94154a1d4fd6eb60b908e31a7ab to your computer and use it in GitHub Desktop.
Material 3のFilterChipに掲載されていたメニューから項目を選択できる機能を実装したもの
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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