demo 地址: https://github.com/iotjin/jh_flutter_demo
代码不定时更新,请前往github查看最新代码
有时需要弹框选择多个数据,因此写了个底部多选弹框组件
支持搜索,设置默认选中数据,暗黑模式适配
效果图
使用方法
final multiDictArr = [{'label': '类型一', 'value': '1'},{'label': '类型二', 'value': '2'},{'label': '类型三', 'value': '3'},{'label': '类型四', 'value': '4'},{'label': '类型五', 'value': '5'},{'label': '类型六', 'value': '6'},{'label': '类型七类型七类型七类型七', 'value': '7'},{'label': '类型八类型八类型八类型八类型八类型八类型八类型八类型八类型八', 'value': '8'},{'label': '类型九', 'value': '9'},
];JhMultiPicker.show(context, data: multiDictArr, values: ['3', '5'], title: '选择类型',clickCallBack: (selectValues, selectItemArr) {print('selectValues== $selectValues');print('selectItemArr== $selectItemArr');showText(selectValues);});
jh_multi_picker代码
/// jh_multi_picker.dart
///
/// Created by iotjin on 2023/08/28.
/// description: 底部多选弹框// ignore_for_file: library_private_types_in_public_apiimport 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '/jh_common/jh_form/jh_searchbar.dart';
import '/jh_common/utils/jh_common_utils.dart';
import '/project/configs/colors.dart';
import '/project/provider/theme_provider.dart';
import '/project/routes/jh_nav_utils.dart';const String _labelKey = 'label';
const String _valueKey = 'value';
const String _titleText = '请选择';
const String _cancelText = '取消';
const String _confirmText = '确定';
const String _searchHintText = '搜索';const double _headerHeight = 50.0;
const double _headerRadius = 10.0;
const double _headerLineHeight = 0.5;
const double _titleFontSize = 18.0;
const double _btnFontSize = 17.0;
const double _textFontSize = 16.0;/// 选择回调,返回所有选中的values数组和所有item数组
typedef _ClickCallBack = void Function(dynamic selectValues, dynamic selectItemArr);class JhMultiPicker {static bool _isShowPicker = false;static void show(BuildContext context, {required List data, // 数据源数组String labelKey = _labelKey, // 数据源数组的文字字段String valueKey = _valueKey, // 数据源数组的数值字段String title = _titleText,List values = const [], // 默认选中的数组(一维数组),通过valuesKey确定是根据value还是label进行比较,最好使用唯一值作为元素String valuesKey = _valueKey, // 选中数组内元素使用的字段,对应valueKey或者labelKeybool isShowSearch = true,String searchHintText = _searchHintText,bool isShowRadius = true,_ClickCallBack? clickCallBack,}) {if (_isShowPicker || data.isEmpty) {return;}var radius = isShowRadius ? _headerRadius : 0.0;showModalBottomSheet<void>(context: context,// 使用true则高度不受16分之9的最高限制isScrollControlled: false,// 设置圆角shape: RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(radius),topRight: Radius.circular(radius),),),// 抗锯齿clipBehavior: Clip.antiAlias,builder: (BuildContext context) {return SafeArea(child: JhMultiPickerView(data: data,labelKey: labelKey,valueKey: valueKey,title: title,values: values,valuesKey: valuesKey,isShowSearch: isShowSearch,searchHintText: searchHintText,clickCallBack: clickCallBack,),);},).then((value) => _isShowPicker = false);}
}class JhMultiPickerView extends StatefulWidget {const JhMultiPickerView({Key? key,required this.data,this.labelKey = _labelKey,this.valueKey = _valueKey,this.title = _titleText,this.values = const [],this.valuesKey = _valueKey,this.isShowSearch = true,this.searchHintText = _searchHintText,this.clickCallBack,}) : super(key: key);final List? data; // 数据源数组final String labelKey; // 数据源数组的文字字段final String valueKey; // 数据源数组的数值字段final String title;final List values; // 默认选中的数组(一维数组),通过valuesKey确定是根据value还是label进行比较,最好使用唯一值作为元素final String valuesKey; // 选中数组内元素使用的字段,对应valueKey或者labelKeyfinal bool isShowSearch;final String searchHintText;final _ClickCallBack? clickCallBack;State<JhMultiPickerView> createState() => _JhMultiPickerViewState();
}class _JhMultiPickerViewState extends State<JhMultiPickerView> {late List<dynamic> _selectedValues = [];// 搜索数据List _searchData = [];bool _isShowSearchResult = false;String _searchKeyword = '';void initState() {super.initState();_selectedValues = List.from(widget.values);}Widget build(BuildContext context) {return _body();}_body() {// 默认颜色var bgColor = KColors.dynamicColor(context, KColors.kPickerBgColor, KColors.kPickerBgDarkColor);var lineColor = KColors.dynamicColor(context, KColors.kLineColor, KColors.kLineDarkColor);return Container(color: bgColor,child: Column(children: <Widget>[_header(),SizedBox(height: _headerLineHeight, child: Container(color: lineColor)),_searchBar(),_mainWidget(),],),);}_header() {// 默认颜色var headerColor = KColors.dynamicColor(context, KColors.kPickerHeaderColor, KColors.kPickerHeaderDarkColor);var titleColor = KColors.dynamicColor(context, KColors.kPickerTitleColor, KColors.kPickerTitleDarkColor);var btnColor = KColors.dynamicColor(context, KColors.kPickerBtnColor, KColors.kPickerBtnDarkColor);return Container(height: _headerHeight,color: headerColor,child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [GestureDetector(child: Container(padding: const EdgeInsets.only(left: 15),child: Text(_cancelText, style: TextStyle(fontSize: _btnFontSize, color: btnColor)),),onTap: () {widget.clickCallBack?.call([], []);JhNavUtils.goBack(context);},),Text(widget.title, style: TextStyle(fontSize: _titleFontSize, color: titleColor)),GestureDetector(child: Container(padding: const EdgeInsets.only(right: 15),child: Text(_confirmText, style: TextStyle(fontSize: _btnFontSize, color: btnColor)),),onTap: () {widget.clickCallBack?.call(_selectedValues, _getSelectItemList());JhNavUtils.goBack(context);}),],),);}Widget _mainWidget() {List dataArr = _isShowSearchResult ? _searchData : (widget.data ?? []);return Expanded(child: ListView.builder(shrinkWrap: true,itemCount: dataArr.length,itemBuilder: (BuildContext context, int index) {return _buildItem(dataArr[index], index);},),);}_buildItem(item, index) {// TODO: 通过ThemeProvider进行主题管理final provider = Provider.of<ThemeProvider>(context);var themeColor = KColors.dynamicColor(context, provider.getThemeColor(), KColors.kThemeColor);var selectValue = widget.valuesKey == widget.valueKey ? item[widget.valueKey] : item[widget.labelKey];return CheckboxListTile(title: Text(item[widget.labelKey].toString(), style: const TextStyle(fontSize: _textFontSize)),value: _selectedValues.contains(selectValue),activeColor: themeColor,onChanged: (bool? checked) {setState(() {if (checked ?? false) {_selectedValues.add(selectValue);} else {_selectedValues.remove(selectValue);}// widget.clickCallBack?.call(_selectedValues, _getSelectItemList());});},);}_getSelectItemList() {var newList = (widget.data ?? []).where((item) => _selectedValues.contains(item[widget.valueKey])).toList();return newList;}Widget _searchBar() {Widget searchbar = JhSearchBar(hintText: widget.searchHintText,text: _searchKeyword,inputCallBack: (value) {JhCommonUtils.debounce(() {setState(() {_searchKeyword = value;if (value.isNotEmpty) {_searchData = _getSearchData(value);_isShowSearchResult = _searchData.isNotEmpty;} else {_isShowSearchResult = false;}});}, 500);},);return !widget.isShowSearch ? Container() : searchbar;}/// 根据搜索文字过滤数据_getSearchData(keyword) {var newList = (widget.data ?? []).where((item) => item[widget.labelKey].toLowerCase().contains(keyword.toLowerCase())).toList();return newList;}
}