flutter系列之:flutter中常用的GridView layout詳解
簡介
GridView是一個網格化的布局,如果在填充的過程中子組件超出了展示的範圍的時候,那麼GridView會自動滾動。
因為這個滾動的特性,所以GridView是一個非常好用的Widget。今天我們一起來探索一下GridView這個layout組件的秘密。
GridView詳解
GridView是一個可滾動的view,也就是ScrollView,事實上GridView繼承自BoxScrollView:
class GridView extends BoxScrollView
而它的父類BoxScrollView,則是繼承自ScrollView:
abstract class BoxScrollView extends ScrollView
可以看到BoxScrollView是一個抽象類,它有兩個子類,分別是今天我們要講的GridView和下期要講的ListView。
這兩個組件的區別是GridView是一個2D的布局,而ListView是一個線性layout的布局。
作為BoxScrollView的子類,GridView需要實現buildChildLayout方法如下所示:
@override
Widget buildChildLayout(BuildContext context) {
return SliverGrid(
delegate: childrenDelegate,
gridDelegate: gridDelegate,
);
}
這裡GridView返回了一個SliverGrid,這個SliverGrid中有兩個屬性,分別是childrenDelegate和gridDelegate。
其中gridDelegate是一個SliverGridDelegate的實例,用來控制子組件在GridView中的布局。
childrenDelegate是一個SliverChildDelegate的實例,用來生成GridView中的子組件。
這兩個屬性在GridView的構造函數中有使用,我們接下來會詳細進行講解。
GridView的構造函數
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,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
Clip clipBehavior = Clip.hardEdge,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String? restorationId,
})
在這個構造函數中,需要傳入自定義的gridDelegate,所以在構造函數中gridDelegate是required狀態:
required this.gridDelegate
上面提到了GridView中的兩個自定義屬性,還有一個是childrenDelegate,這個屬性是根據傳入的其他參數構造而成的,如下所示:
childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
另外一個GridView的構造函數叫做GridView.builder,這個構造函數和默認的構造函數的區別在於childrenDelegate的實現不同,我們來看下GridView.builder中childrenDelegate的實現:
childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
childCount: itemCount,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
對比發現,GridView.builder中的childrenDelegate多了兩個參數,分別是itemBuilder和itemCount。
那麼這個兩個參數是做什麼用的呢?
考慮一下一個有很多chil的GridView,為了提升GridView的展示性能,我們不可能一下取出所有的child元素進行構建,而是會在滾動中進行動態創建和繪製,而這裡的itemCount就是child的最大容量。
而itemBuilder就是一個動態創建child的創建器,從而滿足了動態創建child的需求。
接下來的構造函數叫做GridView.custom,因為叫做custom,所以這個構造函數的SliverGridDelegate和SliverChildDelegate都是可以自定義的,也就是說這兩個參數都可以從外部傳入,所以這兩個參數都是必須的:
required this.gridDelegate,
required this.childrenDelegate
GirdView還有一個構造函數叫做GridView.count,這裡的count是指GridView可以指定cross axis中可以包含的組件個數,所以這裡的gridDelegate使用的是一個SliverGridDelegateWithFixedCrossAxisCount:
gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio,
),
可以設置crossAxisCount的值。
最後一個GridView的構造函數叫做GridView.extent,它和count的構造函數很類似,不過extent提供的是一個maximum cross-axis extent,而不是一個固定的count值,所以這裡的gridDelegate是一個SliverGridDelegateWithMaxCrossAxisExtent對象:
gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: maxCrossAxisExtent,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: childAspectRatio,
),
怎麼理解呢?舉個例子,如果GirdView是豎向滾動的,並且它的width是400 pixels,如果這個時候maxCrossAxisExtent被設置為120,那麼一行只能有三列。我們可以通過調整maxCrossAxisExtent的值,來調整view的展示情況。
我們可以根據需要來選擇對應的構造函數,從而滿足我們不同的需求。
GridView的使用
有了GridView的構造函數,GridView使用起來就很簡單了。
比如我們動態創建一個包含image的child,組成一個gridView:
class GridViewApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
return GridView.extent(
maxCrossAxisExtent: 100,
padding: const EdgeInsets.all(4),
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: buildChild(10));
}
這裡的buildChild用來生成一個包含Widget的list,如下所示:
List<Widget> buildChild(int number) {
return List.generate(
number, (i) => Container(
child: Image.asset('images/head.jpg')));
}
最後將構造的GridViewApp放到Scaffold的body中運行:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: GridViewApp(),
),
);
}
最後我們可以得到下面的影像:
這裡我們使用的是GridView.extent構造函數,大家可以自行嘗試其他的構造函數。
總結
GridView是一個我們在日常工作中經常會使用的組件,希望大家能夠熟練掌握。
本文的例子://github.com/ddean2009/learn-flutter.git