menu_list.dart 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import 'package:flutter/material.dart' hide Banner;
  2. import 'package:flutter_screenutil/flutter_screenutil.dart';
  3. import 'package:get/get.dart';
  4. import '../../../../utils/misc.dart';
  5. import '../../../data/models/banner/banner_list.dart';
  6. import '../../../widgets/ix_image.dart';
  7. import '../controllers/home_controller.dart';
  8. import '../../../extensions/widget_extension.dart';
  9. import '../../../../config/theme/theme_extensions/theme_extension.dart';
  10. /// 菜单列表组件
  11. /// 支持放在 SliverFillRemaining 中
  12. /// 排列规则:
  13. /// - 9个:3排,每排3个
  14. /// - 6个:2排,每排3个
  15. /// - 5个:2排,第一排3个,第二排2个
  16. /// - 4个:2排,每排2个
  17. /// - 3个:1排,每排3个
  18. /// - 2个:1排,每排2个
  19. /// - 1个:1排,占满
  20. class MenuList extends StatelessWidget {
  21. const MenuList({super.key});
  22. /// 根据数量计算每排的布局
  23. /// 返回一个二维列表,每个子列表表示一排的 Banner
  24. List<List<Banner>> _calculateLayout(List<Banner> items) {
  25. final count = items.length.clamp(0, 9);
  26. if (count == 0) return [];
  27. switch (count) {
  28. case 1:
  29. return [items.sublist(0, 1)];
  30. case 2:
  31. return [items.sublist(0, 2)];
  32. case 3:
  33. return [items.sublist(0, 3)];
  34. case 4:
  35. return [items.sublist(0, 2), items.sublist(2, 4)];
  36. case 5:
  37. return [items.sublist(0, 3), items.sublist(3, 5)];
  38. case 6:
  39. return [items.sublist(0, 3), items.sublist(3, 6)];
  40. case 7:
  41. return [items.sublist(0, 3), items.sublist(3, 6), items.sublist(6, 7)];
  42. case 8:
  43. return [items.sublist(0, 3), items.sublist(3, 6), items.sublist(6, 8)];
  44. case 9:
  45. default:
  46. return [items.sublist(0, 3), items.sublist(3, 6), items.sublist(6, 9)];
  47. }
  48. }
  49. @override
  50. Widget build(BuildContext context) {
  51. final controller = Get.find<HomeController>();
  52. return Obx(() {
  53. if (controller.nineBannerList.isEmpty) {
  54. return const SizedBox.shrink();
  55. }
  56. final layout = _calculateLayout(controller.nineBannerList);
  57. return Column(
  58. mainAxisSize: MainAxisSize.min,
  59. children: layout.asMap().entries.map((entry) {
  60. final rowIndex = entry.key;
  61. final rowItems = entry.value;
  62. return Padding(
  63. padding: EdgeInsets.only(top: rowIndex > 0 ? 8.w : 0),
  64. child: _buildRow(rowItems, controller),
  65. );
  66. }).toList(),
  67. );
  68. });
  69. }
  70. /// 构建一排菜单项
  71. Widget _buildRow(List<Banner> items, HomeController controller) {
  72. return Row(
  73. children: items.asMap().entries.map((entry) {
  74. final index = entry.key;
  75. final banner = entry.value;
  76. return Expanded(
  77. child: Padding(
  78. padding: EdgeInsets.only(left: index > 0 ? 8.w : 0),
  79. child: _buildMenuItem(banner, controller),
  80. ),
  81. );
  82. }).toList(),
  83. );
  84. }
  85. /// 构建单个菜单项
  86. Widget _buildMenuItem(Banner banner, HomeController controller) {
  87. final isLight = ReactiveTheme.isLightTheme;
  88. return GestureDetector(
  89. onTap: () => controller.onBannerTap(banner),
  90. child: Container(
  91. height: 56.w,
  92. decoration: BoxDecoration(
  93. color: Get.reactiveTheme.cardColor,
  94. borderRadius: BorderRadius.circular(10.r),
  95. border: isLight
  96. ? Border.all(color: Get.reactiveTheme.dividerColor, width: 1.w)
  97. : null,
  98. ),
  99. child: Column(
  100. mainAxisAlignment: MainAxisAlignment.center,
  101. children: [
  102. // 图标(网络图片)
  103. IXImage(
  104. source: banner.img ?? '',
  105. width: 20.w,
  106. height: 20.w,
  107. sourceType: ImageSourceType.network,
  108. ),
  109. 4.verticalSpaceFromWidth,
  110. // 标题
  111. Text(
  112. banner.title ?? '',
  113. textAlign: TextAlign.center,
  114. maxLines: 1,
  115. overflow: TextOverflow.ellipsis,
  116. style: TextStyle(
  117. fontSize: 13.sp,
  118. height: 1.4,
  119. color: Get.theme.hintColor,
  120. fontWeight: FontWeight.w500,
  121. ),
  122. ),
  123. ],
  124. ),
  125. ).withClickCursor(isDesktop),
  126. );
  127. }
  128. }