【问题标题】:Navigator blocks communication between CustomScrollView and NestedScrollViewNavigator 阻止 CustomScrollView 和 NestedScrollView 之间的通信
【发布时间】:2021-04-12 11:02:04
【问题描述】:

预期行为: 如果自定义滚动视图被滚动并因此滚动应用栏,嵌套滚动视图会正常滚动。

实际情况: 只有自定义滚动视图滚动,而不是应用栏(=嵌套滚动视图)。

想要的行为可以通过不使用 customscrollview 或不使用导航器来实现(在我的情况下两者都没有选项)。如果在任何情况下都有帮助,此实现在 null 安全性之前有效。

代码示例:

import 'dart:math';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: TestScreen(),
    );
  }
}

class TestScreen extends StatefulWidget {
  @override
  _TestScreenState createState() => _TestScreenState();
}

class _TestScreenState extends State<TestScreen> {
  GlobalKey<NavigatorState> get navigatorKey =>
      GlobalKey<NavigatorState>();


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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: NestedScrollView(
          physics: BouncingScrollPhysics(),
          headerSliverBuilder: (context, innerBoxIsScrolled) => [
            SliverOverlapAbsorber(
                handle:
                    NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                sliver: SliverAppBar(
                  title: Text("title"),
                  pinned: true,
                  floating: false,
                  snap: false,
                  expandedHeight: 200,
                ))
          ],
          // body: DemoPage(),
          body: Navigator(
              key: navigatorKey,
              onGenerateRoute: (settings) =>
                  MaterialPageRoute(builder: (context) => DemoPage())),
        ));
  }
}

class DemoPage extends StatelessWidget {

  Color generateRandomColor1() {
    // Define all colors you want here
    const predefinedColors = [
      Colors.red,
      Colors.green,
      Colors.blue,
      Colors.black,
      Colors.white
    ];
    Random random = Random();
    return predefinedColors[random.nextInt(predefinedColors.length)];
  }

  @override
  Widget build(BuildContext context) {
    // return Container(
    //   color: Colors.red,
    //   height: 1000,
    //   width: 500,
    //   child: Center(
    //     child: RaisedButton(
    //       child: Text("Press"),
    //       onPressed: () {
    //         Navigator.of(context)
    //             .push(MaterialPageRoute(builder: (context) => DemoPage()));
    //       },
    //     ),
    //   ),
    // );
    return CustomScrollView(slivers: [
      SliverToBoxAdapter(
        child: Container(
          color: generateRandomColor1(),
          height: 1000,
          width: 500,
          child: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                RaisedButton(
                  child: Text("Press"),
                  onPressed: () {
                    Navigator.of(context)
                        .push(MaterialPageRoute(builder: (context) => DemoPage()));
                  },
                ),
                RaisedButton(
                  child: Text("pop"),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
              ],
            ),
          ),
        ),
      )
    ]);
  }
}

【问题讨论】:

标签: flutter flutter-layout dart-null-safety


【解决方案1】:

当 DemoPage 在 Navigator 小部件下方构建 CustomScrollView 时,您的 NestedScrollView 可能就像有一个单独的滚动视图一样。

您可以将CustomScrollView的物理设置为NeverScrollableScrollPhysics,以便NestedScrollView可以控制它。

...

return CustomScrollView(
  // Add the following line:
  physics: NeverScrollableScrollPhysics(),
  slivers: [
    SliverToBoxAdapter(
      child: Container(
        color: generateRandomColor1(),
        height: 1000,
        width: 500,
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              RaisedButton(
                child: Text("Press"),
                onPressed: () {
                  Navigator.of(context).push(
                    MaterialPageRoute(
                      builder: (context) => DemoPage(),
                    ),
                  );
                },
              ),
              RaisedButton(
                child: Text("pop"),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          ),
        ),
      ),
    ),
  ],
);

...

页面浏览

如果让 CustomScrollView 永远无法滚动并不能达到预期的效果,您可以尝试使用 PageView 而不是 Navigator。由于您希望 SliverAppBar 在所有页面上保持静止,这将仅在 PageView 内提供导航。

您需要做的就是将您的页面列表放入 PageView 并将其控制器传递给您的页面。然后,您可以通过按钮或滑动手势导航到其他页面。

import 'dart:math';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: TestScreen(),
    );
  }
}

class TestScreen extends StatefulWidget {
  @override
  _TestScreenState createState() => _TestScreenState();
}

class _TestScreenState extends State<TestScreen> {
  GlobalKey<NavigatorState> get navigatorKey => GlobalKey<NavigatorState>();

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

  @override
  Widget build(BuildContext context) {
    // Page controller
    final PageController controller = PageController(initialPage: 0);

    return Scaffold(
      body: NestedScrollView(
        physics: BouncingScrollPhysics(),
        headerSliverBuilder: (context, innerBoxIsScrolled) => [
          SliverOverlapAbsorber(
            handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
            sliver: SliverAppBar(
              title: Text("title"),
              pinned: true,
              floating: false,
              snap: false,
              expandedHeight: 200,
            ),
          )
        ],
        // Page view here
        body: PageView(
          // Scroll horizontally
          scrollDirection: Axis.horizontal,
          controller: controller,
          // Your pages
          children: <Widget>[
            // Pass the controller to your page
            DemoPage(pageController: controller),
            DemoPage(pageController: controller),
            DemoPage(pageController: controller),
          ],
        ),
      ),
    );
  }
}

class DemoPage extends StatelessWidget {
  // Page Controller
  final PageController pageController;

  DemoPage({required this.pageController});

  Color generateRandomColor1() {
    // Define all colors you want here
    const predefinedColors = [
      Colors.red,
      Colors.green,
      Colors.blue,
      Colors.black,
      Colors.white
    ];

    Random random = Random();

    return predefinedColors[random.nextInt(predefinedColors.length)];
  }

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverToBoxAdapter(
          child: Container(
            color: generateRandomColor1(),
            height: 1000,
            width: 500,
            child: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  RaisedButton(
                    child: Text("Press"),
                    onPressed: () {
                      // Go to next page
                      pageController.nextPage(
                        duration: Duration(milliseconds: 300),
                        curve: Curves.linear,
                      );
                    },
                  ),
                  RaisedButton(
                    child: Text("pop"),
                    onPressed: () {
                      // Go back to previous page
                      pageController.previousPage(
                        duration: Duration(milliseconds: 300),
                        curve: Curves.linear,
                      );
                    },
                  ),
                ],
              ),
            ),
          ),
        )
      ],
    );
  }
}

【讨论】:

  • 这可能是可能的,但如果我想这样做,我可以使用更简单的解决方案来做到这一点,例如 SingleChildScrollView 中的 Column 。但我想利用诸如StickyHeaders 之类的条子或其他一些东西。
  • 您尝试过 PageView 吗?查看编辑后的答案。
  • PageView 可能有效,不用担心。但我想充分利用应用程序中的路由,例如网络中的深度链接等。
【解决方案2】:

我猜应该可以。 也看看这篇文章参考:nested_reference

headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
      return <Widget>[
        new SliverAppBar(
          pinned: true,
          floating: true,
          forceElevated: innerBoxIsScrolled,
.....

【讨论】:

  • 如果您在正文中添加CustomScrollView,则此解决方案不起作用。
【解决方案3】:

您可以在这里找到答案: Answer from github

有两种解决方案: 首先,获取 NestedScrollView 的 innerController 并通过 Navigator 下面的 PrimaryScrollController 小部件传递 其次,从您需要 NestedScrollView 控制器的小部件访问 NestedScrollView 的状态,获取 NestedScrollViewState 的 innerController 并将该 innerController 分配给您的可滚动小部件 附: 您可以通过 InheritedWidget 传递控制器 第一种方式:

...

  GlobalKey<NavigatorState> get navigatorKey =>
      GlobalKey<NavigatorState>();


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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: NestedScrollView(
          physics: BouncingScrollPhysics(),
          headerSliverBuilder: (context, innerBoxIsScrolled) => [
            SliverOverlapAbsorber(
                handle:
                    NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                sliver: SliverAppBar(
                  title: Text("title"),
                  pinned: true,
                  floating: false,
                  snap: false,
                  expandedHeight: 200,
                ))
          ],
          // body: DemoPage(),
          body: Builder(builder: (context) {
                var scrollController = PrimaryScrollController.of(context);
return Navigator(
              key: navigatorKey,
              onGenerateRoute: (settings) =>
                  MaterialPageRoute(builder: (context) => PrimaryScrollController(controller: scrollController!,
                     child: Scaffold(body:ListView(
                            children:[
                            /// children
                            ],
                             ),
                               ),
                  ))),
        );
}
        );
  }
}

第二种方式: 你可以使用 NestedScrollView 的 innerController

context.findAncestorStateOfType&lt;NestedScrollViewState&gt;()?.innerController 然后将其传递给您的可滚动小部件

CustomScrollView(
      controller: context.findAncestorStateOfType<NestedScrollViewState>()?.innerController,
      ...
      )

我认为Navigator没有通过NestedView的innerController

【讨论】:

    【解决方案4】:

    尝试删除 SliverAppBar 的 pinned 属性。

    Flutter 文档有其功能的交互式示例。

    https://api.flutter.dev/flutter/material/SliverAppBar-class.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-21
      • 2017-03-31
      • 2012-07-03
      • 1970-01-01
      • 2023-02-04
      • 2011-08-23
      • 2017-08-13
      • 2013-10-18
      相关资源
      最近更新 更多