1. Flutter重绘原理
- 渲染树构建:Flutter应用程序在启动时,会构建Widget树,然后将Widget树转换为Element树,最终构建出RenderObject树(渲染树)。每个RenderObject负责确定自身的大小、位置,并绘制到屏幕上。
- 布局与绘制:布局阶段,RenderObject根据父节点约束和子节点大小计算自己的大小和位置。绘制阶段,RenderObject将自身内容绘制到画布上。如果RenderObject的状态、数据或布局发生变化,就可能触发重绘。
- 脏标记机制:当一个RenderObject需要重绘时,它会被标记为“脏”。在每一帧开始时,Flutter会遍历渲染树,找到所有被标记为“脏”的RenderObject,并调用它们的
paint
方法进行重绘。
2. 列表滚动场景下减少重绘提升滚动性能
- ListView
- 使用
itemExtent
:如果列表项高度(或宽度,在水平列表时)固定,设置itemExtent
参数。这样ListView
可以提前计算滚动位置和哪些项需要显示,减少布局计算和重绘。例如:
ListView(
itemExtent: 50, // 假设列表项高度固定为50
children: List.generate(100, (index) => ListTile(title: Text('Item $index'))),
)
- **`cacheExtent`**:通过设置`cacheExtent`,可以指定在视口外预缓存的列表项数量。这确保了列表快速滚动时,即将进入视口的列表项已经准备好,避免临时构建和重绘。比如设置`cacheExtent`为200,表示在视口外额外缓存200像素范围内的列表项。
ListView(
cacheExtent: 200,
children: List.generate(100, (index) => ListTile(title: Text('Item $index'))),
)
- **`shrinkWrap`**:如果列表的大小由子项决定,并且不会无限增长,可以设置`shrinkWrap`为`true`。这样`ListView`在布局时不会占用尽可能多的空间,减少不必要的布局计算和重绘。
ListView(
shrinkWrap: true,
children: List.generate(10, (index) => ListTile(title: Text('Item $index'))),
)
- GridView
crossAxisCount
和childAspectRatio
:固定crossAxisCount
(横轴上的子项数量)和childAspectRatio
(子项宽高比)。如果子项大小固定,这有助于提前计算布局,减少重绘。例如:
GridView.count(
crossAxisCount: 3,
childAspectRatio: 1.5,
children: List.generate(100, (index) => Container(color: Colors.blue, child: Text('Item $index'))),
)
- **同样适用`cacheExtent`**:和`ListView`类似,`GridView`也可以设置`cacheExtent`来预缓存即将显示的子项,减少滚动时的重绘。
GridView.count(
crossAxisCount: 3,
cacheExtent: 200,
children: List.generate(100, (index) => Container(color: Colors.blue, child: Text('Item $index'))),
)
- **`shrinkWrap`**:和`ListView`一样,当`GridView`的大小由子项决定时,设置`shrinkWrap`为`true`可以减少不必要的布局计算和重绘。
GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
children: List.generate(10, (index) => Container(color: Colors.blue, child: Text('Item $index'))),
)