前面了解完Flutter的三个核心对象以及布局的文章,对于Flutter在UI上的设计有一定深入了解,可以尝试做些练习来加深理解。
这里我们做一个动态替换Widget的练习,题目灵感来自重磅! flutter视图局部更新 - 林二鹿 - 博客园 ,主要的目的是在用户点击某个按钮的时候替换一个文本Widget组件。
做作业之前回顾一下组件树的展示过程:build->mount->layout->paint,这里用标准demo举例,初始代码如下:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '动态替换叶子节点的Widget',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: '动态替换叶子节点的Widget'),
);
}
}
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;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
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.headline4,),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
我们试试改造一下,首先去掉_incrementCounter的调用,不使用setState的机制,直接在点击相应事件中,生成新的title widget,然后通过Element来更新组件树。Widget title的可见范围是个点,常见的方式放到_MyHomePageState类级别,也可以放到Widget build(BuildContext context)函数里,通过匿名函数FloatingActionButton(onPressed: () {})来保留引用,因为Widget title在这个过程一直是变化的,所以基本达到要求。
这里需要感谢林二鹿做的尝试,帮助排除了不少坑,最终代码如下:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '动态替换叶子节点的Widget',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: '动态替换叶子节点的Widget'),
);
}
}
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;
Widget title = new Text('another times: 0',);
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;
}
@override
Widget build(BuildContext context) {
print('Enter _MyHomePageState.build()');
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: () {
print('Enter 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),
),
);
}
}
回头总结一下,这个练习还是很必要,有以下收获:
- 不通过
setState也可以直接更新,setState会自动触发更新,以后如果需要运行时更新,也可以通过在Element上执行update方法来达到更新效果 - 这次练习用的是叶子节点,如果是非叶子节点且子树的复杂度低,全部重来也没事,如果子树复杂度高,则可以考虑替换当前节点和复用子树的方法,这时需要留有子树的引用,方便用来重新构建当前节点
- 实际的情况中准确找到Element很重要,这里有性能的考虑也有计算复杂度的考虑,这里可以简化
Element树的引用在Widget build(BuildContext context)中可以访问到,其实就是参数BuildContext context,因为Element是可变对象,会尽量被复用,所以在一个UI树,这个对象会不变- 这里还有一个点,
State里面其实有一个Element的引用,和build方法里的参数是同一个对象 - 这里动态替换的widget要求必须是同一个
runtimeType - 除了
owner.lockState(),还有一个owner.buildScope看样子也比较重要需要琢磨
Reference
- 重磅! flutter视图局部更新 - 林二鹿 - 博客园
- Technical overview - Flutter
- Inside Flutter - Flutter
- Flutter Design Documents - Flutter
- FAQ - Flutter
- 5 Useful Dart Packages - Flutter Community - Medium
- How to improve your Flutter application with gradient designs
- Flutter: Reusable Widgets - Flutter Community - Medium 不错的解读文章
- How to build a Design System in Sketch (Part One) - Design + Sketch - Medium , How to build a Design System in Sketch (Part Two) - Design + Sketch - Medium , How to build a Design System in Sketch (Part Three)
- Flutter System Architecture - Google Slides
- Basic Flutter layout concepts - Flutter