“This article has participated in the call for good writing activities, click to view: the back end, the big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”

Widget rendering process

Flutter abstracts the organization and rendering of view data into three parts: Widgets, Elements and RenderObjects.

The process of Flutter rendering can be divided into three steps: first, generate the corresponding Element tree from the Widget tree; Then create the corresponding RenderObject and associate it with the Element. RenderObject property. Finally, a RenderObject tree is built to complete the final rendering.

The screen refreshes at 60 frames per second. Flutter draws the page onto the screen for the first time. It needs to find the position, color, text, and so on of every element on the screen. That is, in the first rendering, every pixel on the screen needs to be configured.

For subsequent screen refreshes and draws, if nothing has changed, Flutter uses the previous drawing information and quickly draws it onto the screen. Frame drops occur when every screen refresh requires everything on the screen to be counted.

Widget

Widgets are a structured description of a view that stores configuration information about a view’s rendering, including layout, render properties, event response information, and so on.

Widgets are designed to be immutable, so when the configuration of the view rendering changes, Flutter will choose to rebuild the Widget tree to update the data, making it simple and efficient to build a data-driven UI. Although rebuilding involves the destruction and reconstruction of a large number of objects, which can be stressful for garbage collection, the Widget itself does not involve actually rendering the bitmap, so it is a lightweight data structure that can be rebuilt cheaply. In addition, due to the immutability of widgets, rendering nodes can be reused at a low cost. One Widget object can correspond to multiple Element objects.

Element

An Element is an instantiated object of a Widget that holds the context data for view building and is a bridge between structured configuration information and final rendering.

Element holds both widgets and RenderObjects. Neither Widget nor Element is actually responsible for the final rendering, only for calling the shots, and only the RenderObject does the real work.

Widgets are immutable, but elements are mutable. In fact, the Element tree abstracts Widget tree changes (similar to the React virtual DOM Diff), so that only the parts that really need to be modified can be synchronized to the real RenderObject tree, minimizing changes to the real rendering view and improving rendering efficiency. Instead of destroying the entire render view tree rebuild. That’s why the Element tree exists.

BuildContext is the Element of the Widget.

The Element tree does not call build(){… } method when rebuilding.

RenderObject

As we know intuitively from its name, RenderObject is the object that is primarily responsible for rendering views.

Flutter creates different types of render objects from each Widget in the Widget tree, which makes up the render object tree. The rendering of object trees in Flutter is divided into four stages: layout, drawing, composition and rendering. Layouts and drawing are done in RenderObject. Flutter uses a depth-first mechanism to traverse the tree of rendered objects, determine the position and size of each object in the tree, and draw them on different layers. After drawing, Skia takes care of composition and rendering.

  • Every time Flutter encounters an element that has not been rendered before, it creates an element in the element tree through the configuration in the Widget tree.
  • Render trees are also not rebuilt very often.
  • In addition to the layout, draw, compose, and render phases, it has another phase that attaches listeners to widgets so that we can listen for events.

build()The construction process of

Whenever a state change occurs, the Flutter calls the method build(). There are generally two conditions for starting reconstruction:

  1. Call setState(){… }. Will lead to the build () {… } method call.

  2. Second, whenever there is a MediaQuery call or Theme. Of (…) . Call, soft keyboard appear/disappear, etc., will trigger build(){… }.

Call setState () {… } mark the corresponding element as dirty. For the next refresh (which happens 60 times per second), the Flutter will build(){… } method to create a new configuration to analyze, and then update the screen.

Examples of anti-best practices

class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { debugPrint('=======_MyHomePageState.build:'); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Row( children: < widgets > [Text (' I am the first row), Text (' I am the second line), Text (' I am the third row),],), Text (' $_counter, style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); }}Copy the code

Use the counter items created by Flutter and then add a few text. Usually to avoid the hell callback, we usually pull out the nested components into one method. Flutter Outline also has shortcuts:

_buildRow (), / / Row (/ / children: < widgets > [/ / Text (' I am the first Row), / / Text (' I am the second line), / / Text (' I am the third Row), / /, / /).Copy the code
Row _buildRow() {return Row(children: <Widget>[Text(' I am the first Row '), Text(' I am the second Row '),],); }Copy the code

Looks great, removes the region nesting, reduces the number of method lines, and has a clear structure. Seriously, I’ve been doing this for over a year.

The problem

Until I saw this:

Wm is the developer advocate for Flutter.

SetState () is called whenever a Flutter changes its value. This triggers the Widget to call the build() method to rebuild. Then call _buildRow() to rebuild the control returned by this method. _incrementCounter ` ` build ` ` _buildRow () ` ` _counter.

As mentioned earlier in the rendering process, an Element does not have to be rebuilt when it is built (). But because of this method, it’s going to rebuild it every time. Valuable CPU cycles are wasted rebuilding something that doesn’t need to be rebuilt.

The solution

The solution is simple: Instead of breaking the build method into smaller methods, break it up into widgets – stateless widgets.

The Demo above ends like this:

class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { debugPrint('=======_MyHomePageState.build:'); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ _NonsenseWidget(), // _buildRow(), // Row( // children: < widgets > [/ / Text (' I am the first row), / / Text (' I am the second line), / / Text (' I am the third row), / /, / /), Text (' $_counter, style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } Row _buildRow() {return Row(children: <Widget> (children: <Widget> (children: <Widget> (children: <Widget>)); } } class _NonsenseWidget extends StatelessWidget { const _NonsenseWidget(); @override Widget build(BuildContext context) { return Row( children: < widgets > [Text (' I am the first row), Text (' I am the second line), Text (' I am the third row),],); }}Copy the code

When a Flutter is a Widget, it compares whether or not it needs to be rebuilt. Because a Flutter is a StatelessWidget of the same type, it is reused. Reconstruction is avoided.