概要
使用合适的布局Widget和布局算法来避免不必要的布局计算和重绘,以提高应用的性能和响应速度。
flutter三棵树
Flutter的界面渲染离不开三棵树:Widget、Element、RenderObject。当Widget被加载的时候,它并不是马上就会被绘制出来,而是会先创建出它对应的Element,然后Element再根据Widget的配置信息在对应位置生成一个RenderObject,从而进行绘制的
-
Widget树:Widget树是由各种Widget组成的层次结构,用于描述应用程序的用户界面。Widget是Flutter中构建用户界面的基本单元,它们可以是文本、按钮、图片等各种元素。Widget树描述了应用程序的界面结构和布局。
-
Element树:Element树是Widget树的实例化对象,在渲染过程中,Widget树会被实例化为Element树。Element是Flutter中的一个重要概念,它负责维护Widget的状态和生命周期管理。Element树与Widget树一一对应,每个Widget都会对应一个Element。
-
RenderObject树:RenderObject树是Element树的底层实现,它负责将Element树转化为最终的渲染对象。RenderObject是Flutter中的一个核心概念,它负责处理布局、绘制和事件响应等底层操作。RenderObject树是Flutter渲染引擎的基础,它将Element树中的布局和样式信息转换为最终的屏幕显示。 这三棵树在Flutter中协同工作,实现了高效的UI渲染和响应。Widget树描述了界面结构,Element树负责管理状态和生命周期,RenderObject树负责最终的渲染和绘制。通过这种层次结构的组织,Flutter可以实现高性能的跨平台UI开发。
优化布局方法
1.使用具有固定大小的Widget
在Flutter中,布局计算(build)和重绘(setState)是一种相对昂贵的操作。
布局计算是指当Widget树中的某个节点需要进行布局计算时,Flutter会调用该节点对应的build方法来创建该节点的Widget,并将其添加到渲染树中。
重绘是指当Widget树中的某个节点需要进行界面更新时,Flutter会调用该节点的setState方法来通知Flutter框架进行重绘。setState方法是StatefulWidget的一个方法,用于更新该节点的状态,并触发界面重绘的过程。
当Widget的大小不会改变时,可以使用具有固定大小的Widget,例如Container、SizedBox等。这样可以避免不必要的布局计算和重绘。
2.使用Flex布局
flex布局可以根据可用空间自动调整子Widget的布局。当父Widget的大小发生变化时,子Widget会自动适应新的布局,而不需要进行额外的布局计算和重绘。
Flex布局可以根据可用空间自动调整子Widget的布局的过程中难道不是进行了布局计算和重绘吗,为什么说不需要进行额外的布局计算和重绘?
使用Flex布局的过程中确实是进行了布局计算和重绘,但是相对于传统的布局方式,Flex布局减少了额外的布局计算和重绘的工作量。
在传统的布局方式中,当父Widget的大小发生变化时,需要重新计算和调整所有子Widget的位置和大小,然后再进行重绘。
而在Flex布局中,子Widget会根据父Widget的大小自动调整布局,这个调整过程仅仅是对子Widget的位置和大小进行简单的计算,而不需要重新计算所有子Widget的位置和大小。这是因为Flex布局中的子Widget的布局是相对于父Widget的,所以只需要根据父Widget的大小进行简单的计算和调整,而不需要对所有子Widget进行全局的布局计算。 所以说Flex布局相对于传统的布局方式来说,减少了额外的布局计算和重绘的工作量。
3.避免过度使用嵌套
过度使用嵌套的Widget会导致布局计算和重绘的性能损失。尽量减少嵌套的层次,可以提高应用的性能和响应速度。
4.使用RepaintBoundary
如果某个Widget的子树很复杂,但是它本身不会发生变化,可以将其包装在RepaintBoundary中。 RepaintBoundary会将其子树绘制到一个位图中,并在需要时直接使用该位图进行重绘,避免不必要的布局计算和重绘。
使用RepaintBoundary组件可以将其子组件转换为一张图片,并将其展示在界面上。 这样做的好处是可以避免不必要的重绘,提高界面的渲染效率。同时,使用RepaintBoundary组件还可以将其子组件的绘制结果缓存起来,当需要重新绘制时,可以直接使用缓存的结果,减少绘制的时间和资源消耗。
5.使用ListView.builder代替ListView
/// This constructor is appropriate for list views with a large number of
/// item and separator children because the builders are only called for
/// the children that are actually visible.
///
/// The `itemBuilder` callback will be called with indices greater than
/// or equal to zero and less than `itemCount`.
当需要显示大量列表项时,使用ListView.builder可以提高性能。ListView.builder会根据需要动态加载和回收子组件,而不是一次性加载所有列表项。
6.使用ValueKey
/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s. /// /// A new widget will only be used to update an existing element if its key is /// the same as the key of the current widget associated with the element.
Key是用于标识Widget、Element和SemanticsNode的标识符。 只有当新的Widget的key与与Element关联的当前Widget的key相同时,才会使用新的Widget来更新现有的Element。
使用StatefulWidget的build方法进行局部刷新:如果只需要更新部分子组件的内容,可以在StatefulWidget的build方法中只更新需要改变的部分,避免重新构建整个布局。
可以通过在需要刷新的子组件上使用ValueKey来实现
示例代码
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'Counter: $_counter',
style: TextStyle(fontSize: 20),
),
SizedBox(height: 10),
Container(
key: ValueKey(_counter), // 使用ValueKey来标识需要刷新的子组件
color: Colors.blue,
width: 200,
height: 200,
child: Center(
child: Text(
'Child Widget',
style: TextStyle(fontSize: 20, color: Colors.white),
),
),
),
SizedBox(height: 10),
RaisedButton(
onPressed: _incrementCounter,
child: Text('Increment'),
),
],
);
}
}
在上面的示例中,我们使用ValueKey来标识Container组件,当_counter发生变化时,只有带有相同ValueKey的Container组件会被重新构建,从而实现局部刷新。其他子组件不受影响,避免了重新构建整个布局。
示例代码
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'Counter: $_counter',
style: TextStyle(fontSize: 20),
),
SizedBox(height: 10),
Container(
key: ValueKey(_counter), // 使用ValueKey来标识需要刷新的子组件
color: Colors.blue,
width: 200,
height: 200,
child: Center(
child: Text(
'Child Widget',
style: TextStyle(fontSize: 20, color: Colors.white),
),
),
),
SizedBox(height: 10),
RaisedButton(
onPressed: _incrementCounter,
child: Text('Increment'),
),
],
);
}
}