商城網(wǎng)站建設(shè)運營合同天津優(yōu)化公司哪家好
關(guān)于Flutter Sliver組件內(nèi)容可以參考下面這位博主博客,寫的已經(jīng)非常好了,這里就不再贅述。
38、Flutter之 可滾動組件簡介_flutter 可滑動_風(fēng)雨「83」的博客-CSDN博客
通過閱讀上面的博客,我們已經(jīng)知道了Scrollable和Viewport基礎(chǔ)概念,接下來跟隨作者一起,結(jié)合Flutter源碼分析下ViewPort是如何滾動。
先看一個簡單的demo
MaterialApp(home: Scaffold(body: Center(child: Container(height: 300,child: Scrollable(viewportBuilder: (BuildContext context, ViewportOffset position) {return Viewport(offset: position,slivers: [SliverToBoxAdapter(child: Container(width: 100,height: 100,color: Colors.lightBlue,),),SliverToBoxAdapter(child: Container(width: 100,height: 100,color: Colors.pink,),),SliverToBoxAdapter(child: Container(width: 100,height: 100,color: Colors.green,),),SliverToBoxAdapter(child: Container(width: 100,height: 100,color: Colors.black,),),SliverToBoxAdapter(child: Container(width: 100,height: 100,color: Colors.red,),)],);},))),),)
就是一個簡單的滾動列表,可以上下滾動。
?上面是一個簡單的滾動列表,當(dāng)手指觸摸屏幕時,內(nèi)容隨之發(fā)上移動(滾動)。
下面我們從Flutter刷新機制來梳理下列表里面的組件是在什么時機,由誰觸發(fā)重新布局的(組件的位移就是重新布局的體現(xiàn))。
class Viewport extends MultiChildRenderObjectWidget {......@overrideRenderViewport createRenderObject(BuildContext context) {return RenderViewport(axisDirection: axisDirection,crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),anchor: anchor,offset: offset,cacheExtent: cacheExtent,cacheExtentStyle: cacheExtentStyle,clipBehavior: clipBehavior,);}@overridevoid updateRenderObject(BuildContext context, RenderViewport renderObject) {renderObject..axisDirection = axisDirection..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)..anchor = anchor..offset = offset..cacheExtent = cacheExtent..cacheExtentStyle = cacheExtentStyle..clipBehavior = clipBehavior;}
Viewport 繼承MultiChildRenderObjectWidget,與之對應(yīng)的RenderObject是RenderViewport,相關(guān)布局邏輯肯定是在RenderViewport 內(nèi)部實現(xiàn)。
class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentData> {RenderViewport({super.axisDirection,required super.crossAxisDirection,required super.offset,double anchor = 0.0,List<RenderSliver>? children,RenderSliver? center,super.cacheExtent,super.cacheExtentStyle,super.clipBehavior,})
這里我們重點關(guān)注offset這個參數(shù)(其他參數(shù)不是本篇內(nèi)容考慮的范圍),這個參數(shù)在剛剛Viewport 里面賦值,這個參數(shù)是重點,后面會用到。
我們知道RenderViewport管理一個List<RenderSliver> 列表,列表內(nèi)容滾動就是該父組件對子組件列表重新布局的結(jié)果,我們直接找到其performLayou方法
@overridevoid performLayout() {......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 < _maxLayoutCycles);......}());}
performLayout方法中?
correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels + centerOffsetAdjustment);
是我們關(guān)注的重點。
double _attemptLayout(double mainAxisExtent, double crossAxisExtent, double correctedOffset) {......// positive scroll offsetsreturn layoutChildSequence(child: center,scrollOffset: math.max(0.0, -centerOffset),overlap: leadingNegativeChild == null ? math.min(0.0, -centerOffset) : 0.0,layoutOffset: centerOffset >= mainAxisExtent ? centerOffset: reverseDirectionRemainingPaintExtent,remainingPaintExtent: forwardDirectionRemainingPaintExtent,mainAxisExtent: mainAxisExtent,crossAxisExtent: crossAxisExtent,growthDirection: GrowthDirection.forward,advance: childAfter,remainingCacheExtent: forwardDirectionRemainingCacheExtent,cacheOrigin: clampDouble(centerOffset, -_calculatedCacheExtent!, 0.0),);}
我們找到了layoutChildSequence方法,從方法名就能知道,這個方法功能就是對子組件列表按照順序重新布局的。
@protecteddouble layoutChildSequence({required RenderSliver? child,required double scrollOffset,required double overlap,required double layoutOffset,required double remainingPaintExtent,required double mainAxisExtent,required double crossAxisExtent,required GrowthDirection growthDirection,required RenderSliver? Function(RenderSliver child) advance,required double remainingCacheExtent,required double cacheOrigin,}) {......while (child != null) {......child.layout(SliverConstraints(axisDirection: axisDirection,growthDirection: growthDirection,userScrollDirection: adjustedUserScrollDirection,scrollOffset: sliverScrollOffset,precedingScrollExtent: precedingScrollExtent,overlap: maxPaintOffset - layoutOffset,remainingPaintExtent: math.max(0.0, remainingPaintExtent - layoutOffset + initialLayoutOffset),crossAxisExtent: crossAxisExtent,crossAxisDirection: crossAxisDirection,viewportMainAxisExtent: mainAxisExtent,remainingCacheExtent: math.max(0.0, remainingCacheExtent + cacheExtentCorrection),cacheOrigin: correctedCacheOrigin,), parentUsesSize: true);......// move on to the next childchild = advance(child);}// we made it without a correction, whee!return 0.0;}
?這個方法就是在不停循環(huán)獲取下一個child,直到最后一個需要布局的child組件。
大體流程就是這樣的,細心的你一定發(fā)現(xiàn),上面沒有說明組件是如何根據(jù)手指滑動滾動的,也就是如何觸發(fā)RenderViewport 執(zhí)行performLayout方法。
不要急,下面就來說明這一點。
還記得上面提到的offset這個參數(shù)嗎?重頭戲就是它。
set offset(ViewportOffset value) {assert(value != null);if (value == _offset) {return;}if (attached) {_offset.removeListener(markNeedsLayout);}_offset = value;if (attached) {_offset.addListener(markNeedsLayout);}// We need to go through layout even if the new offset has the same pixels// value as the old offset so that we will apply our viewport and content// dimensions.markNeedsLayout();}
當(dāng)上面我們給RenderViewport 配置offset參數(shù)是,offset是一個ChangeNotifer ,可以添加變化監(jiān)聽
abstract class ViewportOffset extends ChangeNotifier {/// Default constructor.////// Allows subclasses to construct this object directly.ViewportOffset();
......
}
當(dāng)offset 只有的變量更新后,通知監(jiān)聽它的回調(diào)函數(shù)markNeedsLayout,這里就回到了Flutter組件渲染大流程里面了。
這個流程下來是不是非常巧妙,希望大家閱讀完后,也能寫出如此巧妙的架構(gòu)。