【问题标题】:How to "merge" scrolls on a TabBarView inside a PageView?如何在 PageView 内的 TabBarView 上“合并”滚动?
【发布时间】:2020-03-11 18:44:40
【问题描述】:

我有一个在其主页上使用 PageView 的应用程序。今天,我被分配在其中一个页面中插入 TabBarView。问题是当我在最后一个选项卡中滚动选项卡之间时,向左滚动不会滚动 PageView。

我需要一种方法来使页面视图的滚动在 tabbarview 的开始或结束时滚动。

我发现了一个关于倒置问题的问题:flutter PageView inside TabBarView: scrolling to next tab at the end of page

但是,那里所说的方法不适合我的问题。

我做了一个最小的例子:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'TabBarView inside PageView',
        home: MyHomePage(),
      );
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final PageController _pageController = PageController();

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text('TabBarView inside PageView'),
        ),
        body: PageView(
          controller: _pageController,
          children: <Widget>[
            Container(color: Colors.red),
            GreenShades(),
            Container(color: Colors.yellow),
          ],
        ),
      );
}

class GreenShades extends StatefulWidget {
  @override
  _GreenShadesState createState() => _GreenShadesState();
}

class _GreenShadesState extends State<GreenShades>
    with SingleTickerProviderStateMixin {
  TabController _tabController;

  @override
  void initState() {
    this._tabController = TabController(length: 3, vsync: this);
    super.initState();
  }

  @override
  Widget build(BuildContext context) => Column(
        children: <Widget>[
          TabBar(
            labelColor: Colors.green,
            indicatorColor: Colors.green,
            controller: _tabController,
            tabs: <Tab>[
              const Tab(text: "Dark"),
              const Tab(text: "Normal"),
              const Tab(text: "Light"),
            ],
          ),
          Expanded(
            child: TabBarView(
              controller: _tabController,
              children: <Widget>[
                Container(color: Colors.green[800]),
                Container(color: Colors.green),
                Container(color: Colors.green[200]),
              ],
            ),
          )
        ],
      );

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }
}

请注意,在此 MRE 中,如果拖动 TabBar 可能会到达第 3 页,但如果拖动 TabBarView 则不会。

我怎样才能实现这种行为?


编辑:

正如@Fethi 所说,有一个类似的问题: Is it possible to swipe from an TabBarView content area to an adjacent PageView page?

但是,这个问题没有得到满意的回答,因为给出的解决方案并没有真正“混合”卷轴,尽管行为与描述的相似。它不会自然滚动。

【问题讨论】:

    标签: flutter dart


    【解决方案1】:

    这可以通过使用PageController.postion 属性的drag 方法实现,该方法在内部拖动屏幕的ScrollPosition。这样,用户可以直观地拖动页面,就像拖到一半然后离开或完全继续。

    这个想法的灵感来自另一篇文章,使用 OverScrollNotification 但添加了更多步骤以继续直观的拖动。

    1. 在用户开始滚动时收集 DragstartDetail。
    2. 监听 OverScrollNotification 并开始拖动,同时使用 drag.update 和 OverscrollNotification 方法中的 DragUpdateDetails 更新拖动。
    3. 在 ScrollEndNotification 上取消拖动。

    为了简单起见,我只粘贴 Tabs 页面的构建方法。

    dart pad 中提供了一个完整的工作示例。

      @override
      Widget build(BuildContext context) {
        // Local dragStartDetail.
        DragStartDetails dragStartDetails;
        // Current drag instance - should be instantiated on overscroll and updated alongside.
        Drag drag;
        return Column(
          children: <Widget>[
            TabBar(
              labelColor: Colors.green,
              indicatorColor: Colors.green,
              controller: _tabController,
              tabs: <Tab>[
                const Tab(text: "Dark"),
                const Tab(text: "Normal"),
                const Tab(text: "Light"),
              ],
            ),
            Expanded(
              child: NotificationListener(
                onNotification: (notification) {
                  if (notification is ScrollStartNotification) {
                    dragStartDetails = notification.dragDetails;
                  }
                  if (notification is OverscrollNotification) {
                    drag = _pageController.position.drag(dragStartDetails, () {});
                    drag.update(notification.dragDetails);
                  }
                  if (notification is ScrollEndNotification) {
                    drag?.cancel();
                  }
                  return true;
                },
                child: TabBarView(
                  controller: _tabController,
                  children: <Widget>[
                    Container(color: Colors.green[800]),
                    Container(color: Colors.green),
                    Container(color: Colors.green[200]),
                  ],
                ),
              ),
            ),
          ],
        );
      }
    

    旧答案

    以上可能无法处理某些极端情况。如果您需要更多控制下面的代码提供相同的结果,但您可以处理UserScrollNotification。我之所以粘贴这个,是因为它可能对其他想知道使用哪个方向滚动 w.r.t ScrollView 的轴的人有用。

                  if (notification is ScrollStartNotification) {
                    dragStartDetails = notification.dragDetails;
                  }
    
                  if (notification is UserScrollNotification &&
                      notification.direction == ScrollDirection.forward &&
                      !_tabController.indexIsChanging &&
                      dragStartDetails != null &&
                      _tabController.index == 0) {
                    _pageController.position.drag(dragStartDetails, () {});
                  }
    
                  // Simialrly Handle the last tab.
                  if (notification is UserScrollNotification &&
                      notification.direction == ScrollDirection.reverse &&
                      !_tabController.indexIsChanging &&
                      dragStartDetails != null &&
                      _tabController.index == _tabController.length - 1) {
                    _pageController.position.drag(dragStartDetails, () {});
                  }
    

    【讨论】:

    • 因为它是作为 OverScrolling 的一部分处理的,必须发生过度滚动。我觉得这很好,因为用户应该知道标签的结尾,否则可能不那么直观。但核心思想是使用drag 方法来拖动。可选地,您可以尝试使用手势检测器包装最后一个选项卡元素,也许可以启动拖动,例如onTapDown
    • 我想我找到了办法。给我一些时间..我会完善它并分享.. :)
    • @MateusFelipe 我已经更新了答案。这是我能做的最好的。希望它对你有帮助。您可能仍然需要处理更多案件。也许您可以向您的用户询问反馈,并决定这种平稳过渡是否对他们有意义。 :D
    • 谢谢,伙计。我以前也有类似的行为,但你的更好。我的和你的解决方案唯一的缺陷是它不能很好地用于快速滑动。也许你想更新你的 dartpad?
    • 完成.. 我想说它必须玩很多才能很好地处理拖动取消.. 快速拖动不是我们的解决方案的问题,而是页面视图和标签视图本身之间的手势斗争.. 如果他们公开一个 api 以真正明确地阻止页面视图监听,那么它可以完成。
    【解决方案2】:

    因此,当您到达选项卡的末尾时,您希望将页面视图向左滚动,并且在第一个选项卡上向右滚动时也是如此,我一直在考虑的是在进入时手动滑动页面视图这些情况如下:

    index 值应该是标签栏页面之前和之后的页面索引。

    pageController.animateToPage(index,
              duration: Duration(milliseconds: 500), curve: Curves.ease); 
    

    here 是您正在寻找的完整代码,希望对您有所帮助!

    【讨论】:

    • 是的,行为是这样的。我很欣赏你提到的链接。尽管第一个答案的行为与我所描述的相似,但我与 jibbers42 对“自然性”有同样的担忧。 Miguel Ruivo 在回答的 cmets 中表示这是一个更复杂的行为,并建议使用嵌套的 TabBarView,但他没有提供任何具体示例,jibbers42 表示他无法自己实现该行为
    • 我昨天没看 cmets,好吧,你在看什么 Miguel 说的很复杂,我尝试了嵌套标签栏,但它似乎不起作用,我建议查看这篇文章了解更多信息帮助medium.com/flutterarsenal/…
    • 我已经看过这篇文章了。我克隆了 repo (github.com/IAmSarthakVerma/NestedTabBar-Flutter),但该应用程序实际上与我提供的 MRE 具有相同的行为......
    • 很抱歉,我只能提供帮助,祝你好运!
    • 太棒了! Abhilash Chandran 先生有一个解决方案
    猜你喜欢
    • 2021-01-14
    • 2019-02-20
    • 1970-01-01
    • 2019-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-07
    • 2021-07-24
    相关资源
    最近更新 更多