flutter 如何从子控件更新父控件的状态?

yfwxisqw  于 2023-06-07  发布在  Flutter
关注(0)|答案(1)|浏览(369)

我跟随这个链接,试图使用回调函数从子控件更新父控件。How to update the state of Parent Widget from its Child Widget while also updating the Child's state in Flutter?
但是我遇到了很多bug。你可以在我的作品的GIF中看到它:

你可以看到在肌肉面板中,有两个按钮来选择视图类型。目标是在窗格(概述、练习、肌肉)之间切换时,我希望保留在肌肉窗格中选择的视图类型的状态。
问题:Table view默认为选中状态,下方显示文字,但当我选择Heatmap diagram时,下方文字发生变化,但按钮颜色不变。然后,当我从练习面板切换回肌肉面板时,按钮的颜色现在更新,但文本返回默认值。基本上,很多事情都出了问题。
父控件的代码:

import 'package:workout_log/screens/select_workout.dart';
import 'package:workout_log/screens/settings/settings.dart';
import '../../widgets/page/pane_button.dart';
import 'exercises_pane.dart';
import 'muscles/muscles_pane.dart';
import 'overview_pane.dart';

Widget pageSection = const OverviewPane();

class LogWorkout extends StatefulWidget {
  const LogWorkout({Key? key}) : super(key: key);

  @override
  State<LogWorkout> createState() => _LogWorkoutState();
}

enum LogWorkoutPanes { overview, exercises, muscles }

enum MuscleViewType { table, heatmapDiagram }

class _LogWorkoutState extends State<LogWorkout> {
  EdgeInsets padding = const EdgeInsets.all(25);
  LogWorkoutPanes selectedPane = LogWorkoutPanes.overview;

  MuscleViewType muscleViewType = MuscleViewType.table;
  updateMuscleViewType(newViewType) {
    setState(() {
      muscleViewType = newViewType;
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
      child: Scaffold(
          backgroundColor: Colors.white,
          body: SingleChildScrollView(
            child: SafeArea(
                child: Center(
              child: Column(children: [
                const LogWorkoutNavBar(),
                const LogWorkoutHeader(),
                Container(
                    width: double.infinity,
                    height: 50,
                    margin: const EdgeInsets.symmetric(
                        vertical: 16.0, horizontal: 25.0),
                    alignment: Alignment.center,
                    child: ElevatedButton(
                        onPressed: () {},
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: const [
                            Icon(Icons.electric_bolt),
                            Text("Start workout"),
                          ],
                        ))),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    InkWell(
                      onTap: () => {
                        setState(() {
                          pageSection = const OverviewPane();
                          selectedPane = LogWorkoutPanes.overview;
                        })
                      },
                      child: PaneButton(
                          title: "Overview",
                          isActive: selectedPane == LogWorkoutPanes.overview),
                    ),
                    InkWell(
                        onTap: () => {
                              setState(() {
                                pageSection = const ExercisesPane();
                                selectedPane = LogWorkoutPanes.exercises;
                              })
                            },
                        child: PaneButton(
                            title: "Exercises",
                            isActive:
                                selectedPane == LogWorkoutPanes.exercises)),
                    InkWell(
                        onTap: () => {
                              setState(() {
                                pageSection = MusclesPane(
                                    updateViewType: updateMuscleViewType,
                                    viewType: muscleViewType);
                                selectedPane = LogWorkoutPanes.muscles;
                              })
                            },
                        child: PaneButton(
                            title: "Muscles",
                            isActive: selectedPane == LogWorkoutPanes.muscles)),
                  ],
                ),
                Padding(padding: padding, child: pageSection),
              ]),
            )),
          )),
    );
  }
}

class LogWorkoutHeader extends StatelessWidget {
  const LogWorkoutHeader({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.only(top: 24.0, left: 24.0, right: 24.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text("Pull Hypertrophy",
              style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: Theme.of(context).textTheme.headline2?.fontSize)),
          Container(
              margin: const EdgeInsets.only(top: 5),
              child: const Text("21 October 2022 - 5:30PM"))
        ],
      ),
    );
  }
}

class LogWorkoutNavBar extends StatelessWidget {
  const LogWorkoutNavBar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    const double padding = 20;
    const EdgeInsets rowPadding =
        EdgeInsets.only(top: padding, left: padding, right: padding);

    return Padding(
      padding: rowPadding,
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Expanded(
              flex: 2,
              child: Align(
                  alignment: Alignment.topLeft,
                  child: ElevatedButton(
                    style: ElevatedButton.styleFrom(
                        minimumSize: const Size(40, 40), primary: Colors.white),
                    onPressed: () {
                      Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) => const SelectWorkout()));
                    },
                    child: const Icon(
                      Icons.navigate_before,
                      color: Colors.black,
                    ),
                  ))),
          Expanded(
              flex: 2,
              child: Align(
                  alignment: Alignment.topLeft,
                  child: ElevatedButton(
                    style: ElevatedButton.styleFrom(
                        minimumSize: const Size(40, 40), primary: Colors.white),
                    onPressed: () {
                      showDialog(
                          context: context,
                          builder: (context) {
                            return const AlertDialog(
                              content: Text("This is a timer"),
                            );
                          });
                    },
                    child: const Icon(
                      Icons.timer,
                      color: Colors.black,
                    ),
                  ))),
          const Spacer(),
          Expanded(
            flex: 2,
            child: Center(
                child: ElevatedButton(
              style: ElevatedButton.styleFrom(
                  minimumSize: const Size(40, 40), primary: Colors.white),
              onPressed: () {
                Navigator.push(context,
                    MaterialPageRoute(builder: (context) => const Settings()));
              },
              child: const Icon(
                Icons.settings,
                color: Colors.black,
              ),
            )),
          ),
          Expanded(
            flex: 2,
            child: Center(
                child: ElevatedButton(
              style: ElevatedButton.styleFrom(
                  minimumSize: const Size(40, 40), primary: Colors.white),
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Icon(
                Icons.check,
                color: Colors.black,
              ),
            )),
          )
        ],
      ),
    );
  }
}

子控件的代码(“肌肉”窗格):

import 'package:flutter/material.dart';
import '../log_workout.dart';
import 'muscles_heatmap_diagram.dart';
import 'muscles_table_view.dart';

class MusclesPane extends StatefulWidget {
  const MusclesPane(
      {Key? key, required this.updateViewType, required this.viewType})
      : super(key: key);
  final Function updateViewType;
  final MuscleViewType viewType;

  @override
  State<MusclesPane> createState() => _MusclesPaneState();
}

class _MusclesPaneState extends State<MusclesPane> {
  EdgeInsets padding = const EdgeInsets.all(25);
  Widget pageSection = const MusclesTableView();

  selectTableView() {
    setState(() {
      pageSection = const MusclesTableView();
      widget.updateViewType(MuscleViewType.table);
    });
  }

  selectHeatmapView() {
    setState(() {
      pageSection = const MusclesHeatmapDiagram();
      widget.updateViewType(MuscleViewType.heatmapDiagram);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            ViewTypeButton(
                title: "Table view",
                isActive: widget.viewType == MuscleViewType.table,
                callback: selectTableView),
            ViewTypeButton(
                title: "Heatmap diagram",
                isActive: widget.viewType == MuscleViewType.heatmapDiagram,
                callback: selectHeatmapView)
          ],
        ),
        Padding(padding: padding, child: pageSection),
      ],
    );
  }
}

class ViewTypeButton extends StatelessWidget {
  const ViewTypeButton(
      {Key? key,
      required this.title,
      required this.isActive,
      required this.callback})
      : super(key: key);
  final String title;
  final bool isActive;
  final Function callback;

  @override
  Widget build(BuildContext context) {
    return Container(
        margin: const EdgeInsets.symmetric(horizontal: 4.0),
        child: ElevatedButton(
            onPressed: () => callback(),
            style: ElevatedButton.styleFrom(
                primary: isActive ? Colors.blue : Colors.grey),
            child: Text(title)));
  }
}

您可以看到我使用updateMuscleViewType作为回调函数,并且更改的值是muscleViewType。这是从子节点更新父节点状态的正确方法吗?

1dkrff03

1dkrff031#

一般来说,以这种方式开始管理状态是可以的。但是,您在这里有一个智能UI,这是一个糟糕的做法。从长远来看,您应该尝试将逻辑和UI分开。有各种状态管理系统,例如Blochttps://docs.flutter.dev/data-and-backend/state-mgmt/options
如果您采用这些系统之一,您可以独立于UI管理状态,然后跨多个小部件使用此状态,不会出现任何问题。这样您就不必使用相对容易出错的方法,如回调(在回调中很容易忘记小部件)。

相关问题