likes
comments
collection
share

【Flutter】熊孩子拆组件系列之拆ListView(六)—— SliverPadding

作者站长头像
站长
· 阅读数 49

前言

在涉及到ListView的第三个重要部分之前,还有一个小小的Widget,也就是SliverPadding,话说为啥要加个SliverPadding?

还是先看注释?虽然从名字好像就能猜出干啥用的

/// A sliver that applies padding on each side of another sliver.
///
/// Slivers are special-purpose widgets that can be combined using a
/// [CustomScrollView] to create custom scroll effects. A [SliverPadding]
/// is a basic sliver that insets another sliver by applying padding on each
/// side.
///
/// {@macro flutter.rendering.RenderSliverEdgeInsetsPadding}

正如SliverPadding这个名字所述的,作用就是给Sliver中加个Padding;

SliverPadding 从何而来的,为啥有这玩意

首先来到ListView的build方法,其中又这么一行:

【Flutter】熊孩子拆组件系列之拆ListView(六)—— SliverPadding

SliverPadding就是在这个方法中被构造出来的:

【Flutter】熊孩子拆组件系列之拆ListView(六)—— SliverPadding

逻辑也挺简单的,说白了,设置过padding就按你设置的来,没有设置过的,就通过MediaQurey查询一个值出来,按那个来;

至于为啥这么搞?是为了方便统一做材料设计的规范??

SliverPadding 的构成:

【Flutter】熊孩子拆组件系列之拆ListView(六)—— SliverPadding

从上面这张图可以看出,SliverPadding一样把逻辑都放到了RenderObject层;

不过需要注意的一点是,SliverPadding这回继承的是SingleChildRenderObjectWidget,应该只允许一个child;

而且这回连Element也不需要自定义唉;

RenderSliverPadding

RenderSliverPadding本体也不怎么复杂:

class RenderSliverPadding extends RenderSliverEdgeInsetsPadding {
  /// Creates a render object that insets its child in a viewport.
  ///
  /// The [padding] argument must not be null and must have non-negative insets.
  RenderSliverPadding({
    required EdgeInsetsGeometry padding,
    TextDirection? textDirection,
    RenderSliver? child,
  }) : assert(padding != null),
       assert(padding.isNonNegative),
       _padding = padding,
       _textDirection = textDirection {
    this.child = child;
  }

  @override
  EdgeInsets? get resolvedPadding => _resolvedPadding;
  EdgeInsets? _resolvedPadding;

  void _resolve() {
    if (resolvedPadding != null)
      return;
    _resolvedPadding = padding.resolve(textDirection);
    assert(resolvedPadding!.isNonNegative);
  }

  void _markNeedsResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
  }

  /// The amount to pad the child in each dimension.
  ///
  /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
  /// must not be null.
  EdgeInsetsGeometry get padding => _padding;
  EdgeInsetsGeometry _padding;
  set padding(EdgeInsetsGeometry value) {
    assert(value != null);
    assert(padding.isNonNegative);
    if (_padding == value)
      return;
    _padding = value;
    _markNeedsResolution();
  }

  /// The text direction with which to resolve [padding].
  ///
  /// This may be changed to null, but only after the [padding] has been changed
  /// to a value that does not depend on the direction.
  TextDirection? get textDirection => _textDirection;
  TextDirection? _textDirection;
  set textDirection(TextDirection? value) {
    if (_textDirection == value)
      return;
    _textDirection = value;
    _markNeedsResolution();
  }

  @override
  void performLayout() {
    _resolve();
    super.performLayout();
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
  }
}

抛开一堆构造器,其实所做的事就一件,让padding通过解析,最后生成 _resolvedPadding,这个才是需要的东西;

不过也可以发现一个东西:

【Flutter】熊孩子拆组件系列之拆ListView(六)—— SliverPadding

在Flutter中,padding,好像是不支持负数的~

既然RenderSliverPadding本身没啥逻辑,就是为了生成 _resolvePadding 的话,那么主要逻辑就在它的父类—— RenderSliverEdgeInsetsPadding 这块喽

RenderSliverEdgeInsetsPadding

来到它的最长的那个方法,performLayout方法这块:

void performLayout() {
  final SliverConstraints constraints = this.constraints;
  assert(resolvedPadding != null);
  final double beforePadding = this.beforePadding;
  final double afterPadding = this.afterPadding;
  final double mainAxisPadding = this.mainAxisPadding;
  final double crossAxisPadding = this.crossAxisPadding;
  if (child == null) {
    geometry = SliverGeometry(
      scrollExtent: mainAxisPadding,
      paintExtent: math.min(mainAxisPadding, constraints.remainingPaintExtent),
      maxPaintExtent: mainAxisPadding,
    );
    return;
  }
  final double beforePaddingPaintExtent = calculatePaintOffset(
    constraints,
    from: 0.0,
    to: beforePadding,
  );
  double overlap = constraints.overlap;
  if (overlap > 0) {
    overlap = math.max(0.0, constraints.overlap - beforePaddingPaintExtent);
  }
  child!.layout(
    constraints.copyWith(
      scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
      cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding),
      overlap: overlap,
      remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding),
      remainingCacheExtent: constraints.remainingCacheExtent - calculateCacheOffset(constraints, from: 0.0, to: beforePadding),
      crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding),
      precedingScrollExtent: beforePadding + constraints.precedingScrollExtent,
    ),
    parentUsesSize: true,
  );
  final SliverGeometry childLayoutGeometry = child!.geometry!;
  if (childLayoutGeometry.scrollOffsetCorrection != null) {
    geometry = SliverGeometry(
      scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection,
    );
    return;
  }
  final double afterPaddingPaintExtent = calculatePaintOffset(
    constraints,
    from: beforePadding + childLayoutGeometry.scrollExtent,
    to: mainAxisPadding + childLayoutGeometry.scrollExtent,
  );
  final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent;
  final double beforePaddingCacheExtent = calculateCacheOffset(
    constraints,
    from: 0.0,
    to: beforePadding,
  );
  final double afterPaddingCacheExtent = calculateCacheOffset(
    constraints,
    from: beforePadding + childLayoutGeometry.scrollExtent,
    to: mainAxisPadding + childLayoutGeometry.scrollExtent,
  );
  final double mainAxisPaddingCacheExtent = afterPaddingCacheExtent + beforePaddingCacheExtent;
  final double paintExtent = math.min(
    beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
    constraints.remainingPaintExtent,
  );
  geometry = SliverGeometry(
    paintOrigin: childLayoutGeometry.paintOrigin,
    scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
    paintExtent: paintExtent,
    layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
    cacheExtent: math.min(mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent, constraints.remainingCacheExtent),
    maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
    hitTestExtent: math.max(
      mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
      beforePaddingPaintExtent + childLayoutGeometry.hitTestExtent,
    ),
    hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
  );

  final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
  assert(constraints.axisDirection != null);
  assert(constraints.growthDirection != null);
  switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
    case AxisDirection.up:
      childParentData.paintOffset = Offset(resolvedPadding!.left, calculatePaintOffset(constraints, from: resolvedPadding!.bottom + childLayoutGeometry.scrollExtent, to: resolvedPadding!.bottom + childLayoutGeometry.scrollExtent + resolvedPadding!.top));
      break;
    case AxisDirection.right:
      childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding!.left), resolvedPadding!.top);
      break;
    case AxisDirection.down:
      childParentData.paintOffset = Offset(resolvedPadding!.left, calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding!.top));
      break;
    case AxisDirection.left:
      childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: resolvedPadding!.right + childLayoutGeometry.scrollExtent, to: resolvedPadding!.right + childLayoutGeometry.scrollExtent + resolvedPadding!.left), resolvedPadding!.top);
      break;
  }
  assert(childParentData.paintOffset != null);
  assert(beforePadding == this.beforePadding);
  assert(afterPadding == this.afterPadding);
  assert(mainAxisPadding == this.mainAxisPadding);
  assert(crossAxisPadding == this.crossAxisPadding);
}

拆分一下,所做的事也不复杂:

  • 如果没有child,那么构造geemetry结束layout。
  • 如果有child ,那么修改当前的约束,将padding属性加入,并调用layout,传给child;
  • 按照惯例,获取child 的 geometry,如果child 需要修正滑动offset,那么结束流程并构造geometry返回
  • 根据child 最终的geometry ,计算出实际上应用的绘制位置、offset等信息,构造并设置为自己的geometry
  • 根据计算结果,将child 的ParentData 的信息也更新上;

出现了一个ParenetData?那么它的作用是?

搜一下调用这个parentData的地方,可以看到实际使用位置又这么几个地方

  1. HitTest
@override
bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
  if (child != null && child!.geometry!.hitTestExtent > 0.0) {
    final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
    result.addWithAxisOffset(
      mainAxisPosition: mainAxisPosition,
      crossAxisPosition: crossAxisPosition,
      mainAxisOffset: childMainAxisPosition(child!),
      crossAxisOffset: childCrossAxisPosition(child!),
      paintOffset: childParentData.paintOffset,
      hitTest: child!.hitTest,
    );
  }
  return false;
}
  1. applyPaintTransform
@override
void applyPaintTransform(RenderObject child, Matrix4 transform) {
  assert(child != null);
  assert(child == this.child);
  final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
  childParentData.applyPaintTransform(transform);
}
  1. paint
@override
void paint(PaintingContext context, Offset offset) {
  if (child != null && child!.geometry!.visible) {
    final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
    context.paintChild(child!, offset + childParentData.paintOffset);
  }
}

说白了,更新paintOffset 就是因为hitTest 和 piant方法需要;

总结

SliverPadding 其实也不复杂,说白了核心逻辑就是修改 constraints ,将padding减去并传给child,供其layout使用;

下面就来到ListView本体,第三个重要部分:SliverList了

转载自:https://juejin.cn/post/7026169867459887112
评论
请登录