Create a new Flutter project. Take the code that the Flutter framework gives us as an example. When we click on the button to update the count _counter, we end up updating our view by calling State
.
setState(() {
_counter++;
})
Copy the code
The first thing you need to understand is why setState, which represents data changes to the current node, notifies the view that it needs to be updated. Which view is updated? View corresponding to the node that holds the current State instance. Note that this node specifically refers to the Element object. The Widget creates the State instance (_MyHomePageState createState()) and does not hold it. State also continues to create child views, and does not hold child views (Widget build(BuildContext Context)). The only argument that holds State is the Element. SetState argument, which is the body of the method to implement specific changes to the data. So instead of setting a state, it is better to call notifyChanges.
Second, you need to understand how the view is updated. Like the Text control, Text is passed directly to the control as a constructor argument. There is no settext-like method at all! So there’s no way to update the displayed data except to create a new view object!
Here lies the big difference between Flutter and traditional mobile interface development: Views are updated by creating new view objects. In previous interface development, the view object is a relatively heavy and large object, so the view should be reused as much as possible rather than created frequently. This is not the case in FLUTTER. The widgets that represent view objects are lightweight objects that hold no State or Widget. All view objects are created by build relationships. It is also important to avoid custom widgets holding data during development, as Widget objects can be quickly replaced.
With the above two points, you can see what happens after setState: Currently the Widget Build (BuildContext Context) method of _MyHomePageState is called, Then generates a new Scaffold object, combined with AppBar, FloatingActionButton, Column for control nature including we need to display the Text object, then the incoming Text is the _counter after the update, then the view to update.
You have to recreate the whole view just to update a little text box, right? !
Yes, that’s the current system. So as the view level increases and the interface interaction becomes more complex, there’s no problem with this kind of re-creation? After all, even small objects have overhead, and the accumulation of so many objects can be costly in the creation process. So our question finally comes up: Is there a way to update only part of the view?
Why don’t you just narrow it down? The update is now large because _myhomepagEstate. build is called to return the entire view, and the corresponding view for _MyHomePageState is MyHomePage. Call State
.setState() when the data is changed. Create a State
and build the Text control instance.
This idea fits into the mechanism of flutter itself, but the question is who creates the State
? As mentioned earlier, only the StatefulWidget can create an instance of State first, and the parent node must create the State
. But Column, the parent node of Text in the example, is not a StatefulWidget in the first place; Even if it were, we would have to declare that the Widget class inherits Column from the build method, and that the State class inherits State
. What if you look for a StatefulWidget from Text up and create it as an ancestor of Text with a little redundancy? This idea is not practical at all, not to mention the fact that there is a specific view object lookup process, and the various class declarations mentioned above are not reduced at all, so this approach is not feasible.
So start with the setState source code to see how a node actually updates the view.
State.setState
Element.markNeedsBuild
Element._dirty = true;
BuildOwner.scheduleBuildFor
BuildOwner._dirtyElements.add
Element._inDirtyList = true;
Copy the code
The process is easier than you might think, just marking the Element node as dirty and adding it to BuildOwner’s _dirtyElements list. The name setState seems correct from Element’s point of view, but it refers to the Element’s dirty state. We just need to find the Element node corresponding to Text and call its markNeedsBuild. Find the Element node corresponding to the Text Widget node.
In the previous tree building process, we said that the Element node structure is like a hook. The Element has only parent and no children. To find the child node, we need to pass a visitor like element.visitChildren to traverse. The judgment condition is, of course, whether the Widget Element holds is the one we need to update, so:
static Element findChild(Element e, Widget w) {
Element child;
void visit(Element element) {
if (w == element.widget)
child = element;
else
element.visitChildren(visit);
}
visit(e);
return child;
}
Copy the code
But setting markNeedsBuild on the found Element doesn’t work! MarkNeedsBuild only calls the build of the current Element node and creates a child node view object of the current node. What we need now is to replace the view object held by the current child node (‘ View updated by creating a new Widget object ‘) without recreating the current Element node and its children. And element.update (Widget) does just that!! If an inflateWidget initializes the Tree of Element nodes, update updates the tree after it has been created. So there are
onPressed: () {
_counter++;
Element e = findChild(context as Element, title);
if (e != null) {
e.update(title);
}
},
Copy the code
Because we are looking for nodes, we use a title to hold the Text to facilitate the lookup argument in the context of onTap(). But that’s not right either! There are two problems:
- The view object is not updated. We need to show a new _counter related text, so we need a new view object, we are passing in the old view object, so nothing is updated…
- Direct call
Element.update
There was an exception. I tracked it down and found a data that indicated the status_debugStateLockLevel
No, it was supposed to beBuildOwner.lockState
Can be executed in.
Here long li eight long-winded writing this tuo is to show that the implementation of a new idea is linked to the details, many times the idea is right, but the details of the implementation error lead to give up halfway, line a hundred miles of the half ninety!
FindChild (); findChild ();
import 'package:flutter/foundation.dart'
import 'package:flutter/material.dart';
import 'utils/ElementUtils.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Pages')); }}class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
@override
Widget build(BuildContext context) {
Widget title = new Text(
'another times: $_counter',);return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
title,
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_counter++;
Element e = findChild(context as Element, title);
if(e ! =null) {
title = new Text(
'another times: $_counter',); e.owner.lockState(() { e.update(title); }); } }, tooltip:'Increment', child: Icon(Icons.add), ), ); }}Copy the code
Now it is just a view that has been recreated. It is not fast.
However, what are the downsides or disadvantages of doing this
First of all, there is obviously a query operation, which is determined by the Element mechanism. Traversal can only be done through the visitor mode, and the time complexity is O(n). Can we avoid this query or create a Widget mapping to the Element? You can do this, but do it at least once, because the widget may not have an Element created or associated with it at the time it is created, so you can only look it up after the Element tree has been created.
Second, if an operation involves updating multiple views, we have to hold multiple widgets and find the element corresponding to each widget, or we have multiple queries, rather than creating all of them.
So can only depend on the situation and decide, there is no plan that takes care of everything once and for all, the right is the best!