天天看點

Flutter中使用ListView時如何更有效的設定Item項間距?

App開發中,清單的使用是比較常見的場景,Android原生開發時,ListView 是最常用的滑動元件,後來 Google 又推出了功能更加強大,使用更加靈活的 RecyclerView。同樣,在 Flutter 開發中,系統也提供了 ListView 用于實作清單滑動的滑動元件,它可以沿一個方向線性排布所有子元件,并且支援基于 Sliver 的延遲構模組化型。

Flutter 中使用 ListView 時,如何更有效的設定 Item 項間距呢?

看官方文檔,可以找到,我們在建構 ListView 時有四種方式可以使用:

  1. ListView的預設構造函數,顯示的構造 List< Widget>,該方式适合于具有少量子元素的清單視圖,因為這種方式需要将所有需要顯示的子Widget 都提前建構好,而不是等到子 Widget 真正顯示的時候再建構,即通過預設的構造函數建構的 ListView 沒有應用基于 Sliver 的懶加載模型。
  2. ListView.builder 利用 IndexedWidgetBuilder 按需建構子 Widget,适合清單項比較多( 或者無限 )的清單視圖,因為建構器隻有當子 Widget 真正需要顯示的時候才會被建構,即使用 builder 方式建構的 ListView 支援基于 Sliver 的懶加載模型的。
  3. ListView.separated 利用兩個 IndexedWidgetBuilder: itemBuilder 根據需要建構子 Widget 和 separatorBuilder 建構子 Widget 之間的分割符子 Widget。此構造函數适用于具有固定數量的子 Widget 的清單視圖。
  4. ListView.custom 使用 SliverChildDelegate 建構,它提供了定制子模型的其它方面的能力,如 SliverChildDelegate 可以控制用于估算實際上不可見的子 Widget 的大小的算法。

關于上述四種方法的使用,這裡不再具體講述,後面我給出示例代碼的位址;

下面就來回答一下,ListView 如果更有效的設定 Item 項間距?

首先滑動方向是豎直方向時如何設定 Item 項間距:

Expanded(
            child: RepaintBoundary(
              child: ListView.separated(
                  itemBuilder: (context, index) {
                    return _CommonItem(stringItem: _words[index]);
                  },
                  scrollDirection: Axis.vertical,
                  padding: _listVerticalPadding,
                  physics: BouncingScrollPhysics(),
                  separatorBuilder: (BuildContext context, int index) =>
                      Divider(
                        height: 16.0,
                        color: Color(0xFFFFFFFF),
                      ),
                  itemCount: _words.length),
            ),
          ),
           

接下來滑動方向是水準方向是如何設定 Item 項間距:

Container(
              constraints: BoxConstraints(maxHeight: 100.0, minHeight: 0),
              child: ListView.separated(
                  itemBuilder: (context, index) {
                    return _CommonItem(stringItem: _words[index]);
                  },
                  scrollDirection: Axis.horizontal,
                  padding: _listHorizontalPadding,
                  physics: BouncingScrollPhysics(),
                  separatorBuilder: (BuildContext context, int index) =>
                      VerticalDivider(
                        width: 16.0,
                        color: Color(0xFFFFFFFF),
                      ),
                  itemCount: _words.length),
            ),
           

上面使用到的 Divider 和 VerticalDivider 都是系統提供的,當然我們也可以使用自定義的 分割符子項:

Container(
              constraints: BoxConstraints(maxHeight: 100.0, minHeight: 0),
              child: ListView.separated(
                  itemBuilder: (context, index) {
                    return _CommonItem(stringItem: _words[index]);
                  },
                  scrollDirection: Axis.horizontal,
                  padding: _listHorizontalPadding,
                  physics: BouncingScrollPhysics(),
                  separatorBuilder: (BuildContext context, int index) =>
                       Container(
                        width: 16.0,
                        color: Color(0xFFFFFFFF),
                      ),
                  itemCount: _words.length),
            ),
           

是不是很簡單啦,檢視系統提供的 Divider 和 VerticalDivider 的源代碼,會發現它們也隻是封裝組合了一下系統已有的 Widget 而已,下面看一下 Divider 的源碼:

/// Creates a material design divider.
class MyDivider extends StatelessWidget {
  const MyDivider({
    Key key,
    this.height,
    this.thickness,
    this.indent,
    this.endIndent,
    this.color,
  }) : assert(height == null || height >= 0.0),
        assert(thickness == null || thickness >= 0.0),
        assert(indent == null || indent >= 0.0),
        assert(endIndent == null || endIndent >= 0.0),
        super(key: key);

  /// 水準分割器的高度值,都不設定,預設為:16.0
  final double height;

  /// 分割器内繪制的線的厚度
  final double thickness;

  /// 分割器内繪制的線距離前邊緣的空間
  final double indent;

  /// 分割器内繪制的線距離後邊緣的間距
  final double endIndent;

  /// 分割器内繪制的線的顔色值
  final Color color;

  /// 建立并計算分割器的邊界
  static BorderSide createBorderSide(BuildContext context, { Color color, double width }) {
    final Color effectiveColor = color
        ?? (context != null ? (DividerTheme.of(context).color ?? Theme.of(context).dividerColor) : null);
    final double effectiveWidth =  width
        ?? (context != null ? DividerTheme.of(context).thickness : null)
        ?? 0.0;

    // Prevent assertion since it is possible that context is null and no color
    // is specified.
    if (effectiveColor == null) {
      return BorderSide(
        width: effectiveWidth,
      );
    }
    return BorderSide(
      color: effectiveColor,
      width: effectiveWidth,
    );
  }

  @override
  Widget build(BuildContext context) {
    final DividerThemeData dividerTheme = DividerTheme.of(context);
    final double height = this.height ?? dividerTheme.space ?? 16.0;
    final double thickness = this.thickness ?? dividerTheme.thickness ?? 0.0;
    final double indent = this.indent ?? dividerTheme.indent ?? 0.0;
    final double endIndent = this.endIndent ?? dividerTheme.endIndent ?? 0.0;

    /// 組合一些系統 Widget 來實作
    return SizedBox(
      // 分割器的高度
      height: height,
      child: Center(
        child: Container(
          // 分割器内繪制分割線的厚度
          height: thickness,
          // 分割器内繪制的分割線距離前後邊緣的間距
          margin: EdgeInsetsDirectional.only(start: indent, end: endIndent),
          decoration: BoxDecoration(
            border: Border(
              bottom: createBorderSide(context, color: color, width: thickness),
            ),
          ),
        ),
      ),
    );
  }
}
           

完整執行個體代碼可點選進入檢視,讀者可以試着運作一下,檢視具體效果;