简介
ListView 是蕴含多个 child 组件的 widget,在 ListView 中所有的 child widget 都是以 list 的模式来出现的,你能够自定义 List 的方向,然而和 GridView 不同的是 ListView 中的每一个 List 外面都只蕴含一个 widget。
明天咱们来具体理解一下 ListView 的底层实现和具体的利用。
ListView 详解
和 GridView 一样,ListView 也是继承自 ScrollView,示意它是一个能够滚动的 View。
具体而言,ListView 首先继承自 BoxScrollView:
class ListView extends BoxScrollView
而后 BoxScrollView 又继承自 ScrollView:
abstract class BoxScrollView extends ScrollView
ListView 中的特有属性
首先咱们来看下 ListView 中的特有属性,ListView 和它的父类相比,多了三个属性,别离是 itemExtent,prototypeItem 和 childrenDelegate。
其中 itemExtent 是一个 double 类型的数据,如果给定的是一个非空值,那么示意的是 child 在 scroll 方向的 extent 大小。这个属性次要用来管制 children 的 extend 信息,这样每个 child 就不须要自行来判断本人的 extend。
应用 itemExtent 的益处在于,ListView 能够对立的在滚动机制上进行优化,从而晋升性能体现。
prototypeItem 是一个 widget,从名字就能够看出,这个一个 prototype 的 widget,也就是说是一个原型,其余的 child 能够参照这个原型 widget 的大小进行 extent 的设置。
ListView 中的最初一个自定义属性是 childrenDelegate,这个 childrenDelegate 和 GridView 中的含意是一样的,用来生成 ListView 中 child。
之前咱们在解说 GirdView 的时候有提到过,GirdView 中还有一个自定义的属性叫做 gridDelegate,这个 gridDelegate 是一个 SliverGridDelegate 的实例,用来管制子组件在 GridView 中的布局。
因为 ListView 的子组件的布局是曾经确定的,所以就不再须要 gridDelegate 了,这是 ListView 和 GridView 的一大区别。
ListView 作为一个继承的类,须要实现一个 buildChildLayout 的办法:
@override
Widget buildChildLayout(BuildContext context) {if (itemExtent != null) {
return SliverFixedExtentList(
delegate: childrenDelegate,
itemExtent: itemExtent!,
);
} else if (prototypeItem != null) {
return SliverPrototypeExtentList(
delegate: childrenDelegate,
prototypeItem: prototypeItem!,
);
}
return SliverList(delegate: childrenDelegate);
}
这个办法的实现逻辑和咱们之前讲到的三个属性是相关联的,在 buildChildLayout 中,如果 itemExtent 有值的话,因为 itemExtent 自身就是一个固定值,所以返回的是 SliverFixedExtentList。
如果 itemExtent 没有设置,并且 prototypeItem 有值的话,返回的是一个 SliverPrototypeExtentList。
最初,如果 itemExtent 和 prototypeItem 都没有设置的话,返回的是一个 SliverList 对象。
ListView 的构造函数
和 GridView 一样,为了满足咱们的多样性的设计需要,ListView 也提供了多个构造函数。
首先咱们来看下 ListView 的最根本的构造函数:
ListView({
Key? key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController? controller,
bool? primary,
ScrollPhysics? physics,
bool shrinkWrap = false,
EdgeInsetsGeometry? padding,
this.itemExtent,
this.prototypeItem,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double? cacheExtent,
List<Widget> children = const <Widget>[],
int? semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String? restorationId,
Clip clipBehavior = Clip.hardEdge,
})
这里 itemExtent 和 prototypeItem 这两个属性是内部传入的,childrenDelegate 是通过其余的参数结构而来的:
childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
ListView 中所有的 child 组件都在 List Widget 的 children 中。
这个默认的构造函数,实用于 child 比拟少的状况,因为须要一次传入所有的 child 组件到 list 中,所以对性能的影响还是挺大的,并且传入的 child 是不可变的。
如果 child 比拟多的状况下,就须要应用到其余的构造函数了, 比方 ListView.builder。
ListView.builder 应用的是 builder 模式来构建 child 组件,具体而言他的 childrenDelegate 实现如下:
childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
childCount: itemCount,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
这里的 childrenDelegate 是一个 SliverChildBuilderDelegate, 通过传入 itemBuilder 和总的 itemCount 就能够实现动态创建 child 的性能。
在 ListView 的理论应用过程中,为了页面难看或者更有区分度,咱们个别会在 list 的 item 中增加一些分隔符 separator, 为了自动化实现这个性能,ListView 提供了一个 ListView.separated 的构造函数,用来提供 list item 两头的分隔符。
ListView.separated 须要传入两个 IndexedWidgetBuilder, 别离是 itemBuilder 和 separatorBuilder。
上面是 childrenDelegate 的具体实现:
childrenDelegate = SliverChildBuilderDelegate((BuildContext context, int index) {
final int itemIndex = index ~/ 2;
final Widget widget;
if (index.isEven) {widget = itemBuilder(context, itemIndex);
} else {widget = separatorBuilder(context, itemIndex);
assert(() {if (widget == null) {throw FlutterError('separatorBuilder cannot return null.');
}
return true;
}());
}
return widget;
},
childCount: _computeActualChildCount(itemCount),
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
semanticIndexCallback: (Widget _, int index) {return index.isEven ? index ~/ 2 : null;},
),
能够看到,如果 index 是 even 的话就会应用 itemBuilder 生成一个 widget,如果 index 是 odd 的话,就会应用 separatorBuilder 来生成一个 separator 的 widget。
最初,ListView 还有一个更加凋谢的构造函数 ListView.custom,custom 和其余构造函数不同的中央在于他能够自定义 childrenDelegate,从而提供了更多的扩大空间。
ListView 的应用
有了下面的构造函数,咱们能够很不便的依据本人的须要来应用 ListView,上面是一个简略的应用图片做 child 的例子:
class ListViewApp extends StatelessWidget{const ListViewApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return Container(constraints: const BoxConstraints(maxWidth:100,maxHeight: 100),
child: Image.asset('images/head.jpg')
);
},
);
}
}
下面的例子中,咱们应用的是 ListView.builder 构造函数,返回的 Widget 中,中的 widget 个数是 5,每个 item 是由 itemBuilder 来生成的。
这里咱们把 Image 封装在一个 Container 中,并且为 Container 设置了一个 constraints 来管制图片的大小。
最终生成的界面如下:
下面的例子中,item 之间是没有分隔符的,咱们能够讲下面的例子进行略微的批改一下,应用 ListView.separated 来结构 ListView,如下所示:
class ListViewSeparatedApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: 10,
separatorBuilder: (BuildContext context, int index) => const Divider(),
itemBuilder: (BuildContext context, int index) {
return Container(constraints: const BoxConstraints(maxWidth:50,maxHeight: 50),
child: Image.asset('images/head.jpg')
);
},
);
}
}
这里咱们须要传入 separatorBuilder 来作为分隔符,为了简略起见,咱们间接应用了 Divider 这个 widget。
最初生成的界面如下:
总结
以上就是 ListView 的介绍和根本的应用。
本文的例子:https://github.com/ddean2009/learn-flutter.git