开始继续分析Flutter相关的样式和布局控件,不过这次内容比较难,怕分析不到位,所以这篇时间仅供参考。最好自己看代码,理解更深。Sliver布局Flutter有两大布局体系(就目前分析的来说),一是Box布局,二是Sliver布局;但是Sliver布局明显比Box布局复杂。这真是一道坎,那为什么Sliver更复杂呢?尼,请看对比:首先是Box布局,主要看输入的BoxConstraints(约束)和输出的Size(大小)classBoxConstraintsextendsConstraints{constBoxConstraints({this.minWidth:0.0,this.maxWidth:double.infinity,this.minHeight:0.0,this.maxHeight:double.infinity});}classSizeextendsOffsetBase{constSize(doublewidth,doubleheight):super(width,height);}而Sliver布局、SliverConstraints(约束)和输出SliverGeometryclassSliverConstraintsextendsConstraints{constSliverConstraints({@requiredthis.axisDirection,@需要this.growthDirection,@requiredthis.userScrollDirection,@requiredthis.scrollOffset,@requiredthis.overlap,@requiredthis.remainingPaintExtent,@requiredthis.crossAxisExtent,@requiredthis.crossAxisDirection,@requiredthis.viewportMainAxisExtent,})}类SliverGeometry扩展可诊断{constSliverGeometry({this.scrollExtent:0.0,this.paintExtent:0.0,this.paintOrigin:0.0,doublelayoutExtent,this.maxPaintExtent:0.0,this.maxScrollObstructionExtent:0.0,doublehitTestExtent,boolvisibleObstructionExtent,this.has.scrollOffsetCorrection,})}两者对比,Box布局显然参数更少,更直观:maxWidth、width、minWidth等,一眼就能明白它们的作用;但是Sliver布局无论输入输出都有很多参数,这些参数的作用是什么,为什么需要这些参数呢?Viewport组件居然引入了Sliver布局,不看代码真的很难理解。必须先引入Viewport组件,因为Sliver相关组件需要在Viewport组件下使用,而Viewport组件的主要作用是提供滚动机制,可以根据传入的offset参数显示具体的内容;在Flutter中,不像web,只需要给每个元素样式加上overflow:auto,元素内容就可以自动滚动了。是因为Flutter的主要思想是一切皆组件,无论样式、布局还是功能,都以组件的形式出现。classViewportextendsMultiChildRenderObjectWidget{Viewport({keykey,this.axisDirection:AxisDirection.down,//轴方向,默认向下this.crossAxisDirection,//垂直轴方向this.anchor:0.0,//确定scrollOffset=0分界线在视口的位置(0<=anchor<=1.0)@requiredthis.offset,//视口偏移位置this.center,//标记哪个是中心组件Listslivers:const[],//sliver组件双向链表})}虽然简单描述了各个参数的作用,但还是不够直观。..我们来画一张图:首先大家可以看到上图中Center参数的作用,它可以标记整个列表应该以哪个组件为基线进行布局。Center组件总是在scrollOffset=0.0的初始行开始布局,anchor参数可以控制scrollOffset=0.0初始行在Viewport上的位置,这里设置为0.3,所以初始行的位置为506*.3=151.8从顶部算起。虽然好像已经弄清楚了参数的作用,但是还是不知道为什么需要这些参数。继续深入RenderViewport,了解布局的核心。直接跳转到performLayout方法:voidperformLayout(){...finaldoublecenterOffsetAdjustment=center.centerOffsetAdjustment;双重修正;整数计数=0;做{断言(offset.pixels!=null);correction=_attemptLayout(mainAxisExtent,crossAxisExtent,offset.pixels+centerOffsetAdjustment);如果(校正!=0.0){offset.correctBy(校正);}else{if(offset.applyContentDimensions(math.min(0.0,_minScrollExtent+mainAxisExtent*anchor),math.max(0.0,_maxScrollExtent-mainAxisExtent*(1.0-anchor)),))中断;}计数+=1;}while(count<_kMaxLayoutCycles);这里可以注意到performLayout中有一个循环,只要元素在布局过程中需要调整偏移量就会更新滚动偏移量然后重新布局,但是重新布局的次数不能超过_kMaxLayoutCycles,这是10倍。这显然也是出于性能考虑;另外,Center组件还有一个centerOffsetAdjustment属性,比如centerOffsetAdjustment为50.0,Center组件会在原来的基础上上调到50.0,但是这里的处理可以看出相当于改变了scrolloffset,增加了偏移位置50.0,效果达到。然后直接把Viewport的宽高和调整后的滚动偏移量传入_attemptLayout方法:double_attemptLayout(doublemainAxisExtent,doublecrossAxisExtent,doublecorrectedOffset){_minScrollExtent=0.0;_maxScrollExtent=0.0;_hasVisualOverflow=假;finaldoublecenterOffset=mainAxisExtent*anchor-correctedOffset;finaldoubleclampedForwardCenter=math.max(0.0,math.min(mainAxisExtent,centerOffset));finaldoubleclampedReverseCenter=math.max(0.0,math.min(mainAxisExtent,mainAxisExtent-centerOffset));finalRenderSliv??erleadingNegativeChild=childBefore(center);if(leadingNegativeChild!=null){//负滚动偏移finaldoubleresult=layoutChildSequence(leadingNegativeChild,math.max(mainAxisExtent,centerOffset)-mainAxisExtent,0.0,clampedReverseCenter,clampedForwardCenter,mainAxisExtent,crossAxisExtent,GrowthDirection.reverse,childBefore,);如果(结果!=0.0)返回-结果;}//正滚动偏移量returnlayoutChildSequence(center,math.max(0.0,-centerOffset),leadingNegativeChild==null?math.min(0.0,-centerOffset):0.0,clampedForwardCenter,clampedReverseCenter,mainAxisExtent,crossAxisExtent,GrowthDirection.forward,孩子之后,);}这里,提前说一下layoutOffset和remainingPaintExtent这两个关键属性:layoutOffset表示组件在开始布局之前在Viewport中偏移了多少,remainingPaintExtent表示remainingPaintExtent在Viewport中绘图区域的大小。一旦remainingPaintExtent为0,控件就不需要绘制了,因为即使绘制了,用户也看不到这些代码行:finaldoublecenterOffset=mainAxisExtent*anchor-correctedOffset;finaldoubleclampedForwardCenter=math.max(0.0,math.min(mainAxisExtent,centerOffset));finaldoubleclampedReverseCenter=math.max(0.0,math.min(mainAxisExtent,mainAxisExtent-centerOffset));就是计算这两个关键属性的过程。可以假设当centerOffset为0.0时,clampedForwardCenter等于0.0,clampedReverseCenter等于mainAxisExtent;所以等于layoutOffset等于0.0,remainingPaintExtent等于mainAxisExtent。然后分析,当Center组件前面有组件时,就会进入刚才代码的处理流程:(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方法:doublelayoutChildSequence(RenderSliv??erchild,doublescrollOffset,doubleoverlap,doublelayoutOffset,doubleremainingPaintExtent,doublemainAxisExtent,doublecrossAxisExtent,GrowthDirectiongrowthDirection,RenderSliv??eradvance(RenderSliv??erchild),){assert(scrollOffset.isFinite);断言(scrollOffset>=0.0);finaldoubleinitialLayoutOffset=layoutOffset;finalScrollDirectionadjustedUserScrollDirection=applyGrowthDirectionToScrollDirection(offset.userScrollDirection,growthDirection);断言(调整用户滚动方向!=空);doublemaxPaintOffset=layoutOffset+overlap;while(child!=null){assert(scrollOffset>=0.0);child.layout(newSliverConstraints(axisDirection:axisDirection,growthDirection:growthDirection,userScrollDirection:adjustedUserScrollDirection,scrollOffset:scrollOffset,重叠:maxPaintOffset-layoutOffset,remainingPaintExtent:math.max(0.0,remainingPaintExtent-layoutOffset+initialLayoutOffset),crossAxisExtent:crossAxisExtent,crossAxisDirection:crossAxisDirection,viewportMainAxisExtent:mainAxisExtent,),parentUsesSize:true);最后的SliverGeometrychildLayoutGeometry=child.geometry;驴rt(childLayoutGeometry.debugAssertIsValid());//如果要应用更正,我们将不得不重新开始。如果(childLayoutGeometry.scrollOffsetCorrection!=null)返回childLayoutGeometry.scrollOffsetCorrection;//我们在我们的坐标系中使用孩子的绘画原点作为我们存储在孩子的父数据中的//layoutOffset。finaldoubleeffectiveLayoutOffset=layoutOffset+childLayoutGeometry.paintOrigin;updateChildLayoutOffset(child,effectiveLayoutOffset,growthDirection);maxPaintOffset=math.max(effectiveLayoutOffset+childLayoutGeometry.paintExtent,maxPaintOffset);scrollOffset-=childLayoutGeometry.scrollExtent;layoutOffset+=childLayoutGeometry.layoutExtent;如果(scrollOffset<=0.0)scrollOffset=0.0;updateOutOfBandData(growthDirection,childLayoutGeometry);//移动到下一个孩子child=advance(child);}//我们做到了更正,哇!返回0.0;}这个方法比较长,不能简化。scrollOffset属性指示超出视口边界的距离。这里可以看到传入的scrollOffset必须大于等于0,也就是说scrollOffset其实相当于现在web的scrollTop属性,但是如果scrollOffset大于0,layoutOffset必须等于0,remainingPaintExtent一定要等于mainAxisExtent,只要想想刚才的图,就可以推导出他们的关系。关于SliverConstraints.overlap属性,意思是前面的Sliver组件的layoutExtent(布局区域)和paintExtent(绘图区域)重叠。这里红色部分多于绿色部分,重叠的大小也受SliverGeometry.paintOrigin的影响,所以必须算进去:所以这里是计算:首先layoutOffset+paintOrigin+paintExtent=maxPaintOffset;然后layoutOffset+=layoutExtent;最后maxPintOffset-layoutOffset=下一个条子的重叠。finaldoubleeffectiveLayoutOffset=layoutOffset+childLayoutGeometry.paintOrigin;maxPaintOffset=math.max(effectiveLayoutOffset+childLayoutGeometry.paintExtent,maxPaintOffset);scrollOffset-=childLayoutGeometry.scrollExtent;layoutOffset+=childLayoutGeometry.layoutExtent;而layoutOffset不停止增加,最终导致remainingPaintExtent变为0.0,也就是告诉Sliver不用画了,remainingPaintExtent对Sliver来说是0.0,SliverGeometry的paintExtent和layoutExtent一般都是最后计算的时候是0.0,但是scrollExtent不能是0.0,因为需要加上这个值来决定下一次滚动是否可以继续。还有SliverGeometry.scrollOffsetCorrection属性的作用。只要这个值不为0.0,就会触发Viewport根据这个值修正偏移量,然后重新布局(这里的一个用途可能是定位每个页面的开始)结束?当然不是,下次继续写。Sliver的布局还有很多值得探索的地方,今天就先到这里吧。