classSliverConstraintsextendsConstraints{
/// Creates sliver constraints with the given information.////// All of the argument must not be null.const SliverConstraints({
//滾動的方向@requiredthis.axisDirection,
//這個是給center使用的,center之前的sliver是顛倒的@requiredthis.growthDirection,
//使用者手勢的方向@requiredthis.userScrollDirection,
//滾動的偏移量,注意這裡是針對這個Sliver的,而且非整個Slivers的總滾動偏移量@requiredthis.scrollOffset,
//前面Slivers的總的大小@requiredthis.precedingScrollExtent,
//為pinned和floating設計的,如果前一個Sliver繪製大小為100,但是佈局大小隻有50,那麼這個Sliver的overlap為50.@requiredthis.overlap,
//還有多少內容可以繪製,參考viewport以及cache。比如多Slivers的時候,前一個佔了100,那麼後面能繪製的區域就要減掉前面繪製的區域大小,得到剩餘的繪製區域大小@requiredthis.remainingPaintExtent,
//縱軸的大小@requiredthis.crossAxisExtent,
//縱軸的方向,這裡會影響GridView同一行元素的擺放順序,是0~x,還是x~0@requiredthis.crossAxisDirection,
//viewport中還有多少內容可以繪製@requiredthis.viewportMainAxisExtent,
//剩餘的快取區域大小@requiredthis.remainingCacheExtent,
//相對於scrollOffset快取區域大小@requiredthis.cacheOrigin,
})
複製程式碼
@immutableclassSliverGeometryextendsDiagnosticable{
/// Creates an object that describes the amount of space occupied by a sliver.////// If the [layoutExtent] argument is null, [layoutExtent] defaults to the/// [paintExtent]. If the [hitTestExtent] argument is null, [hitTestExtent]/// defaults to the [paintExtent]. If [visible] is null, [visible] defaults to/// whether [paintExtent] is greater than zero.////// The other arguments must not be null.const SliverGeometry({
//預估的Sliver能夠滾動大小this.scrollExtent = 0.0,
//對後一個的overlap屬性有影響,它小於[SliverConstraints.remainingPaintExtent],為Sliver在viewport範圍(包含cache)內第一個元素到最後一個元素的大小this.paintExtent = 0.0,
//相對Sliver位置的繪製起點this.paintOrigin = 0.0,
//這個sliver在viewport的第一個顯示位置到下一個sliver的第一個顯示位置的大小double layoutExtent,
//最大能繪製的總大小,這個引數是用於[SliverConstraints.remainingPaintExtent] 是無窮大的,就是使用在shrink-wrapping viewport中this.maxPaintExtent = 0.0,
//如果sliver被pinned在邊界的時候,這個大小為Sliver的自身的高度。其他情況為0this.maxScrollObstructionExtent = 0.0,
//點選有效區域的大小,預設為paintExtentdouble hitTestExtent,
//可見,paintExtent為0不可見。bool visible,
//是否需要做clip,免得chidren溢位this.hasVisualOverflow = false,
//viewport layout sliver的時候,如果sliver出現了一些問題,那麼這個值將不等於0,通過這個值來修正整個滾動的ScrollOffsetthis.scrollOffsetCorrection,
//該Sliver使用了多少[SliverConstraints.remainingCacheExtent],針對多Slivers的情況double cacheExtent,
})
複製程式碼
// Find the last child that is at or before the scrollOffset.
RenderBox earliestUsefulChild = firstChild;
//當第一個child的layoutOffset小於我們的滾動位置的時候,說明前面是空的,如果在第一個child的簽名插入一個新的child來填充for (double earliestScrollOffset =
childScrollOffset(earliestUsefulChild);
earliestScrollOffset > scrollOffset;
earliestScrollOffset = childScrollOffset(earliestUsefulChild)) {
// We have to add children before the earliestUsefulChild.// 這裡就是在插入新的child
earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
//處理當前面已經沒有child的時候if (earliestUsefulChild == null) {
final SliverMultiBoxAdaptorParentData childParentData = firstChild.parentData as SliverMultiBoxAdaptorParentData;
childParentData.layoutOffset = 0.0;
//已經到0.0的位置了,所以不需要再向前找了,breakif (scrollOffset == 0.0) {
// insertAndLayoutLeadingChild only lays out the children before// firstChild. In this case, nothing has been laid out. We have// to lay out firstChild manually.
firstChild.layout(childConstraints, parentUsesSize: true);
earliestUsefulChild = firstChild;
leadingChildWithLayout = earliestUsefulChild;
trailingChildWithLayout ??= earliestUsefulChild;
break;
} else {
// We ran out of children before reaching the scroll offset.// We must inform our parent that this sliver cannot fulfill// its contract and that we need a scroll offset correction.// 這裡就是我們上一章講的,出現出錯了。將scrollOffsetCorrection設定為不為0,傳遞給viewport,這樣它會整體重新移除掉這個差值,重新進行layout佈局。
geometry = SliverGeometry(
scrollOffsetCorrection: -scrollOffset,
);
return;
}
}
/// 滾動的位置減掉firstChild的大小,用來繼續計算是否還需要插入更多child來補足前面。finaldouble firstChildScrollOffset = earliestScrollOffset - paintExtentOf(firstChild);
// firstChildScrollOffset may contain double precision error// 同樣的道理,如果發現最終減掉之後,數值小於0.0(precisionErrorTolerance這是一個接近0.0的極小數)的話,肯定是不對的,所以又告訴viewport移除掉差值,重新佈局if (firstChildScrollOffset < -precisionErrorTolerance) {
// The first child doesn't fit within the viewport (underflow) and// there may be additional children above it. Find the real first child// and then correct the scroll position so that there's room for all and// so that the trailing edge of the original firstChild appears where it// was before the scroll offset correction.// TODO(hansmuller): do this work incrementally, instead of all at once,// i.e. find a way to avoid visiting ALL of the children whose offset// is < 0 before returning for the scroll correction.double correction = 0.0;
while (earliestUsefulChild != null) {
assert(firstChild == earliestUsefulChild);
correction += paintExtentOf(firstChild);
earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
}
geometry = SliverGeometry(
scrollOffsetCorrection: correction - earliestScrollOffset,
);
final SliverMultiBoxAdaptorParentData childParentData = firstChild.parentData as SliverMultiBoxAdaptorParentData;
childParentData.layoutOffset = 0.0;
return;
}
// ok,這裡就是正常的情況final SliverMultiBoxAdaptorParentData childParentData = earliestUsefulChild.parentData as SliverMultiBoxAdaptorParentData;
// 設定child繪製的開始點
childParentData.layoutOffset = firstChildScrollOffset;
assert(earliestUsefulChild == firstChild);
leadingChildWithLayout = earliestUsefulChild;
trailingChildWithLayout ??= earliestUsefulChild;
}
複製程式碼
bool inLayoutRange = true;
RenderBox child = earliestUsefulChild;
int index = indexOf(child);
double endScrollOffset = childScrollOffset(child) + paintExtentOf(child);
bool advance() { // returns true if we advanced, false if we have no more children// This function is used in two different places below, to avoid code duplication.assert(child != null);
if (child == trailingChildWithLayout)
inLayoutRange = false;
child = childAfter(child);
///不在render tree裡面if (child == null)
inLayoutRange = false;
index += 1;
if (!inLayoutRange) {
if (child == null || indexOf(child) != index) {
// We are missing a child. Insert it (and lay it out) if possible.//不在樹裡面,嘗試新增進去
child = insertAndLayoutChild(childConstraints,
after: trailingChildWithLayout,
parentUsesSize: true,
);
if (child == null) {
// We have run out of children.returnfalse;
}
} else {
// Lay out the child.
child.layout(childConstraints, parentUsesSize: true);
}
trailingChildWithLayout = child;
}
assert(child != null);
final SliverMultiBoxAdaptorParentData childParentData = child.parentData as SliverMultiBoxAdaptorParentData;
//設定繪製位置
childParentData.layoutOffset = endScrollOffset;
assert(childParentData.index == index);
//設定endScrollOffset為child的繪製結束位置
endScrollOffset = childScrollOffset(child) + paintExtentOf(child);
returntrue;
}
複製程式碼
// Find the first child that ends after the scroll offset.while (endScrollOffset < scrollOffset) {
//如果是小於,說明需要被回收,這裡+1記錄一下。
leadingGarbage += 1;
if (!advance()) {
assert(leadingGarbage == childCount);
assert(child == null);
//找到最後都沒有滿足的話,將以最後一個child為準// we want to make sure we keep the last child around so we know the end scroll offset
collectGarbage(leadingGarbage - 1, 0);
assert(firstChild == lastChild);
finaldouble extent = childScrollOffset(lastChild) + paintExtentOf(lastChild);
geometry = SliverGeometry(
scrollExtent: extent,
paintExtent: 0.0,
maxPaintExtent: extent,
);
return;
}
}
複製程式碼
// Now find the first child that ends after our end.// 直到佈局區域的結束位置while (endScrollOffset < targetEndScrollOffset) {
if (!advance()) {
reachedEnd = true;
break;
}
}
// Finally count up all the remaining children and label them as garbage.//到上面位置是需要佈局的最後一個child,所以在它之後的child就是需要被回收的if (child != null) {
child = childAfter(child);
while (child != null) {
trailingGarbage += 1;
child = childAfter(child);
}
}
複製程式碼
// At this point everything should be good to go, we just have to clean up// the garbage and report the geometry.// 使用之前計算出來的回收引數
collectGarbage(leadingGarbage, trailingGarbage);
@protectedvoid collectGarbage(int leadingGarbage, int trailingGarbage) {
assert(_debugAssertChildListLocked());
assert(childCount >= leadingGarbage + trailingGarbage);
invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
//從第一個向後刪除while (leadingGarbage > 0) {
_destroyOrCacheChild(firstChild);
leadingGarbage -= 1;
}
//從最後一個向前刪除while (trailingGarbage > 0) {
_destroyOrCacheChild(lastChild);
trailingGarbage -= 1;
}
// Ask the child manager to remove the children that are no longer being// kept alive. (This should cause _keepAliveBucket to change, so we have// to prepare our list ahead of time.)
_keepAliveBucket.values.where((RenderBox child) {
final SliverMultiBoxAdaptorParentData childParentData = child.parentData as SliverMultiBoxAdaptorParentData;
return !childParentData.keepAlive;
}).toList().forEach(_childManager.removeChild);
assert(_keepAliveBucket.values.where((RenderBox child) {
final SliverMultiBoxAdaptorParentData childParentData = child.parentData as SliverMultiBoxAdaptorParentData;
return !childParentData.keepAlive;
}).isEmpty);
});
}
void _destroyOrCacheChild(RenderBox child) {
final SliverMultiBoxAdaptorParentData childParentData = child.parentData as SliverMultiBoxAdaptorParentData;
//如果child被標記為快取的話,從tree中移除並且放入快取中if (childParentData.keepAlive) {
assert(!childParentData._keptAlive);
remove(child);
_keepAliveBucket[childParentData.index] = child;
child.parentData = childParentData;
super.adoptChild(child);
childParentData._keptAlive = true;
} else {
assert(child.parent == this);
//直接移除
_childManager.removeChild(child);
assert(child.parent == null);
}
}
複製程式碼
///override this method, so that you can handle raw image data,///for example, compress
Future<ui.Codec> instantiateImageCodec(
Uint8List data, DecoderCallback decode) async {
_rawImageData = data;
returnawait decode(data);
}
複製程式碼