You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中自定义复选框、单选按钮与RangeSlider滑块组件

问题描述

我正在尝试自定义Flutter中的按钮组件,但暂时没有找到实现预期设计的方案,具体需求如下:

  • 多选CheckBox为灰色背景、黑色对勾样式
  • RangeSlider的未激活轨道设置为灰色
  • Radio Button为黑色样式,且支持全部取消选择;我清楚原生Radio Button默认不支持全取消,使用其他替代方案实现该需求也可以
  • 方形选框为灰色背景、黑色对勾样式

效果截图

  • 预期设计:我的设计
  • 实际运行UI:实际运行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 10:54:01