【问题标题】:SliverAppBar with TabBar and SliverList inside CustomScrollView causes: ScrollController is currently attached to more than one ScrollPositionSliverAppBar with TabBar and SliverList inside CustomScrollView 导致: ScrollController 当前附加到多个 ScrollPosition
【发布时间】:2022-01-20 11:44:32
【问题描述】:

拥有一个SliverAppBar 和一个TabBar 和一个项目列表会导致滚动控制器抱怨。

我已经通过以下方式实现了它,有一个NestedScrollView 有一个SliverAppBar,一个SliverPersistentHeader 包含TabBar,然后NestedScrollView 的主体包含TabBarView SliverListsCustomScrollViews 内。

滚动控制器似乎会导致问题。它说它附加到多个视图。所以我尝试为每个自定义滚动视图添加一个新的滚动控制器,但这会阻止 sliver 应用栏在滚动列表本身时折叠。

除此之外,它也不记得切换标签时的滚动状态...我尝试过使用AutomaticKeepAliveClientMixin,但这似乎不起作用。关于如何解决滚动问题和滚动控制器状态的任何建议都会受到欢迎? :)

另外注意:我只在 Flutter Web 上进行测试,而不是在移动设备上...

不知道这是我代码的bug还是flutter的bug。

请看下面我的颤振医生:

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel unknown, 2.5.0, on Microsoft Windows [Version 10.0.22000.434], locale en-ZA)
[!] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
    X cmdline-tools component is missing
      Run `path/to/sdkmanager --install "cmdline-tools;latest"`
      See https://developer.android.com/studio/command-line for more details.
    X Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.
[√] Chrome - develop for the web
[√] Android Studio (version 4.0)
[√] Connected device (2 available)

! Doctor found issues in 1 category.

滚动控制器抛出的异常如下:

══╡ EXCEPTION CAUGHT BY ANIMATION LIBRARY ╞═════════════════════════════════════════════════════════
The following assertion was thrown while notifying status listeners for AnimationController:
The provided ScrollController is currently attached to more than one ScrollPosition.
The Scrollbar requires a single ScrollPosition in order to be painted.
When the scrollbar is interactive, the associated Scrollable widgets must have unique
ScrollControllers. The provided ScrollController must be unique to a Scrollable widget.

When the exception was thrown, this was the stack:
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49  throw_
packages/flutter/src/widgets/scrollbar.dart 1315:9                                                                         <fn>
packages/flutter/src/widgets/scrollbar.dart 1338:14                                                                        [_debugCheckHasValidScrollPosition]
packages/flutter/src/widgets/scrollbar.dart 1257:14                                                                        [_validateInteractions]
packages/flutter/src/animation/listener_helpers.dart 233:27                                                                notifyStatusListeners
packages/flutter/src/animation/animation_controller.dart 814:7                                                             [_checkStatusChanged]
packages/flutter/src/animation/animation_controller.dart 748:5                                                             [_startSimulation]
packages/flutter/src/animation/animation_controller.dart 611:12                                                            [_animateToInternal]
packages/flutter/src/animation/animation_controller.dart 495:12                                                            reverse
packages/flutter/src/widgets/scrollbar.dart 1412:37                                                                        <fn>
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart 48:19       internalCallback

The AnimationController notifying status listeners was:
  AnimationController#25a6e(◀ 1.000)

下面是我的代码:

class MyApp extends StatefulWidget {

  MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() {
    return _MyAppState();
  } 
}

class _MyAppState extends State<MyApp> { // with AutomaticKeepAliveClientMixin

  ScrollController _scrollController = ScrollController();
  bool _isAppBarExpanded = true;

  @override
  void initState() { 
    super.initState();

    _scrollController.addListener(() {
      _isAppBarExpanded = _scrollController.hasClients && _scrollController.offset < (200 - kToolbarHeight);
      setState(() { });
    });
  }

  @override
  Widget build(BuildContext context) {
    // super.build(context); // AutomaticKeepAlive

    const String title = "Floating App Bar";

    return MaterialApp(
      title: title,
      home: Scaffold(
        body: DefaultTabController(
          length: 2,
          child: NestedScrollView(
            controller: _scrollController,
            headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
              return <Widget> [
                SliverAppBar(
                  backgroundColor: (_isAppBarExpanded) ? Colors.white : Colors.blue,
                  expandedHeight: 200.0,
                  floating: false,
                  pinned: true,
                  flexibleSpace: FlexibleSpaceBar(
                    centerTitle: true,
                    title: Text(
                      "Collapsing Toolbar",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 16.0,
                      ),
                    ),
                    background: ClipRRect(
                      borderRadius: BorderRadius.only(
                        bottomLeft: Radius.circular(30.0), 
                        bottomRight: Radius.circular(30.0),
                      ),
                      child: Image.network(
                        "https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&h=350",
                        fit: BoxFit.cover,
                      ),
                    ),
                  ),
                ),
                SliverPersistentHeader(
                  delegate: _SliverAppBarDelegate(
                    TabBar(
                      labelColor: Colors.black87,
                      unselectedLabelColor: Colors.grey,
                      tabs: <Widget> [
                        Tab(
                          icon: Icon(Icons.info), 
                          text: "Tab 1",
                        ),

                        Tab(
                          icon: Icon(Icons.lightbulb_outline), 
                          text: "Tab 2",
                        ),
                      ],
                    ),
                  ),
                  pinned: true,
                ),
              ];
            },
            body: TabBarView(
              children: <Widget> [
                CustomScrollView(
                  slivers: <Widget> [
                    SliverList(
                      delegate: SliverChildBuilderDelegate(
                        (BuildContext context, int index) {
                          return Text("Item " + index.toString());
                        },
                        childCount: 200,
                        // addAutomaticKeepAlives: true,
                      ),
                    ),
                  ],
                ),
                CustomScrollView(
                  slivers: <Widget> [
                    SliverList(
                      delegate: SliverChildBuilderDelegate(
                        (BuildContext context, int index) {
                          return Text("Test " + index.toString());
                        },
                        childCount: 100,
                        // addAutomaticKeepAlives: true,
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  // for AutomaticKeepAlive
  // @override
  // bool get wantKeepAlive {
  //   return true;
  // }
}

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate(this._tabBar);

  final TabBar _tabBar;

  @override
  double get minExtent {
    return _tabBar.preferredSize.height + 30;
  }

  @override
  double get maxExtent {
    return _tabBar.preferredSize.height + 30;
  }

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      alignment: Alignment.center,
      color: Theme.of(context).scaffoldBackgroundColor,
      child: Padding(
        padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
        child: _tabBar,
      ),
    );
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return false;
  }
}

【问题讨论】:

    标签: flutter flutter-web nestedscrollview sliverappbar customscrollview


    【解决方案1】:

    这个问题可以通过在CustomScrollView上添加另一个ScrollController来解决。

    final ScrollController _scrollController2 = ScrollController();
    ....
     TabBarView(
      children: <Widget>[
        CustomScrollView(
          controller: _scrollController2,
    

    您还可以将另一个添加到下一个CustomScrollView

    【讨论】:

    • 您好 Yeasin,感谢您的帮助。然而,我看到这改变了 SliverAppBar 的滚动行为。因此,如果没有您的建议,您将看到 sliver 应用栏随着选项卡中的项目列表滚动。根据您的建议,列表首先滚动,然后一旦到达列表中的最后一项,SliverAppBar 就会向上滚动。阅读本文时:pub.dev/documentation/flutter_for_web/latest/widgets/… 我推断您不应该为自定义滚动视图提供显式控制器,因为它们与 NestedScrollView 有着内在的联系。
    • 我还可以补充一点,将这个小部件从有状态更改为无状态可以解决我注意到的问题。然而,这确实归因于 Widget 在切换选项卡时不记得滚动位置。
    猜你喜欢
    • 2021-12-21
    • 2020-04-29
    • 2021-12-19
    • 2021-02-14
    • 2019-09-23
    • 2020-04-17
    • 1970-01-01
    • 2019-02-09
    • 2021-08-12
    相关资源
    最近更新 更多