App開發中,清單的使用是比較常見的場景,Android原生開發時,ListView 是最常用的滑動元件,後來 Google 又推出了功能更加強大,使用更加靈活的 RecyclerView。同樣,在 Flutter 開發中,系統也提供了 ListView 用于實作清單滑動的滑動元件,它可以沿一個方向線性排布所有子元件,并且支援基于 Sliver 的延遲構模組化型。
Flutter 中使用 ListView 時,如何更有效的設定 Item 項間距呢?
看官方文檔,可以找到,我們在建構 ListView 時有四種方式可以使用:
- ListView的預設構造函數,顯示的構造 List< Widget>,該方式适合于具有少量子元素的清單視圖,因為這種方式需要将所有需要顯示的子Widget 都提前建構好,而不是等到子 Widget 真正顯示的時候再建構,即通過預設的構造函數建構的 ListView 沒有應用基于 Sliver 的懶加載模型。
- ListView.builder 利用 IndexedWidgetBuilder 按需建構子 Widget,适合清單項比較多( 或者無限 )的清單視圖,因為建構器隻有當子 Widget 真正需要顯示的時候才會被建構,即使用 builder 方式建構的 ListView 支援基于 Sliver 的懶加載模型的。
- ListView.separated 利用兩個 IndexedWidgetBuilder: itemBuilder 根據需要建構子 Widget 和 separatorBuilder 建構子 Widget 之間的分割符子 Widget。此構造函數适用于具有固定數量的子 Widget 的清單視圖。
- 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),
),
),
),
),
);
}
}
完整執行個體代碼可點選進入檢視,讀者可以試着運作一下,檢視具體效果;