option_select_widget.dart 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import 'package:cached_network_image/cached_network_image.dart';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_screenutil/flutter_screenutil.dart';
  5. import 'package:keyboard/data/bean/option_select_item.dart';
  6. import '../../../data/bean/option_select_config.dart';
  7. import '../../../resource/colors.gen.dart';
  8. /// 切换选中时回调
  9. typedef OnOptionSelectCallback =
  10. void Function(OptionSelectConfig config, OptionSelectItem optionItem);
  11. /// 选项选择组件
  12. class OptionSelectWidget extends StatelessWidget {
  13. /// 选项行的配置
  14. final OptionSelectConfig optionSelect;
  15. /// 切换选中时回调
  16. final OnOptionSelectCallback optionSelectCallback;
  17. const OptionSelectWidget({
  18. super.key,
  19. required this.optionSelect,
  20. required this.optionSelectCallback,
  21. });
  22. @override
  23. Widget build(BuildContext context) {
  24. return SizedBox(
  25. // 宽度匹配父组件
  26. width: double.infinity,
  27. child: Column(
  28. crossAxisAlignment: CrossAxisAlignment.start,
  29. children: [
  30. // 标题
  31. _buildTitle(),
  32. // 选项
  33. _buildOptionList(),
  34. ],
  35. ),
  36. );
  37. }
  38. /// 图标和标题
  39. Widget _buildTitle() {
  40. String icon = optionSelect.iconUrl;
  41. String title = optionSelect.title;
  42. // 是否不显示标题
  43. bool isNotTitle = icon.isEmpty && title.isEmpty;
  44. return Visibility(
  45. visible: !isNotTitle,
  46. child: Container(
  47. margin: EdgeInsets.only(bottom: 16.h),
  48. child: Row(
  49. children: [
  50. Visibility(
  51. visible: icon.isNotEmpty,
  52. child: Row(
  53. children: [
  54. // 图标
  55. CachedNetworkImage(
  56. imageUrl: icon,
  57. height: 14.h,
  58. width: 14.w,
  59. fit: BoxFit.fill,
  60. ),
  61. SizedBox(width: 2),
  62. ],
  63. ),
  64. ),
  65. // 标题
  66. Visibility(
  67. visible: title.isNotEmpty,
  68. child: Text(
  69. optionSelect.title,
  70. style: TextStyle(
  71. fontSize: 14.sp,
  72. fontWeight: FontWeight.w700,
  73. color: ColorName.black80,
  74. ),
  75. ),
  76. ),
  77. ],
  78. ),
  79. ),
  80. );
  81. }
  82. /// 选项列表
  83. Widget _buildOptionList() {
  84. // 流式布局,类似安卓的FlowLayout,1列不固定3个,放不下就自动换行
  85. if (optionSelect.isCustom) {
  86. return Wrap(
  87. direction: Axis.horizontal,
  88. alignment: WrapAlignment.start,
  89. runAlignment: WrapAlignment.start,
  90. // 主轴间距
  91. spacing: 6.0,
  92. // 交叉轴间距
  93. runSpacing: 7.0,
  94. children: [
  95. for (var option in optionSelect.options)
  96. _buildOptionItem(optionSelect, option),
  97. ],
  98. );
  99. } else {
  100. // 固定一行3列,每项的宽度一样
  101. return GridView.builder(
  102. // 去掉默认的Padding,默认会有一个默认的顶部padding大小
  103. padding: EdgeInsets.zero,
  104. // 包裹内容
  105. shrinkWrap: true,
  106. // 禁止滚动
  107. physics: const NeverScrollableScrollPhysics(),
  108. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  109. // 一行3列
  110. crossAxisCount: 3,
  111. // 水平方向的间距
  112. crossAxisSpacing: 6.0,
  113. // 垂直方向的间距
  114. mainAxisSpacing: 12.0,
  115. // 子项的宽高比
  116. childAspectRatio: 2.6,
  117. ),
  118. itemCount: optionSelect.options.length,
  119. itemBuilder: (context, index) {
  120. OptionSelectItem option = optionSelect.options[index];
  121. return _buildOptionItem(optionSelect, option);
  122. },
  123. );
  124. }
  125. }
  126. /// 选项
  127. /// [rowConfig] 选项行的配置
  128. /// [optionItem] 当前构建的那一项
  129. Widget _buildOptionItem(
  130. OptionSelectConfig rowConfig,
  131. OptionSelectItem optionItem,
  132. ) {
  133. // 背景圆角
  134. double bgBorderRadius = 31.r;
  135. // 背景,选中时为渐变色,未选中时为灰色
  136. BoxDecoration bgDecoration;
  137. if (optionItem.selected) {
  138. bgDecoration = BoxDecoration(
  139. gradient: LinearGradient(
  140. colors: [
  141. ColorName.bgOptionSelectSelected1,
  142. ColorName.bgOptionSelectSelected2,
  143. ],
  144. begin: Alignment.centerLeft,
  145. end: Alignment.centerRight,
  146. ),
  147. borderRadius: BorderRadius.circular(bgBorderRadius),
  148. );
  149. } else {
  150. bgDecoration = BoxDecoration(
  151. color: ColorName.bgOptionSelectNormal,
  152. borderRadius: BorderRadius.circular(bgBorderRadius),
  153. );
  154. }
  155. Widget textWidget = Text(
  156. optionItem.name,
  157. textAlign: TextAlign.center,
  158. maxLines: 1,
  159. style: TextStyle(
  160. overflow: TextOverflow.ellipsis,
  161. fontSize: 13.sp,
  162. fontWeight: FontWeight.w400,
  163. // 文字颜色,选中时为白色,未选中时为黑色
  164. color: optionItem.selected ? ColorName.white : ColorName.black80,
  165. ),
  166. );
  167. Widget contentWidget;
  168. if (rowConfig.isCustom) {
  169. // 自定义,通过Wrap来实现,子项的宽度由自己的内容撑开,通过padding来撑大左右宽度
  170. contentWidget = Container(
  171. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
  172. decoration: bgDecoration,
  173. child: textWidget,
  174. );
  175. } else {
  176. // 非自定义,通过GridView来实现,每个子项会平分宽度,通过Center来居中文字
  177. contentWidget = Container(
  178. decoration: bgDecoration,
  179. child: Center(child: textWidget),
  180. );
  181. }
  182. return GestureDetector(
  183. onTap: () {
  184. optionSelectCallback(rowConfig, optionItem);
  185. },
  186. child: contentWidget,
  187. );
  188. }
  189. }