“This is the second day of my participation in the November Gwen Challenge. See details of the event: The last Gwen Challenge 2021”.

preface

Now, before we get to the third important part of the ListView, there’s a little Widget called SliverPadding, but why SliverPadding?

Or look at the notes first? Although the name seems to tell you what it’s for

/// 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}Copy the code

SliverPadding, as the name suggests, is just adding a Padding to the Sliver;

Where does SliverPadding come from, and why does it even exist

Let’s start with the Build method of the ListView, which again looks like this:

SliverPadding is constructed in this method:

The logic is pretty simple. If the padding is set, press on it. If the padding is not set, press on it through MediaQurey.

And why? Is for the convenience of uniform material design specifications?

SliverPadding:

As you can see from the above image, the SliverPadding puts all the logic in the RenderObject layer as well;

Is important to note, however, SliverPadding inheritance is SingleChildRenderObjectWidget, this time should be allowed only one child;

And there’s no need to customize Element this time.

RenderSliverPadding

The RenderSliverPadding ontology isn’t too complicated either:

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)); }}Copy the code

All you need to do instead of a bunch of constructors is let the padding pass through and generate _resolvedPadding, which is what you need;

But there’s one thing you can see:

In Flutter, padding does not seem to support negative ~

Since RenderSliverPadding itself do not have what logic, is to generate _resolvePadding, then the main logic in its parent – RenderSliverEdgeInsetsPadding this

RenderSliverEdgeInsetsPadding

Go to its longest method, the performLayout method:

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, the 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, the 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); }Copy the code

Break it down and it’s not complicated:

  • If there is no child, construct Geemetry to end the layout.
  • If there is a child, modify the current constraint by adding the padding property and calling layout to the child.
  • By convention, the geometry of the child is obtained, and if the child needs to correct the sliding offset, the process ends and the geometry returns is constructed
  • According to the final geometry of the child, the drawing position, offset and other information of the actual application are calculated, and the geometry is constructed and set as its own
  • According to the calculation results, the information of the ParentData of the child is also updated;

Is there a ParenetData? So what does it do?

If you search for where parentData is called, you can see several more places where parentData is actually used

  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; }Copy the code
  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); }Copy the code
  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); }}Copy the code

Basically, update the paintOffset because the hitTest and Piant methods need it;

conclusion

The SliverPadding is not complicated. The core logic is to modify the constraints by subtracting the padding and passing it to the child for layout.

This brings us to the third important part of the ListView body: the SliverList