menu_list.dart 4.1 KB

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