## GridView
GridView可以構建一個二維網格列表,其默認構造函數定義如下:
```
GridView({
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required SliverGridDelegate gridDelegate, //控制子widget layout的委托
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
})
```
我們可以看到,GridView和ListView的大多數參數都是相同的,它們的含義也都相同,如有疑惑讀者可以翻閱ListView一節,在此不再贅述。我們唯一需要關注的是`gridDelegate`參數,類型是SliverGridDelegate,它的作用是控制GridView子widget如何排列(layout),SliverGridDelegate是一個抽象類,定義了GridView Layout相關接口,子類需要通過實現它們來實現具體的布局算法,Flutter中提供了兩個SliverGridDelegate的子類SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent,下面我們分別介紹:
### SliverGridDelegateWithFixedCrossAxisCount
該子類實現了一個橫軸為固定數量子元素的layout算法,其構造函數為:
```
SliverGridDelegateWithFixedCrossAxisCount({
@required double crossAxisCount,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})
```
- crossAxisCount:橫軸子元素的數量。此屬性值確定后子元素在橫軸的長度就確定了,即ViewPort橫軸長度/crossAxisCount。
- mainAxisSpacing:主軸方向的間距。
- crossAxisSpacing:橫軸方向子元素的間距。
- childAspectRatio:子元素在橫軸長度和主軸長度的比例。由于crossAxisCount指定后子元素橫軸長度就確定了,然后通過此參數值就可以確定子元素在主軸的長度。
可以發現,子元素的大小是通過crossAxisCount和childAspectRatio兩個參數共同決定的。注意,這里的子元素指的是子widget的最大顯示空間,注意確保子widget的實際大小不要超出子元素的空間。
下面看一個例子:
```
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //橫軸三個子widget
childAspectRatio: 1.0 //寬高比為1時,子widget
),
children:<Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast)
]
);
```

#### GridView.count
GridView.count構造函數內部使用了SliverGridDelegateWithFixedCrossAxisCount,我們通過它可以快速的創建橫軸固定數量子元素的GridView,上面的示例代碼等價于:
```
GridView.count(
crossAxisCount: 3,
childAspectRatio: 1.0,
children: <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast),
],
);
```
### SliverGridDelegateWithMaxCrossAxisExtent
該子類實現了一個橫軸子元素為固定最大長度的layout算法,其構造函數為:
```
SliverGridDelegateWithMaxCrossAxisExtent({
double maxCrossAxisExtent,
double mainAxisSpacing = 0.0,
double crossAxisSpacing = 0.0,
double childAspectRatio = 1.0,
})
```
maxCrossAxisExtent為子元素在橫軸上的最大長度,之所以是“最大”長度,是**因為橫軸方向每個子元素的長度仍然是等分的**,舉個例子,如果ViewPort的橫軸長度是450,那么當maxCrossAxisExtent的值在區間(450/4,450/3\]內的話,子元素最終實際長度都為150,而`childAspectRatio`所指的子元素橫軸和主軸的長度比為**最終的長度比**。其它參數和SliverGridDelegateWithFixedCrossAxisCount相同。
下面我們看一個例子:
```
GridView(
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
childAspectRatio: 2.0 //寬高比為2
),
children: <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast),
],
);
```

#### GridView.extent
GridView.extent構造函數內部使用了SliverGridDelegateWithMaxCrossAxisExtent,我們通過它可以快速的創建縱軸子元素為固定最大長度的的GridView,上面的示例代碼等價于:
```
GridView.extent(
maxCrossAxisExtent: 120.0,
childAspectRatio: 2.0,
children: <Widget>[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast),
],
);
```
### GridView.builder
上面我們介紹的GridView都需要一個Widget數組作為其子元素,這些方式都會提前將所有子widget都構建好,所以只適用于子Widget數量比較少時,當子widget比較多時,我們可以通過`GridView.builder`來動態創建子Widget。`GridView.builder` 必須指定的參數有兩個:
```
GridView.builder(
...
@required SliverGridDelegate gridDelegate,
@required IndexedWidgetBuilder itemBuilder,
)
```
其中itemBuilder為子widget構建器。
#### 示例
假設我們需要從一個異步數據源(如網絡)分批獲取一些Icon,然后用GridView來展示:
```
class InfiniteGridView extends StatefulWidget {
@override
_InfiniteGridViewState createState() => new _InfiniteGridViewState();
}
class _InfiniteGridViewState extends State<InfiniteGridView> {
List<IconData> _icons = []; //保存Icon數據
@override
void initState() {
// 初始化數據
_retrieveIcons();
}
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //每行三列
childAspectRatio: 1.0 //顯示區域寬高相等
),
itemCount: _icons.length,
itemBuilder: (context, index) {
//如果顯示到最后一個并且Icon總數小于200時繼續獲取數據
if (index == _icons.length - 1 && _icons.length < 200) {
_retrieveIcons();
}
return Icon(_icons[index]);
}
);
}
//模擬異步獲取數據
void _retrieveIcons() {
Future.delayed(Duration(milliseconds: 200)).then((e) {
setState(() {
_icons.addAll([
Icons.ac_unit,
Icons.airport_shuttle,
Icons.all_inclusive,
Icons.beach_access, Icons.cake,
Icons.free_breakfast
]);
});
});
}
}
```
- `_retrieveIcons()`:在此方法中我們通過`Future.delayed`來模擬從異步數據源獲取數據,每次獲取數據需要200毫秒,獲取成功后將新數據添加到\_icons,然后調用setState重新構建。
- 在itemBuilder中,如果顯示到最后一個時,判斷是否需要繼續獲取數據,然后返回一個Icon。
### 更多
Flutter的GridView默認子元素顯示空間是相等的,但在實際開發中,你可能會遇到子元素大小不等的情況,如下面這樣的布局:

Pub上有一個包“flutter\_staggered\_grid\_view” ,它實現了一個交錯GridView的布局模型,可以很輕松的實現這種布局,詳情讀者可以自行了解。
- 緣起
- 起步
- 移動開發技術簡介
- Flutter簡介
- 搭建Flutter開發環境
- 常見配置問題
- Dart語言簡介
- 第一個Flutter應用
- 計數器示例
- 路由管理
- 包管理
- 資源管理
- 調試Flutter APP
- Dart線程模型及異常捕獲
- 基礎Widgets
- Widget簡介
- 文本、字體樣式
- 按鈕
- 圖片和Icon
- 單選框和復選框
- 輸入框和表單
- 布局類Widgets
- 布局類Widgets簡介
- 線性布局Row、Column
- 彈性布局Flex
- 流式布局Wrap、Flow
- 層疊布局Stack、Positioned
- 容器類Widgets
- Padding
- 布局限制類容器ConstrainedBox、SizeBox
- 裝飾容器DecoratedBox
- 變換Transform
- Container容器
- Scaffold、TabBar、底部導航
- 可滾動Widgets
- 可滾動Widgets簡介
- SingleChildScrollView
- ListView
- GridView
- CustomScrollView
- 滾動監聽及控制ScrollController
- 功能型Widgets
- 導航返回攔截-WillPopScope
- 數據共享-InheritedWidget
- 主題-Theme
- 事件處理與通知
- 原始指針事件處理
- 手勢識別
- 全局事件總線
- 通知Notification
- 動畫
- Flutter動畫簡介
- 動畫結構
- 自定義路由過渡動畫
- Hero動畫
- 交錯動畫
- 自定義Widget
- 自定義Widget方法簡介
- 通過組合現有Widget實現
- 實例:TurnBox
- CustomPaint與Canvas
- 實例:圓形漸變進度條(自繪)
- 文件操作與網絡請求
- 文件操作
- Http請求-HttpClient
- Http請求-Dio package
- 實例:Http分塊下載
- WebSocket
- 使用Socket API
- Json轉Model
- 包與插件
- 開發package
- 插件開發:平臺通道簡介
- 插件開發:實現Android端API
- 插件開發:實現IOS端API
- 系統能力調用
- 國際化
- 讓App支持多語言
- 實現Localizations
- 使用Intl包
- Flutter核心原理
- Flutter UI系統
- Element和BuildContext
- RenderObject與RenderBox
- Flutter從啟動到顯示