簡體   English   中英

如何將ListView放在PageView中並垂直滾動?

[英]How to Put ListView Inside PageView and Scroll Both of Them Vertically?

正如標題所說,我們想將垂直的 ListView 放在垂直的 PageView 中並使它們平滑滾動,

我們將實現這樣的目標:

在此處輸入圖像描述

這個概念:

當用戶滾動列表時,如果他們到達底部並再次向同一方向滾動,我們希望頁面滾動到下一個而不是列表。 反之亦然。

為了實現這一點,我們將根據用戶的觸摸手勢手動處理兩個小部件的滾動。

編碼:

首先,在父widget的state中,聲明這些字段。

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的使用,這樣如果用戶回滾到上一頁,我們就可以保存列表的滾動position。

  • 如果 PageView 的每個頁面都包含一個 ListView,則會拋出一個異常,說ScrollController attached to multiple scroll views 它不是那么致命,您可以忽略它,一切都會正常工作。 或者,如果您有解決方案,我很樂意編輯答案。

    更新:為每個ListView創建ScrollController並將其注入到handleDragStarthandleDragUpdate然后您將不會再次遇到該異常。

    我已經更新了上面的代碼。

參考:

如果你有什么要說的,我在這里回復。 謝謝。

當滾動視圖太小而無法容納整個屏幕時,我修改了 Tayan 對訴訟案件的回應。 我還添加了水平方向支持,並將拖動處理邏輯移至小部件本身。 還支持任何類型的滾動視圖(不僅是 ListView)這是代碼:

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

///This widget provides correct scrolling and swiping behavior when scrolling view are placed inside pageview with same direction
///The widget works both for vertical and horizontal scrolling direction
///To use this widget you have to do following:
///* set physics: NeverScrollableScrollPhysics(parent: ClampingScrollPhysics()) argument for both PageView and ScrollView
///* create scrollController for ScrollView and pageController for PageView. Do not forget to dispose then at dispose() State callback
///* make sure that scrolling direction on both views are the same and equals to scrollDirection argument here

class PageViewScrollableChild extends StatefulWidget {
  final Widget child;
  final ScrollController scrollController;
  final PageController pageController;
  final Axis scrollDirection;

  const PageViewScrollableChild(
      {Key? key,
      required this.scrollController,
      required this.pageController,
      required this.child,
      required this.scrollDirection})
      : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _PageViewScrollableChildState();
  }
}

class _PageViewScrollableChildState extends State<PageViewScrollableChild> {
  late bool atTheStart;
  late bool atTheEnd;
  ///true if scroll view content does not overscroll screen size
  late bool bothSides;

  ScrollController? activeScrollController;
  Drag? drag;

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

    atTheStart = true;
    atTheEnd = false;
    bothSides = false;
  }

  void handleDragStart(DragStartDetails details, ScrollController scrollController) {
    if (scrollController.hasClients) {
      if (scrollController.position.minScrollExtent == 0 && scrollController.position.maxScrollExtent == 0) {
        bothSides = true;
      } else if (scrollController.position.pixels <= scrollController.position.minScrollExtent) {
        atTheStart = true;
      } else if (scrollController.position.pixels >= scrollController.position.maxScrollExtent) {
        atTheEnd = true;
      } else {
        atTheStart = false;
        atTheEnd = false;

        activeScrollController = scrollController;
        drag = activeScrollController?.position.drag(details, disposeDrag);
        return;
      }
    }

    activeScrollController = widget.pageController;
    drag = widget.pageController.position.drag(details, disposeDrag);
  }

  void handleDragUpdate(DragUpdateDetails details, ScrollController scrollController) {
    final offset = widget.scrollDirection == Axis.vertical ? details.delta.dy : details.delta.dx;
    if (offset > 0 && (atTheStart || bothSides)) {
      //Arrow direction is to the bottom.
      //Swiping up.

      activeScrollController = widget.pageController;
      drag?.cancel();
      drag = widget.pageController.position.drag(
          DragStartDetails(globalPosition: details.globalPosition, localPosition: details.localPosition), disposeDrag);
    } else if (offset < 0 && (atTheEnd || bothSides)) {
      //Arrow direction is to the top.
      //Swiping down.

      activeScrollController = widget.pageController;
      drag?.cancel();
      drag = widget.pageController.position.drag(
          DragStartDetails(
            globalPosition: details.globalPosition,
            localPosition: details.localPosition,
          ),
          disposeDrag);
    } else if (atTheStart || atTheEnd) {
      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 (atTheStart) {
      atTheStart = false;
    } else if (atTheEnd) {
      atTheEnd = false;
    }
  }

  void handleDragCancel() {
    drag?.cancel();
  }

  void disposeDrag() {
    drag = null;
  }

  @override
  Widget build(BuildContext context) {
    final scrollDirection = widget.scrollDirection;
    return GestureDetector(
      onVerticalDragStart:
          scrollDirection == Axis.vertical ? (details) => handleDragStart(details, widget.scrollController) : null,
      onVerticalDragUpdate:
          scrollDirection == Axis.vertical ? (details) => handleDragUpdate(details, widget.scrollController) : null,
      onVerticalDragEnd: scrollDirection == Axis.vertical ? (details) => handleDragEnd(details) : null,
      onHorizontalDragStart:
          scrollDirection == Axis.horizontal ? (details) => handleDragStart(details, widget.scrollController) : null,
      onHorizontalDragUpdate:
          scrollDirection == Axis.horizontal ? (details) => handleDragUpdate(details, widget.scrollController) : null,
      onHorizontalDragEnd: scrollDirection == Axis.horizontal ? (details) => handleDragEnd(details) : null,
      child: widget.child,
    );
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM