flutter - 通过单击 FancyBottomNavigation 选项卡屏幕上的列表项导航到新屏幕时,Flutter 应用程序崩溃
问题描述
我正在重建一个已经使用 Flutter 使用原生 Android 开发的应用程序。我是 Flutter 和 Dart 的新手。但是,我保留在我以前的 UI 中,并进行了最小的更改。但是,到目前为止,浏览一切都做得很好。所以,让我解释一下我的工作。B'coz 我找不到这个问题的确切根本原因。首先,我有一个带抽屉的主屏幕。在其中,我使用 FancyBottomNavigation 实现了一个带有 3 个子选项卡屏幕的bottomNavigationBar. 一个选项卡有一个从 PHP 脚本中检索数据的动态列表视图。这部分完全可以正常工作,没有任何错误。接下来,我为每个项目列表实现了一个 onTap。当用户单击这些列表项时,导航到具有另一个动态列表视图的第三个屏幕。这就是错误的来源。第三个屏幕加载了列表数据而没有崩溃。但是这个导航抛出异常。此外,该屏幕上还有底部导航栏的剩余部分。然后,当按下后退按钮时应用程序崩溃。我已经多次尝试解决这个在 Internet 上解决的问题。* 为选项卡实现了 PageStorage * 为控制器添加了 dispose * MetiialApp 用于第三个屏幕,而不是 Container、Column...
所以,我需要解决这个崩溃问题并从第三个屏幕中删除那个底部导航栏图标。请帮助我解决这些问题并了解根本原因。
请参阅下面的导航到第三个屏幕时的例外情况。
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building TickerMode(mode: disabled):
setState() or markNeedsBuild() called during build.
This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: Overlay-[LabeledGlobalKey<OverlayState>#9de6b]
state: OverlayState#b259c(entries: [OverlayEntry#ab600(opaque: true; maintainState: false), OverlayEntry#dfdfa(opaque: false; maintainState: true), OverlayEntry#69acb(opaque: true; maintainState: false), OverlayEntry#fcf4a(opaque: false; maintainState: true), OverlayEntry#2439e(opaque: false; maintainState: false)])
The widget which was currently being built when the offending call was made was: TickerMode
mode: disabled
The relevant error-causing widget was:
MaterialApp file:///D:/DEVELOPMENT/Flutter/quiz_master/lib/screens/home_screen.dart:21:12
When the exception was thrown, this was the stack:
#0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:3896:11)
#1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:3911:6)
#2 State.setState (package:flutter/src/widgets/framework.dart:1168:14)
#3 _FancyBottomNavigationState._setSelected (package:fancy_bottom_navigation/fancy_bottom_navigation.dart:194:21)
#4 _FancyBottomNavigationState.didChangeDependencies (package:fancy_bottom_navigation/fancy_bottom_navigation.dart:64:5)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
════════ (2) Exception caught by widgets library ═══════════════════════════════════════════════════
'package:flutter/src/widgets/framework.dart': Failed assertion: line 5158 pos 14: 'oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active': is not true.
The relevant error-causing widget was:
Scaffold file:///D:/DEVELOPMENT/Flutter/quiz_master/lib/screens/home_screen.dart:23:13
════════════════════════════════════════════════════════════════════════════════════════════════════
════════ (3) Exception caught by widgets library ═══════════════════════════════════════════════════
'package:flutter/src/widgets/framework.dart': Failed assertion: line 3037 pos 12: '_debugLifecycleState == _ElementLifecycle.active
&& widget != null
&& newWidget != null
&& newWidget != widget
&& depth != null
&& _active
&& Widget.canUpdate(widget, newWidget)': is not true.
The relevant error-causing widget was:
FancyBottomNavigation file:///D:/DEVELOPMENT/Flutter/quiz_master/lib/screens/home_screen.dart:25:30
════════════════════════════════════════════════════════════════════════════════════════════════════
I/flutter (10800): *JSON DATA*
请参阅下文了解按下返回按钮时的例外情况。(应用程序崩溃)
════════ (5) Exception caught by widgets library ═══════════════════════════════════════════════════
'package:flutter/src/widgets/framework.dart': Failed assertion: line 4277 pos 12: 'child == _child': is not true.
The relevant error-causing widget was:
MaterialApp file:///D:/DEVELOPMENT/Flutter/quiz_master/lib/screens/home_screen.dart:21:12
════════════════════════════════════════════════════════════════════════════════════════════════════
════════ (6) Exception caught by widgets library ═══════════════════════════════════════════════════
Duplicate GlobalKey detected in widget tree.
════════════════════════════════════════════════════════════════════════════════════════════════════
════════ (7) Exception caught by widgets library ═══════════════════════════════════════════════════
'package:flutter/src/widgets/framework.dart': Failed assertion: line 5540 pos 12: '_children.contains(child)': is not true.
The relevant error-causing widget was:
MaterialApp file:///D:/DEVELOPMENT/Flutter/quiz_master/lib/screens/home_screen.dart:21:12
════════════════════════════════════════════════════════════════════════════════════════════════════
════════ (8) Exception caught by widgets library ═══════════════════════════════════════════════════
The getter 'userGestureInProgress' was called on null.
Receiver: null
Tried calling: userGestureInProgress
The relevant error-causing widget was:
MaterialApp file:///D:/DEVELOPMENT/Flutter/quiz_master/lib/screens/home_screen.dart:21:12
════════════════════════════════════════════════════════════════════════════════════════════════════
════════ (9) Exception caught by widgets library ═══════════════════════════════════════════════════
Duplicate GlobalKey detected in widget tree.
════════════════════════════════════════════════════════════════════════════════════════════════════
HomeScreen - 带有 3 个选项卡的第一个屏幕
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentPage = 0;
final _tabOptions = [ HomeScreen(), LearnHomeScreen(), DiscussionHomeScreen() ];
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: _tabOptions[currentPage],
bottomNavigationBar: FancyBottomNavigation(
circleColor: HexColor("#3DAEDF"),
inactiveIconColor: HexColor("#3DAEDF"),
tabs: [
TabData(iconData: Icons.home, title: "Home"),
TabData(iconData: Icons.book, title: "Learn"),
TabData(iconData: Icons.chat, title: "Discussion")
],
onTabChangedListener: (position) {
setState(() {
currentPage = position;
});
},
),
),
);
}
}
子选项卡屏幕 - 包括列表视图的第二个屏幕
class LearnHomeScreen extends StatefulWidget {
@override
_LearnHomeScreenState createState() => _LearnHomeScreenState();
}
class _LearnHomeScreenState extends State<LearnHomeScreen> with TickerProviderStateMixin {
AnimationController animationController;
Animation<double> topBarAnimation;
final ScrollController scrollController = ScrollController();
double topBarOpacity = 0;
Future<List<Subject>> subjectsList;
List<Widget> listViews = <Widget>[];
String uri;
@override
void initState() {
uri = "xxxxx";
subjectsList = getSubjects();
animationController = AnimationController(duration: const Duration(milliseconds: 1000), vsync: this);
topBarAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: animationController,
curve: Interval(0, 0.5, curve: Curves.fastOutSlowIn)));
addAllListData();
scrollController.addListener(() {
if (scrollController.offset >= 24) {
if (topBarOpacity != 1.0) {
setState(() {
topBarOpacity = 1.0;
});
}
} else if (scrollController.offset <= 24 &&
scrollController.offset >= 0) {
if (topBarOpacity != scrollController.offset / 24) {
setState(() {
topBarOpacity = scrollController.offset / 24;
});
}
} else if (scrollController.offset <= 0) {
if (topBarOpacity != 0.0) {
setState(() {
topBarOpacity = 0.0;
});
}
}
});
super.initState();
}
Future<bool> getData() async {
await Future<dynamic>.delayed(const Duration(milliseconds: 200));
return true;
}
Future<List<Subject>> getSubjects() async {
var res = await http.get(uri);
if (res.statusCode == 200) {
print(res.body);
List<dynamic> body = jsonDecode(res.body);
List<Subject> subjectList = body
.map(
(dynamic item) => Subject.fromJson(item),
).toList();
return subjectList;
} else {
throw "Can't get subjects.";
}
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
void addAllListData() {
listViews.add(
getListView(),
);
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.grey[50],
child: Scaffold(
backgroundColor: Colors.transparent,
body: Stack(
children: <Widget>[
getListView(),
getAppBarUI(),
SizedBox(
height: MediaQuery.of(context).padding.bottom,
)
],
),
),
);
}
Widget getListView(){
return Container(
decoration: new BoxDecoration(
image: DecorationImage(
image: new ExactAssetImage('assets/images/main_background.png'),
fit: BoxFit.cover,
),
),
child: FutureBuilder(
future: subjectsList,
builder: (BuildContext context, AsyncSnapshot<List<Subject>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: Lottie.asset('assets/animations/dotloading.json'));
case ConnectionState.done:
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
}else {
List<Subject> subjects = snapshot.data;
return ListView.builder(
controller: scrollController,
itemCount: subjects.length,
padding: const EdgeInsets.only(top: 110, bottom: 25),
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
final int count = subjects.length > 10 ? 10 : subjects.length;
final Animation<double> animation =
Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: animationController,
curve: Interval(
(1 / count) * index, 1.0,
curve: Curves.fastOutSlowIn)));
animationController.forward();
return SubjectListView(
callback: () {},
subjectData: subjects[index],
animation: animation,
animationController: animationController,
);
},
);
}
break;
default:
return Container();// also check your listWidget(snapshot) as it may return null.
}
},
),
);
}
Widget getAppBarUI() {
return Column(
children: <Widget>[
AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return FadeTransition(
opacity: topBarAnimation,
child: Transform(
transform: Matrix4.translationValues(
0.0, 30 * (1.0 - topBarAnimation.value), 0.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(topBarOpacity),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(32.0),
),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.grey
.withOpacity(0.4 * topBarOpacity),
offset: const Offset(1.1, 1.1),
blurRadius: 10.0),
],
),
child: Column(
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).padding.top,
),
Padding(
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 2,
bottom: 8 - 8.0 * topBarOpacity),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 50, top: 15, bottom: 20),
child: Text(
'Choose a subject!',
textAlign: TextAlign.left,
style: TextStyle(
fontFamily: "Roboto",
fontWeight: FontWeight.w600,
fontSize: 26 + 6 - 6 * topBarOpacity,
letterSpacing: 1,
color: Colors.black,
),
),
),
),
],
),
)
],
),
),
),
);
},
),
],
);
}
}
onTap - 列表视图项
child: GestureDetector(
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (context) {
return PaperSelectScreen(
subjectName: subjectData.subject,
imageName: subjectData.image,
);
}));
},
第三个屏幕 - 包括 seconf 动态列表视图
class PaperSelectScreen extends StatefulWidget {
@override
_PaperSelectScreenState createState() => _PaperSelectScreenState();
const PaperSelectScreen({Key key, this.subjectName, this.imageName}) : super(key: key);
final subjectName;
final imageName;
}
class _PaperSelectScreenState extends State<PaperSelectScreen> with TickerProviderStateMixin {
static String subjectName;
static String imageName;
final ScrollController scrollController = ScrollController();
AnimationController animationController;
Animation<double> topBarAnimation;
double topBarOpacity = 0;
Future<List<Paper>> paperList;
List<Widget> listViews = <Widget>[];
String uri;
@override
void initState() {
super.initState();
subjectName = widget.subjectName;
imageName = widget.imageName;
uri = "xxxxxx";
paperList = getPapers();
animationController = AnimationController(duration: const Duration(milliseconds: 1000), vsync: this);
topBarAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: animationController,
curve: Interval(0, 0.5, curve: Curves.fastOutSlowIn)));
addAllListData();
scrollController.addListener(() {
if (scrollController.offset >= 24) {
if (topBarOpacity != 1.0) {
setState(() {
topBarOpacity = 1.0;
});
}
} else if (scrollController.offset <= 24 &&
scrollController.offset >= 0) {
if (topBarOpacity != scrollController.offset / 24) {
setState(() {
topBarOpacity = scrollController.offset / 24;
});
}
} else if (scrollController.offset <= 0) {
if (topBarOpacity != 0.0) {
setState(() {
topBarOpacity = 0.0;
});
}
}
});
}
Future<bool> getData() async {
await Future<dynamic>.delayed(const Duration(milliseconds: 200));
return true;
}
Future<List<Paper>> getPapers() async {
var res = await http.get(uri);
if (res.statusCode == 200) {
print(res.body);
List<dynamic> body = jsonDecode(res.body);
List<Paper> paperList = body
.map(
(dynamic item) => Paper.fromJson(item),
).toList();
return paperList;
} else {
throw "Can't get subjects.";
}
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
void addAllListData() {
listViews.add(
getListView(),
);
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.grey[50],
child: Scaffold(
backgroundColor: Colors.transparent,
body: Stack(
children: <Widget>[
getListView(),
getAppBarUI(),
/*Padding(
padding: const EdgeInsets.only(left: 8,top: 30),
child: IconButton(
icon: Icon(Icons.arrow_back),
color: Colors.black,
onPressed: (){
Navigator.pop(context);
},
),
),*/
SizedBox(
height: MediaQuery.of(context).padding.bottom,
)
],
),
),
);
}
Widget getListView(){
return Container(
decoration: new BoxDecoration(
image: DecorationImage(
image: new ExactAssetImage('assets/images/main_background.png'),
fit: BoxFit.cover,
),
),
child: FutureBuilder(
future: paperList,
builder: (BuildContext context, AsyncSnapshot<List<Paper>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: Lottie.asset('assets/animations/dotloading.json'));
case ConnectionState.done:
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
}else {
List<Paper> paper = snapshot.data;
return ListView.builder(
controller: scrollController,
itemCount: paper.length,
padding: const EdgeInsets.only(top: 110, bottom: 25),
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
final int count =
paper.length > 10 ? 10 : paper.length;
final Animation<double> animation =
Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: animationController,
curve: Interval(
(1 / count) * index, 1.0,
curve: Curves.fastOutSlowIn)));
animationController.forward();
return PaperListView(
callback: () {},
paperData: paper[index],
image: imageName,
animation: animation,
animationController: animationController,
);
},
);
}
break;
default:
return Container();// also check your listWidget(snapshot) as it may return null.
}
},
),
);
}
Widget getAppBarUI() {
return Column(
children: <Widget>[
AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return FadeTransition(
opacity: topBarAnimation,
child: Transform(
transform: Matrix4.translationValues(
0.0, 30 * (1.0 - topBarAnimation.value), 0.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(topBarOpacity),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(32.0),
),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.grey
.withOpacity(0.4 * topBarOpacity),
offset: const Offset(1.1, 1.1),
blurRadius: 10.0),
],
),
child: Column(
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).padding.top,
),
Padding(
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 2,
bottom: 8 - 8.0 * topBarOpacity),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 50, top: 15, bottom: 20),
child: Text(
'Choose a paper!',
textAlign: TextAlign.left,
style: TextStyle(
fontFamily: "Roboto",
fontWeight: FontWeight.w600,
fontSize: 26 + 6 - 6 * topBarOpacity,
letterSpacing: 1,
color: Colors.black,
),
),
),
),
],
),
)
],
),
),
),
);
},
),
],
);
}
}
解决方案
此错误“在构建期间调用了 setState() 或 markNeedsBuild()。” 甚至在Widget build
完成渲染小部件之前调用 setState() 引起的。
您可以在这里做的一种解决方法是等待小部件被渲染
WidgetsBinding.instance.addPostFrameCallback((_){
// Add your code here
});
或使用 SchedulerBinding
SchedulerBinding.instance.addPostFrameCallback((_) {
// Add your code here
});
推荐阅读
- c - 没有 SSE4.1 的高效 SSE FP `floor()` / `ceil()` / `round()` 舍入函数?
- javascript - 从action发送的数据没有被reducer接收
- jquery - 如何根据所选选项卡的状态更改按钮文本
- javascript - IE 11 中 JS 过滤器功能面临的问题
- plsqldeveloper - DBMS_OUTPUT 不输出任何东西,为什么呢?到目前为止,我已经尝试了一切来修复它
- content-management-system - 将单独的页面与默认文化相结合
- reactjs - 更新从组件中的 reducer 获取的 props
- unix - 是否应该在 Makefile 中使用较长的局部变量同义词?
- asp.net-mvc - IIS URL 重定向 - 某些 URL 的 igonre 匹配所有
- ios - 在 Swift 中,值类型是否有 passUnretained() 的等价物?