基本页面布局
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。