Flutter中如何从有状态组件向其他组件传递变量并维护状态?
Flutter 标签切换状态不联动问题解决
你遇到的核心问题是状态隔离:JobsHeaderWidget内部维护的current状态和JobsView里的current是两个完全独立的变量,点击标签时只有JobsHeaderWidget的状态更新了,但JobsView的current没有同步变化,导致CategoriesBuilder接收的参数始终是初始值0,自然不会切换内容。
解决步骤
1. 改造JobsHeaderWidget,添加状态回调
让JobsHeaderWidget支持将内部选中的current值传递给父组件:
class JobsHeaderWidget extends StatefulWidget { // 新增回调参数,用于向父组件传递选中的current值 final ValueChanged<int> onCurrentChanged; const JobsHeaderWidget({ Key key, this.onCurrentChanged, }) : super(key: key); @override State<JobsHeaderWidget> createState() => _JobsHeaderWidgetState(); } class _JobsHeaderWidgetState extends State<JobsHeaderWidget> { List<String> items = [ "All", "Critical", "Open", "Closed", "Overdue", ]; int current = 0; List<DropdownMenuItem<String>> get dropdownItems { List<DropdownMenuItem<String>> menuItems = [ DropdownMenuItem( child: Text("Today"), value: "Today"), ]; return menuItems; } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(left: 15.0, right: 15.0), child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Jobs', style: GoogleFonts.poppins( color: Colors.black, fontSize: 18, fontWeight: FontWeight.w600), ), Row( children: [ Text( 'View Insights ', style: GoogleFonts.poppins( color: Color(0xff3498DB), fontSize: 12, fontWeight: FontWeight.w500), ), Icon( Icons.arrow_forward_ios, color: Color(0xff3498DB), size: 12, ), ], ), SizedBox(height: 10), filterJobs(), ], ), ), ); } Widget filterJobs() { String selectedValue = "Today"; return Column( children: [ Container( constraints: const BoxConstraints(maxWidth: 600, maxHeight: 100), width: double.infinity, child: IntrinsicWidth( child: FittedBox( fit: BoxFit.fitWidth, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ for (int i = 0; i < items.length; i++) ...[ GestureDetector( onTap: () { setState(() { current = i; }); // 调用回调,将选中的current值传给父组件 widget.onCurrentChanged?.call(current); }, child: AnimatedContainer( height: 40, duration: const Duration(milliseconds: 300), margin: const EdgeInsets.all(5), padding: const EdgeInsets.only(left: 14.0, right: 14.0, top: 4, bottom: 4), decoration: BoxDecoration( color: current == i ? const Color(0xff34495E) : const Color(0xffF5F5F5), borderRadius: BorderRadius.circular(50), ), child: Center( child: Text( items[i], style: GoogleFonts.poppins( fontSize: 15, fontWeight: FontWeight.w500, color: current == i ? Colors.white : Colors.grey), ), ), ), ), ] ], ), ), ), ), Divider(color: Color(0xff34495E).withOpacity(0.2)), Row( children: [ Text( 'All Jobs', style: GoogleFonts.poppins(fontSize: 9, fontWeight: FontWeight.w400), ), SizedBox(width: 5), Text( ' * This Week', style: GoogleFonts.poppins(fontSize: 9, fontWeight: FontWeight.w400), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '25', style: GoogleFonts.poppins(fontSize: 20, fontWeight: FontWeight.w600), ), Container( height: 30, decoration: BoxDecoration( borderRadius: BorderRadius.circular(2), color: Color(0xffF4F4F4)), child: Padding( padding: const EdgeInsets.only(left: 8.0), child: Row( children: [ Container( decoration: BoxDecoration( color: Color(0xff34495E), borderRadius: BorderRadius.circular(2)), child: Icon( Icons.tune, size: 15, color: Colors.white, ), ), SizedBox(width: 5), DropdownMenuItem( child: DropdownButtonHideUnderline( child: Container( child: DropdownButton( isDense: true, style: GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w500, color: Color(0xff34495E), ), onChanged: (value) {}, items: dropdownItems, value: selectedValue, ), ), ), ), ], ), ), ), ], ), ], ); } } class CategoriesBuilder extends StatelessWidget { const CategoriesBuilder({ Key key, @required this.current, }) : super(key: key); final int current; @override Widget build(BuildContext context) { return Builder( builder: (context) { switch (current) { case 0: return AllJobsListView(); case 1: return CriticalJobsListView(); case 2: return OpenJobsListView(); case 3: return ClosedJobsListView(); case 4: return OverdueJobsListView(); default: return SizedBox.shrink(); } }, ); } }
2. 修改JobsView,接收回调并更新状态
在JobsView中接收JobsHeaderWidget传递的current值,更新自身状态后传给CategoriesBuilder:
class JobsView extends StatefulWidget { const JobsView({Key key}) : super(key: key); @override State<JobsView> createState() => _JobsViewState(); } class _JobsViewState extends State<JobsView> { int current = 0; @override Widget build(BuildContext context) { final controller = Get.put(EServicesController()); return Scaffold( floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, body: RefreshIndicator( onRefresh: () async { Get.find<LaravelApiClient>().forceRefresh(); controller.refreshEServices(showMessage: true); Get.find<LaravelApiClient>().unForceRefresh(); }, child: CustomScrollView( controller: controller.scrollController, physics: AlwaysScrollableScrollPhysics(), shrinkWrap: false, slivers: <Widget>[ SliverAppBar( backgroundColor: Color(0xffFFFFFF), expandedHeight: MediaQuery.of(context).size.height * 0.4, elevation: 0.5, primary: true, pinned: false, floating: false, centerTitle: false, automaticallyImplyLeading: false, actions: [SearchButtonWidget()], flexibleSpace: FlexibleSpaceBar( collapseMode: CollapseMode.parallax, // 传入回调,更新JobsView的current状态 title: JobsHeaderWidget( onCurrentChanged: (newCurrent) { setState(() { current = newCurrent; }); }, ), )), SliverToBoxAdapter( child: Wrap( children: [CategoriesBuilder(current: current)], ), ), ], ), ), ); } }
为什么之前的尝试没用?
- 单独给
JobsView加current状态:JobsHeaderWidget的点击事件不会触发JobsView的setState,两者状态完全独立,所以JobsView的current始终是初始值。 - 使用全局变量:变量值虽会变化,但没有调用
setState通知Flutter框架重新构建CategoriesBuilder,UI不会更新。
内容的提问来源于stack exchange,提问作者tesseract
相关产品推荐
相关产品推荐

