最近在项目中使用到了ValueNotifier这个方式去触发界面的刷新,替代了使用setState(){}的使用,因为我们知道在使用setState(){}时,会导致整个界面的重新build,而往往我们想更新的widget只是某一个或几个,所以整个界面的重绘,会造成不必要的性能损耗,当然再界面元素少的时候看不出差异,但设想一下,某个界面有1000个widget,但我们只选改变状态的只是其中一个widget,当我们使用setState(){}时,就会造成不必要的999个widget的绘制,所以基于这些问题,今天我们就研究一下ValueNotifier的使用方式和注意事项
例:先来解决一个简单的需求,比如我的界面中心有个显示数字的文案,当通过按钮改变数字时,需要立即刷新数字文案的改变.
先来看一下简单的界面效果:
当点击加号按钮是改变数字,并且及时刷新界面,传统使用setState(){}的写法:
import 'package:flutter/material.dart';
class NumberChangeDemo extends StatefulWidget {
@override
_NumberChangeDemoState createState() => _NumberChangeDemoState();
}
class _NumberChangeDemoState extends State<NumberChangeDemo> {
int number = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: Center(
child: Text("当前的数字:$number",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
),
floatingActionButton: FloatingActionButton(
child: Text("+",style: TextStyle(fontSize: 20),),
onPressed: (){
// 数字改变
number ++;
// 刷新界面
setState(() {
});
},
),
);
}
}
使用ValueNotifier改变后的代码,实现效果一样:
import 'package:flutter/material.dart';
class NumberChangeDemo extends StatefulWidget {
@override
_NumberChangeDemoState createState() => _NumberChangeDemoState();
}
class _NumberChangeDemoState extends State<NumberChangeDemo> {
int number = 0;
// 创建一个监听实例,在需要监听这个改变的地方使用ValueListenableBuilder去包裹你的Widget
ValueNotifier<int> numberNotifier = new ValueNotifier(0);
@override
void initState() {
// TODO: implement initState
super.initState();
numberNotifier.value = number;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: ValueListenableBuilder(
valueListenable: numberNotifier,
// value前面的int代表值的类型,使用时一定明确指定该类型
builder: (BuildContext context, int value, Widget child) {
return Center(
child: Text("当前的数字:$value",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
);
},
),
floatingActionButton: FloatingActionButton(
child: Text("+",style: TextStyle(fontSize: 20),),
onPressed: (){
// 数字改变
number ++;
// 只刷新监听了numberNotifier的界面
numberNotifier.value = number;
},
),
);
}
}
可以看到达到的效果是一样的,但是ValueNotifier只是对监听的地方做了一个脏标记,然后在下次信号来的时候,才会去对打了脏标记的widget做一个重绘工作,减少了其他8个不必要的Widget的绘制,而使用了setState(){}后会对所有的Widget打脏标记,然后再绘制,优势显而易见!
上面是对基本数据类型: int的使用,bool, String类型同理.
这里再说说使用ValueNotifier遇到的一些坑,当ValueNotifier监听的对象是一个数据模型时,比如监听下面的这个数据模型:
import 'package:flutter/material.dart';
// 要监听的数据模型
class DemoModel {
int number;
DemoModel({this.number});
DemoModel.fromJson(Map<String, dynamic> json) {
number = json['number'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['number'] = this.number;
return data;
}
}
// 界面
class NumberChangeDemo extends StatefulWidget {
@override
_NumberChangeDemoState createState() => _NumberChangeDemoState();
}
class _NumberChangeDemoState extends State<NumberChangeDemo> {
DemoModel demoModel = DemoModel(number: 0);
// 创建一个监听实例,在需要监听这个改变的地方使用ValueListenableBuilder去包裹你的Widget
ValueNotifier<DemoModel> numberNotifier = new ValueNotifier(DemoModel(number: 0));
@override
void initState() {
// TODO: implement initState
super.initState();
numberNotifier.value = demoModel;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: Column(
children: <Widget>[
Text("不需要改变的Widget1",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
Text("不需要改变的Widget2",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
Text("不需要改变的Widget3",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
Text("不需要改变的Widget4",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
Text("不需要改变的Widget5",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
Text("不需要改变的Widget6",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
Text("不需要改变的Widget7",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
Text("不需要改变的Widget8",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
ValueListenableBuilder(
valueListenable: numberNotifier,
// value前面的int代表值的类型,使用时一定明确指定该类型
builder: (BuildContext context, DemoModel value, Widget child) {
return Center(
child: Text("需要改变的Widget的数字:${value.number}",style: TextStyle(color: Colors.red,fontSize: 20,fontWeight: FontWeight.bold),),
);
},
),
],
),
floatingActionButton: FloatingActionButton(
child: Text("+",style: TextStyle(fontSize: 20),),
onPressed: (){
// 数据模型中的某个值改变
demoModel.number ++;
// 只刷新监听了numberNotifier的界面
numberNotifier.value = demoModel;
},
),
);
}
}
你会发现点击加号,虽然 demoModel.number 的值改变了,并且也通过 numberNotifier.value = demoModel;赋值了监听的数据模型,但是真正界面显示时,数字并没有改变.这就很奇怪,和预想的有差别,通过研究发现,在监听对象时,单存改变对象的某一个属性值,是不会触发 ValueListenableBuilder 的构建的,因为实际上对象的内存地址没有改变,所以机制上判定为不触犯监听,所以当监听对象时,我们需要的是监听的对象本身,需要变成一个新对象,改造后的代码如下:
单存展示需要注意的地方的代码:
// 数据模型中的某个值改变
demoModel.number ++;
// **************需要改变监听的对象,可以通过创建一个新对象的方式来出发监听*****************
DemoModel newDemoModel = DemoModel(number: demoModel.number);
numberNotifier.value = newDemoModel;
发现这样修改后的代码可以正常的出发界面的刷新了