ListView 学完了来学 GridView,其实这两个 Widget 的属性大同小异,并且 GridView 也和 ListView 一样,提供了好几中创建的方法:
- 直接使用构造函数 GridView
- 使用
GridView.builder
方法创建 - 使用
GridView.custom
方法创建 - 使用
GridView.count
方法创建 - 使用
GridView.extent
方法创建
可以看到 GridView 也有 builder 和 custom 方法创建,还另外加了两种,废话不多说,一一开始吧
直接使用构造函数创建 GridView
GridView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
})
你会发现,很多参数和 ListView 都是一样的:
scrollDirection,滚动方向。
reverse,滚动方向是否是阅读方向。
controller,滚动控制,可以设置滚动位置等。
primary,是否使用 widget 树中默认的
PrimaryScrollController
。当滑动方向为垂直方向(scrollDirection
值为Axis.vertical
)并且controller
没有指定时,primary
默认为true。(这个属性我也没搞明白是干啥的,后续明白了再来修改吧)。physics,列表滚动至边缘后继续拖动的物理效果,
Android
与iOS
效果不同。Android
会呈现出一个波纹状(对应ClampingScrollPhysics
),而iOS
上有一个回弹的弹性效果(对应BouncingScrollPhysics
)。如果你想不同的平台上呈现各自的效果可以使用AlwaysScrollableScrollPhysics
,它会根据不同平台自动选用各自的物理效果。如果你想禁用在边缘的拖动效果,那可以使用NeverScrollableScrollPhysics
;shrinkWrap,该属性将决定列表的长度是否仅包裹其内容的长度。当
ListView
嵌在一个无限长的容器组件中时,shrinkWrap
必须为true,否则Flutter
会给出警告;padding,列表内边距;
gridDelegate,这个是重点,它的作用是控制 GridView Item 如何排列,它的类型是
SliverGridDelegate
,这是一个抽象类,Flutter 提供了两个它的实现类供我们使用:- SliverGridDelegateWithFixedCrossAxisCount
- SliverGridDelegateWithMaxCrossAxisExtent
稍后我们会介绍到这两个子类的区别。
addAutomaticKeepAlives,该属性表示是否将列表项(子组件)包裹在
AutomaticKeepAlive
组件中;典型地,在一个懒加载列表中,如果将列表项包裹在AutomaticKeepAlive
中,在该列表项滑出视口时它也不会被GC(垃圾回收),它会使用KeepAliveNotification
来保存其状态。如果列表项自己维护其KeepAlive
状态,那么此参数必须置为false
。addRepaintBoundaries,该属性表示是否将列表项(子组件)包裹在
RepaintBoundary
组件中。当可滚动组件滚动时,将列表项包裹在RepaintBoundary
中可以避免列表项重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary
反而会更高效。和addAutomaticKeepAlive
一样,如果列表项自己维护其KeepAlive
状态,那么此参数必须置为false
。addSemanticIndexes,这个是跟语音识别辅助工具有关系的。
cacheExtent,预渲染区域长度,
ListView
会在其可视区域的两边留一个cacheExtent
长度的区域作为预渲染区域(对于ListView.build
或ListView.separated
构造函数创建的列表,不在可视区域和预渲染区域内的子元素不会被创建或会被销毁);children,容纳子元素的组件数组。
semanticChildCount,真实的语义数量。
其他属性和 ListView 一样,我们就不赘述了,可以翻看之前的章节,这里只是一下 gridDelegate
参数:
SliverGridDelegateWithFixedCrossAxisCount
从名称大致就可以推测出这个是用于列数固定的情况下使用的,看一下这个类的构造函数:
const SliverGridDelegateWithFixedCrossAxisCount({
@required this.crossAxisCount,
this.mainAxisSpacing = 0.0,
this.crossAxisSpacing = 0.0,
this.childAspectRatio = 1.0,
})
- crossAxisCount,每行几个
- mainAxisSpacing,行间距
- crossAxisSpacing,列间距
- childAspectRatio,子元素的宽高比
偷一张图来解释这四个属性的含义,图来自 掘金 - SmallStoneSK - Flutter网格型布局 - GridView篇:
四个参数的含义应该解释的很清楚了吧,下面是一个小例子:
//模拟数据的获取
List<Widget> getList() {
var list = new List<Widget>();
for (var i = 0; i < 100; i++) {
list.add(
Container(
alignment: Alignment.center,
margin: EdgeInsets.all(5),
width: 100,
height: 100,
color: Colors.lightGreenAccent,
child: Text("哈哈哈${i}"),
),
);
}
return list;
}
//GridView
body: GridView(
padding: EdgeInsets.all(10),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount:4,
mainAxisSpacing:0,
crossAxisSpacing:20,
childAspectRatio:1,
),
children: getList(),
)
显示效果如下:
注意一下 List
当中设置的 margin 参数。
SliverGridDelegateWithMaxCrossAxisExtent
用于 Item 宽度有限制的情况,看其构造函数:
const SliverGridDelegateWithMaxCrossAxisExtent({
@required this.maxCrossAxisExtent,
this.mainAxisSpacing = 0.0,
this.crossAxisSpacing = 0.0,
this.childAspectRatio = 1.0,
})
除了第一个 maxCrossAxisExtent
参数之外,其余三个和 SliverGridDelegateWithFixedCrossAxisCount
一样,那么 maxCrossAxisExtent
是干什么的呢?我们来看个例子:
假如手机屏宽 375
,crossAxisSpacing
值为 0
,
maxCrossAxisExtent
值为125
时,网格列数将是3
。因为125 * 3 = 375
,刚好,每一列的宽度就是375/3
。maxCrossAxisExtent
值为126
时,网格列数将是3
。因为126 * 3 > 375
,显示不下,每一列的宽度将是375/3
。maxCrossAxisExtent
值为124
时,网格列数将是4
。因为124 * 3 < 375
,仍有多余,每一列的宽度将是375/4
。
可以看到,maxCrossAxisExtent
其实就是告诉 GridView
组件子元素的最大宽度可能是多少,然后计算得到合适的列宽(实际上,我们也很少这么应用,所以这种方法的使用频率不高)。
使用 GridView.builder 方法创建 GridView
还是先看源码:
GridView.builder({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
int semanticChildCount,
})
builder 方法和之前的构造方法有两点不同:
- itemBuilder
- itemCount
这两个参数和 ListView.builder 一样,这里也不多做解释了。
使用 GridView.count 方法创建 GridView
效果和使用构造方法创建 GridView,然后 gridDelegate
设置为 SliverGridDelegateWithFixedCrossAxisCount
效果相同。
使用 GridView.extent 方法创建 GridView
效果和使用构造方法创建 GridView,然后 gridDelegate
设置为 SliverGridDelegateWithMaxCrossAxisExtent
效果相同。
Item 的点击事件
在 Flutter 中的点击事件以及其他手势处理都是由 GestureRecognizer(手势识别)
和 GestureDetector(手势检测)
来实现的,可以看这里 延伸知识点:手势处理