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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 08:22:50