我跟随这个链接,试图使用回调函数从子控件更新父控件。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
。这是从子节点更新父节点状态的正确方法吗?
1条答案
按热度按时间1dkrff031#
一般来说,以这种方式开始管理状态是可以的。但是,您在这里有一个智能UI,这是一个糟糕的做法。从长远来看,您应该尝试将逻辑和UI分开。有各种状态管理系统,例如
Bloc
:https://docs.flutter.dev/data-and-backend/state-mgmt/options。如果您采用这些系统之一,您可以独立于UI管理状态,然后跨多个小部件使用此状态,不会出现任何问题。这样您就不必使用相对容易出错的方法,如回调(在回调中很容易忘记小部件)。