我的Flutter应用程序从ScrollController要控制的页面离开时抛出了异常(将ScrollController附加到多个滚动视图)NestedScrollView,我不确定自己在做什么错。
ScrollController
NestedScrollView
我用下面的简单示例重新创建了异常。我可以从导航FirstPage到SecondPage(也可以选择返回)就好了,但是当我从导航SecondPage到ThirdPage异常时,会引发异常。该SecondPage页面包含我CollapsingAppBarPage认为有问题的自定义窗口小部件。CollapsingAppBarPage在此示例中被简化了,但是在我的真实应用中,它会根据滚动位置更改组件的颜色/大小。在此示例中,当_scrollController.offset导航开始到时会在调用时发生异常ThirdPage。另外,我知道FirstPage和ThirdPage可以是Stateless而不是StatefulWidgets,但我想使其尽可能与我的应用程序一样。这是我充分工作的示例。
FirstPage
SecondPage
ThirdPage
CollapsingAppBarPage
_scrollController.offset
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: FirstPage(), ); } } class FirstPage extends StatefulWidget { @override _FirstPageState createState() => _FirstPageState(); } class _FirstPageState extends State<FirstPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("First Page"), ), body: Center( child: RaisedButton( child: Text("Navigate Next"), onPressed: () async { await Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()) ); }, ), ), ); } } class SecondPage extends StatefulWidget { @override _SecondPageState createState() => _SecondPageState(); } class _SecondPageState extends State<SecondPage> { @override Widget build(BuildContext context) { return Material( child: CollapsingAppBarPage( titleText: "Second Page", bodyCreator: (context) { return ListView( children: <Widget>[ Center( child: RaisedButton( child: Text("Navigate Next"), onPressed: () async { await Navigator.push(context, MaterialPageRoute(builder: (context) => ThirdPage()) ); }, ), ) ], ); }, ), ); } } class ThirdPage extends StatefulWidget { @override _ThirdPageState createState() => _ThirdPageState(); } class _ThirdPageState extends State<ThirdPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Page") ), body: Container() ); } } typedef CollapsingAppBarBodyCreator = Widget Function(BuildContext context); class CollapsingAppBarPage extends StatefulWidget { final String titleText; final CollapsingAppBarBodyCreator bodyCreator; CollapsingAppBarPage({ Key key, this.titleText, @required this.bodyCreator, }) : super(key: key); @override _CollapsingAppBarPageState createState() => _CollapsingAppBarPageState(); } class _CollapsingAppBarPageState extends State<CollapsingAppBarPage> { static const _kExpandedHeight = 200.0; ScrollController _scrollController; @override void initState() { super.initState(); _scrollController = ScrollController() ..addListener(() { setState(() { // force a refresh so the app bar can be updated }); }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return NestedScrollView( controller: _scrollController, headerSliverBuilder: _createSliverAppBar, body: widget.bodyCreator(context), ); } List<Widget> _createSliverAppBar(BuildContext context, bool innerBoxIsScrolled) { // change the icon color as the page scrolls var collapsePercent = _getAppBarCollapsePercent(); int rgb = ((1.0 - collapsePercent) * 255).round(); var color = Color.fromARGB(255, rgb, rgb, rgb); return <Widget>[ SliverAppBar( expandedHeight: _kExpandedHeight, pinned: true, iconTheme: IconThemeData(color: color), title: Text(widget.titleText), ) ]; } double _getAppBarCollapsePercent() { if (!_scrollController.hasClients) return 0.0; return (_scrollController.offset / (_kExpandedHeight - kToolbarHeight)).clamp(0.0, 1.0); } }
这是该异常的截断的痕迹:
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ flutter: The following assertion was thrown building Builder(dirty): flutter: ScrollController attached to multiple scroll views. flutter: 'package:flutter/src/widgets/scroll_controller.dart': Failed assertion: line 111 pos 12: flutter: '_positions.length == 1' flutter: flutter: Either the assertion indicates an error in the framework itself, or we should provide substantially flutter: more information in this error message to help you determine and fix the underlying cause. flutter: In either case, please report this assertion by filing a bug on GitHub: flutter: https://github.com/flutter/flutter/issues/new?template=BUG.md flutter: flutter: When the exception was thrown, this was the stack: flutter: #2 ScrollController.position (package:flutter/src/widgets/scroll_controller.dart:111:12) flutter: #3 ScrollController.offset (package:flutter/src/widgets/scroll_controller.dart:118:24) flutter: #4 _CollapsingAppBarPageState._getAppBarCollapsePercent (package:flutter_scroll_test/main.dart:160:31) flutter: #5 _CollapsingAppBarPageState._createSliverAppBar (package:flutter_scroll_test/main.dart:142:27) flutter: #6 NestedScrollView._buildSlivers (package:flutter/src/widgets/nested_scroll_view.dart:271:20) flutter: #7 _NestedScrollViewState.build.<anonymous closure> (package:flutter/src/widgets/nested_scroll_view.dart:347:29) flutter: #8 Builder.build (package:flutter/src/widgets/basic.dart:5736:41) flutter: #9 StatelessElement.build (package:flutter/src/widgets/framework.dart:3774:28) flutter: #10 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3721:15)
不要_scrollController.offset直接使用推入路线,因为当您再次弹出时接近滚动时,显然会附加要获取的滚动位置
所以我们需要将其转移到一个状态
这是完整的修订代码。
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: FirstPage(), ); } } class FirstPage extends StatefulWidget { @override _FirstPageState createState() => _FirstPageState(); } class _FirstPageState extends State<FirstPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("First Page"), ), body: Center( child: RaisedButton( child: Text("Navigate Next"), onPressed: () async { await Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()) ); }, ), ), ); } } class SecondPage extends StatefulWidget { @override _SecondPageState createState() => _SecondPageState(); } class _SecondPageState extends State<SecondPage> { @override Widget build(BuildContext context) { return Material( child: CollapsingAppBarPage( titleText: "Second Page", bodyCreator: (context) { return ListView( children: <Widget>[ Center( child: RaisedButton( child: Text("Navigate Next"), onPressed: () async { await Navigator.push(context, MaterialPageRoute(builder: (context) => ThirdPage()) ); }, ), ) ], ); }, ), ); } } class ThirdPage extends StatefulWidget { @override _ThirdPageState createState() => _ThirdPageState(); } class _ThirdPageState extends State<ThirdPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Third Page") ), body: Container() ); } } typedef CollapsingAppBarBodyCreator = Widget Function(BuildContext context); class CollapsingAppBarPage extends StatefulWidget { final String titleText; final CollapsingAppBarBodyCreator bodyCreator; CollapsingAppBarPage({ Key key, this.titleText, @required this.bodyCreator, }) : super(key: key); @override _CollapsingAppBarPageState createState() => _CollapsingAppBarPageState(); } class _CollapsingAppBarPageState extends State<CollapsingAppBarPage> { static const _kExpandedHeight = 200.0; ScrollController _scrollController; //Offset state <------------------------------------- double offset = 0.0 ; @override void initState() { super.initState(); //print("init state is called"); _scrollController = ScrollController() //keepScrollOffset: false removed ..addListener(() { setState(() { //<----------------------------- offset = _scrollController.offset; // force a refresh so the app bar can be updated }); }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return NestedScrollView( controller: _scrollController, headerSliverBuilder: _createSliverAppBar, body: widget.bodyCreator(context), ); } List<Widget> _createSliverAppBar(BuildContext context, bool innerBoxIsScrolled) { // change the icon color as the page scrolls print("_createSliverAppBar is called"); var collapsePercent = _getAppBarCollapsePercent(); int rgb = ((1.0 - collapsePercent) * 255).round(); var color = Color.fromARGB(255, rgb, rgb, rgb); return <Widget>[ SliverAppBar( expandedHeight: _kExpandedHeight, pinned: true, iconTheme: IconThemeData(color: color), title: Text(widget.titleText), ) ]; } double _getAppBarCollapsePercent() { if (!_scrollController.hasClients ){ print("positions is ${_scrollController.positions.length}"); return 0.0; } //print("offset is${_scrollController.offset} and positions is ${_scrollController.positions.length}"); return (offset / (_kExpandedHeight - kToolbarHeight)).clamp(0.0, 1.0); } }