menu_dialog.dart 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import 'package:flutter/material.dart';
  2. import 'package:lszlgl/base/base_lifecycle_state.dart';
  3. import 'package:lszlgl/widget/button.dart';
  4. import '../config/colors.dart';
  5. import 'card_item.dart';
  6. /// 弹出菜单选项弹框
  7. /// * [list] 选项列表数据源
  8. /// * [title] 标题
  9. /// * [multiple] 多选,默认单选
  10. /// * [selectCountMax] 最大选择数量
  11. /// * [backgroundColor] 背景颜色
  12. /// * [borderRadius] 圆角
  13. /// * [constraints] 尺寸约束
  14. /// * [isScrollControlled] 高度自适应, 默认true
  15. /// * [isDismissible] 点击空白处关闭
  16. /// * [enableDrag] 拖动关闭
  17. Future<T?> showMenuDialog<T>(
  18. List<CardMenuData>? list, {
  19. String? title,
  20. bool multiple = false,
  21. int? selectCountMax,
  22. Color? backgroundColor = Colors.white,
  23. BorderRadiusGeometry? borderRadius,
  24. BoxConstraints? constraints,
  25. bool isScrollControlled = true,
  26. bool isDismissible = true,
  27. bool enableDrag = true,
  28. }) {
  29. return showModalBottomSheet(
  30. context: MyNavigator.navigator.currentState!.context,
  31. constraints: constraints,
  32. isScrollControlled: isScrollControlled,
  33. isDismissible: isDismissible,
  34. enableDrag: enableDrag,
  35. useSafeArea: true,
  36. builder: (_) => MenuDialog(
  37. list: list,
  38. title: title,
  39. multiple: multiple,
  40. selectCountMax: selectCountMax,
  41. backgroundColor: backgroundColor,
  42. ),
  43. );
  44. }
  45. class MenuDialog extends StatelessWidget {
  46. final List<CardMenuData> list;
  47. final String? title;
  48. final bool multiple;
  49. final int? selectCountMax;
  50. final Color? backgroundColor;
  51. final BorderRadiusGeometry? borderRadius;
  52. final ValueNotifier<int> selectCount;
  53. MenuDialog({
  54. super.key,
  55. List<CardMenuData>? list,
  56. this.title,
  57. required this.multiple,
  58. this.selectCountMax,
  59. this.backgroundColor,
  60. this.borderRadius,
  61. }) : list = list ?? [],
  62. selectCount = (list ?? []).where((e) => e.select).length.notifier<int>();
  63. void onEnterClick() {
  64. if (list.isEmpty) return;
  65. List<CardMenuData> selectList = list.where((item) => item.select).toList();
  66. MyNavigator.pop(selectList);
  67. }
  68. @override
  69. Widget build(BuildContext context) {
  70. return Container(
  71. decoration: BoxDecoration(
  72. color: backgroundColor,
  73. borderRadius: borderRadius ?? const BorderRadius.only(topLeft: Radius.circular(16), topRight: Radius.circular(16)),
  74. ),
  75. child: Column(
  76. mainAxisSize: MainAxisSize.min,
  77. crossAxisAlignment: CrossAxisAlignment.start,
  78. children: [
  79. buildTitle(),
  80. buildList(),
  81. buildButton(),
  82. const SizedBox(width: double.infinity, height: 24),
  83. ],
  84. ),
  85. );
  86. }
  87. Widget buildTitle() {
  88. return Padding(
  89. padding: const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
  90. child: Row(
  91. children: [
  92. Expanded(
  93. child: Text(
  94. '请选择${title ?? ''}:',
  95. style: const TextStyle(color: Color(0xFF333333), fontSize: 16, fontWeight: FontWeight.w500),
  96. ),
  97. ),
  98. MyButton('关闭', alignment: null, onTap: () => MyNavigator.pop()),
  99. ],
  100. ),
  101. );
  102. }
  103. Widget buildList() {
  104. if (list.isEmpty) {
  105. return Container(
  106. alignment: Alignment.center,
  107. padding: const EdgeInsets.symmetric(vertical: 24),
  108. child: const Text(
  109. '没有选项数据',
  110. style: TextStyle(fontSize: 16, color: MyColor.c_666666),
  111. ),
  112. );
  113. }
  114. return Flexible(
  115. child: SingleChildScrollView(
  116. child: Column(
  117. mainAxisSize: MainAxisSize.min,
  118. children: List.generate(list.length, buildItem).toList(),
  119. ),
  120. ),
  121. );
  122. }
  123. Widget buildItem(int index) {
  124. var item = list[index];
  125. return StatefulBuilder(builder: (_, setState) {
  126. return GestureDetector(
  127. behavior: HitTestBehavior.opaque,
  128. onTap: () {
  129. if (multiple) {
  130. if (!item.select && selectCountMax != null && selectCountMax == selectCount.value) {
  131. MyNavigator.showToast('选中不能超过$selectCountMax个');
  132. return;
  133. }
  134. item.select = !item.select;
  135. setState(() {});
  136. } else {
  137. // 取消之前选择
  138. for (var value in list) {
  139. if (value.select) {
  140. value.select = false;
  141. break;
  142. }
  143. }
  144. // 选中当前项
  145. item.select = true;
  146. onEnterClick();
  147. }
  148. },
  149. child: Column(
  150. mainAxisSize: MainAxisSize.min,
  151. children: [
  152. Container(
  153. padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
  154. alignment: Alignment.center,
  155. color: Colors.white,
  156. child: Text(
  157. item.name ?? '',
  158. style: TextStyle(
  159. color: item.select ? const Color(0xFF01B2C8) : MyColor.c_666666,
  160. fontSize: 14,
  161. fontWeight: FontWeight.w500,
  162. ),
  163. ),
  164. ),
  165. index < list.length - 1
  166. ? const Divider(
  167. color: Color(0xFFF3F3F3),
  168. height: 0,
  169. thickness: 1,
  170. indent: 16,
  171. endIndent: 16,
  172. )
  173. : const SizedBox.shrink(),
  174. ],
  175. ),
  176. );
  177. });
  178. }
  179. Widget buildButton() {
  180. if (!multiple || list.isEmpty) return const SizedBox.shrink();
  181. return Padding(
  182. padding: const EdgeInsets.only(top: 16),
  183. child: Row(
  184. children: [
  185. const Spacer(),
  186. Expanded(flex: 2, child: MyButton('确认', onTap: onEnterClick)),
  187. const Spacer(),
  188. ],
  189. ),
  190. );
  191. }
  192. }