flutter系列之:flutter中常用的GridView layout詳解

2022-09-13 21:01:19

簡介

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是一個我們在日常工作中經常會使用的元件,希望大家能夠熟練掌握。

本文的例子:https://github.com/ddean2009/learn-flutter.git