我们在做项目开发的时候,经常发现有些组件需要我们传递一个controller
,通过这个controller
来控制当前组件的行为例如 通过 ScrollController
来监听可滚动组件的滚动距离、控制滑动距离,使用TextEditingController
来控制TextField
处理输入框的文本信息,达到在页面内控制组件状态 的行为,这个控制器 controller
到底是什么东西,是如何来做到没有调用setState
就达到页面刷新的效果呢,我们进入到 ListView
内部看一下,发现这个ScrollController
是一个继承于ChangeNotifier的类
从字面意思我们就可以了解到即'变化 通知',就是有了变化就会发出一个通知,我们收到这个通知,做一些页面UI的刷新,具体是如何做到的呢,下面我们自己定义个组件,并通过自定义一个controller
来详细介绍一下 ChangeNotifier
的使用方式。
1、 自定义一个卡片组件
代码很简单,就是一个Container
放到页面中:
scala
class CustomCard extends StatelessWidget {
const CustomCard({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.red[100], borderRadius: BorderRadius.circular(8)),
width: 100,
height: 100,
alignment: Alignment.center,
child: const Text('Card'),
);
}
}
2、下面开始,把组件中的宽高、颜色、文字抽取到外面,并在页面中定义一个按钮改变CustomCard
的状态:
less
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomCard(width: _width, color: _color, text: _text),
ElevatedButton(onPressed: (){
setState(() {
_width += 10;
_color = Colors.primaries[ Random().nextInt( Colors.primaries.length)];
_text = Random().nextInt(100).toString();
});
}, child: const Text('Change'))
],
),
arduino
class CustomCard extends StatelessWidget {
double ? width;
Color ? color;
String ? text;
CustomCard({Key? key, this.width, this.color, this.text}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: color ?? Colors.red[100], borderRadius: BorderRadius.circular(8)),
width: width ?? 100,
height: width ??100,
alignment: Alignment.center,
child: Text(text??''),
);
}
}
点击Change 按钮,卡片放大,更换随机背景色,更改text 文本,这时候还没有我们想要的控制器,别着急,我们慢慢来。
3、定义一个Controller
控制器:
ini
class CustomCardController extends ChangeNotifier {
double? _width = 100;
Color? _color = Colors.red.withOpacity(0.5);
String? _text = 'Card';
double get width => _width ?? 0;
set width(newVal) {
_width = newVal;
notifyListeners();
}
Color get color => _color ?? Colors.white;
set color(newVal) {
_color = newVal;
notifyListeners();
}
String get text => _text ?? '';
set text(newVal) {
_text = newVal;
notifyListeners();
}
}
这里面改变属性的时候我们用到了 set
方法,并且在set
方法内调用了notifListeners
方法,这是很重要的一点,在属性值改变时必须调用notifyListeners
,并且在组件内需要配合使用 ListenableBuilder
(在旧版本的Flutter中使用AnimatedBuilder
,属性是animation
,这两个仅仅是名称和属性名不同,用法是一模一样的) 来触发更新:
页面组件:
less
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomCard(controller: controller),
Padding(
padding: const EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () {
controller.width += 10;
},
child: const Text('change width'),
),
),
ElevatedButton(
onPressed: () {
controller.color = Colors
.primaries[Random().nextInt(Colors.primaries.length)];
},
child: const Text('change color')),
ElevatedButton(
onPressed: () {
controller.text =
controller.text + controller.text.length.toString();
},
child: const Text('change text'))
],
)
自定义卡片组件:
scala
class CustomCard extends StatelessWidget {
CustomCard({Key? key, required this.controller}) : super(key: key);
CustomCardController controller = CustomCardController();
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: controller,
builder: (BuildContext context, Widget? child) {
return Container(
width: controller.width,
height: controller.width,
color: controller.color,
alignment: Alignment.center,
child: Text(controller.text),
);
},
);
}
}
使用自定义的Controller
后,在页面中通过controller
操作我们的卡片组件,并且没有调用setState
就达到的刷新页面的效果,这在很大程度上能提升App的性能。
4、Controller
的优化
我们在定义一个controler 时,内部使用了很多get set 方法,并且每个set 方法内都调用了一次 notifiyListeners, 这样看起来不够优雅,实际上Flutter内已经封装好一个类,实现了监听一个值的改变并且自动调用notifyListeners 方法那就是ValueNotifier,下面我们把Controller 改造一下:
scss
class CustomCardController {
ValueNotifier<double> width = ValueNotifier(100);
ValueNotifier<Color> color = ValueNotifier(Colors.red[100]!);
ValueNotifier<String> text = ValueNotifier('Card');
}
我们的自定义卡片也需要做一下改变:
scala
class CustomCard extends StatelessWidget {
CustomCard({Key? key, required this.controller}) : super(key: key);
CustomCardController controller = CustomCardController();
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: Listenable.merge([controller.width, controller.color, controller.text]),
builder: (BuildContext context, Widget? child) {
return Container(
width: controller.width.value,
height: controller.width.value,
color: controller.color.value,
alignment: Alignment.center,
child: Text(controller.text.value),
);
},
);
}
}
由于我们监听的是三个值的变化,所以在listenable
属性这里需要使用 Listenable.merge
页面组件也需要改动一下:
less
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomCard(controller: controller),
Padding(
padding: const EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () {
controller.width.value += 10;
},
child: const Text('change width'),
),
),
ElevatedButton(
onPressed: () {
controller.color.value = Colors
.primaries[Random().nextInt(Colors.primaries.length)];
},
child: const Text('change color')),
ElevatedButton(
onPressed: () {
controller.text.value =
controller.text.value + controller.text.value.length.toString();
},
child: const Text('change text'))
],
)
到此,我们自定义的一个Controller
就完成了。