摘要:開始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深的體會(huì)。關(guān)于屬性,指前一個(gè)組件的布局區(qū)域和繪制區(qū)域重疊了。
開始
繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深的體會(huì)。
Sliver布局Flutter存在著兩大布局體系(就目前分析),一個(gè)是Box布局,還有另外一個(gè)就是Sliver布局;但是Sliver布局明顯比Box會(huì)更加復(fù)雜,這真是一個(gè)坎,那么為啥說Sliver更加復(fù)雜尼,請(qǐng)看一下對(duì)比:
首先是Box布局,主要看輸入的BoxConstraints(約束)和輸出Size(尺寸)
class BoxConstraints extends Constraints { const BoxConstraints({ this.minWidth: 0.0, this.maxWidth: double.infinity, this.minHeight: 0.0, this.maxHeight: double.infinity }); }
class Size extends OffsetBase { const Size(double width, double height) : super(width, height); }
而Sliver布局,SliverConstraints(約束)和輸出SliverGeometry
class SliverConstraints extends Constraints { const SliverConstraints({ @required this.axisDirection, @required this.growthDirection, @required this.userScrollDirection, @required this.scrollOffset, @required this.overlap, @required this.remainingPaintExtent, @required this.crossAxisExtent, @required this.crossAxisDirection, @required this.viewportMainAxisExtent, }) }
class SliverGeometry extends Diagnosticable { const SliverGeometry({ this.scrollExtent: 0.0, this.paintExtent: 0.0, this.paintOrigin: 0.0, double layoutExtent, this.maxPaintExtent: 0.0, this.maxScrollObstructionExtent: 0.0, double hitTestExtent, bool visible, this.hasVisualOverflow: false, this.scrollOffsetCorrection, }) }
兩者一對(duì)比,Box布局明顯參數(shù)更少,也更直觀:maxWidth,width,minWidth這些一看就明白其起到的作用;但是Sliver布局無論輸入輸出都是一大堆參數(shù),這些參數(shù)究竟起到什么作用,為什么需要這些參數(shù),不看代碼真的很難明白。
Viewport組件其實(shí)介紹Sliver布局,必須得先介紹Viewport組件,因?yàn)镾liver相關(guān)組件需要在Viewport組件下使用,而Viewport組件的主要作用就是提供滾動(dòng)機(jī)制,可以根據(jù)傳入的offset參數(shù)來顯示特定的內(nèi)容;在Flutter中并不像web只需在每個(gè)元素樣式上加上overflow: auto,元素內(nèi)容就可以自動(dòng)滾動(dòng),這是因?yàn)镕lutter主要一個(gè)思想就是萬物皆組件,無論樣式還是布局或者功能都是以組件形式出現(xiàn)。
class Viewport extends MultiChildRenderObjectWidget { Viewport({ Key key, this.axisDirection: AxisDirection.down, //主軸方向,默認(rèn)往下 this.crossAxisDirection, //縱軸方向 this.anchor: 0.0, //決定scrollOffset = 0分割線在viewport的位置(0 <= anchor <= 1.0) @required this.offset, //viewport偏移位置 this.center, //標(biāo)記哪個(gè)作為center組件 Listslivers: const [], //sliver組件雙向列表 }) }
雖然簡單描述了各個(gè)參數(shù)的作用,但是還是不夠直觀。。。還是畫圖吧:
首先上圖整個(gè)可以看到Center參數(shù)的作用可以標(biāo)出整個(gè)列表應(yīng)該以哪個(gè)組件為基線來布局,Center組件始終在scrollOffset = 0.0的初始線上開始布局,而anchor參數(shù)則可以控制scrollOffset = 0.0這個(gè)初始線在Viewport上的位置,這里設(shè)置的是0.3,所以初始線的位置是距離頂端506 * .3 = 151.8這個(gè)位置上放置的。
雖然這樣好像把參數(shù)的作用都搞清楚了,但是仍然沒有知道為什么需要這些參數(shù),繼續(xù)深入RenderViewport,了解一下布局的核心。
直接跳到performLayout方法:
void performLayout() { ... final double centerOffsetAdjustment = center.centerOffsetAdjustment; double correction; int count = 0; do { assert(offset.pixels != null); correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels + centerOffsetAdjustment); if (correction != 0.0) { offset.correctBy(correction); } else { if (offset.applyContentDimensions( math.min(0.0, _minScrollExtent + mainAxisExtent * anchor), math.max(0.0, _maxScrollExtent - mainAxisExtent * (1.0 - anchor)), )) break; } count += 1; } while (count < _kMaxLayoutCycles);
這里可以注意到performLayout里面存在一個(gè)循環(huán),只要哪個(gè)元素布局的過程中需要調(diào)整滾動(dòng)的偏移量,就會(huì)更新滾動(dòng)偏移量之后再重新布局,但是重新布局的次數(shù)不能超過_kMaxLayoutCycles也就是10次,這里也是明顯從性能考慮;
另外Center組件還有一個(gè)centerOffsetAdjustment屬性,例如centerOffsetAdjustment為50.0的時(shí)候,Center組件就會(huì)再原來基礎(chǔ)上往上50.0,但是這里的處理可以看到只是等同于改變了滾動(dòng)偏移量,增加50.0的偏移位置,所做到的效果。
然后直接把Viewport的寬高和調(diào)整后的滾動(dòng)偏移量傳入_attemptLayout方法:
double _attemptLayout(double mainAxisExtent, double crossAxisExtent, double correctedOffset) { _minScrollExtent = 0.0; _maxScrollExtent = 0.0; _hasVisualOverflow = false; final double centerOffset = mainAxisExtent * anchor - correctedOffset; final double clampedForwardCenter = math.max(0.0, math.min(mainAxisExtent, centerOffset)); final double clampedReverseCenter = math.max(0.0, math.min(mainAxisExtent, mainAxisExtent - centerOffset)); final RenderSliver leadingNegativeChild = childBefore(center); if (leadingNegativeChild != null) { // negative scroll offsets final double result = layoutChildSequence( leadingNegativeChild, math.max(mainAxisExtent, centerOffset) - mainAxisExtent, 0.0, clampedReverseCenter, clampedForwardCenter, mainAxisExtent, crossAxisExtent, GrowthDirection.reverse, childBefore, ); if (result != 0.0) return -result; } // positive scroll offsets return layoutChildSequence( center, math.max(0.0, -centerOffset), leadingNegativeChild == null ? math.min(0.0, -centerOffset) : 0.0, clampedForwardCenter, clampedReverseCenter, mainAxisExtent, crossAxisExtent, GrowthDirection.forward, childAfter, ); }
這里先提前說一下兩個(gè)關(guān)鍵屬性layoutOffset和remainingPaintExtent:
layoutOffset表示組件在Viewport中偏移多少距離才開始布局,而remainingPaintExtent表示在Viewport中剩余繪制區(qū)域大小,一旦remainingPaintExtent為0的時(shí)候,控件是不需要繪制的,因?yàn)榫退憷L制了用戶也看不到。
而這幾行代碼:
final double centerOffset = mainAxisExtent * anchor - correctedOffset; final double clampedForwardCenter = math.max(0.0, math.min(mainAxisExtent, centerOffset)); final double clampedReverseCenter = math.max(0.0, math.min(mainAxisExtent, mainAxisExtent - centerOffset));
就是計(jì)算這兩個(gè)關(guān)鍵屬性過程,可以假設(shè)centerOffset為0.0的時(shí)候,clampedForwardCenter就等于0.0,clampedReverseCenter 等于 mainAxisExtent;所以也就等于layoutOffset等于0.0,remainingPaintExtent等于mainAxisExtent。
接著分析,當(dāng)Center組件前面還有組件的時(shí)候,就會(huì)進(jìn)入剛才代碼的處理流程:
if (leadingNegativeChild != null) { // negative scroll offsets final double result = layoutChildSequence( leadingNegativeChild, math.max(mainAxisExtent, centerOffset) - mainAxisExtent, 0.0, clampedReverseCenter, clampedForwardCenter, mainAxisExtent, crossAxisExtent, GrowthDirection.reverse, childBefore, ); if (result != 0.0) return -result; }
Center前面的組件會(huì)一個(gè)接一個(gè)布局,但是對(duì)于Center前面的組件,剛才描述layoutOffset和remainingPaintExtent的圖得要倒著來看,也就是說會(huì)變成這樣:
所以Center組件其實(shí)就是一個(gè)分割線把內(nèi)容分成上下兩部分,一部分順著Viewport主軸方向,另外一部分是反主軸的方向發(fā)展的,再看看layoutChildSequence方法:
double layoutChildSequence( RenderSliver child, double scrollOffset, double overlap, double layoutOffset, double remainingPaintExtent, double mainAxisExtent, double crossAxisExtent, GrowthDirection growthDirection, RenderSliver advance(RenderSliver child), ) { assert(scrollOffset.isFinite); assert(scrollOffset >= 0.0); final double initialLayoutOffset = layoutOffset; final ScrollDirection adjustedUserScrollDirection = applyGrowthDirectionToScrollDirection(offset.userScrollDirection, growthDirection); assert(adjustedUserScrollDirection != null); double maxPaintOffset = layoutOffset + overlap; while (child != null) { assert(scrollOffset >= 0.0); child.layout(new SliverConstraints( axisDirection: axisDirection, growthDirection: growthDirection, userScrollDirection: adjustedUserScrollDirection, scrollOffset: scrollOffset, overlap: maxPaintOffset - layoutOffset, remainingPaintExtent: math.max(0.0, remainingPaintExtent - layoutOffset + initialLayoutOffset), crossAxisExtent: crossAxisExtent, crossAxisDirection: crossAxisDirection, viewportMainAxisExtent: mainAxisExtent, ), parentUsesSize: true); final SliverGeometry childLayoutGeometry = child.geometry; assert(childLayoutGeometry.debugAssertIsValid()); // If there is a correction to apply, we"ll have to start over. if (childLayoutGeometry.scrollOffsetCorrection != null) return childLayoutGeometry.scrollOffsetCorrection; // We use the child"s paint origin in our coordinate system as the // layoutOffset we store in the child"s parent data. final double effectiveLayoutOffset = layoutOffset + childLayoutGeometry.paintOrigin; updateChildLayoutOffset(child, effectiveLayoutOffset, growthDirection); maxPaintOffset = math.max(effectiveLayoutOffset + childLayoutGeometry.paintExtent, maxPaintOffset); scrollOffset -= childLayoutGeometry.scrollExtent; layoutOffset += childLayoutGeometry.layoutExtent; if (scrollOffset <= 0.0) scrollOffset = 0.0; updateOutOfBandData(growthDirection, childLayoutGeometry); // move on to the next child child = advance(child); } // we made it without a correction, whee! return 0.0; }
這個(gè)方法比較長,而且沒法精簡了。
scrollOffset屬性表示超出Viewport邊界的距離,這里可以看到傳進(jìn)來的scrollOffset是必須大于等于0,也就是說scrollOffset其實(shí)等同于web的scrollTop屬性了,但是如果scrollOffset大于0的時(shí)候,layoutOffset必然是等于0,remainingPaintExtent必然等于mainAxisExtent,只要聯(lián)想一下剛才的圖的就可以推出他們的關(guān)系了。
關(guān)于SliverConstraints.overlap屬性,指前一個(gè)Sliver組件的layoutExtent(布局區(qū)域)和paintExtent(繪制區(qū)域)重疊了。
這里紅色部分比綠色部分多出地方及時(shí)overlap的大小
但是也受SliverGeometry.paintOrigin影響,所以必須計(jì)算在內(nèi):
所以這里計(jì)算是這樣:首先layoutOffset + paintOrigin + paintExtent = maxPaintOffset;再layoutOffset += layoutExtent;最后maxPintOffset - layoutOffset = 下個(gè)sliver的overlap。
final double effectiveLayoutOffset = layoutOffset + childLayoutGeometry.paintOrigin; maxPaintOffset = math.max(effectiveLayoutOffset + childLayoutGeometry.paintExtent, maxPaintOffset); scrollOffset -= childLayoutGeometry.scrollExtent; layoutOffset += childLayoutGeometry.layoutExtent;
而layoutOffset不停增加,最終導(dǎo)致remainingPaintExtent變成0.0,也就是告訴Sliver無需繪制了,而remainingPaintExtent為0.0的Sliver,最終計(jì)算的SliverGeometry的paintExtent和layoutExtent一般都是0.0,唯有scrollExtent不能為0.0,因?yàn)檫@個(gè)值需要加起來,決定下次是否能夠繼續(xù)滾動(dòng)。
還有SliverGeometry.scrollOffsetCorrection屬性的作用,這個(gè)值只要返回不是0.0,就會(huì)觸發(fā)Viewport根據(jù)這個(gè)值修正偏移量后重新布局(這里存在的一個(gè)用途可能是滑動(dòng)翻頁的時(shí)候每次都能定位每一頁的開始)
結(jié)束?當(dāng)然沒有,下次接著寫,Sliver布局還有挺多可以挖掘的地方,今天先到這里。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107816.html
摘要:開始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深的體會(huì)。關(guān)于屬性,指前一個(gè)組件的布局區(qū)域和繪制區(qū)域重疊了。 開始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深...
摘要:開始繼續(xù)接著分析相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深的體會(huì)。關(guān)于屬性,指前一個(gè)組件的布局區(qū)域和繪制區(qū)域重疊了。 開始 繼續(xù)接著分析Flutter相關(guān)的樣式和布局控件,但是這次內(nèi)容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當(dāng)做一個(gè)參考,大家最好可以自己閱讀一下代碼,應(yīng)該會(huì)有更深...
摘要:但是好像反其道而行之,樣式糅合在結(jié)構(gòu)里面,這樣究竟有啥意思尼首先應(yīng)該是一個(gè)性能的考慮,瀏覽器解析其實(shí)也是一個(gè)性能消耗點(diǎn),沒有解析自然也可以加快頁面的顯示。 開始 搞前端的同學(xué)可能都習(xí)慣了CSS局部的思維,過去也出現(xiàn)過一些跟布局或者樣式相關(guān)的標(biāo)簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經(jīng)不推薦使用。但是在Flutter里...
摘要:但是好像反其道而行之,樣式糅合在結(jié)構(gòu)里面,這樣究竟有啥意思尼首先應(yīng)該是一個(gè)性能的考慮,瀏覽器解析其實(shí)也是一個(gè)性能消耗點(diǎn),沒有解析自然也可以加快頁面的顯示。 開始 搞前端的同學(xué)可能都習(xí)慣了CSS局部的思維,過去也出現(xiàn)過一些跟布局或者樣式相關(guān)的標(biāo)簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經(jīng)不推薦使用。但是在Flutter里...
閱讀 4001·2021-11-16 11:44
閱讀 5268·2021-10-09 09:54
閱讀 2050·2019-08-30 15:44
閱讀 1712·2019-08-29 17:22
閱讀 2788·2019-08-29 14:11
閱讀 3418·2019-08-26 13:25
閱讀 2353·2019-08-26 11:55
閱讀 1637·2019-08-26 10:37