學習Fullter也有些時間了,寫過很多demo,對一些經常使用的widget使用也比較熟練,可是總以爲對Flutter的框架沒有一個大體的瞭解,碰到有些細節的地方又沒有文檔能夠查詢,例如在寫UI時總不知道爲何container添加了child就變小了;widget中key的做用,雖然官方有解釋可是憑空來說的話有點難理解。因此以爲深刻一點的瞭解Flutter框架仍是頗有必要的。node
flutter的入口main方法直接調用了runApp(Widget app)方法,app參數就是咱們的根視圖的Widget,咱們直接跟進runApp方法canvas
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()//此方法是對flutter的框架作一些必要的初始化
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
複製代碼
runApp方法先調用了WidgetsFlutterBinding.ensureInitialized()方法,這個方法是作一些必要的初始化緩存
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
複製代碼
WidgetsFlutterBinding混入了很多的其餘的Bindingapp
以上是這些Binding的主要做用,在此不作過多贅述,WidgetsFlutterBinding.ensureInitialized()返回的是WidgetsBinding對象,而後立刻調用了WidgetsBinding的attachRootWidget(app)方法,將咱們的根視圖的Widget對象穿進去,咱們繼續看attachRootWidget方法框架
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
複製代碼
建立了一個RenderObjectToWidgetAdapter,讓後直接調用它的attachToRenderTree方法,BuildOwner是Widget framework的管理類less
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
element.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}
複製代碼
element爲空,owner先鎖定狀態,而後調用了RenderObjectToWidgetAdapter的createElement()返回了RenderObjectToWidgetElement對象,讓後將owner賦值給element(assignOwner方法),讓後就是owner調用buildScope方法dom
void buildScope(Element context, [VoidCallback callback]) {
if (callback == null && _dirtyElements.isEmpty)
return;
Timeline.startSync('Build', arguments: timelineWhitelistArguments);
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
_dirtyElementsNeedsResorting = false;
try {
callback();
} finally {}
}
...
}
複製代碼
省略了部分以及後續代碼,能夠看到buildScope方法首先就調用了callback(就是element.mount(null, null)方法),回到RenderObjectToWidgetElement的mount方法ide
@override
void mount(Element parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
複製代碼
首先super.mount(parent, newSlot)調用了RootRenderObjectElement的mount方法(只是斷定parent和newSlot都爲null),讓後又繼續向上調用了RenderObjectElement中的mount方法佈局
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
複製代碼
RenderObjectElement中的mount方法又調用了Element的mount方法post
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner;
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this);
}
_updateInheritance();
}
複製代碼
Element的mount方法其實就是進行了一些賦值,以確認當前Element在整個樹種的位置,讓後回到RenderObjectElement中的mount方法,調用了widget.createRenderObject(this)方法,widget是RenderObjectToWidgetAdapter的實例,它返回的是RenderObjectWithChildMixin對象,讓後調用attachRenderObject方法
@override
void attachRenderObject(dynamic newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();//獲取此RenderObjectElement最近的RenderObjectElement對象
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);//將renderObject插入RenderObjectElement中
final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
///RenderObjectToWidgetElement中的insertChildRenderObject方法,簡單將子RenderObject賦值給父RenderObject的child字段
@override
void insertChildRenderObject(RenderObject child, dynamic slot) {
assert(slot == _rootChildSlot);
assert(renderObject.debugValidateChild(child));
renderObject.child = child;
}
複製代碼
Element的mount方法肯定當前Element在整個樹種的位置並插入,RenderObjectElement中的mount方法來建立RenderObject對象並將其插入到渲染樹中,讓後再回到RenderObjectToWidgetElement方法,mount以後調用_rebuild()方法,_rebuild()方法中主要是調用了Element的updateChild方法
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {//當子Widget沒有的時候,直接將child deactivate掉
if (child != null)
deactivateChild(child);
return null;
}
if (child != null) {//有子Element的時候
if (child.widget == newWidget) {//Widget沒有改變
if (child.slot != newSlot)//再判斷slot有沒有改變,沒有則不更新slot
updateSlotForChild(child, newSlot);//更新child的slot
return child;//返回child
}
if (Widget.canUpdate(child.widget, newWidget)) {//Widget沒有改變,再判斷Widget可否update,若是能仍是重複上面的步驟
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
return child;
}
deactivateChild(child);//若是不能更新的話,直接將child deactivate掉,而後在inflateWidget(newWidget, newSlot)建立新的Element
}
return inflateWidget(newWidget, newSlot);//根據Widget對象以及slot建立新的Element
}
複製代碼
因爲咱們是第一次構建,child是null,因此就直接走到inflateWidget方法建立新的Element對象,跟進inflateWidget方法
@protected
Element inflatinflateWidgeteWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
if (key is GlobalKey) {//newWidget的key是GlobalKey
final Element newChild = _retakeInactiveElement(key, newWidget);//複用Inactive狀態的Element
if (newChild != null) {
newChild._activateWithParent(this, newSlot);//activate 此Element(將newChild出入到Element樹)
final Element updatedChild = updateChild(newChild, newWidget, newSlot);//直接將newChild更新
return updatedChild;//返回更新後的Element
}
}
final Element newChild = newWidget.createElement();//調用createElement()進行建立
newChild.mount(this, newSlot);//繼續調用newChild Element的mount方法(如此就行一直遞歸下去,當遞歸完成,整個構建過程也就結束了)
return newChild;//返回子Element
}
複製代碼
inflateWidget中其實就是經過Widget獲得Element對象,讓後繼續調用子Element的mount的方將進行遞歸。
不一樣的Element,mount的實現會有所不一樣,咱們看一下比較經常使用的StatelessElement、StatefulElement,他們的mount方法實如今ComponentElement中
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
void _firstBuild() {
rebuild();//調用了Element的rebuild()方法
}
//Element的rebuild方法,一般被三處地方調用
//1.當BuildOwner.scheduleBuildFor被調用標記此Element爲dirty時
//2.當Element第一次構建由mount方法去調用
//3.當Widget改變時,被update方法調用
void rebuild() {
if (!_active || !_dirty)
return;
performRebuild();//調用performRebuild方法(抽象方法)
}
//ComponentElement的performRebuild實現
@override
void performRebuild() {
Widget built;
try {
built = build();//構建Widget(StatelessElement直接調用build方法,StatefulElement直接調用state.build方法)
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));//有錯誤的化就建立一個ErrorWidget
} finally {
_dirty = false;
}
try {
_child = updateChild(_child, built, slot);//讓後仍是根據Wdiget來更新子Element
} catch (e, stack) {
built = ErrorWidget.builder(_debugReportException('building $this', e, stack));
_child = updateChild(null, built, slot);
}
}
複製代碼
再看一看MultiChildRenderObjectElement的mount方法
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_children = List<Element>(widget.children.length);
Element previousChild;
for (int i = 0; i < _children.length; i += 1) {
final Element newChild = inflateWidget(widget.children[i], previousChild);//遍歷children直接inflate根據Widget建立新的Element
_children[i] = newChild;
previousChild = newChild;
}
}
複製代碼
能夠看到不一樣的Element構建方式會有些不一樣,Element(第一層Element)的mount方法主要是肯定當前Element在整個樹種的位置並插入;ComponentElement(第二層)的mount方法先構建Widget樹,讓後再遞歸更新(包括重用,更新,直接建立inflate)其Element樹;RenderObjectElement(第二層)中的mount方法來建立RenderObject對象並將其插入到渲染樹中。MultiChildRenderObjectElement(RenderObjectElement的子類)在RenderObjectElement還要繼續建立children Element。
總結:首先是由WidgetBinding建立RenderObjectToWidgetAdapter而後調用它的attachToRenderTree方法,建立了RenderObjectToWidgetElement對象,讓後將它mount(調用mount方法),mount方法中調用的_rebuild,繼而調用updateChild方法,updateChild會進行遞歸的更新Element樹,若child沒有則須要從新建立新的Element,讓後將其mount進Element樹中(若是是RenderobjectElement的化,mount的過程當中會去建立RenderObject對象,並插入到RenderTree)。
一般咱們在應用中要更新狀態都是經過State中的setState方法來觸發界面重繪,setState方法就是先調用了callback讓後調用該State的Element對象的markNeedsBuild方法,markNeedsBuild中將Element標記爲dirty並經過BuildOwner將其添加到dirty列表中並調用onBuildScheduled回調(在WidgetsBinding初始化時設置的,它回去調用window.scheduleFrame方法),讓後window的onBeginFrame,onDrawFrame回調(在SchedulerBinding初始化時設置的,這兩個回調會執行一些callback)會被調用,SchedulerBinding經過persisterCallbacks來調用到BuildOwner中buildScope方法。上面咱們只看了buildScope的一部分,當經過setState方法來觸發界面重繪時,buildScope的callBack爲null
void buildScope(Element context, [VoidCallback callback]) {
if (callback == null && _dirtyElements.isEmpty)
return;
Timeline.startSync('Build', arguments: timelineWhitelistArguments);
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
Element debugPreviousBuildTarget;
_dirtyElementsNeedsResorting = false;
try {
callback();//調用callback
} finally {}
}
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
_dirtyElements[index].rebuild();//遍歷dirtyElements並執行他們的rebuild方法來使這些Element進行rebuild
} catch (e, stack) {}
index += 1;
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
while (index > 0 && _dirtyElements[index - 1].dirty) {
index -= 1;
}
}
}
} finally {
for (Element element in _dirtyElements) {//最後解除Element的dirty標記,以及清空dirtyElements
assert(element._inDirtyList);
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
Timeline.finishSync();
}
}
複製代碼
很明顯就是對dirtyElements中的元素進行遍歷而且對他們進行rebuild。
window經過scheduleFrame方法會讓SchedulerBinding來執行handleBeginFrame方法(執行transientCallbacks)和handleDrawFrame方法(執行persistentCallbacks,postFrameCallbacks),在RendererBinding初始化時添加了_handlePersistentFrameCallback,它調用了核心的繪製方法drawFrame。
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();//佈局
pipelineOwner.flushCompositingBits();//刷新dirty的renderobject的數據
pipelineOwner.flushPaint();//繪製
renderView.compositeFrame(); // 將二進制數據發送給GPU
pipelineOwner.flushSemantics(); // 將語義發送給系統
}
複製代碼
flushLayout觸發佈局,將RenderObject樹的dirty節點經過調用performLayout方法進行逐一佈局,咱們先看一下RenderPadding中的實現
@override
void performLayout() {
_resolve();//解析padding參數
if (child == null) {//若是沒有child,直接將constraints與padding綜合計算得出本身的size
size = constraints.constrain(Size(
_resolvedPadding.left + _resolvedPadding.right,
_resolvedPadding.top + _resolvedPadding.bottom
));
return;
}
final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);//將padding減去,生成新的約束innerConstraints
child.layout(innerConstraints, parentUsesSize: true);//用新的約束去佈局child
final BoxParentData childParentData = child.parentData;
childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top);//設置childParentData的offset值(這個值是相對於parent的繪製偏移值,在paint的時候傳入這個偏移值)
size = constraints.constrain(Size(//將constraints與padding以及child的sieze綜合計算得出本身的size
_resolvedPadding.left + child.size.width + _resolvedPadding.right,
_resolvedPadding.top + child.size.height + _resolvedPadding.bottom
));
}
複製代碼
能夠看到RenderPadding中的佈局分兩種狀況。若是沒有child,那麼就直接拿parent傳過來的約束以及padding來肯定本身的大小;不然就先去佈局child,讓後再拿parent傳過來的約束和padding以及child的size來肯定本身的大小。RenderPadding是典型的單child的RenderBox,咱們看一下多個child的RenderBox。例如RenderFlow
@override
void performLayout() {
size = _getSize(constraints);//直接先肯定本身的size
int i = 0;
_randomAccessChildren.clear();
RenderBox child = firstChild;
while (child != null) {//遍歷孩子
_randomAccessChildren.add(child);
final BoxConstraints innerConstraints = _delegate.getConstraintsForChild(i, constraints);//獲取child的約束,此方法爲抽象
child.layout(innerConstraints, parentUsesSize: true);//佈局孩子
final FlowParentData childParentData = child.parentData;
childParentData.offset = Offset.zero;
child = childParentData.nextSibling;
i += 1;
}
}
複製代碼
能夠看到RenderFlow的size直接就根據約束來肯定了,並沒去有先佈局孩子,因此RenderFlow的size不依賴與孩子,後面依舊是對每個child依次進行佈局。
還有一種比較典型的樹尖類型的RenderBox,LeafRenderObjectWidget子類建立的RenderObject對象都是,他們沒有孩子,他們纔是最終須要渲染的對象,例如
@override
void performLayout() {
size = _sizeForConstraints(constraints);
}
複製代碼
很是簡單就經過約束肯定本身的大小就結束了。因此performLayout過程就是兩點,肯定本身的大小以及佈局孩子。咱們上面提到的都是RenderBox的子類,這些RenderObject約束都是經過BoxConstraints來完成,可是RenderSliver的子類的約束是經過SliverConstraints來完成,雖然他們對child的約束方式不一樣,但他們在佈局過程須要執行的操做都是一致的。
佈局完成了,PipelineOwner就經過flushPaint來進行繪製
void flushPaint() {
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// 對dirty nodes列表進行排序,最深的在第一位
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._layer != null);
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
} finally {}
}
複製代碼
PaintingContext.repaintCompositedChild(node)會調用到child._paintWithContext(childContext, Offset.zero)方法,進而調用到child的paint方法,咱們來看一下第一次繪製的狀況,dirty的node就應該是RenderView,跟進RenderView的paint方法
@override
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset);//直接繪製child
}
複製代碼
本身沒有什麼繪製的內容,直接繪製child,再看一下RenderShiftedBox
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
final BoxParentData childParentData = child.parentData;
context.paintChild(child, childParentData.offset + offset);//直接繪製child
}
}
複製代碼
好像沒有繪製內容就直接遞歸的進行繪製child,那找一個有繪製內容的吧,咱們看看RenderDecoratedBox
@override
void paint(PaintingContext context, Offset offset) {//Offset由parent去paintChild的時候傳入,該值存放在child的parentdata字段中,該字段是BoxParentData或如下實例
_painter ??= _decoration.createBoxPainter(markNeedsPaint);//獲取painter畫筆
final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
if (position == DecorationPosition.background) {//畫背景
_painter.paint(context.canvas, offset, filledConfiguration);//繪製過程,具體細節再painter中
if (decoration.isComplex)
context.setIsComplexHint();
}
super.paint(context, offset);//畫child,裏面直接調用了paintChild
if (position == DecorationPosition.foreground) {//畫前景
_painter.paint(context.canvas, offset, filledConfiguration);
if (decoration.isComplex)
context.setIsComplexHint();
}
}
複製代碼
若是本身有繪製內容,paint方法中的實現就應該包括繪製本身以及繪製child,若是沒有孩子就只繪製本身的內容,看一下RenderImage
@override
void paint(PaintingContext context, Offset offset) {
if (_image == null)
return;
_resolve();
paintImage(//直接繪製Image,具體細節再此方法中
canvas: context.canvas,
rect: offset & size,
image: _image,
scale: _scale,
colorFilter: _colorFilter,
fit: _fit,
alignment: _resolvedAlignment,
centerSlice: _centerSlice,
repeat: _repeat,
flipHorizontally: _flipHorizontally,
invertColors: invertColors,
filterQuality: _filterQuality
);
}
複製代碼
因此基本上繪製須要完成的流程就是,若是本身有繪製內容,paint方法中的實現就應該包括繪製本身以及繪製child,若是沒有孩子就只繪製本身的內容,流程比較簡單。
以上是本身學習的一些總結,若有錯誤之處請指出,你們共同探討,以爲不錯的話,點個讚唄!