摘要:開始繼續接著分析相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深的體會。關于屬性,指前一個組件的布局區域和繪制區域重疊了。
開始
繼續接著分析Flutter相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深的體會。
Sliver布局Flutter存在著兩大布局體系(就目前分析),一個是Box布局,還有另外一個就是Sliver布局;但是Sliver布局明顯比Box會更加復雜,這真是一個坎,那么為啥說Sliver更加復雜尼,請看一下對比:
首先是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, }) }
兩者一對比,Box布局明顯參數更少,也更直觀:maxWidth,width,minWidth這些一看就明白其起到的作用;但是Sliver布局無論輸入輸出都是一大堆參數,這些參數究竟起到什么作用,為什么需要這些參數,不看代碼真的很難明白。
Viewport組件其實介紹Sliver布局,必須得先介紹Viewport組件,因為Sliver相關組件需要在Viewport組件下使用,而Viewport組件的主要作用就是提供滾動機制,可以根據傳入的offset參數來顯示特定的內容;在Flutter中并不像web只需在每個元素樣式上加上overflow: auto,元素內容就可以自動滾動,這是因為Flutter主要一個思想就是萬物皆組件,無論樣式還是布局或者功能都是以組件形式出現。
class Viewport extends MultiChildRenderObjectWidget { Viewport({ Key key, this.axisDirection: AxisDirection.down, //主軸方向,默認往下 this.crossAxisDirection, //縱軸方向 this.anchor: 0.0, //決定scrollOffset = 0分割線在viewport的位置(0 <= anchor <= 1.0) @required this.offset, //viewport偏移位置 this.center, //標記哪個作為center組件 Listslivers: const [], //sliver組件雙向列表 }) }
雖然簡單描述了各個參數的作用,但是還是不夠直觀。。。還是畫圖吧:
首先上圖整個可以看到Center參數的作用可以標出整個列表應該以哪個組件為基線來布局,Center組件始終在scrollOffset = 0.0的初始線上開始布局,而anchor參數則可以控制scrollOffset = 0.0這個初始線在Viewport上的位置,這里設置的是0.3,所以初始線的位置是距離頂端506 * .3 = 151.8這個位置上放置的。
雖然這樣好像把參數的作用都搞清楚了,但是仍然沒有知道為什么需要這些參數,繼續深入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里面存在一個循環,只要哪個元素布局的過程中需要調整滾動的偏移量,就會更新滾動偏移量之后再重新布局,但是重新布局的次數不能超過_kMaxLayoutCycles也就是10次,這里也是明顯從性能考慮;
另外Center組件還有一個centerOffsetAdjustment屬性,例如centerOffsetAdjustment為50.0的時候,Center組件就會再原來基礎上往上50.0,但是這里的處理可以看到只是等同于改變了滾動偏移量,增加50.0的偏移位置,所做到的效果。
然后直接把Viewport的寬高和調整后的滾動偏移量傳入_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, ); }
這里先提前說一下兩個關鍵屬性layoutOffset和remainingPaintExtent:
layoutOffset表示組件在Viewport中偏移多少距離才開始布局,而remainingPaintExtent表示在Viewport中剩余繪制區域大小,一旦remainingPaintExtent為0的時候,控件是不需要繪制的,因為就算繪制了用戶也看不到。
而這幾行代碼:
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));
就是計算這兩個關鍵屬性過程,可以假設centerOffset為0.0的時候,clampedForwardCenter就等于0.0,clampedReverseCenter 等于 mainAxisExtent;所以也就等于layoutOffset等于0.0,remainingPaintExtent等于mainAxisExtent。
接著分析,當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; }
Center前面的組件會一個接一個布局,但是對于Center前面的組件,剛才描述layoutOffset和remainingPaintExtent的圖得要倒著來看,也就是說會變成這樣:
所以Center組件其實就是一個分割線把內容分成上下兩部分,一部分順著Viewport主軸方向,另外一部分是反主軸的方向發展的,再看看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; }
這個方法比較長,而且沒法精簡了。
scrollOffset屬性表示超出Viewport邊界的距離,這里可以看到傳進來的scrollOffset是必須大于等于0,也就是說scrollOffset其實等同于web的scrollTop屬性了,但是如果scrollOffset大于0的時候,layoutOffset必然是等于0,remainingPaintExtent必然等于mainAxisExtent,只要聯想一下剛才的圖的就可以推出他們的關系了。
關于SliverConstraints.overlap屬性,指前一個Sliver組件的layoutExtent(布局區域)和paintExtent(繪制區域)重疊了。
這里紅色部分比綠色部分多出地方及時overlap的大小
但是也受SliverGeometry.paintOrigin影響,所以必須計算在內:
所以這里計算是這樣:首先layoutOffset + paintOrigin + paintExtent = maxPaintOffset;再layoutOffset += layoutExtent;最后maxPintOffset - layoutOffset = 下個sliver的overlap。
final double effectiveLayoutOffset = layoutOffset + childLayoutGeometry.paintOrigin; maxPaintOffset = math.max(effectiveLayoutOffset + childLayoutGeometry.paintExtent, maxPaintOffset); scrollOffset -= childLayoutGeometry.scrollExtent; layoutOffset += childLayoutGeometry.layoutExtent;
而layoutOffset不停增加,最終導致remainingPaintExtent變成0.0,也就是告訴Sliver無需繪制了,而remainingPaintExtent為0.0的Sliver,最終計算的SliverGeometry的paintExtent和layoutExtent一般都是0.0,唯有scrollExtent不能為0.0,因為這個值需要加起來,決定下次是否能夠繼續滾動。
還有SliverGeometry.scrollOffsetCorrection屬性的作用,這個值只要返回不是0.0,就會觸發Viewport根據這個值修正偏移量后重新布局(這里存在的一個用途可能是滑動翻頁的時候每次都能定位每一頁的開始)
結束?當然沒有,下次接著寫,Sliver布局還有挺多可以挖掘的地方,今天先到這里。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/113386.html
摘要:開始繼續接著分析相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深的體會。關于屬性,指前一個組件的布局區域和繪制區域重疊了。 開始 繼續接著分析Flutter相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深...
摘要:開始繼續接著分析相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深的體會。關于屬性,指前一個組件的布局區域和繪制區域重疊了。 開始 繼續接著分析Flutter相關的樣式和布局控件,但是這次內容難度感覺比較高,怕有分析不到位的地方,所以這次僅僅當做一個參考,大家最好可以自己閱讀一下代碼,應該會有更深...
摘要:但是好像反其道而行之,樣式糅合在結構里面,這樣究竟有啥意思尼首先應該是一個性能的考慮,瀏覽器解析其實也是一個性能消耗點,沒有解析自然也可以加快頁面的顯示。 開始 搞前端的同學可能都習慣了CSS局部的思維,過去也出現過一些跟布局或者樣式相關的標簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經不推薦使用。但是在Flutter里...
摘要:但是好像反其道而行之,樣式糅合在結構里面,這樣究竟有啥意思尼首先應該是一個性能的考慮,瀏覽器解析其實也是一個性能消耗點,沒有解析自然也可以加快頁面的顯示。 開始 搞前端的同學可能都習慣了CSS局部的思維,過去也出現過一些跟布局或者樣式相關的標簽,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已經不推薦使用。但是在Flutter里...
閱讀 2240·2021-11-23 09:51
閱讀 1073·2021-11-22 15:35
閱讀 4831·2021-11-22 09:34
閱讀 1597·2021-10-08 10:13
閱讀 3017·2021-07-22 17:35
閱讀 2517·2019-08-30 15:56
閱讀 3079·2019-08-29 18:44
閱讀 3089·2019-08-29 15:32