如何在Flutter中自定义复选框、单选按钮与RangeSlider滑块组件
问题描述
我正在尝试自定义Flutter中的按钮组件,但暂时没有找到实现预期设计的方案,具体需求如下:
- 多选CheckBox为灰色背景、黑色对勾样式
- RangeSlider的未激活轨道设置为灰色
- Radio Button为黑色样式,且支持全部取消选择;我清楚原生Radio Button默认不支持全取消,使用其他替代方案实现该需求也可以
- 方形选框为灰色背景、黑色对勾样式
效果截图
- 预期设计:

- 实际运行UI:

相关代码
主文件
import 'package:filters2/filter_screen.dart'; import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Dialog', home: HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Filters'), ), body: Center( child: FlatButton( onPressed: (){ filterBottomSheet(); }, color: Colors.green, padding: EdgeInsets.fromLTRB(15, 15, 15, 15), child: Text('Filters',style: TextStyle(fontSize: 16,color: Colors.white),), ), ), ); } filterBottomSheet(){ showModalBottomSheet( context: context, isScrollControlled: true, isDismissible: true, builder: (BuildContext context){ return Filters(); } ); } }
Filters类
import 'package:filters2/Fruit_model_class.dart'; import 'package:filters2/color_model_class.dart'; import 'package:filters2/mobile_model_class.dart'; import 'package:flutter/material.dart'; class Filters extends StatefulWidget { @override _FiltersState createState() => _FiltersState(); } class _FiltersState extends State<Filters> { /*--------MobileList-multiple checklist-------*/ List<MobileList> availableMobiles = [ MobileList(id: 0, companyName: "Google", model: "Pixel 6 Pro"), MobileList(id: 1, companyName: "Apple", model: "Iphone Xr"), MobileList(id: 2, companyName: "Xiaomi", model: "Redmi note 10"), ]; List<MobileList> selectedMobiles = []; /*------Price Range Selection-RangeSlider--------*/ RangeValues _currentRangeValues = const RangeValues(40000, 80000); int minPrice = 20000; int maxPrice = 90000; /*Radio--fruits*/ String radioItem1 = 'Mango'; int fruitGroupId = 0; List<FruitsList> fList = [ FruitsList(id: 0, name: "Mango",), FruitsList(id: 1, name: "Apple",), FruitsList(id: 2, name: "Banana",), FruitsList(id: 3, name: "Cherry",), ]; /*Radio--Colors*/ String radioItem2 = 'Red'; int? colorGroupId ; List<ColorsList> cList = [ ColorsList(id: 0, name: "Blue",), ColorsList(id: 1, name: "Red",), ColorsList(id: 2, name: "Green",), ColorsList(id: 3, name: "Yellow",), ]; @override Widget build(BuildContext context) { return Container( height: MediaQuery.of(context).size.height * 0.85, decoration: BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(10), topRight: Radius.circular(10), ) ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 10,), Container( child: Text("Filter",style: TextStyle(color: Colors.black, fontSize: 24,fontWeight: FontWeight.bold),), ), SizedBox(height: 10,), Container( child: Text("Select Mobiles (multiple selection)",style: TextStyle(color: Colors.black, fontSize: 20,fontWeight: FontWeight.bold),), ), SizedBox(height: 10,), Divider(), Container( child: Row( children: [ Expanded( flex:1, child: ListView.builder( shrinkWrap: true, itemCount: availableMobiles.length, itemBuilder: (context, playerIndex){ return CheckboxListTile( controlAffinity: ListTileControlAffinity.trailing, contentPadding: EdgeInsets.zero, title:Text(availableMobiles[playerIndex].companyName), value: selectedMobiles.contains(availableMobiles[playerIndex]), onChanged: (value) { if(selectedMobiles.contains(availableMobiles[playerIndex])){ selectedMobiles.remove(availableMobiles[playerIndex]); } else { selectedMobiles.add(availableMobiles[playerIndex]); } setState(() { }); } ); } ), ), Expanded( flex: 2, child:Container( color: Colors.white , ) ), ], ), ), SizedBox(height: 10,), Divider(), Container( child: Text("Year",style: TextStyle(color: Colors.black, fontSize: 20,fontWeight: FontWeight.bold),), ), SizedBox(height: 10,), Divider(), Container( child: Row( children: [ Align( alignment: Alignment.centerLeft, child: Text("20000",style: TextStyle(color: Colors.black, fontSize: 20)), ), Expanded( child: RangeSlider( values: _currentRangeValues, min: minPrice.toDouble(), max: maxPrice.toDouble(), divisions: maxPrice - minPrice, activeColor: Colors.black, labels: RangeLabels( _currentRangeValues.start.round().toString(), _currentRangeValues.end.round().toString(), ), onChanged: (RangeValues values) { setState(() { _currentRangeValues = values; }); }, ), ), Align( alignment: Alignment.centerRight, child: Text("90000",style: TextStyle(color: Colors.black, fontSize: 20))), ], ), ), SizedBox(height: 10,), Divider(), Container( child: Row( children: [ Expanded( flex: 1, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Fruits (Single selection)",style: TextStyle(color: Colors.black, fontSize: 20,fontWeight: FontWeight.bold),), Column( children: fList.map((data) => RadioListTile( controlAffinity: ListTileControlAffinity.trailing, contentPadding: EdgeInsets.zero, title: Text("${data.name}"), groupValue: fruitGroupId, value: data.id, onChanged: (val){ setState(() { radioItem1 = data.name; fruitGroupId = data.id; }); }, )).toList(), ), ], ), ), Expanded( flex:2, child: Container( color: Colors.white, )), ], ), ), Container( child: Row( children: [ Expanded( flex: 1, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Colors",style: TextStyle(color: Colors.black, fontSize: 20,fontWeight: FontWeight.bold),), Column( children: cList.map((colorData) => RadioListTile( controlAffinity: ListTileControlAffinity.trailing, contentPadding: EdgeInsets.zero, title: Text("${colorData.name}"), groupValue: colorGroupId, value: colorData.id, onChanged: (val){ setState(() { radioItem2 = colorData.name; colorGroupId = colorData.id; }); }, )).toList(), ), ], ), ), Expanded( flex: 2, child:Container( color: Colors.white , ) ), ], ), ), ], ), ), ); } }
mobile_model_class.dart
class MobileList { int id; String companyName; String model; MobileList({required this.id, required this.companyName, required this.model}); }
Fruit_model_class.dart(原代码存在复制错误,已修正)
class FruitsList { int id; String name; FruitsList({required this.id, required this.name}); }
color_model_class.dart
class ColorsList { String name; int id; ColorsList({required this.name,required this.id}); }
实现方案
按四个需求分别修改对应代码即可:
1. 多选CheckBox灰色背景+黑色对勾
给CheckboxListTile添加如下样式配置:
CheckboxListTile( // 原有配置保持不变 checkColor: Colors.black, fillColor: MaterialStateProperty.resolveWith((states) { return states.contains(MaterialState.selected) ? Colors.grey : Colors.grey[200]; }), // 原有onChanged逻辑保持不变 )
2. RangeSlider未激活轨道设为灰色
给RangeSlider添加inactiveColor属性:
RangeSlider( // 原有配置保持不变 inactiveColor: Colors.grey[300], // 原有onChanged逻辑保持不变 )
3. Radio黑色样式+支持取消选择
首先将fruitGroupId改为可空类型int?,再给所有RadioListTile添加如下配置:
RadioListTile<int?>( // 原有配置保持不变 activeColor: Colors.black, toggleable: true, // 原生支持点击已选选项取消选中 onChanged: (val){ setState(() { fruitGroupId = val; radioItem1 = val == null ? '' : data.name; }); }, )
颜色选择的RadioListTile同理修改即可。
4. 方形选框样式
和第一点的CheckBox配置一致,复用上面的checkColor和fillColor配置即可满足需求。
内容的提问来源于stack exchange,提问作者Shanmukha
相关产品推荐
相关产品推荐

