Flutter:ListView:当子 ListView 到达底部时滚动父 ListView - ClampingScrollPhysics 在大小的容器中不起作用

Eng*_*Eng 8 flutter flutter-layout

我正在使用 Flutter 版本 1.12.13+hotfix。

我正在寻找一种能够在 ListView 内滚动的解决方案,当到达底部时,自动将滚动引导到父 ListView。

在此处输入图片说明

实现这一目标的第一个解决方案是使用“physics: ClampingScrollPhysics()”和“shrinkWrap: true”。所以我将此解决方案应用于除第一个(红色)之外的所有子 Listview,因为我需要将它包装在一个大小合适的 Container() 中。

问题来自第一个...... ClampingScrollPhysics() 不适用于 Container() !

所以,当我滚动红色 Listview 并到达它的底部时,滚动停止......我需要将手指放在这个 ListView 之外才能再次滚动。

@override
  Widget build(BuildContext context) {
    super.build(context);

    print("build MySongs");

    return ListView(
      children: <Widget>[
        Container(
          height: 170,
          margin: EdgeInsets.all(16),
          child: ListView(
            children: <Widget>[
              Container(color: Colors.red, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.red, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.red, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
            ],
          ),
        ),
        Container(
          margin: EdgeInsets.all(16),
          child: ListView(
            physics: ClampingScrollPhysics(),
            shrinkWrap: true,
            children: <Widget>[
              Container(color: Colors.orange, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.orange, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.orange, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
            ],
          ),
        ),
        Container(
          margin: EdgeInsets.all(16),
          child: ListView(
            shrinkWrap: true,
            physics: ClampingScrollPhysics(),
            children: <Widget>[
              Container(color: Colors.blue, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.blue, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.blue, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
            ],
          ),
        ),
        Container(
          margin: EdgeInsets.all(16),
          child: ListView(
            physics: ClampingScrollPhysics(),
            shrinkWrap: true,
            children: <Widget>[
              Container(color: Colors.green, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.green, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
              Container(color: Colors.green, width: 100, height: 100, padding: EdgeInsets.all(8), margin: EdgeInsets.all(8)),
            ],
          ),
        ),
      ],
    );
  }
Run Code Online (Sandbox Code Playgroud)

也许需要在 Flutter github 问题上发布这个问题:/

Eng*_*Eng 15

感谢 Hamed Hamedi 解决方案 :) !我认为,基于 NotificationListener ,我提出了一个更好的解决方案!(感谢他,我发现了这个功能)。

@override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(8),
      color: Colors.yellow,
      child: ListView.builder(
        controller: controller,
        itemBuilder: (c, i) =>
        i == 10
          ? Container(
          height: 150,
          color: Colors.red,
          child: NotificationListener<OverscrollNotification>(
            onNotification: (OverscrollNotification value) {
              if (value.overscroll < 0 && controller.offset + value.overscroll <= 0) {
                if (controller.offset != 0) controller.jumpTo(0);
                return true;
              }
              if (controller.offset + value.overscroll >= controller.position.maxScrollExtent) {
                if (controller.offset != controller.position.maxScrollExtent) controller.jumpTo(controller.position.maxScrollExtent);
                return true;
              }
              controller.jumpTo(controller.offset + value.overscroll);
              return true;
            },
            child: ListView.builder(
              itemBuilder: (c, ii) => Text('-->' + ii.toString()),
              itemCount: 20,
            ),
          ),
        )
          : Text(i.toString()),
        itemCount: 45,
      ),
    );
  }
Run Code Online (Sandbox Code Playgroud)

解决方案包含在 StatelessWidget 中:

import 'package:flutter/material.dart';

class ScrollParent extends StatelessWidget {
  final ScrollController controller;
  final Widget child;

  ScrollParent({this.controller, this.child});

  @override
  Widget build(BuildContext context) {
    return NotificationListener<OverscrollNotification>(
      onNotification: (OverscrollNotification value) {
        if (value.overscroll < 0 && controller.offset + value.overscroll <= 0) {
          if (controller.offset != 0) controller.jumpTo(0);
          return true;
        }
        if (controller.offset + value.overscroll >= controller.position.maxScrollExtent) {
          if (controller.offset != controller.position.maxScrollExtent) controller.jumpTo(controller.position.maxScrollExtent);
          return true;
        }
        controller.jumpTo(controller.offset + value.overscroll);
        return true;
      },
      child: child,
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

更进一步,看看 NotificationListener 的其他实现,它对分页很有用:)。你也可以试试这个:

NotificationListener<ScrollStartNotification>(
  onNotification: (ScrollStartNotification value) {
    final ScrollMetrics metrics = value.metrics;
    if (!metrics.atEdge || metrics.pixels != 0) return true;
    print("Your callback here");
    return true;
  },
  child: child,
)

Run Code Online (Sandbox Code Playgroud)

或这个 :

NotificationListener<ScrollEndNotification>(
  onNotification: (ScrollEndNotification value) {
    final ScrollMetrics metrics = value.metrics;
    if (!metrics.atEdge || metrics.pixels == 0) return true;
    print("Your callback here");
    return true;
  },
  child: child,
)

Run Code Online (Sandbox Code Playgroud)


Ham*_*edi 5

一个棘手的方法可能是使用NotificationListener. 将过度滚动通知侦听器放在您的子滚动小部件上,然后在过度滚动的情况下忽略指针。要让子部件再次向相反方向滚动,您必须在短时间内设置 ignoring false。详细的代码示例:

class _MyHomePageState extends State<MyHomePage> {

  var _scrollParent = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: Colors.yellow,
        child: ListView.builder(
          itemBuilder: (c, i) => i == 10
              ? Container(
                  height: 150,
                  color: Colors.red,
                  child: IgnorePointer(
                    ignoring: _scrollParent,
                    child: NotificationListener<OverscrollNotification>(
                      onNotification: (_) {

                        setState(() {
                          _scrollParent = true;
                        });

                        Timer(Duration(seconds: 1), () {
                          setState(() {
                            _scrollParent = false;
                          });
                        });

                        return false;
                      },
                      child: ListView.builder(
                        itemBuilder: (c, ii) => Text('-->' + ii.toString()),
                        itemCount: 100,
                      ),
                    ),
                  ),
                )
              : Text(i.toString()),
          itemCount: 100,
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

会存在一些缺陷,例如用户需要双滚动才能激活父滚动事件(第一个将忽略指针),或者使用计时器禁用忽略导致快速滚动操作中的不当行为。但是对于其他解决方案的实施简单性将是巨大的。