card_item.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. import 'package:lszlgl/config/colors.dart';
  4. import 'package:lszlgl/ext/value_notifier_ext.dart';
  5. import '../config/pics.dart';
  6. import '../router/my_navigator.dart';
  7. import 'menu_dialog.dart';
  8. class CardItemWidget extends StatelessWidget {
  9. /// 标题文本
  10. final String title;
  11. /// 左侧icon
  12. final Widget? leading;
  13. /// 右侧icon
  14. final Widget? trailing;
  15. /// 右侧文本
  16. final String? rightText;
  17. final String? hint;
  18. /// 自定义右侧组件
  19. final Widget? rightChild;
  20. /// 右侧组件对齐方式
  21. final AlignmentGeometry rightAlign;
  22. final bool showTopLine;
  23. final bool bottomLine;
  24. /// 点击事件
  25. final VoidCallback? onTap;
  26. final Color? backgroundColor;
  27. const CardItemWidget(
  28. this.title, {
  29. Key? key,
  30. this.leading,
  31. this.trailing,
  32. this.rightText,
  33. this.hint,
  34. this.rightChild,
  35. this.onTap,
  36. this.rightAlign = Alignment.centerRight,
  37. this.showTopLine = false,
  38. this.bottomLine = false,
  39. this.backgroundColor = Colors.white,
  40. }) : super(key: key);
  41. @override
  42. Widget build(BuildContext context) {
  43. return GestureDetector(
  44. onTap: onTap,
  45. behavior: HitTestBehavior.opaque,
  46. child: Column(
  47. mainAxisSize: MainAxisSize.min,
  48. children: [
  49. showTopLine ? const Divider(color: Color(0xFFF3F3F3), height: 0, thickness: 1) : const SizedBox.shrink(),
  50. Container(
  51. constraints: const BoxConstraints(minHeight: 48),
  52. padding: const EdgeInsets.all(12),
  53. color: backgroundColor,
  54. child: Row(
  55. children: [
  56. buildLeading(),
  57. buildTitle(),
  58. const SizedBox(width: 4),
  59. buildRight(),
  60. buildTrailing(),
  61. ],
  62. ),
  63. ),
  64. bottomLine ? const Divider(color: Color(0xFFF3F3F3), height: 0, thickness: 1) : const SizedBox.shrink(),
  65. ],
  66. ),
  67. );
  68. }
  69. Widget buildLeading() {
  70. if (leading == null) return const SizedBox.shrink();
  71. return Padding(
  72. padding: const EdgeInsets.only(right: 8),
  73. child: leading,
  74. );
  75. }
  76. Widget buildTrailing() {
  77. if (trailing == null) return const SizedBox.shrink();
  78. return Padding(
  79. padding: const EdgeInsets.only(left: 8),
  80. child: trailing,
  81. );
  82. }
  83. Widget buildTitle() {
  84. return Text(
  85. title,
  86. style: const TextStyle(
  87. color: Color(0xFF333333),
  88. fontSize: 14,
  89. fontWeight: FontWeight.w500,
  90. ),
  91. );
  92. }
  93. Widget buildRight() {
  94. Widget child;
  95. if (rightChild != null) {
  96. child = rightChild!;
  97. } else {
  98. child = Text(
  99. (rightText?.isNotEmpty ?? false) ? rightText! : hint ?? '',
  100. textAlign: TextAlign.right,
  101. style: TextStyle(
  102. color: (rightText?.isNotEmpty ?? false) ? const Color(0xFF01B2C8) : MyColor.c_666666,
  103. fontSize: 14,
  104. fontWeight: FontWeight.w500,
  105. ),
  106. );
  107. }
  108. return Expanded(
  109. child: Container(
  110. alignment: rightAlign,
  111. child: child,
  112. ),
  113. );
  114. }
  115. }
  116. //ignore: must_be_immutable
  117. class CardItemMenuWidget extends StatelessWidget {
  118. /// 标题文本
  119. final String title;
  120. /// 左侧icon
  121. final Widget? leading;
  122. final String? hint;
  123. /// 右侧组件对齐方式
  124. final AlignmentGeometry rightAlign;
  125. final bool showTopLine;
  126. final bool bottomLine;
  127. final Color? backgroundColor;
  128. final Color? menuColor;
  129. final ValueNotifier<List<CardMenuData>> listNotifier;
  130. final ValueNotifier<CardMenuData?> selNotifier;
  131. final void Function(ValueNotifier<CardMenuData?>, CardMenuData)? onSelectTap;
  132. late MenuController _ctrl;
  133. CardItemMenuWidget(
  134. this.title,
  135. this.listNotifier,
  136. this.selNotifier, {
  137. Key? key,
  138. this.leading,
  139. this.hint,
  140. this.rightAlign = Alignment.centerRight,
  141. this.showTopLine = false,
  142. this.bottomLine = false,
  143. this.backgroundColor = Colors.white,
  144. this.menuColor = Colors.white,
  145. this.onSelectTap,
  146. }) : super(key: key);
  147. void clickMenu() {
  148. if (listNotifier.value.isEmpty) {
  149. MyNavigator.showToast('没有选项数据');
  150. return;
  151. }
  152. if (_ctrl.isOpen) {
  153. _ctrl.close();
  154. } else {
  155. _ctrl.open();
  156. }
  157. }
  158. @override
  159. Widget build(BuildContext context) {
  160. return CardItemWidget(
  161. title,
  162. leading: leading,
  163. rightAlign: rightAlign,
  164. showTopLine: showTopLine,
  165. bottomLine: bottomLine,
  166. backgroundColor: backgroundColor,
  167. rightChild: buildMenu(),
  168. );
  169. }
  170. Widget buildMenu() {
  171. return listNotifier.builder(
  172. (list) => selNotifier.builder(
  173. (selData) {
  174. return MenuAnchor(
  175. style: MenuStyle(
  176. padding: const MaterialStatePropertyAll(EdgeInsets.zero),
  177. alignment: Alignment.bottomRight,
  178. surfaceTintColor: MaterialStatePropertyAll(menuColor),
  179. elevation: const MaterialStatePropertyAll(16),
  180. shape: const MaterialStatePropertyAll(RoundedRectangleBorder(
  181. borderRadius: BorderRadius.all(Radius.circular(16)),
  182. ))),
  183. builder: (_, ctrl, __) => buildMenuLabel(ctrl, selData),
  184. menuChildren: List.generate(list.length, (index) {
  185. return buildMenuItem(list[index], selData);
  186. }).toList(),
  187. );
  188. },
  189. ),
  190. );
  191. }
  192. /// 菜单栏
  193. Widget buildMenuLabel(MenuController ctrl, CardMenuData? selData) {
  194. _ctrl = ctrl;
  195. return GestureDetector(
  196. behavior: HitTestBehavior.opaque,
  197. onTap: clickMenu,
  198. child: Row(
  199. mainAxisSize: MainAxisSize.min,
  200. children: [
  201. Expanded(
  202. child: Text(
  203. selData?.name != null ? selData!.name! : hint ?? '',
  204. textAlign: TextAlign.right,
  205. style: TextStyle(
  206. color: selData?.name != null ? const Color(0xFF01B2C8) : MyColor.c_666666,
  207. fontSize: 14,
  208. fontWeight: FontWeight.w500,
  209. ),
  210. ),
  211. ),
  212. selData == null
  213. ? Padding(
  214. padding: const EdgeInsets.only(left: 8),
  215. child: Image.asset(imgItemArrowDown, width: 20, color: const Color(0xFF01B2C8)),
  216. )
  217. : const SizedBox.shrink(),
  218. ],
  219. ),
  220. );
  221. }
  222. Widget buildMenuItem(CardMenuData item, CardMenuData? selData) {
  223. return MenuItemButton(
  224. onPressed: () {
  225. if (item.value == selData?.value) return;
  226. selNotifier.value = item;
  227. onSelectTap?.call(selNotifier, item);
  228. },
  229. child: Container(
  230. constraints: const BoxConstraints(minWidth: 120),
  231. alignment: Alignment.center,
  232. child: Text(
  233. item.name ?? '',
  234. style: TextStyle(color: item.value == selData?.value ? const Color(0xFF01B2C8) : MyColor.c_666666),
  235. ),
  236. ),
  237. );
  238. }
  239. }
  240. class CardMenuData {
  241. String? name;
  242. dynamic value;
  243. bool select = false;
  244. CardMenuData(this.name, this.value);
  245. @override
  246. bool operator ==(Object other) {
  247. if (other is! CardMenuData) return false;
  248. return name == other.name && value == other.value;
  249. }
  250. }
  251. class CardWidgets {
  252. CardWidgets._();
  253. static Widget buildEdit(
  254. bool isDetail,
  255. String title,
  256. String? value, {
  257. String? hint = '点击填写',
  258. TextInputType? inputType,
  259. ValueChanged<String>? onChanged,
  260. List<TextInputFormatter>? formatters,
  261. Color? backgroundColor = Colors.white,
  262. bool bottomLine = true,
  263. TextEditingController? ctrl,
  264. }) {
  265. if (isDetail) {
  266. return CardItemWidget(
  267. title,
  268. rightText: value,
  269. bottomLine: bottomLine,
  270. backgroundColor: backgroundColor,
  271. );
  272. }
  273. return CardItemWidget(
  274. title,
  275. rightChild: TextFormField(
  276. controller: ctrl,
  277. initialValue: ctrl == null ? value : null,
  278. keyboardType: inputType,
  279. textAlign: TextAlign.right,
  280. decoration: InputDecoration(
  281. hintText: hint,
  282. hintStyle: const TextStyle(color: MyColor.c_666666, fontSize: 14),
  283. border: InputBorder.none,
  284. contentPadding: EdgeInsets.zero,
  285. isDense: true,
  286. ),
  287. style: const TextStyle(color: Color(0xFF01B2C8), fontSize: 14, fontWeight: FontWeight.w500),
  288. textInputAction: TextInputAction.next,
  289. inputFormatters: formatters,
  290. onChanged: onChanged,
  291. ),
  292. backgroundColor: backgroundColor,
  293. bottomLine: bottomLine,
  294. );
  295. }
  296. static Widget buildDate(
  297. bool isDetail,
  298. String title,
  299. ValueNotifier<String?> notifier, {
  300. String? Function(DateTime?)? onResult,
  301. DateTime? firstDate,
  302. DateTime? lastDate,
  303. }) {
  304. return notifier.builder((v) {
  305. if (isDetail) {
  306. return CardItemWidget(title, rightText: v, bottomLine: true);
  307. }
  308. return CardItemWidget(
  309. title,
  310. rightText: v,
  311. trailing: v == null
  312. ? Image.asset(
  313. imgItemArrowDown,
  314. width: 20,
  315. color: const Color(0xFF01B2C8),
  316. )
  317. : const SizedBox.shrink(),
  318. bottomLine: true,
  319. onTap: () async {
  320. var date = await showDatePicker(
  321. context: MyNavigator.navigator.currentState!.context,
  322. firstDate: firstDate ?? DateTime(2020),
  323. lastDate: lastDate ?? DateTime.now(),
  324. );
  325. if (date != null && onResult != null) {
  326. notifier.value = onResult.call(date);
  327. }
  328. },
  329. );
  330. });
  331. }
  332. static Widget buildMenuDialog(
  333. bool isDetail,
  334. String title,
  335. String? value,
  336. List<CardMenuData>? list,
  337. void Function(List<CardMenuData>)? onSelectTap, {
  338. bool multiple = false,
  339. String? hint,
  340. Widget? trailing,
  341. int? selectCountMax,
  342. bool bottomLine = true,
  343. bool showTopLine = false,
  344. }) {
  345. return CardItemWidget(
  346. title,
  347. rightText: value,
  348. bottomLine: bottomLine,
  349. showTopLine: showTopLine,
  350. hint: hint,
  351. trailing: isDetail || value != null
  352. ? null
  353. : trailing ??
  354. Image.asset(
  355. imgItemArrowDown,
  356. width: 20,
  357. color: const Color(0xFF01B2C8),
  358. ),
  359. onTap: isDetail
  360. ? null
  361. : () async {
  362. List<CardMenuData>? selectList = await showMenuDialog(
  363. list,
  364. title: title,
  365. multiple: multiple,
  366. selectCountMax: selectCountMax,
  367. );
  368. if (selectList == null) return;
  369. onSelectTap?.call(selectList);
  370. },
  371. );
  372. }
  373. static Widget buildMenu(
  374. bool isDetail,
  375. String title,
  376. ValueNotifier<List<CardMenuData>> listNotifier,
  377. ValueNotifier<CardMenuData?> selNotifier,
  378. void Function(ValueNotifier<CardMenuData?>, CardMenuData)? onSelectTap, {
  379. String? hint,
  380. bool showTopLine = false,
  381. bool bottomLine = true,
  382. }) {
  383. if (isDetail) {
  384. return selNotifier.builder(
  385. (v) => CardItemWidget(
  386. title,
  387. rightText: v?.name,
  388. bottomLine: bottomLine,
  389. hint: hint,
  390. showTopLine: showTopLine,
  391. ),
  392. );
  393. }
  394. return CardItemMenuWidget(
  395. title,
  396. listNotifier,
  397. selNotifier,
  398. onSelectTap: onSelectTap,
  399. bottomLine: bottomLine,
  400. hint: hint,
  401. showTopLine: showTopLine,
  402. );
  403. }
  404. }