Flutter中自定义一个Controller控制器

我们在做项目开发的时候,经常发现有些组件需要我们传递一个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 就完成了。

相关推荐
火柴就是我2 天前
flutter 之真手势冲突处理
android·flutter
Speed1232 天前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间2 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
恋猫de小郭2 天前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
玲珑Felone2 天前
从flutter源码看其渲染机制
android·flutter
ALLIN3 天前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei3 天前
Flutter 国际化
flutter
Dabei3 天前
Flutter MQTT 通信文档
flutter
Dabei3 天前
Flutter 中实现 TCP 通信
flutter
孤鸿玉3 天前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter