Translator: Goulman Bruce
This article is one of a series of articles on Flutter performance optimization. It documents the practice of the Flutter team to optimize the Flutter Gallery (Gallery. Flutter. Dev /#/). This article focuses on how to build high-performance widgets. Original link: medium.com/flutter/bui…
All stateless and stateful widgets implement the Build () method, which determines how they are rendered. A single screen in an app might have hundreds or thousands of parts that are built only once, or multiple times if there are animations or certain kinds of interactions. If you want to build fast widgets, be very careful about which widgets you build and when you build them.
This article focuses on building only what is necessary and only when necessary, and then shares how we can use this method to significantly improve Flutter Gallery performance. We will also share some advanced techniques for diagnosing similar problems in your Web app.
Build only when necessary
One important optimization approach is to build widgets only when absolutely necessary.
Call cautiouslysetState()
Calling the setState method causes a build() method call. If called too many times, performance will slow down.
Take a look at the animation below, where the black widget shown in the front slides down to reveal a checkerboard-like panel behind, similar to the behavior of the Bottom sheet. The front black widgets are simple, but the back widgets are busy.
Stack(
children: [
Back(),
PositionedTransition(
rect: RelativeRectTween(
begin: RelativeRect.fromLTRB(0.0.0.0),
end: RelativeRect.fromLTRB(0, MediaQuery.of(context).size.height, 0.0),
).animate(_animationController),
child: Front(),
)
],
),
Copy the code
You might write the parent widget like this, but in this scenario that would be wrong:
// BAD CODE
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: Duration(seconds: 3),
vsync: this,); _animationController.addListener(() { setState(() {// Rebuild when animation ticks
});
});
}
Copy the code
That’s not good performance. Why is that? Because animation is doing unnecessary work.
Here is the code in question:
// BAD CODE
_animationController.addListener(() {
setState(() {
// Rebuild when animation ticks.
});
});
Copy the code
- This type of animation is recommended only if you need to make the widget move, but that’s not what we need in this layout.
- Called in the animation listener
setState()
Will cause the wholeStack
Rebuild, it’s totally unnecessary PositionedTransition
One component alreadyAnimatedWidget
So it will automatically rebuild when the animation starts- You don’t need to call it here
setState()
Even if the later components are busy, the first component can animate at 60 FPS. For more on properly calling the setState method, see the animation of Flutter caton: You shouldn’t setState this way
Build only what is necessary
In addition to building only when necessary, you need to build only the parts of the UI that change. The following sections focus on creating a high-performance list.
Use listView.Builder () first
First, let’s briefly look at the basics of displaying a list:
- Set the list to use
Column
- If the list needs scrolling, use
ListView
- If the list has many items, use
ListView.builder
Instead of creating all items at once, this method creates items as they scroll onto the screen. This has obvious performance advantages when lists are complex and widgets are deeply nested.
To explain the advantages of listView. builder over ListView in the multi-item case, let’s look at a few examples.
Run the following ListView in the DartPad example. You can see that all eight items have been created. Click the Console button in the lower left corner, and then click the Run button. There is no scroll bar in the output panel on the right, but you can scroll through the content and then see through the console what was created and when it was built)
ListView(
children: [
_ListItem(index: 0),
_ListItem(index: 1),
_ListItem(index: 2),
_ListItem(index: 3),
_ListItem(index: 4),
_ListItem(index: 5),
_ListItem(index: 6),
_ListItem(index: 7),]);Copy the code
Next, run listView.Builder in the DartPad example. You can see that only visible items are created, and when you scroll, new items are created.
ListView.builder(
itemBuilder: (context, index) {
return _ListItem(index: index);
},
itemCount: 8,);Copy the code
Now, run the example. In this example, the ListView children are created once and in advance. In this scenario, using ListView is more efficient.
final listItems = [
_ListItem(index: 0),
_ListItem(index: 1),
_ListItem(index: 2),
_ListItem(index: 3),
_ListItem(index: 4),
_ListItem(index: 5),
_ListItem(index: 6),
_ListItem(index: 7)];@override
Widget build(BuildContext context) {
// There is no performance benefit to listView. builder in this case
return ListView.builder(
itemBuilder: (context, index) {
return listItems[index];
},
itemCount: 8,); }Copy the code
For more on the delayed build list, see Slivers, Demystified.
How can you more than double performance with one line of code
Flutter Gallery supports over 100 regions; These regions, as you might have guessed, are displayed via listView.Builder (). By looking at how many times widgets were rebuilt, we noticed that these items were unnecessarily built at startup. This is a bit hard to spot, because the items are hidden under a menu that folds up two layers: the Settings panel and the region list. (We later discovered that because ScaleTransitioin is used, the Settings panel is also rendered invisible, meaning it is constantly being built).
By simply setting the itemCount of the ListView. Builder to 0 when unexpanded, we ensure that the item is only built in the expanded, visible Settings panel. This one-line change increases rendering time in a Web environment by nearly two times, and the key is to locate excessive widget builds.
How do I view the number of widgets built
While the Build of Flutter is efficient, there can be situations where overbuild can cause performance problems. There are several ways to help locate excessive widget builds:
Using Android Studio/IntelliJ
Android Studio and IntelliJ developers can use built-in tools to view widget rebuild information.
Modify the Flutter frame itself
If you are not using one of the above editors or want to know how many widgets have been rebuilt in a Web environment, you can add a few simple lines of code to the Flutter framework.
Let’s take a look at the output:
RaisedButton 1
RawMaterialButton 2
ExpensiveWidget 538
Header 5
Copy the code
To locate the file: < Flutter path > / packages/Flutter/lib/SRC/widgets/framework. The dart, then add the following code. This code counts the number of widgets built at startup and outputs the results after a period of time (10 seconds in this case).
bool _outputScheduled = false;
Map<String.int> _outputMap = <String.int> {};void _output(Widget widget) {
final String typeName = widget.runtimeType.toString();
if (_outputMap.containsKey(typeName)) {
_outputMap[typeName] = _outputMap[typeName] + 1;
} else {
_outputMap[typeName] = 1;
}
if (_outputScheduled) {
return;
}
_outputScheduled = true;
Timer(const Duration(seconds: 10), () {
_outputMap.forEach((String key, int value) {
switch (widget.runtimeType.toString()) {
// Filter out widgets whose build counts we don't care about
case 'InkWell':
case 'RawGestureDetector':
case 'FocusScope':
break;
default:
print('$key $value'); }}); }); }Copy the code
Then modify the StatelessElement and StatelessElement build methods to call _output(widget).
class StatelessElement extends ComponentElement {...@override
Widget build() {
final Widget w = widget.build(this);
_output(w);
return w;
}
class StatefulElement extends ComponentElement {...@override
Widget build() {
final Widget w = _state.build(this);
_output(w);
return w;
}
Copy the code
You can view the modified Framework.dart file here.
Note that a few builds won’t necessarily cause problems, but this can help you debug performance problems by verifying that invisible widgets are being built.
Web-specific Tips: You can add a resetOutput function (which can be called from the browser console) to get the number of builds of widgets at any time.
import 'dart:js' as js;
void resetOutput() {
_outputScheduled = false;
_outputMap = <String.int> {}; }void _output(Widget widget) {
// Add this line
js.context['resetOutput'] = resetOutput; .Copy the code
View the modified Framework. dart file.
conclusion
Effective performance tuning requires an understanding of the underlying workings. The tips in this article can help you decide when to build widgets to keep your app high performance in all scenarios.
This article is part of a series we learned about improving the performance of Flutter Gallery. Hopefully this will help you learn something you can use in your Flutter app. The series is as follows:
- Tree Shaking and Lazy Loading for Flutter Performance Optimization series
- Image placeholders, precaching, and disable navigation transition animation for the Flutter performance Optimization series
- Build High-performance Widgets with Flutter Performance Optimization series
You can also view the Flutter UI performance documentation for developers of all levels.