您的位置 首页 > 德语词汇

layer是什么意思(初识Flutter中的Layer)

大家好,如果您还对layer是什么意思不太了解,没有关系,今天就由本站为大家分享layer是什么意思的知识,包括初识Flutter中的Layer的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!

接触Flutter开发一段时间后发现自己对Flutter渲染流程重要的一环Layer的认知比较少,虽然Flutter对Widget的封装非常全面了开发者基本上只要面向Widget编程就可以完成绝大部分的功能,但是它作为一个UI框架我们还是需要尽可能的掌握它渲染体系的来龙去脉,因此借此篇文章简单介绍笔者对Layer的探索。

layer是什么意思(初识Flutter中的Layer)

参与UI的构建和显示涉及到两个线程分别是界面线程(UIThread)和光栅线程(GPUThread),UI线程做构建流水线工作(开发者编写的代码),光栅线程做UI绘制工作(图形库Skia在此线程上运行)。

1,GPU每隔一定的时间发出一个Vsycn信号这个时间由屏幕的刷新率决定,以60HZ的刷新率为例那么它的时间间隔就是1000/60=16.7ms一次。

2,UI线程收到Vsycn信号后就会做UI的构建工作(需要在16.7ms内完成否则出现丢帧),然后发送到光栅线程GPU线程。

3,GPUThread收到UIThread发来的UI数据后就会通过Skia去上屏渲染。

上图摘自Flutter官网介绍,从上图可以看到有个LayerTree这也是本文探索的目标。

Flutter的开发更像是面向Widget编程,Widget内部又封装了Element以及RenderObject那么我们先从Flutter中的三棵树说起:

main(){\nrunApp(MaterialApp(\nhome:Row(\nmainAxisAlignment:MainAxisAlignment.center,\nchildren:[\nMyWidget(),\nMyWidget()\n],\n),\n));\n}\nclassMyWidgetextendsStatelessWidget{\n@override\nWidgetbuild(BuildContextcontext){\nreturnContainer(\nheight:100,\nwidth:200,\nchild:Text('MyWidget',style:TextStyle(fontSize:25),),);\n}\n}

上述代码中Widget,Element,RenderObject三棵树的对应关系:

从上图可以看出Widget和Element的数量是一一对应的,而RenderObject不是。查看framework.dart源码后可以发现只有RenderObjectWidget的派生类才会有RenderObject,其他的Widget都不具备渲染能力。

当Flutter收到Vsycn的时候就会做UI的构建工作,最终会调用RendererBinding的drawFrame()

@protected\nvoiddrawFrame(){\nassert(renderView!=null);\n//1,布局逻辑,确定大小\npipelineOwner.flushLayout();\npipelineOwner.flushCompositingBits();\n//2,绘制逻辑,拿到SkCanvas绘制到layer上。具体逻辑见RenderObject中的paint方法\npipelineOwner.flushPaint();\nif(sendFramesToEngine){\nrenderView.compositeFrame();//thissendsthebitstotheGPU\npipelineOwner.flushSemantics();//thisalsosendsthesemanticstotheOS.\n_firstFrameSent=true;\n}\n}

renderView.compositeFrame()最终生成UI数据发送到GPU实现渲染,RenderView是Flutter中根部的RenderObject。compositeFrame的核心代码如下:

voidcompositeFrame(){\n//创建SceneBuilder,获取到引擎层的句柄\nfinalui.SceneBuilderbuilder=ui.SceneBuilder();\n\n//Scene最最终是通过SceneBuilder生成的,也是引擎层的句柄,此处的layer就是根部的layer,它会合成所有的layer\nfinalui.Scenescene=layer!.buildScene(builder);\n//发送Scene到引擎\n_window.render(scene);\nscene.dispose();\n}

在上述代码中可以找到layer的身影,layer!.buildScene(builder)就是做LayerTree的合成。此处的layer是一个TransformLayer是ContainerLayer的子类。

在绘制过程中渲染树RenderObjectTree将生成一个图层树LayerTree,LayerTree合成后发送到引擎渲染上屏。大多数Layer的特性都可以更改,并且可以将图层移动到不同的父层,且Layer树不会保持其自身的脏状态。要合成树先要在根部的Layer创建SceneBuilder对象,并调用Layer中的addToScene方法添加到SceneBuilder上(Flutter中默认根部的layer是一个TransformLayer)。

接下来用几个示例来了解Flutter常见的Layer

PictureLayer是Flutter中最常使用到的layer,先看看它的类结构

classPictureLayerextendsLayer{\n//省略无关代码\nui.Picture?_picture;\n\n@override\nvoidaddToScene(ui.SceneBuilderbuilder,[OffsetlayerOffset=Offset.zero]){\nassert(picture!=null);\nbuilder.addPicture(layerOffset,picture!,isComplexHint:isComplexHint,willChangeHint:willChangeHint);\n}\n}

PictureLayer中有一个成员属性Picture,Picture是Engine层绘制图像重要的一个环节,可以参考Flutter官方的示例:

[https://github.com/flutter/flutter/blob/449f4a6673f6d89609b078eb2b595dee62fd1c79/examples/layers/raw/canvas.dart]

按照官方的示例我们精简一下代码流程:

finalrecorder=ui.PictureRecorder();\n///基于画板创建的画布\nfinalcanvas=Canvas(recorder,cullRect);\n///缩放因子\nfinalratio=ui.window.devicePixelRatio;\n///设置缩放比\ncanvas.scale(ratio,ratio);\ncanvas.drawRect(Rect.fromLTRB(0,0,200,200),Paint()..color=Colors.blue);\n\n///录制结束,生成一个Picture\nPicturepicture=recorder.endRecording();\n\nSceneBuildersceneBuilder=ui.SceneBuilder();\n//对应PictureLayer中的addToScene方法\nsceneBuilder.addPicture(Offset(0,0),picture);\n\nsceneBuilder.pop();\n///生成scene\nfinalscene=sceneBuilder.build();\n//通知引擎在合适的时机渲染\nui.window.render(scene);\n\nscene.dispose();

上面的代码就可以渲染出一个图层:

小结:脱离Widget后我们也可以直接使用framework的api渲染出图像。

在了解到Flutter的绘制流程后我们再来看RenderObject的绘制流程:

voidpaint(PaintingContextcontext,Offsetoffset){//通过重写paint决定绘制逻辑}2.这里的PaintingContext内部封装了Canvas,除此之外还会创建出一个PictureLayer,PaintingContext的构造函数需要传一个ContainerLayer进去,然后ContainerLayer把创建的Picturelayerappend到ContainerLayer上,最终rootlayer会遍历调用layer的addToScene(builder)方法。

PaintingContext的构造方法:

PaintingContext(this._containerLayer,this.estimatedBounds)3.把当前的PictureLayerappend到_containerLayer上。

void_startRecording(){\nassert(!_isRecording);\n_currentLayer=PictureLayer(estimatedBounds);\n_recorder=ui.PictureRecorder();\n_canvas=Canvas(_recorder!);\n_containerLayer.append(_currentLayer!);\n}

上述流程可以用如下代码简单替换,下面的代码可以绘制出一个PictureLayer的图像:

PaintingContextcontext=PaintingContext(rootLayer,Rect.fromLTRB(0,0,1000,1000));\n//模拟paint方法\ncontext.canvas.drawRect(Rect.fromLTRB(200,200,800,800),Paint()..color=Colors.blue);\n\ncontext.stopRecordingIfNeeded();\n\nfinalSceneBuilderbuilder=ui.SceneBuilder();\n\nfinalScenescene=rootLayer.buildScene(builder);\nui.window.render(scene);\nscene.dispose();\n

接下来绘制多个PictureLayer的图像,每一帧只绘制了一个颜色的Rect在PictureLayer上,通过合成Layer达到一帧显示多个Rect。

main()async{\n\nui.window.onBeginFrame=beginFrame;\nui.window.onDrawFrame=draw1stFrame;\n///画第一帧\nui.window.scheduleFrame();\n\n///画第二帧,\nawaitFuture.delayed(Duration(milliseconds:500),(){\nui.window.onDrawFrame=draw2ndFrame;\nui.window.scheduleFrame();\n});\n\n///画第三帧\nawaitFuture.delayed(Duration(milliseconds:500),(){\nui.window.onDrawFrame=draw3rdFrame;\nui.window.scheduleFrame();\n});\n\n///画第四帧\nawaitFuture.delayed(Duration(milliseconds:500),(){\nui.window.onDrawFrame=draw4thFrame;\nui.window.scheduleFrame();\n});\n\n\n}\n\nvoidbeginFrame(Durationduration){\n\n}\n\nOffsetLayerrootLayer=OffsetLayer();\nvoiddraw1stFrame(){\nprint('draw1stFrame');\nPaintingContextcontext=PaintingContext(rootLayer,Rect.fromLTRB(0,0,1000,1000));\ncontext.canvas.drawRect(Rect.fromLTRB(200,200,800,800),Paint()..color=Colors.blue);\ncontext.stopRecordingIfNeeded();\n\n\nfinalSceneBuilderbuilder=ui.SceneBuilder();\nfinalScenescene=rootLayer.buildScene(builder);\nui.window.render(scene);\nscene.dispose();\n\n}\n\n\nvoiddraw2ndFrame(){\nprint('draw2ndFrame');\nPaintingContextcontext=PaintingContext(rootLayer,Rect.fromLTRB(0,0,1000,1000));\ncontext.canvas.drawRect(Rect.fromLTRB(400,400,1000,1000),Paint()..color=Colors.red);\ncontext.stopRecordingIfNeeded();\n\nfinalSceneBuilderbuilder=ui.SceneBuilder();\nfinalScenescene=rootLayer.buildScene(builder);\nui.window.render(scene);\nscene.dispose();\n\n}\n\nvoiddraw3rdFrame(){\nprint('draw3rdFrame');\nPaintingContextcontext=PaintingContext(rootLayer,Rect.fromLTRB(0,0,1200,1200));\ncontext.canvas.drawRect(Rect.fromLTRB(600,600,1200,1200),Paint()..color=Colors.yellow);\ncontext.stopRecordingIfNeeded();\n\nfinalSceneBuilderbuilder=ui.SceneBuilder();\nfinalScenescene=rootLayer.buildScene(builder);\nui.window.render(scene);\nscene.dispose();\n\n}\n\n\nvoiddraw4thFrame(){\nprint('draw4thFrame');\nPaintingContextcontext=PaintingContext(rootLayer,Rect.fromLTRB(0,0,1000,2000));\ncontext.canvas.drawRect(Rect.fromLTRB(200,800,800,1400),Paint()..color=Colors.deepPurpleAccent);\ncontext.stopRecordingIfNeeded();\n\nfinalSceneBuilderbuilder=ui.SceneBuilder();\nfinalScenescene=rootLayer.buildScene(builder);\nui.window.render(scene);\nscene.dispose();\n\n}

多个PictureLayer效果图:

以上四个色块代表都有自己的PictureLayer,然后append到根部的rootLayer上合成一帧数据。

每一个Layer都对应着SceneBuilder一个api操作,PictureLayer对应的是SceneBuilder.addPicture方法(可以查看具体Layer中addToScene方法),除了PictureLayer还有类型的Layer,下面就简单介绍几种:

SceneBuilder.addTexture

2,ClipPathLayer剪裁图层--->剪裁子图层

SceneBuilder.pushClipPath

注意:图层的剪裁是比较消耗性能的,尽可能避免使用。

3,ColorFilterLayer滤色器图层--->滤色子图层

SceneBuilder.pushColorFilter

其中pushColorFilter和pushClipPath这类的方法会得到一个EngineLayer,EngineLayer是dart层持有Engine层的一个引用,其他还有很多图层操作的API这里就不一一举例了。

通过上面的示例,我们了解到RenderObject最终的绘制都是在Layer上的,它是通过PaintingContext和Layer关联上的

在Renderobject中有个isRepaintBoundary的方法,默认返回值是false,当它的返回值是true的时候就不会使用父节点的PaintingContext,而是重新创建一个PaintingContext来绘制。PaintingContext中会创建一个新的Picturelayer。

在RenderObject中有一个isRepaintBoundary的方法,通过重写isRepaintBoundary方法的返回值为true时可以做指定当前RenderObject节点使用独立的PictureLayer进行渲染。

@override\nboolgetisRepaintBoundary=>super.isRepaintBoundary;

代码逻辑如下:

voidpaintChild(RenderObjectchild,Offsetoffset){\n//childisRepaintBoundary=true就会\nif(child.isRepaintBoundary){\nstopRecordingIfNeeded();\n//合成\n_compositeChild(child,offset);\n}else{\nchild._paintWithContext(this,offset);\n}\n\nassert((){\nif(debugProfilePaintsEnabled)\nTimeline.finishSync();\nreturntrue;\n}());\n}

PaintingContext._compositeChild

void_compositeChild(RenderObjectchild,Offsetoffset){\n\n//Createalayerforourchild,andpaintthechildintoit.\nif(child._needsPaint){\nrepaintCompositedChild(child,debugAlsoPaintedParent:true);\n}else{\n\n}\n\nfinalOffsetLayerchildOffsetLayer=child._layer!asOffsetLayer;\nchildOffsetLayer.offset=offset;\nappendLayer(child._layer!);\n}

PaintingContext.repaintCompositedChild--->PaintingContext._repaintCompositedChild

staticvoid_repaintCompositedChild(\nRenderObjectchild,{\nbooldebugAlsoPaintedParent=false,\nPaintingContext?childContext,\n}){\n\nOffsetLayer?childLayer=child._layerasOffsetLayer?;\nif(childLayer==null){\nchild._layer=childLayer=OffsetLayer();\n}else{\n\nchildLayer.removeAllChildren();\n}\n\n//创建新的PaintingContext\nchildContext??=PaintingContext(child._layer!,child.paintBounds);\n//绘制child\nchild._paintWithContext(childContext,Offset.zero);\n\nchildContext.stopRecordingIfNeeded();\n}验证RenderObject使用独立的Layer

通过自定义一个RandomColorRenderObject,重写isRepaintBoundary的返回值,分别返回true和false。点击文字会发现返回false的时候RandomColorRenderObject的piant会被调用,而返回true的时候RandomColorRenderObject的piant不会会被调用。

voidmain(){\n\nrunApp(MaterialApp(\nhome:Column(\nmainAxisSize:MainAxisSize.min,\ncrossAxisAlignment:CrossAxisAlignment.start,\nchildren:[\nContainer(child:RandomColorWidget(),),\nMyText(),\n],\n),\n));\n}\n\nclassMyTextextendsStatefulWidget{\n@override\nState<StatefulWidget>createState(){\nreturnMyTextState();\n}\n}\n\nclassMyTextStateextendsState{\nStringtext=_text();\n\n@override\nWidgetbuild(BuildContextcontext){\nreturnContainer(\nheight:100,\nwidth:300,\nchild:GestureDetector(\nchild:Text(text),\nonTap:(){\nsetState((){\ntext=_text();\n});\n},\n),\n);\n}\n}\n\nString_text(){\nreturn"12345678${Random().nextInt(10)}";\n}\n\nclassRandomColorWidgetextendsRenderObjectWidget{\n@override\nRenderObjectcreateRenderObject(BuildContextcontext){\nreturnRandomColorRenderObject(context);\n}\n@override\nRandomColorElementcreateElement(){\nreturnRandomColorElement(this);\n}\n}\n\nclassRandomColorElementextendsRenderObjectElement{\nRandomColorElement(RenderObjectWidgetwidget):super(widget);\n}\n\nclassRandomColorRenderObjectextendsRenderBox{\nRandomColorRenderObject(BuildContextcontext);\n\nViewConfigurationcreateViewConfiguration(){\nfinaldoubledevicePixelRatio=window.devicePixelRatio;\nreturnViewConfiguration(\nsize:window.physicalSize/devicePixelRatio,\ndevicePixelRatio:devicePixelRatio,\n);\n}\n\n@override\nRectgetpaintBounds{\nreturnRect.fromLTRB(\n0,\n0,\n200,\n200);\n}\n\n@override\nvoidperformLayout(){\nsize=paintBounds.size;\n//print('RandomColorRenderObjectperformLayout');\n}\n\n@override\nboolgetisRepaintBoundary=>true;\n\n@override\nvoidpaint(PaintingContextcontext,Offsetoffset){\nsuper.paint(context,offset);\ncontext.canvas.save();\n///画Rect\ncontext.canvas.drawRect(\nRect.fromLTWH(0,0,200,200),Paint()..color=_randomColor());\ncontext.canvas.restore();\n}\n\n@override\nRectgetsemanticBounds=>paintBounds;\n}\n\n\nColor_randomColor(){\nreturnColor.fromARGB(255,Random().nextInt(255),Random().nextInt(255),Random().nextInt(255));\n}

优势:当某个Layer的绘制很消耗性能又不会频繁的刷新,在不影响其他Layer的前提下可以通过复用提升性能。这样其它的RenderObject在刷新重绘的时候这个Layer不会被重绘。

通过此次探索希望能帮助大家加深对Layer的认知,简而言之RenderObject只负责绘制逻辑而Layer才是最终输出到Skia的产物。不同的Layer对应着SceneBuilder中图层操作不同的Api,因篇幅有限此次就不表述其它Layer的效果及作用了,有兴趣的同学可以自行参照SceneBuilder的源码去研究。

参考Flutter官方文档【https://docs.flutter.dev/perf/rendering】

OK,本文到此结束,希望对大家有所帮助。

本站涵盖的内容、图片、视频等数据,部分未能与原作者取得联系。若涉及版权问题,请及时通知我们并提供相关证明材料,我们将及时予以删除!谢谢大家的理解与支持!

Copyright © 2023