【无标题】


先从这一块朴素的代码开启我们的旅程:

ListView.builder(
        itemBuilder: ((context, index) {
   
          return ListTile(
            title: Text("this is $index"),
          );
        }),
        itemCount: 20,
      )

我很好奇:

  1. ListView是怎么渲染的?
  2. ListView怎么做到控制性能?
  3. 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(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值