先从这一块朴素的代码开启我们的旅程:
ListView.builder(
itemBuilder: ((context, index) {
return ListTile(
title: Text("this is $index"),
);
}),
itemCount: 20,
)
我很好奇:
- ListView是怎么渲染的?
- ListView怎么做到控制性能?
- Sliver系列和普通滚动组件有啥区别?
ListView是怎么渲染的?
核心代码是这块,主要是定义了一个childrenDelegate
childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
findChildIndexCallback: findChildIndexCallback,
childCount: itemCount,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
)
它在哪里用到的,往上找,原来用在了ListView 的这里:
@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在哪里调用?继续顺藤摸瓜,找到了抽象类BoxScrollView,其中override了一个buildSlivers
List<Widget> buildSlivers(BuildContext context) {
Widget sliver = buildChildLayout(context);
EdgeInsetsGeometry? effectivePadding = padding;
if (padding == null) {
///包裹一个MediaQuery
///根据这个可以获取到设备的信息
final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
if (mediaQuery != null) {
// Automatically pad sliver with padding from MediaQuery.
final EdgeInsets mediaQueryHorizontalPadding =
mediaQuery.padding.copyWith(top: 0.0, bottom: 0.0);
final EdgeInsets mediaQueryVerticalPadding =
mediaQuery.padding.copyWith(left: 0.0, right: 0.0);
// Consume the main axis padding with SliverPadding.
effectivePadding = scrollDirection == Axis.vertical
? mediaQueryVerticalPadding
: mediaQueryHorizontalPadding;
// Leave behind the cross axis padding.
sliver = MediaQuery(
data: mediaQuery.copyWith(
padding: scrollDirection == Axis.vertical
? mediaQueryHorizontalPadding
: mediaQueryVerticalPadding,
),
child: sliver,
);
}
}
///如果有 padding 那就再包裹一个SliverPadding
if (effectivePadding != null) {
sliver = SliverPadding(padding: effectivePadding, sliver: sliver);
}
return <Widget>[ sliver ];
}
继续网上翻,终于找到正主了,位于抽象类ScrollView里的 build 方法
Widget build(BuildContext context) {
///拿到实现类返回的具体 Widget 列表
final List<Widget> slivers = buildSlivers(context);
final AxisDirection axisDirection = getDirection(context);
///[PrimaryScrollController.shouldInherit(context, scrollDirection)]
///检查当前的[PrimaryScrollController]是否自动继承[Axis]------区分各个平台
///
///
/// ************************************
///如果没有设置[primary]
///那就找它parents,看是否继承
final bool effectivePrimary = primary
?? controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection);
/// 如果[effectivePrimary]
/// true: 使用上下文的滑动控制器
/// false: 使用自定义的滑动控制器
final ScrollController? scrollController = effectivePrimary
? PrimaryScrollController.maybeOf(context)
: controller;
///根据传入的参数生成对应的[Scrollable]
final Scrollable scrollable = Scrollable(
dragStartBehavior: dragStartBehavior,
axisDirection: axisDirection,
controller: scrollController,
physics: physics,
scrollBehavior: scrollBehavior,
semanticChildCount: semanticChildCount,
restorationId: restorationId,
viewportBuilder: (BuildContext context, ViewportOffset offset) {
return buildViewport(context, offset, axisDirection, slivers);
},
clipBehavior: clipBehavior,
);
///根据[effectivePrimary]以及是否自定义了[ScrollController]生成不同的Scrollable
///如果我定义了[ScrollController] 但是我又希望[effectivePrimary]不受主控制器控制
///那么就使用[PrimaryScrollController.none(child: scrollable)]包裹它
///
/// 反之
///
/// 就正常使用
final Widget scrollableResult = effectivePrimary && scrollController != null
// Further descendant ScrollViews will not inherit the same PrimaryScrollController
? PrimaryScrollController.none(child: scrollable)
: scrollable;
///如果我要求我的键盘在滑动的时候被关闭掉
///那么就添加一个监听
///
/// 反之
///
/// 直接返回
if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
return NotificationListener<ScrollUpdateNotification>(
child: scrollableResult,
onNotification: (ScrollUpdateNotification notification) {
final FocusScopeNode focusScope = FocusScope.of(context);
if (notification.dragDetails != null && focusScope.hasFocus) {
focusScope.unfocus();
}
return false;
},
);
} else {
return scrollableResult;
}
}
这里又出现了一个新面孔Scrollable,它一个用于监听用户手势,从而响应滚动的 widget,从它的 build 方法可以看出来:
Widget build(BuildContext context) {
assert(_position != null);
///[_ScrollableScope]继承了[InheritedWidget]
///可共享传递[position]属性
Widget result = _ScrollableScope(
scrollable: this,
position: position,
child: Listener(
///监听 point 的信号变化
onPointerSignal: _receivedPointerSignal,
child: RawGestureDetector(
key: _gestureDetectorKey,
gestures: _gestureRecognizers,
behavior: HitTestBehavior.opaque,
excludeFromSemantics: widget.excludeFromSemantics,
child: Semantics(