Flutter单选Checkbox实现优化方案求助
优化Flutter Checkbox单选功能的解决方案
原代码存在的问题
- 全局变量
count0、count1导致状态管理混乱,易引发不可预期的状态错误 onChanged和onTap中重复编写大量逻辑代码,增加维护成本与出错概率- 多处出现赋值错误(使用
==而非=),导致状态更新失效,进而引发卡顿或崩溃 - 直接操作全局数组的
isSelected属性,状态变更缺乏统一管理
优化方案
使用单个状态变量记录选中项的索引,替代复杂的条件判断与全局计数器,统一处理单选逻辑:
import 'package:dotted_border/dotted_border.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/src/foundation/key.dart'; import 'package:flutter/src/widgets/framework.dart'; import 'package:image_picker/image_picker.dart'; import 'package:loviser/modules/post/free_post/create_free_post.dart'; import 'package:loviser/pages/job_type_problem_page.dart'; import 'package:loviser/pages/post_to_find_page.dart'; import 'package:loviser/utils/color.dart'; import 'package:loviser/utils/utils.dart'; import 'package:loviser/values/app_color.dart'; class RowContent { final String iconLeadingURL; final String content; final Function(BuildContext) itemFunction; RowContent({ required this.iconLeadingURL, required this.content, required this.itemFunction, }); } final allRowContent = [ RowContent( iconLeadingURL: 'assets/images/free.png', content: 'Đăng bài nhờ cộng đồng hỗ trợ miễn phí', itemFunction: (ctxRoot) { print('chose to post free'); }, ), RowContent( iconLeadingURL: 'assets/images/PosttoFindFreelancers.png', content: 'Đăng bài tìm freelancer tư vấn', itemFunction: (ctxRoot) { print('chose post find'); }, ), ]; class PostOption extends StatefulWidget { const PostOption({Key? key}) : super(key: key); @override State<PostOption> createState() => _PostOptionState(); } class _PostOptionState extends State<PostOption> { // 用索引记录选中项,null表示未选中 int? _selectedIndex; // 处理选中逻辑的统一方法 void _handleSelect(int index) { setState(() { // 点击已选中项时取消选中,否则切换选中项 _selectedIndex = _selectedIndex == index ? null : index; // 执行对应项的回调 allRowContent[index].itemFunction(context); }); } Widget itemSwitch(RowContent item, Widget flutterSwitch) { return ListTile( tileColor: Colors.white, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10))), leading: Image(image: AssetImage(item.iconLeadingURL)), title: Text( item.content, style: const TextStyle( fontFamily: 'AvertaStdCY-Semibold', fontSize: 12, fontWeight: FontWeight.w400), ), trailing: Container( margin: const EdgeInsets.fromLTRB(24, 0, 0, 0), height: 30, width: 30, child: FittedBox(child: flutterSwitch)), ); } @override Widget build(BuildContext context) { final mediaQuery = MediaQuery.of(context); final isLandscape = mediaQuery.orientation == Orientation.landscape; final bool keyboardIsOpen = MediaQuery.of(context).viewInsets.bottom != 0; final PreferredSizeWidget appBar = AppBar( elevation: 0.0, backgroundColor: const Color(0xFFF9F9F9), leading: Container( margin: const EdgeInsets.fromLTRB(24, 0, 0, 0), child: IconButton( padding: const EdgeInsets.fromLTRB(0, 8, 8, 8), alignment: Alignment.center, onPressed: () { print('Exit Job Type Freelancer page'); Navigator.pop(context); }, icon: const Image(image: AssetImage('assets/images/close.png')), ), )); return Scaffold( backgroundColor: const Color(0xFFFFAFAFD), appBar: appBar, body: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( children: [ Center( child: Container( child: Column( children: const <Widget>[ Text( 'LOVISER', style: TextStyle( color: Color(0xFF356899), fontSize: 22, fontWeight: FontWeight.bold), ), SizedBox(height: 7), Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: Text( 'Chọn giải pháp để tư vấn về vấn đề tình cảm của bạn!', style: TextStyle( color: Color(0xFF0D0D26), fontSize: 20, fontWeight: FontWeight.bold), ), ), ], ), ), ), const SizedBox(height: 10), for (int i = 0; i < allRowContent.length; i++) ...[ const SizedBox(height: 7), Column( children: [ ListTile( tileColor: Colors.white, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10))), leading: Image( width: 32, height: 31, image: AssetImage(allRowContent[i].iconLeadingURL)), title: Text( allRowContent[i].content, style: const TextStyle( color: Color(0xff0D0D26), fontFamily: 'AvertaStdCY-Semibold', fontSize: 15, fontWeight: FontWeight.w100), ), trailing: Checkbox( activeColor: Colors.red, shape: const CircleBorder(), side: const BorderSide( width: 1, color: Color(0xFFCACBCE)), value: _selectedIndex == i, onChanged: (value) { _handleSelect(i); }, ), onTap: () { _handleSelect(i); }, ), ], ), ], const SizedBox(height: 10), const SizedBox(height: 10), Padding( padding: const EdgeInsets.fromLTRB(0, 20, 0, 0), child: Align( alignment: Alignment.bottomCenter, child: TextButton( style: TextButton.styleFrom( backgroundColor: const Color(0xffEC1C24), minimumSize: const Size(327, 56), ), onPressed: () => { if (_selectedIndex == 0) { _showModalBottomSheetForFreePost() } else if (_selectedIndex == 1) { Navigator.push( context, MaterialPageRoute( builder: (context) => PostToFind()), ) } }, child: const Text( 'Tiếp tục', style: TextStyle( fontFamily: 'Poppins-Regular', fontSize: 16, color: Color(0xffffffff), ), ), ), ), ), ]), ), ); } void _showModalBottomSheetForFeePost() { showModalBottomSheet( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), context: context, builder: (builder) { final width = MediaQuery.of(context).size.width; final height = MediaQuery.of(context).size.height; return Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(30.0), topRight: Radius.circular(100.0), ), ), height: height * 0.32, child: Column( children: [ const Icon(Icons.remove, size: 40), SizedBox( width: width * 0.8, child: const Text( "Bạn đang gặp vấn đề về chuyện tình cảm? xxx", textAlign: TextAlign.center, style: TextStyle( color: Color(0xFF150B3D), fontWeight: FontWeight.bold, fontSize: 24, ), ), ), SizedBox( height: height * 0.01, ), SizedBox( width: width * 0.6, child: const Text( "Đăng vấn đề của bạn và nhờ một freelancer có kinh nghiệm tư vấn!", textAlign: TextAlign.center, ), ), SizedBox( height: height * 0.05, ), SizedBox( height: 50, width: 315, child: ElevatedButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const PostOption(), ), ); }, style: ElevatedButton.styleFrom( primary: Colors.red, ), child: const Text( "Đăng vấn đề", style: TextStyle( color: Colors.white, ), ), ), ), ], ), ); }, ); } void _showModalBottomSheetForFreePost() { print('222'); Navigator.of(context).push( MaterialPageRoute( builder: (_) => const CreateFreePost(), ), ); } }
优化说明
- 移除全局计数器与
isSelected属性,改用_selectedIndex状态变量统一管理选中状态,逻辑更清晰 - 封装
_handleSelect方法,统一处理onChanged与onTap事件,消除代码冗余 - 修复原代码中
==赋值错误的问题,确保状态更新正确 - 简化Checkbox渲染逻辑,无需根据选中状态拆分组件,提升渲染性能与代码可读性
内容的提问来源于stack exchange,提问作者Trong Nhan Nguyen Le
相关产品推荐
相关产品推荐

