扑。 PageView 内的嵌套滚动

Posted

技术标签:

【中文标题】扑。 PageView 内的嵌套滚动【英文标题】:Flutter. Nested scroll inside PageView 【发布时间】:2019-12-03 20:48:53 【问题描述】:

我有带有PageView 小部件的简单页面,里面有ListView。滚动PageView 不起作用。原因很简单。因为嵌套子级消耗的指针事件。

  @override
  Widget build(BuildContext context) 
    setupController();

    return PageView(
      controller: controllerPage,
      scrollDirection: Axis.vertical,
      children: <Widget>[
          ListView.builder(
            controller: controller,
            padding: EdgeInsets.all(AppDimens.bounds),
            itemCount: 15,
            itemBuilder: (context, index)
              return Container(
                height: 100,
                color: index %2 == 0 
                    ? Colors.amber : Colors.blueAccent,
              );
            ,
          ),
        Container(color: Colors.green),
        Container(color: Colors.blue),
      ],
    );
  

我的问题是有什么理智的方法可以让它一起工作吗?您可能会看到PageView 的垂直轴,但使用PageView 的水平轴会出现完全相同的问题和水平的ListView

到目前为止我尝试了什么?我有一些解决方法。即使它并不复杂,只是感觉不太好和笨重。通过使用AbsorbPointer 和自定义控制器进行滚动。

  final controller = ScrollController();
  final controllerPage = PageController(keepPage: true);
  bool hasNestedScroll = true;

  void setupController() 
    controller.addListener(() 

      if (controller.offset + 5 > controller.position.maxScrollExtent &&
          !controller.position.outOfRange) 
        /// Swap to Inactive, if it was not
        if (hasNestedScroll) 
          setState(() 
            hasNestedScroll = false;
          );
        
       else 
        /// Swap to Active, if it was not
        if (!hasNestedScroll) 
          setState(() 
            hasNestedScroll = true;
          );
        
      
    );

    controllerPage.addListener(() 
      if (controllerPage.page == 0) 
        setState(() 
          hasNestedScroll = true;
        );
      
    );
  

  @override
  Widget build(BuildContext context) 
    setupController();

    return PageView(
      controller: controllerPage,
      scrollDirection: Axis.vertical,
      children: <Widget>[
        AbsorbPointer(
          absorbing: !hasNestedScroll,
          child: ListView.builder(
            controller: controller,
            padding: EdgeInsets.all(AppDimens.bounds),
            itemCount: 15,
            itemBuilder: (context, index)
              return Container(
                height: 100,
                color: index %2 == 0
                    ? Colors.amber : Colors.blueAccent,
              );
            ,
          ),
        ),
        Container(color: Colors.green),
        Container(color: Colors.blue),
      ],
    );
  

【问题讨论】:

那么您是否要同时在 PageView 和 ListView 上滚动? @AlaricJamesHartsock 不,我正在尝试滚动ListView,只要它有空间。一旦 ListView 的滚动到达终点,我需要滚动 PageView @AlaricJamesHartsock 你有什么建议吗? 对不起,我被打败了。也许尝试查看 API,看看您是否可以手动放弃从列表视图到页面视图的滚动控制,一旦它一直向下滚动。我希望你能找到你要找的东西。 【参考方案1】:

我最近陷入了类似的情况,经过一些研究和阅读有关手势如何在 Flutter 中工作的信息后,我找到了一个足够好的工作解决方案。 所以我使用了 RawGestureDetector,它为您提供了一个低级别的手势小部件处理,并通过将 PageView 和 ListView 的 physics 设置为 NeverScrollableScrollPhysics 来禁用滚动。这意味着使用它们各自的控制器滚动这些小部件 - PageViewControllerScrollController。现在根据 ListView 的位置和状态,使用控制器选择并拖动两者中的一个活动控制器。主类看起来像这样。

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

class App extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(home: Scaffold(appBar: AppBar(), body: NestedContainerWidget()));
  


class NestedContainerWidget extends StatefulWidget 
  @override
  _NestedContainerWidgetState createState() => _NestedContainerWidgetState();


class _NestedContainerWidgetState extends State<NestedContainerWidget> 
  PageController _pageController;
  ScrollController _listScrollController;
  ScrollController _activeScrollController;
  Drag _drag;

  @override
  void initState() 
    super.initState();
    _pageController = PageController();
    _listScrollController = ScrollController();
  

  @override
  void dispose() 
    _pageController.dispose();
    _listScrollController.dispose();
    super.dispose();
  

  void _handleDragStart(DragStartDetails details) 
    if (_listScrollController.hasClients &&
        _listScrollController.position.context.storageContext != null) 
      final RenderBox renderBox =
          _listScrollController.position.context.storageContext.findRenderObject();
      if (renderBox.paintBounds
          .shift(renderBox.localToGlobal(Offset.zero))
          .contains(details.globalPosition)) 
        _activeScrollController = _listScrollController;
        _drag = _activeScrollController.position.drag(details, _disposeDrag);
        return;
      
    
    _activeScrollController = _pageController;
    _drag = _pageController.position.drag(details, _disposeDrag);
  

  void _handleDragUpdate(DragUpdateDetails details) 
    if (_activeScrollController == _listScrollController &&
        details.primaryDelta < 0 &&
        _activeScrollController.position.pixels ==
            _activeScrollController.position.maxScrollExtent) 
      _activeScrollController = _pageController;
      _drag?.cancel();
      _drag = _pageController.position.drag(
          DragStartDetails(
              globalPosition: details.globalPosition, localPosition: details.localPosition),
          _disposeDrag);
    
    _drag?.update(details);
  

  void _handleDragEnd(DragEndDetails details) 
    _drag?.end(details);
  

  void _handleDragCancel() 
    _drag?.cancel();
  

  void _disposeDrag() 
    _drag = null;
  

  @override
  Widget build(BuildContext context) 
    return RawGestureDetector(
      gestures: <Type, GestureRecognizerFactory>
        VerticalDragGestureRecognizer:
            GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
                () => VerticalDragGestureRecognizer(), (VerticalDragGestureRecognizer instance) 
          instance
            ..onStart = _handleDragStart
            ..onUpdate = _handleDragUpdate
            ..onEnd = _handleDragEnd
            ..onCancel = _handleDragCancel;
        )
      ,
      behavior: HitTestBehavior.opaque,
      child: PageView(
        controller: _pageController,
        scrollDirection: Axis.vertical,
        physics: const NeverScrollableScrollPhysics(),
        children: [
          Center(child: Text('Page 1')),
          ListView(
            controller: _listScrollController,
            physics: const NeverScrollableScrollPhysics(),
            children: List.generate(
              20,
              (int index) 
                return ListTile(title: Text('Item $index'));
              ,
            ),
          ),
        ],
      ),
    );
  

查看_handleDragStart_handleDragUpdate 中的逻辑,它根据ListView 小部件的状态及其滚动状态确定应该滚动的控制器。

我写了一篇关于这个问题的详细文章,你可以在这里查看-https://medium.com/@Mak95/nested-scrolling-listview-inside-pageview-in-flutter-a57b7a6241b1

解决方案可以改进,因此非常欢迎输入。

【讨论】:

始终为您的问题添加标签,以便正确的人群可以查看。【参考方案2】:

我使用this answer 帮助我解决了同样的问题。

基本上,您使用NotificationListener 监听OverscrollNotification 的可滚动子级,并使用其控制器滚动父级。

代码:

final pageCntrler = PageController();

_scrollDown() async 
  await pageCntrler.nextPage(
    duration: scrollDuration,
    curve: scrollCurve,
  );


_scrollUp() async 
  await pageCntrler.previousPage(
    duration: scrollDuration,
    curve: scrollCurve,
  );


@override
Widget build(BuildContext context) 
  return PageView(
    controller: pageCntrler,
    scrollDirection: Axis.vertical,
    physics: NeverScrollableScrollPhysics(),
    children: [
      NotificationListener(
        onNotification: (notification) 
          if (notification is OverscrollNotification) 
            if (notification.overscroll > 0) 
              _scrollDown();
             else 
              _scrollUp();
            
          
        ,
        child: SingleChildScrollView(),
      )
    ],
  );

【讨论】:

以上是关于扑。 PageView 内的嵌套滚动的主要内容,如果未能解决你的问题,请参考以下文章

cocos studio pageview看不到indicator指示点

嵌套滚动视图内的 Recyclerview 滚动,但不像普通 Recyclerview 或嵌套滚动视图那样快速滚动

Flutter 小技巧之 ListView 和 PageView 的各种花式嵌套

Flutter 小技巧之 ListView 和 PageView 的各种花式嵌套

PageView:禁用默认滚动并将其替换为 Tap 事件

如何在 Flutter 中创建垂直滚动的 PageView?