【问题标题】:How to Put ListView Inside PageView and Scroll Both of Them Vertically?如何将 ListView 放入 PageView 并垂直滚动它们?
【发布时间】:2021-08-03 12:12:14
【问题描述】:

正如标题所说,我们想在一个垂直的 PageView 中放置一个垂直的 ListView 并让它们滚动 顺利,

我们将实现这样的目标:

【问题讨论】:

    标签: flutter listview scroll flutter-pageview


    【解决方案1】:

    概念:

    当用户滚动列表时,如果他们到达底部并再次向同一方向滚动,我们希望页面滚动到下一个而不是列表。反之亦然。

    为了实现这一点,我们将根据用户的触摸手势手动处理两个小部件的滚动。

    守则:

    首先,在父小部件的状态下,声明这些字段。

    PageController pageController;
    ScrollController activeScrollController;
    Drag drag;
    
    //These variables To detect if we are at the
    //top or bottom of the list.
    bool atTheTop;
    bool atTheBottom;
    

    然后初始化并释放它们:

    @override
    void initState() {
      super.initState();
    
      pageController = PageController();
    
      atTheTop = true;
      atTheBottom = false;
    }
    
    @override
    void dispose() {
      pageController.dispose();
    
      super.dispose();
    }
    

    现在让我们创建五个方法来处理用户的垂直拖动。

    void handleDragStart(DragStartDetails details, ScrollController 
    scrollController) {
      if (scrollController.hasClients) {
        if (scrollController.position.context.storageContext != null) {
          if (scrollController.position.pixels == scrollController.position.minScrollExtent) {
            atTheTop = true;
          } else if (scrollController.position.pixels == scrollController.position.maxScrollExtent) {
            atTheBottom = true;
          } else {
            atTheTop = false;
            atTheBottom = false;
    
            activeScrollController = scrollController;
            drag = activeScrollController.position.drag(details, disposeDrag);
            return;
          }
        }
      }
    
      activeScrollController = pageController;
      drag = pageController.position.drag(details, disposeDrag);
    }
    
    void handleDragUpdate(DragUpdateDetails details, ScrollController 
    scrollController) {
      if (details.delta.dy > 0 && atTheTop) {
        //Arrow direction is to the bottom.
        //Swiping up.
    
        activeScrollController = pageController;
        drag?.cancel();
        drag = pageController.position.drag(
            DragStartDetails(globalPosition: details.globalPosition, localPosition: details.localPosition),
            disposeDrag);
      } else if (details.delta.dy < 0 && atTheBottom) {
        //Arrow direction is to the top.
        //Swiping down.
    
        activeScrollController = pageController;
        drag?.cancel();
        drag = pageController.position.drag(
            DragStartDetails(
              globalPosition: details.globalPosition,
              localPosition: details.localPosition,
            ),
            disposeDrag);
      } else {
        if (atTheTop || atTheBottom) {
          activeScrollController = scrollController;
          drag?.cancel();
          drag = scrollController.position.drag(
              DragStartDetails(
                globalPosition: details.globalPosition,
                localPosition: details.localPosition,
              ),
              disposeDrag);
        }
      }
      drag?.update(details);
    }
    
    void handleDragEnd(DragEndDetails details) {
      drag?.end(details);
    
      if (atTheTop) {
        atTheTop = false;
      } else if (atTheBottom) {
        atTheBottom = false;
      }
    }
    
    void handleDragCancel() {
      drag?.cancel();
    }
    
    void disposeDrag() {
      drag = null;
    }
    

    最后,让我们构建小部件:

    页面视图:

    @override
    Widget build(BuildContext context) {
      return PageView(
        controller: pageController,
        scrollDirection: Axis.vertical,
        physics: const NeverScrollableScrollPhysics(),
        children: [
          MyListView(
            handleDragStart: handleDragStart,
            handleDragUpdate: handleDragUpdate,
            handleDragEnd: handleDragEnd,
            pageStorageKeyValue: '1', //Should be unique for each widget.
          ),
          ...
        ],
      );
    }
    

    列表视图:

    class MyListView extends StatefulWidget {
      const MyListView({
        Key key,
        @required this.handleDragStart,
        @required this.handleDragUpdate,
        @required this.handleDragEnd,
        @required this.pageStorageKeyValue,
      })  : assert(handleDragStart != null),
            assert(handleDragUpdate != null),
            assert(handleDragEnd != null),
            assert(pageStorageKeyValue != null),
            super(key: key);
    
      final ValuesChanged<DragStartDetails, ScrollController> handleDragStart;
      final ValuesChanged<DragUpdateDetails, ScrollController> handleDragUpdate;
      final ValueChanged<DragEndDetails> handleDragEnd;
      
      //Notice here, the key to save the position scroll of the list.
      final String pageStorageKeyValue;
    
      @override
      _MyListViewState createState() => _MyListViewState();
    }
    
    class _MyListViewState extends State<MyListView> {
      ScrollController scrollController;
    
      @override
      void initState() {
        super.initState();
    
        scrollController = ScrollController();
      }
    
      @override
      void dispose() {
        scrollController.dispose();
    
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onVerticalDragStart: (details) {
            widget.handleDragStart(details, scrollController);
          },
          onVerticalDragUpdate: (details) {
            widget.handleDragUpdate(details, scrollController);
          },
          onVerticalDragEnd: widget.handleDragEnd,
          child: ListView.separated(
            key: PageStorageKey<String>(widget.pageStorageKeyValue),
            physics: const NeverScrollableScrollPhysics(),
            controller: scrollController,
            itemCount: 15,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text('Item $index'),
              );
            },
            separatorBuilder: (context, index) {
              return const Divider(
                thickness: 3,
              );
            },
          ),
        );
      }
    }
    

    typedef 用于注入方法:

    typedef ValuesChanged<T, E> = void Function(T value, E valueTwo);
    

    注意事项:

    • 注意ListView中PageStorageKey的使用,这样当用户回滚到上一页时,我们可以保存列表的滚动位置。

    • 如果PageView 的每个页面都包含一个ListView,则会抛出一个异常,说ScrollController attached to multiple scroll views。它不是那么致命,您可以忽略它,一切都会正常工作。要么 如果您有解决方案,我很乐意编辑答案。

      更新: 为每个 ListView 创建 ScrollController 并注入它 to to handleDragStart & handleDragUpdate 那么你就不会遇到 又是那个异常。

      我已经更新了上面的代码。

    参考资料:

    如果你有什么要说的,我在这里回复。 谢谢。

    【讨论】:

    • 我可以将此控制器与“pageSnapping: false”一起使用吗?当我尝试这样做时,它不会滚动并显示“ScrollController 附加到多个滚动视图”的错误。
    • 对不起,我不太清楚你的评论。您的意思是当您设置pageSnapping:false 时不会遇到该错误并且滚动工作正常?
    • @이상진 不管怎样,我已经更新了上面的代码来修复这个异常。
    • 有没有办法让列表视图只在末尾有 bouncingScrollPhysics() ,,我试图 setState 一个额外的 bool _canBounce 变量,,,但是它搞砸了,一旦它在 setstate 中重建,滚动位置捕捉到零,,,我迷失了如何解决这个问题,,有什么想法吗?
    猜你喜欢
    • 2020-02-14
    • 1970-01-01
    • 2020-02-04
    • 2020-10-02
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 2020-09-22
    • 1970-01-01
    相关资源
    最近更新 更多