在flutter项目中,我们通常会需要根据值的变化来让界面重新渲染,从而展示新的效果。一般情况下,我们会直接使用 setState((){}) 让整个组件进行重新渲染。但是在有些情况下,例如我们在快速的进行重新赋值,如果这个时候,我们依旧使用 setState 触发整个组件的重新渲染,就很有可能导致操作卡顿甚至渲染抖动,这是由于,值的快速变化,而引起组件的快速重复渲染。例如,我们在做滚动选择器的时候,需要实时展示选择的值。
界面效果如下:

由界面可以看出,我们在滚动选择时间的时候,需要在顶部,实时展示出选择的结果。这个时候,如果我们让组件整体重新渲染的话,就会引起选择器的抖动,因为页面重新渲染的时候,会根据值来进行行数定位,而滚动的每一次操作距离是小于每一项的行高的。这样一来就会出现抖动效果。
解决思路
为了避免类似的情况,我们期望的效果是,每次值更新的时候,只重新渲染顶部时间,而不去做整体重新渲染,也就是局部渲染。在flutter 中,我们可以通过 ValueNotifier 来实现这个效果。一般来说,这是一个类似监听值变化的组件,当值监听的值发生了变化,那么就会触发内部的重新渲染,而其他部分则不会。
关键代码
ValueNotifier<DateTime?> _selectedDateTime = ValueNotifier(null);
Widget build(BuildContext context) {
AntThemeData themeData = AntTheme.of(context);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 40,
decoration: BoxDecoration(
border:
Border(bottom: BorderSide(color: themeData.colorBorder, width: 0.5))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ValueListenableBuilder(
valueListenable: _selectedDateTime,
builder: (context, value, child) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16),
child: _Label(
index: _showIndex,
selectedDateTime: value,
onIndexChange: (index) {
setState(() {
_showIndex = index;
});
},
));
})),
GestureDetector(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text("确定",
style: TextStyle(color: themeData.colorPrimary)),
),
onTap: () {
widget.onOk?.call(_selectedDateTime.value);
},
)
],
),
),
......
],
);
}
当 _selectedDateTime 值发生变化的时候,就只会触发 ValueListenableBuilder 内部的重新渲染。
根据以上的结论,在复杂界面中可以通过局部渲染的方案优化界面的渲染效果。