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。

相关推荐
程序员老刘3 小时前
Flutter 3.44 有哪些变化?(官方blog完整翻译)
flutter·ai编程·客户端
山屿落星辰5 小时前
Flutter 企业级架构设计实战:Clean Architecture + 分层模块化 + 依赖注入全解析
flutter
山屿落星辰6 小时前
Flutter 高级特性实战:动画、自定义绘制、平台通道与 Web 优化
前端·flutter
程序软件分享7 小时前
2026旗舰版 Java+Flutter 期货微交易系统源码全开源多语言平台
flutter·交易所源码·微盘源码·微交易源码
飞龙14775657467507 小时前
Flutter 安全存储插件全面解析:从入门到进阶
flutter
带带弟弟学爬虫__8 小时前
dyAPP数据采集-个人主页、发布、搜索、评论
服务器·python·算法·flutter·java-ee·django
icc_tips8 小时前
Flutter runAppAsync() 详解:干净的异步应用启动
前端·flutter
恋猫de小郭10 小时前
Android 发布全新性能分析器,实用性和性能大升级
android·前端·flutter
恋猫de小郭11 小时前
Flutter 3.44 发布啦,超级大版本更新!!!
android·flutter·ios
张3蜂11 小时前
Flutter macOS 安装文档
flutter·macos