如何将ListView放入PageView并垂直滚动它们? [英] How to Put ListView Inside PageView and Scroll Both of Them Vertically?

查看:85
本文介绍了如何将ListView放入PageView并垂直滚动它们?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

As the title says, we wanna put a vertical ListView inside a vertical PageView and make them scroll smoothly,

我们将实现这样的目标:

We will achieve something like that:

推荐答案

概念:

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

The Concept:

When the user scrolls the list, if they reach its bottom and scroll in the same direction again, we want the page to scroll to the next one not the list. And vice versa.

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

To achieve that we are gonna handle the scrolling of both widgets manually, depending on the touch gestures of the user.

首先,在父widget的状态下,声明这些字段.

Firstly, in the state of the parent widget, declare these fields.

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;

然后初始化并处理它们:

Then initialize and dispose them:

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

  pageController = PageController();

  atTheTop = true;
  atTheBottom = false;
}

@override
void dispose() {
  pageController.dispose();

  super.dispose();
}

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

now let's create five methods for handling the vertical dragging of the user.

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;
}

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

And Finally, let's build the widgets:

页面浏览:

@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的使用,这样我们可以在用户回滚到上一页时保存列表的滚动位置.

    Notes:

    • Notice the using of PageStorageKey in the ListView, so that we can save the scroll position of the list if the user scrolls back to the previous page.

      如果PageView的每个页面都包含一个ListView,则会抛出一个异常,说明ScrollController附加到多个滚动视图.这不是致命的,您可以忽略它,一切都会正常进行.或者如果您有解决方案,我很乐意编辑答案.

      If each page of the PageView will contain a ListView, an exception will be thrown saying that ScrollController attached to multiple scroll views. It's not that fatal, you can ignore it and everything will work fine. Or if you have a solution, I'll gladly edit the answer.

      更新:为每个ListView创建ScrollController并注入它到 handleDragStart &handleDragUpdate 那么你就不会遇到又是那个例外.

      Update: create ScrollController for each ListView and inject it to to handleDragStart & handleDragUpdate then you will not encounter that exception again.

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

      I've updated the code above.

      如何检查滚动位置是在 ListView 的顶部还是底部?

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

      If you have anything to say, I'm here to reply. Thanks.

      这篇关于如何将ListView放入PageView并垂直滚动它们?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆