Widget-Element-RenderObject

The rendering process of Flutter

1.1. The Widget – Element – RenderObject relationship

Relationship between three trees

1.2. What are widgets?

image-20200302153223929

Official notes on Widgets:

  • The Idea behind Flutter Widgets is to build your UI to use them, inspired by React.
  • The Widget uses configuration and state to describe what the View should look like.
  • When a Widget changes, the Widget rebuilds its description, and the framework compares the previous description to use minimal changes to move from one state to another in the rendering tree.

Own understanding:

  • Widgets are description files that are constantly built as we make state changes.
  • But for render objects, only minimal overhead is used to update the render interface.

1.3. What is Element?

image-20200302154618370

Official description of Element:

  • Element is an instance of a Widget with a detailed location in the tree.
  • Widgets describe and configure what subtrees look like, while Elements actually configure specific locations in the Element tree.

1.4. RenderObject

image-20200302155014847

Official description of RenderObject:

  • Renders an object in the tree
  • The RenderObject layer is the core of the rendering library.

Object creation process

Let’s use Padding as an example, where Padding is used to set the inside margin

2.1. The Widget

The Padding is a Widget, and inherited from SingleChildRenderObjectWidget

The inheritance relationship is as follows:

Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget
Copy the code

We used to use statelessWidgets and statefulWidgets when creating widgets. This Widget is just a build method that assembles other widgets, not a real renderable Widget (as mentioned in the previous tutorial).

In the Padding class, we can’t find any code related to rendering. This is because the Padding is only a configuration information, which will be destroyed and created frequently depending on the properties we set.

Q: Does frequent destruction and creation affect Flutter performance?

  • No, the answer is in another article of mine;
  • https://mp.weixin.qq.com/s/J4XoXJHJSmn8VaMoz3BZJQ

So where does the actual rendering code go?

  • RenderObject

2.2. RenderObject

If we look at the Padding, there’s a very important method:

  • This method is actually a class from RenderObjectWidget, where it’s an abstract method;
  • Abstract methods must be subclasses, but its subclasses SingleChildRenderObjectWidget is an abstract class, so can not realize the abstract methods of the parent
  • But the Padding is not an abstract class, so the corresponding abstract method must be implemented here, and its implementation is the following one
@override
RenderPadding createRenderObject(BuildContext context) {
  return RenderPadding(
    padding: padding,
    textDirection: Directionality.of(context),
 ); } Copy the code

What does the above code create? RenderPadding

What’s the inheritance of RenderPadding?

RenderPadding -> RenderShiftedBox -> RenderBox -> RenderObject
Copy the code

Let’s look at the RenderPadding source code in detail:

  • If the _padding passed is the same as the saved value, return;
  • If not, _markNeedResolution is called, which internally calls markNeedsLayout;
  • The purpose of markNeedsLayout is to indicate that performLayout needs to be rearranged when the next frame is drawn;
  • If Opacity is desired, RenderOpacity calls markNeedsPaint, and RenderOpacity has a paint method;
  set padding(EdgeInsetsGeometry value) {
    assert(value ! =null);
    assert(value.isNonNegative);
    if (_padding == value)
      return;
 _padding = value;  _markNeedResolution();  } Copy the code

Element 2.3.

Let’s consider a question:

  • Many of the widgets we wrote had references in the tree structure, but the widgets were constantly destroyed and rebuilt, which meant that the tree was very unstable;
  • So who is responsible for stabilizing the tree structure of the Flutter application?
  • The answer is Element.
  • Official description: Element is an instance of a Widget with a detailed location in the tree.

When is Element created?

Each time a Widget is created, a corresponding Element is created and inserted into the tree.

  • Element holds references to widgets;

In SingleChildRenderObjectWidget, we can find the following code:

  • In the Widget, an Element is created, and this (Widget) is passed when it is created;
  • Element holds applications to widgets;
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
Copy the code

After creating an Element, the Framework calls the mount method to insert the Element at a specific location in the tree:

The mount method

When the mount method is called, the Widget is used to create the RenderObject and the reference to the RenderObject is kept:

  • _renderObject = widget.createRenderObject(this);
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    assert(() {
 _debugUpdateRenderObjectOwner();  return true; } ()); assert(_slot == newSlot);  attachRenderObject(newSlot);  _dirty = false;  } Copy the code

However, if you look at a Widget of a composite class like Text, it also performs a mount method, but there is no createRenderObject called in the mount method.

  • We’ve found that the primary purpose of ComponentElement is to call the _firstBuild method once it’s mounted
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    assert(_child == null);
    assert(_active);
 _firstBuild();  assert(_child ! =null);  }   void _firstBuild() {  rebuild();  } Copy the code

If it is a StatefulWidget, a StatefulElement is created

Let’s look at the StatefulElement constructor:

  • Call widget createState()
  • So StatefulElement has a reference to the created State
  • In turn, _state has a reference to the widget
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
. Omit code  _state._widget = widget;
Copy the code

When we call build, we essentially call the build method in _state:

Widget build() => state.build(this);
Copy the code

2.4. What is the build context

In StatelessElement, we find that we pass this in, so essentially BuildContext is the current Element

Widget build() => widget.build(this);
Copy the code

Let’s take a look at the inheritance diagram:

  • Element implements the BuildContext class.
abstract class Element extends DiagnosticableTree implements BuildContext
Copy the code

In StatefulElement, the build method is similar, calling state’s build method with this passed in

Widget build() => state.build(this);
Copy the code

2.5. Create a process summary

The Widget simply describes configuration information:

  • This includes the createElement method to create the Element
  • CreateRenderObject is also included, but it is not called by itself

Element is an object that actually holds the tree structure:

  • Once created, the framework calls the mount method;
  • The mount method calls the Widget’s createRenderObject;
  • And Element has references to both widgets and RenderObjects;

RenderObject is the object that is actually rendered:

  • One of themarkNeedsLayout performLayout markNeedsPaint paintMethods such as

Key of Widget

When we create a Widget, we always see a key parameter. What does it do?

3.1. Case requirements for key

Let’s do a case requirement for key

Case requirements for Key

The basic code for the home interface:

class _HYHomePageState extends State<HYHomePage> {
  List<String> names = ["aaa"."bbb"."ccc"];

  @override
  Widget build(BuildContext context) {
 return Scaffold(  appBar: AppBar(  title: Text("Test Key"),  ),  body: ListView(  children: names.map((name) {  return ListItemLess(name);  }).toList(),  ),   floatingActionButton: FloatingActionButton(  child: Icon(Icons.delete),  onPressed: () {  setState(() {  names.removeAt(0);  });  }  ),  );  } } Copy the code

Note: We will change the returned ListItem to ListItemLess or ListItemFul later

3.2. Implementation of StatelessWidget

ListItem is implemented using a StatelessWidget:

class ListItemLess extends StatelessWidget {
  final String name;
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  ListItemLess(this.name);
  @override  Widget build(BuildContext context) {  return Container(  height: 60. child: Text(name),  color: randomColor,  );  } } Copy the code

The effect is that every time you delete one, all the colors will see a change

  • The reason for this is very simple: after deleting the widget, call setState to rebuild it, and the new StatelessWidget will be rebuilt with a new random color
image-20200320151331285

3.3. StatefulWidget implementation (without keys)

We use the StatefulWidget for ListItem

class ListItemFul extends StatefulWidget {
  final String name;
  ListItemFul(this.name): super(a);  @override
  _ListItemFulState createState() => _ListItemFulState();
}  class _ListItemFulState extends State<ListItemFul> {  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));   @override  Widget build(BuildContext context) {  return Container(  height: 60. child: Text(widget.name),  color: randomColor,  );  } } Copy the code

We found a very strange phenomenon, the color did not change, but the data moved up

  • This is because the Element corresponding to the Widget does not change when the first data is deleted;
  • The corresponding State reference in Element has not changed;
  • When the Widget is updated, the Widget uses the State in the Element that has not changed.
image-20200320151747199

3.4. Implementation of StatefulWidget (Random Key)

We use a random key

ListItemFul is modified as follows:

class ListItemFul extends StatefulWidget {
  final String name;
  ListItemFul(this.name, {Key key}): super(key: key);
  @override
  _ListItemFulState createState() => _ListItemFulState();
} Copy the code

The home interface code is modified as follows:

body: ListView(
  children: names.map((name) {
    return ListItemFul(name, key: ValueKey(Random().nextInt(10000)));  }).toList(),
),
Copy the code

This time we find that every delete will appear a random color phenomenon:

  • This is because when the key is changed, Element is forced to refresh and the corresponding State is recreated
// Code in the Widget class
static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
    && oldWidget.key == newWidget.key;
}
Copy the code
image-20200320152321905

3.5. Implementation of StatefulWidget (Name as key)

This time, we use name as the key to see the result:

body: ListView(
  children: names.map((name) {
    return ListItemFul(name, key: ValueKey(name));
  }).toList(),
),
Copy the code

Our ideal effect:

  • Because this diff algorithm is performed on the key during widget updates
  • After the comparison, it is found that the Element corresponding to BBB and the Element corresponding to CCC will continue to be used, so the Element corresponding to AAA will be deleted instead of the last Element
image-20200320152610235

3.6. Classification of keys

The Key itself is an abstraction, but it also has a factory constructor that creates a ValueKey

Direct subclasses include LocalKey and GlobalKey

  • LocalKey, which is used to compare widgets with the same parent Element and is at the heart of the DIff algorithm;
  • GlobalKey, usually we use GlobalKey for the Widget or State or Element corresponding to a Widget

3.6.1. LocalKey

There are three subclasses of LocalKey

ValueKey:

  • ValueKey is used when we have a specific value as the key, such as a string, number, and so on

ObjectKey:

  • If two students have the same name, it is not appropriate to use name as their key
  • We can create a student object and use the object as the key

UniqueKey

  • If we want to ensure that the key is unique, we can use UniqueKey;
  • For example, we used random numbers to ensure that the key is different, so we can use UniqueKey;

3.6.2. GlobalKey

GlobalKey allows us to access information about a Widget, including widgets or objects such as State or Element

Let’s look at the following example: I want to be able to access the contents of HYHomePage directly from HYHomePage

class HYHomePage extends StatelessWidget {
  final GlobalKey<_HYHomeContentState> homeKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
 return Scaffold(  appBar: AppBar(  title: Text("List test"),  ),  body: HYHomeContent(key: homeKey),  floatingActionButton: FloatingActionButton(  child: Icon(Icons.data_usage),  onPressed: () {  print("${homeKey.currentState.value}");  print("${homeKey.currentState.widget.name}");  print("${homeKey.currentContext}");  },  ),  );  } }  class HYHomeContent extends StatefulWidget {  final String name = "123";   HYHomeContent({Key key}): super(key: key);   @override  _HYHomeContentState createState() => _HYHomeContentState(); }  class _HYHomeContentState extends State<HYHomeContent> {  final String value = "abc";   @override  Widget build(BuildContext context) {  return Container();  } } Copy the code

Note: All content will be published on our official website. Later, Flutter will also update other technical articles, including TypeScript, React, Node, Uniapp, MPvue, data structures and algorithms, etc. We will also update some of our own learning experiences

The public,