ScrollView 与 SliverPadding 的关系

基本页面布局

Scaffold 中有个 ListView,ListView 中有 100 个高 50 的 Container 用作辅助观看,ListView 中第三个元素是一个 GridView,GridView 的滑动效果被禁止。

dart 复制代码
class GiveView extends GetView<GiveController> {
  const GiveView({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GiveView'),
        centerTitle: true,
      ),
      body: ListView.builder(
        itemCount: 100,
        itemBuilder: (context, index) {
          if (index == 3) {
            return SizedBox(
              height: 220,
              child: GridView.builder(
                itemCount: 10,
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 5,
                  mainAxisExtent: 100,
                  crossAxisSpacing: 20,
                  mainAxisSpacing: 20,
                ),
                itemBuilder: (BuildContext context, int index) {
                  return Image.network(
                      'https://img.xjh.me/random_img.php?return=302&time=${DateTime.now().microsecond}');
                },
              ),
            );
          }
          return Container(
            margin: const EdgeInsets.symmetric(vertical: 5),
            height: 50,
            decoration: BoxDecoration(
              border: Border.all(),
              color: Colors.amber,
            ),
          );
        },
      ),
    );
  }
}

如上图所示,要探究的问题就以此为基础。

与 AppBar 的关系

目前为止,页面布局看着都很正常,没有问题。

去掉 AppBar

有时候是不需要 AppBar 的,比如:

去掉之后变成了如下摸样,明显不符合预期,上边的状态栏、刘海都空白了,没有完全占满。就好像 ListView 与最上边有了一个状态栏高度的距离,至于是 Margin,还是 Padding,又或者是一个空的 Container,继续向下查看。

通过查看 ListView 源码发现,在 packages/flutter/lib/src/widgets/scroll_view.dart:807 处发现会获取当前上下文的 mediaQuery 对象,并使用其中的 Padding。

ScrollView(ListView、GridView、BoxScrollView 的抽象类),在 build 时,会调用 buildSlivers,先看自身有没有设置 padding,如果设置了,那就用自己的。没设置就看 Widget 树中,最近的一个 MediaQuery 它的 padding 信息,以其为主。

找到了对应的代码逻辑,可以通过 Debug 和 DevTools 进行确认一下。

Debug 查看:

DevTools 查看:

由此也能看出,离 ListView 最近的一个 MediaQuery,其 Padding top 是 59,也就是状态栏的高度。

加上 AppBar

上面看到了去掉 AppBar 的情况,为了相互印证,把 AppBar 再加上进行查看。

Debug 查看:

DevTools 查看:

解决去掉 AppBar 的问题

方案 1

通过刚才的分析,现在已经很明白了,只需要给自己的 ScrollView 设置上 padding 即可,他就不会进入下面的代码:

最终代码:

效果:

完美,咦,不对,怎么中间的 GridView 的布局不对劲了?看着应该是离上边有个状态栏高度的 padding,真是"拆了东墙补西墙"。细细想想,把 ListView 的 padding 设置上了,它用自己的 padding 了,可是 GridView 也是 ScrollView 的实现,所以它也会走这块代码:

方案 2

GridView 可没有给他设置 padding,所以它还是用 Scaffold 下、GridView 上(Widget 树中)的那个 MediaQuery 中的 padding 信息,所以还是有个 top: 59 的距离,简单做法就是给 GridView 也设置上他自己的 padding,可是我已经烦了,难道每多一个 ScrollView 就要给他设置个 padding?

既然知道是 ScrollView 最近的 MediaQuery 导致的,那可以不可以一劳永逸,把这个最近的 MediaQuery 换了?当然可以,这里在 ListView 外边加一个自己的 MediaQuery:

dart 复制代码
Widget build(BuildContext context) {
  return Scaffold(
    // 手动增加一层 MediaQuery,默认的 padding 为 0
    body: MediaQuery(
      data: const MediaQueryData(),
      child: ListView.builder(
        itemCount: 100,
        itemBuilder: (context, index) {
          if (index == 3) {
            return SizedBox(
              height: 220,
              child: GridView.builder(
                physics: const NeverScrollableScrollPhysics(),
                itemCount: 10,
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 5,
                  mainAxisExtent: 100,
                  crossAxisSpacing: 20,
                  mainAxisSpacing: 20,
                ),
                itemBuilder: (BuildContext context, int index) {
                  return Image.network(
                      'https://img.xjh.me/random_img.php?return=302&time=${DateTime.now().microsecond}');
                },
              ),
            );
          }
          return Container(
            margin: const EdgeInsets.symmetric(vertical: 5),
            height: 50,
            decoration: BoxDecoration(
              border: Border.all(),
              color: Colors.amber,
            ),
          );
        },
      ),
    ),
  );
}

这样在 Scaffold 下、ListView 上就是自己创建的这个 MediaQuery 了。

还有另一种方式是使用 MediaQuery.removePadding 是一样的道理。

外部 ScrollView 使用 padding 与不使用的区别

恢复到自己插入 MediaQuery 之前的代码:

dart 复制代码
Widget build(BuildContext context) {
  return Scaffold(
    body: ListView.builder(
      itemCount: 100,
      itemBuilder: (context, index) {
        if (index == 3) {
          return SizedBox(
            height: 220,
            child: GridView.builder(
              physics: const NeverScrollableScrollPhysics(),
              itemCount: 10,
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 5,
                mainAxisExtent: 100,
                crossAxisSpacing: 20,
                mainAxisSpacing: 20,
              ),
              itemBuilder: (BuildContext context, int index) {
                return Image.network(
                    'https://img.xjh.me/random_img.php?return=302&time=${DateTime.now().microsecond}');
              },
            ),
          );
        }
        return Container(
          margin: const EdgeInsets.symmetric(vertical: 5),
          height: 50,
          decoration: BoxDecoration(
            border: Border.all(),
            color: Colors.amber,
          ),
        );
      },
    ),
  );
}

给 ListView 加上 padding:

这两个的区别就在于 ListView 加不加 padding。

  • 没加:内部的 ScrollView 布局正常。
  • 加了:内部的 ScrollView 布局异常,有一个高度为状态栏高度的 top padding。

那来分析分析原因,还得回到 ScrollView 的 buildSlivers 中(packages/flutter/lib/src/widgets/scroll_view.dart:803)。

前者

先说前者,ListView 没有设置 padding,所以会执行红框范围的代码,其中 819 行包裹了一层 MediaQuery,这就和上边自行加一层 MediaQuery 的效果是一样的了,后续在 ListView 中的 ScrollView 都将默认使用此 MediaQuery,所以布局没有问题。

后者

后者,由于 ListView 设置有 padding,就不会走红框范围的代码,那也就不会在 Widget 树中增加一个 MediaQuery,所以在 ListView 内部的 ScrollView 如果自己不设置 padding,还是会使用那个 padding top = 59 的 MediaQuery。

相关推荐
比格丽巴格丽抱11 小时前
flutter项目苹果编译运行打包上线
flutter·ios
SoaringHeart11 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
AiFlutter15 小时前
Flutter通过 Coap发送组播
flutter
嘟嘟叽2 天前
初学 flutter 环境变量配置
flutter
iFlyCai2 天前
深入理解Flutter生命周期函数之StatefulWidget(一)
flutter·生命周期·dart·statefulwidget
sunly_2 天前
Flutter:photo_view图片预览功能
android·javascript·flutter
Summer不秃2 天前
Flutter中sqflite的使用案例
flutter
sunly_2 天前
Flutter:TweenAnimationBuilder自定义隐式动画
flutter
AiFlutter2 天前
Flutter-Web首次加载时添加动画
前端·flutter
Allen Su2 天前
【Flutter 问题系列第 84 篇】如何清除指定网络图片的缓存
flutter·缓存·如何清除指定网络图片的缓存·网络图片缓存