【Flutter】熊孩子拆元件系列之拆ListView(六)—— SliverPadding
theme: condensed-night-purple
「這是我參與11月更文挑戰的第2天,活動詳情檢視:2021最後一次更文挑戰」。
前言
在涉及到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方法,其中又這麼一行:
SliverPadding就是在這個方法中被構造出來的:
邏輯也挺簡單的,說白了,設定過padding就按你設定的來,沒有設定過的,就通過MediaQurey查詢一個值出來,按那個來;
至於為啥這麼搞?是為了方便統一做材料設計的規範??
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
拋開一堆構造器,其實所做的事就一件,讓padding通過解析,最後生成 _resolvedPadding,這個才是需要的東西;
不過也可以發現一個東西:
在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的地方,可以看到實際使用位置又這麼幾個地方
- 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;
}
- 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);
}
- 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了
- 【Flutter】小說閱讀器改版 —— 翻頁動畫(三)
- 【Flutter】小說閱讀器改版 (六)—— 在動畫播放中攔截手勢
- 【Flutter】小說閱讀器改版 (五)—— 整合ScrollActivity
- 【Flutter】小說閱讀器改版 (四)—— 讓ScrollActivity追蹤手勢最新位置
- 【Flutter】小說閱讀器改版 (三)—— 實現支援 Drag 的ScrollActivity
- 【Flutter】小說閱讀器改版 (二)—— 改進一下模擬翻頁的效果
- 【Flutter】小說閱讀器改版 (一)—— 模擬翻頁的思路優化
- 【Flutter】自定義ListView開發記錄(五)—— 提供手勢等資訊
- 【Flutter】自定義ListView開發記錄(四)—— 關於ParentData的設想和分析與簡單實踐
- 【Flutter】自定義ListView開發記錄(三)—— 處理HitTest手勢事件
- 【Flutter】自定義ListView開發記錄(二)——設計LayoutManager
- 【Flutter】自定義ListView開發記錄(一)——設計滑動效果的處理方式
- 【Flutter】熊孩子拆元件系列之拆ListView(十)—— 按自己的方式組裝修改ListView
- 【Flutter】熊孩子拆元件系列之拆ListView(九)—— AutomaticKeepAlive和KeepAlive
- 【Flutter】熊孩子拆元件系列之拆ListView(八)—— SliverList的運作機制
- 【Flutter】熊孩子拆元件系列之拆ListView(七)—— SliverList的基礎結構
- 【Flutter】熊孩子拆元件系列之拆ListView(六)—— SliverPadding
- 【Flutter】熊孩子拆元件系列之拆ListView(五)—— ViewPort
- 【Flutter】熊孩子拆元件系列之拆ListView(四)—— _ScrollableScope
- 【Flutter】熊孩子拆元件系列之拆ListView(三)—— GlowingOverscrollIndicator