Flutte3.0遥遥领先系列|一文教你完全掌握getX, 手写getX框架

1. GetX相关优势

1.1 )依赖注入

GetX是通过依赖注入的方式,存储相应的XxxGetxController;已经脱离了InheritedWidget那一套玩法,自己手动去管理这些实例,使用场景被大大拓展
简单的思路,却能产生深远的影响:优雅的跨页面功能便是基于这种设计而实现的、获取实例无需BuildContext、GetBuilder自动化的处理及其减少了入参等等

1.2 )跨页面交互的状态管理

这绝对是GetX的一个优点!对于复杂的生产环境,跨页面交互的场景,实在太常见了,GetX的跨页面交互,实现的也较为优雅

1.3 )路由管理

getx内部实现了路由管理,而且用起来,非常简单!bloc没实现路由管理,我不得不找一个star量高的路由框架,就选择了fluro,但是不得不吐槽下,fluro用起来真的很折磨人,每次新建一个页面,最让我抗拒的就是去写fluro路由代码,横跨几个文件来回写,头皮发麻
GetX实现了动态路由传参,也就是说直接在命名路由上拼参数,然后能拿到这些拼在路由上的参数,也就是说用flutter写H5,直接能通过Url传值,OMG!可以无脑舍弃复杂的fluro了

1.4 ) 实现了全局BuildContext
1.5 )国际化,主题实现
生命周期

用了Getx的state管理之后, 你再也用不着StatefulWidget了. 仅仅StatelessWidget就够你用了! 性能自然也提升很多!

2. GetX怎么将逻辑层和界面层解耦的

此处需要划分三个结构了:state(状态层),logic(逻辑层),view(界面层)

为什么写成这样三个模块,需要把State单独提出来,为了复杂的业务, 显的更简单!

举例:

之前的写法

class IdentificationCard extends StatefulWidget {
  
  State<StatefulWidget> createState() {
    return IdentificationState();
  }
}

class IdentificationState extends State<IdentificationCard> {
  String date = "555";
  String name = "666";

  
  Widget build(BuildContext context) {
    return Column(
      children: [
                Text('date : $date'),
                Text('name : $name'), 
                GestureDetector(onTap: () {
                  setState(() {
                    date = "777";
                  });
                }, child: const Text('修改'))],
    );
  }
}


用getx的实现
新建一个类, 定义一个状态, GetxController
添加一个Obx就能自动监听状态的改变并且刷新UI了

/// 状态
class IdentificationState {
  RxString date = "555".obs;
  RxString name = "666".obs;
}

/// 业务逻辑
class IdentificationController extends GetxController {
  IdentificationState state = IdentificationState();
}

/// 展示
class IdentificationCard extends StatelessWidget {
  IdentificationController controller = Get.put(IdentificationController());

  
  Widget build(BuildContext context) {
    return Obx(() {
      return Column(
        children: [
          Text('date : ${controller.state.date}'),
          Text('name : ${controller.state.name}'),
          GestureDetector(
              onTap: () {
                controller.state.date.value = "777";
              },
              child: const Text('修改'))
        ],
      );
    });
  }
}

3.GetX实现状态管理

GetX 的刷新方案分为手动刷新与自动刷新,GetBuilder 与 Obx ,分别对应范围刷新与局部刷新

2者的区别:

Obx 响应式状态管理

Obx 可以配合响应式字段局部的精准刷新避免父容器无效重构,缺点是字段变为响应式的Rx包装类,布局也需要被Obx包裹了,破坏了原生代码观赏性。

GetBuilder 状态管理器

GetBuilder 就是指定区域范围手动去刷新的,可以分区设置多个刷新区域,可选择单个控件或容器,在一些特定场景下有奇效,但是如果不理解滥用一样会导致性能问题。

3.1 Obx 的基本原理是什么?

Obx是配合Rx响应式变量使用
这样一来我们就明白了Obx实际上是一个StatefulWidget,它里面监听了一个GetStream,一旦GetStream有事件通知,它就会进行setState重新进行Widget的构造.
GetBuilder 与 Obx 两者结合,一个指定区域范围手动刷新,一个是局部控件点对点刷新

var build = () => Text(name.value)

Obx(build);

源码: Obx继承了一个抽象ObxWidget类,将传递进来的build方法给了ObxWidget

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

  
  Widget build() => builder();
}


ObxWidget继承了有状态组件,并且build函数让Obx类实现了

abstract class ObxWidget extends StatefulWidget {
  const ObxWidget({Key? key}) : super(key: key);

  
  _ObxState createState() => _ObxState();

  
  Widget build();
}

对GexX的状态管理做一个简单总结,

基于Obx收集依赖状态, 实际一个StatefulWidget,它的State也就是ObxState中监听了GetStream事件流,通过接收GetStream事件流调用setState重新构建Obx,Rx对象在改变value的时候会向GetStream事件流发送事件,这样就会导致Obx进行刷新了.

3.2 GetBuilder

GetBuilder 是一个 Widget 组件, 在 GetX 的状态管理中,GetBuilder 的主要作用是结合 GetxController 实现界面数据的更新
demo使用

class CounterBinding extends Bindings {
  
  void dependencies() {
    Get.lazyPut(() => CounterController());
  }
}


class CounterController extends GetxController {
  int count = 0;
  
  void increase(){
    count += 1;
    update();
  }
}

class CounterPage extends StatelessWidget {

  final controller = Get.find<CounterController>();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Counter"),
      ),
      body: Center(
        child: GetBuilder<CounterController>(builder: (logic) {
          return Text("${controller.count}", style: const TextStyle(fontSize: 50),);
        }),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: controller.increase,
      ),
    );
  }
}

demo调用总结: 然后调用 update 方法更新界面数据,从而实现计数器的功能。
状态管理源码分析:

class GetBuilder<T extends GetxController> extends StatefulWidget {
  final GetControllerBuilder<T> builder;
  final bool global;
  final Object? id;
  final String? tag;
  final bool autoRemove;
  final bool assignId;
  final Object Function(T value)? filter;
  final void Function(GetBuilderState<T> state)? initState,
      dispose,
      didChangeDependencies;
  final void Function(GetBuilder oldWidget, GetBuilderState<T> state)?
      didUpdateWidget;
  final T? init;

  const GetBuilder({
    Key? key,
    this.init,
    this.global = true,
    required this.builder,
    this.autoRemove = true,
    this.assignId = false,
    this.initState,
    this.filter,
    this.tag,
    this.dispose,
    this.id,
    this.didChangeDependencies,
    this.didUpdateWidget,
  }) : super(key: key);

  
  GetBuilderState<T> createState() => GetBuilderState<T>();
}

GetBuilder 是继承自 StatefulWidget

GetBuilder 就是指定区域范围手动去刷新的,可以分区设置多个刷新区域,可选择单个控件或容器,在一些特定场景下有奇效,但是如果不理解滥用一样会导致性能问题。

GetBuilderState

class GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
    with GetStateUpdaterMixin {
  T? controller;
  bool? _isCreator = false;
  VoidCallback? _remove;
  Object? _filter;

  
  void initState() {...}

  void _subscribeToController() {...}

  void _filterUpdate() {...}

  
  void dispose() {...}

  
  void didChangeDependencies() {...}

  
  void didUpdateWidget(GetBuilder oldWidget) {...}

  
  Widget build(BuildContext context) {...}
}


方法: build()


Widget build(BuildContext context) {
  return widget.builder(controller!);
}

通过对 GetBuilder 的源码分析,基本了解了 GetBuilder 各个参数的作用和实现原理。

GetBuilder 参数作用总结如下:

builder: Widget 构建器,创建界面显示的 Widget
init: 初始化 Controller 值,当 global 为 false 时使用该值作为 Controller,当 global 为 true 时且 Controller 未注册依赖,则将 init 的值注入依赖使用。
global: 是否全局,作用于 Controller 初始化中,与 init 结合使用
autoRemove: 是否自动移除 Controller 依赖,结合 assignId 一起使用
assignId: 为 true 时结合 autoRemove 使用会自动移除 Controller 依赖关系
filter: 过滤器,通过返回值过滤是否需要刷新,返回值变化时才会刷新界面
tag: Controller 依赖注入的 tag,根据 tag 获取 Controller 实例
id: 刷新标识,结合 Controller 的 update 使用,可以刷新指定 GetBuilder 控件内的 Widget
initState: 回调函数,生命周期 initState 方法中调用
dispose: 回调函数,生命周期 dispose 中调用
didUpdateWidget: 回调函数,生命周期 didUpdateWidget 中调用
didChangeDependencies: 回调函数,生命周期 didChangeDependencies 中调用

4. 依赖管理: Binding:

依赖注入: 就是赋值, 但是很多类给你的类赋值, 这样就很乱了
Binding的使用: 一般和controller在一起使用
binding模块需要在getx路由页面进行绑定;进入页面的时候,统一懒注入binding模块的GetXController

class _GetImpl extends GetInterface {}

final Get = _GetImpl();

extension Inst on GetInterface {
  S put<S>(S dependency,
          {String? tag,
          bool permanent = false,
          InstanceBuilderCallback<S>? builder}) =>
      GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
}

class GetInstance {
  factory GetInstance() => _getInstance ??= GetInstance._();

  const GetInstance._();

  static GetInstance? _getInstance;

  static final Map<String, _InstanceBuilderFactory> _singl = {};

  S put<S>(
    S dependency, {
    String? tag,
    bool permanent = false,
     InstanceBuilderCallback<S>? builder,
  }) {
    _insert(
        isSingleton: true,
        name: tag,
        permanent: permanent,
        builder: builder ?? (() => dependency));
    return find<S>(tag: tag);
  }

  void _insert<S>({
    bool? isSingleton,
    String? name,
    bool permanent = false,
    required InstanceBuilderCallback<S> builder,
    bool fenix = false,
  }) {
    final key = _getKey(S, name);
    _singl.putIfAbsent(
      key,
      () => _InstanceBuilderFactory<S>(
        isSingleton,
        builder,
        permanent,
        false,
        fenix,
        name,
      ),
    );
  }

  String _getKey(Type type, String? name) {
    return name == null ? type.toString() : type.toString() + name;
  }

  S find<S>({String? tag}) {
    final key = _getKey(S, tag);
    if (isRegistered<S>(tag: tag)) {
      if (_singl[key] == null) {
        if (tag == null) {
          throw 'Class "$S" is not registered';
        } else {
          throw 'Class "$S" with tag "$tag" is not registered';
        }
      }
      final i = _initDependencies<S>(name: tag);
      return i ?? _singl[key]!.getDependency() as S;
    } else {
      // ignore: lines_longer_than_80_chars
      throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"';
    }
  }
}

5. 路由管理之命名路由

特点:封装了context, 封装了拦截器!
路由管理之简单路由

GetMaterialApp(
    unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
    routingCallback: (routing) {
      if(routing?.current == '/second'){
       ///处理一些业务
      }
    },
    initialRoute: '/',
    getPages: [
      GetPage(name: '/first', page: ()=>First()),
      GetPage(name: '/second', page: ()=>Second())
    ],
  )

问题: 为何不用Flutter自己的Router系统?

使用时还需要有一个context实例. 但我们并不是随时随地都持有一个context的, 这也局限了我们的使用场景.

6. GETX的缺点:

第一个缺点

Get.to(widgetObj, bindings)是可以注入binding.

但是Get.toNamed()并不支持binding参数啊. 我的跳转一般都是用toNamed的, 所以注定了这种方式我用不了.

第二个缺点

这个缺点很隐藏, 很容易出问题. 以上面的binding为例

HomeBinding中提供了 HomeController, Service 两个对象
DetailsBinding中提供了 DetailsController 对象 但其实我们的Details页中也会用到Service对象.
之所以不出现"details页中说找不到Service"的crash, 是因为用户先打开的home页, Home已经往Get中写入了Service对象了, 所以等之后打开detail页时, serivce对象已经有了, 能够Get.find()得到, 所以不会有NPE错误.

但要是deep link的场景呢?

你直接跳到了Detail页, 结果就因为没有经过home页, 所以Service service = Get.find()找不到service对象, 应用会crash.

所以现在就明白了, 第二个缺点就是: 上面两个Binding有隐藏的依赖性 DetailsBinding其实依赖于HomeBinding. HomeBinding不先放好service, 那DetailsBinding提供不了Serivce, 就可能会让Detail页crash.

第三个缺点: obs会频繁刷新;

7. 手写getX

3大核心功能
7.1 依赖注入

///依赖注入,外部可将实例,注入该类中,由该类管理
class Easy {
  ///注入实例
  static T put<T>(T dependency, {String? tag}) =>
      _EasyInstance().put(dependency, tag: tag);

  ///获取注入的实例
  static T find<T>({String? tag, String? key}) =>
      _EasyInstance().find<T>(tag: tag, key: key);

  ///删除实例
  static bool delete<T>({String? tag, String? key}) =>
      _EasyInstance().delete<T>(tag: tag, key: key);
}

///具体逻辑
class _EasyInstance {
  factory _EasyInstance() => _instance ??= _EasyInstance._();

  static _EasyInstance? _instance;

  _EasyInstance._();

  static final Map<String, _InstanceInfo> _single = {};

  ///注入实例
  T put<T>(T dependency, {String? tag}) {
    final key = _getKey(T, tag);
    //只保存第一次注入:针对自动刷新机制优化,每次热重载的时候,数据不会重置
    _single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
    return find<T>(tag: tag);
  }

  ///获取注入的实例
  T find<T>({String? tag, String? key}) {
    final newKey = key ?? _getKey(T, tag);
    var info = _single[newKey];

    if (info?.value != null) {
      return info!.value;
    } else {
      throw '"$T" not found. You need to call "Easy.put($T())""';
    }
  }

  ///删除实例
  bool delete<T>({String? tag, String? key}) {
    final newKey = key ?? _getKey(T, tag);
    if (!_single.containsKey(newKey)) {
      print('Instance "$newKey" already removed.');
      return false;
    }

    _single.remove(newKey);
    print('Instance "$newKey" deleted.');
    return true;
  }

  String _getKey(Type type, String? name) {
    return name == null ? type.toString() : type.toString() + name;
  }
}

class _InstanceInfo<T> {
  _InstanceInfo(this.value);

  T value;
}

7.2 状态管理

///自定义个监听触发类
class EasyXNotifier {
  List<VoidCallback> _listeners = [];

  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }

  void removeListener(VoidCallback listener) {
    for (final entry in _listeners) {
      if (entry == listener) {
        _listeners.remove(entry);
        return;
      }
    }
  }

  void dispose() {
    _listeners.clear();
  }

  void notify() {
    if (_listeners.isEmpty) return;

    for (final entry in _listeners) {
      try {
        entry.call();
      } catch (e) {
        print(e.toString());
      }
    }
  }
}

7.3 路由管理

///刷新控件,自带回收机制
class EasyBuilder<T extends EasyXController> extends StatefulWidget {
  final Widget Function(T logic) builder;

  final String? tag;
  final bool autoRemove;

  const EasyBuilder({
    Key? key,
    required this.builder,
    this.autoRemove = true,
    this.tag,
  }) : super(key: key);

  
  _EasyBuilderState<T> createState() => _EasyBuilderState<T>();
}

class _EasyBuilderState<T extends EasyXController>
    extends State<EasyBuilder<T>> {
  late T controller;

  
  void initState() {
    super.initState();

    controller = Easy.find<T>(tag: widget.tag);
    controller.xNotifier.addListener(() {
      if (mounted) setState(() {});
    });
  }

  
  void dispose() {
    if (widget.autoRemove) {
      Easy.delete<T>(tag: widget.tag);
    }
    controller.xNotifier.dispose();

    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return widget.builder(controller);
  }
}


最后

为了能够方便大家快速学习Flutter, 这里整理了Flutter学习路线图以及《Flutter Dart 语言编程入门到精通》&《Flutter实战:第二版》帮助大家配置相关环境,学习Flutter 的基本语法以及最后的项目实际利用。

学习路线:
在这里插入图片描述

Dart语言是Flutter的开发语言,所以我们需要掌握Dart语言的基础知识, 由于内容过多,截图展示的目录及部分内容,完整文档领取方式扫描下方二维码即可免费获取!

《Flutter Dart 语言编程入门到精通》

第一章 Dart语言基础

  • 环境准备
  • 基础语法

第二章 Dart 异步编程

  • Dart的事件循环
  • 调度任务
  • 延时任务
  • Future详解
  • async和await
  • lsolate

img

第三章 异步之 Stream 详解

  • 什么是Stream
  • 单订阅流
  • 广播流
  • Stream Transformer
  • 总结

第四章 Dart标准输入输出流

  • 文件操作

img

第五章 Dart 网络编程

  • TCP服务端
  • TCP客户端
  • UDP服务端
  • UDP客户端
  • HTTP服务器与请求
  • WebSocket

第六章 Flutter 爬虫与服务端

  • Dart爬虫开发
  • Dart服务端
  • 创建Flutter项目演示
  • 总结

第七章 Dart 的服务端开发

  • 注册登录实现

第八章 Dart 调用C语言混合编程

  • 环境准备
  • 测试Dart ffi接口
  • 总结

第九章 LuaDardo中Dart与Lua的相互调用

  • Lua C API
  • 创建运行时
  • Dart调Lua
  • Lua调Dart

img

掌握了Dart语言之后,咱们就可以通过实战来掌握Flutter的知识点, 由于内容过多,截图展示的目录及部分内容,完整文档领取方式扫描下方二维码即可免费获取!

《Flutter实战:第二版》

第一章:起步

  • 1.1 移动开发技术简介
  • 1.2 初始Flutter
  • 1.3 搭建Flutter开发环境
  • 1.4 Dart语言简介

第二章:第一个Flutter应用

  • 2.1 计数器应用实例
  • 2.2 Widget简介
  • 2.3 状态管理
  • 2.4路由管理
  • 2.5包管理
  • 2.6 资源管理
  • 2.7 调试Flutter应用
  • 2.8 Flutter异常捕获

在这里插入图片描述

第三章:基础组件

  • 3.1 文本及样式
  • 3.2 按钮
  • 3.3 图片及ICON
  • 3.4 单选开关和复选框
  • 3.5 输入框及表单
  • 3.6 进度指示器

第四章:布局类组件

  • 4.1 布局类组件简介
  • 4.2 布局原理与约束(constraints)
  • 4.3 线性布局(Row和Column)
  • 4.4 弹性布局(Flex)

在这里插入图片描述

第五章:容器类组件

  • 5.1 填充(Padding)
  • 5.2 装饰容器(DecoratedBox)
  • 5.3 变换(Transform)
  • 5.4 容器组件(Container)
  • 5.5 剪裁(Clip)
  • 5.6 空间适配(FittedBox)
  • 5.7 页面骨架(Scaffold)

第六章:可滚动组件

  • 6.1 可滚动组件简介
  • 6.2 SingleChildScrollView
  • 6.3 ListView
  • 6.4 滚动监听及控制

在这里插入图片描述

第七章:功能型组件

  • 7.1 导航返回拦截(WillPopScope)
  • 7.2 数据共享(InheritedWidget)
  • 7.3 跨组件状态共享
  • 7.4 颜色和主题
  • 7.5 按需rebuild(ValueListenableBuilder)
  • 7.6 异步UI更新(FutureBuilder、StreamBuilder)
  • 7.7 对话框详解

第八章:事件处理与通知

  • 8.1 原始指针事件处理
  • 8.2 手势识别
  • 8.3 Flutter事件机制
  • 8.4 手势原理与手势冲突
  • 8.5 事件总线
  • 8.6 通知 Notification

在这里插入图片描述

第九章:动画

  • 9.1 Flutter动画简介
  • 9.2 动画基本结构及状态监听
  • 9.3 自定义路由切换动画
  • 9.4 Hero动画
  • 9.5 交织动画
  • 9.6 动画切换组件(AnimatedSwitcher)
  • 9.7 动画过渡组件

第十章:自定义组件

  • 10.1 自定义组件方法简介
  • 10.2 组合现有组件
  • 10.3 组合实例:TurnBox
  • 10.4 CustomPaint 与 Canvas
  • 10.5 自绘实例:圆形背景渐变进度条
  • 10.6 自绘组件:CustomCheckbox
  • 10.7 自绘组件: DoneWidget
  • 10.8 水印实例: 文本绘制与离屏渲染

img

第十一章:文件操作与网络请求

  • 11.1 文件操作
  • 11.2 通过HttpClient发起HTTP请求
  • 11.3 Http请求库-dio
  • 11.4 实例:Http分块下载

第十二章:Flutter扩展

  • 12.1 包和插件
  • 12.2 Flutter Web

第十三章:国际化

  • 13.1 让App支持多语言
  • 13.2 实现Localizations
  • 13.3 使用Intl包
  • 13.4 国际化常见问题

在这里插入图片描述

第十四章:Flutter核心原理

  • 14.1 Flutter UI 框架(Framework)
  • 14.2 Element、BuildContext和RenderObject
  • 14.3 Flutter启动流程和渲染管线
  • 14.4 布局(Layout)过程
  • 14.5 绘制(一)绘制原理及Layer

第十五章:一个完整的Flutter应用

  • 15.1 Github客户端示例
  • 15.2 Flutter APP代码结构
  • 15.3 Model类定义
  • 15.4 全局变量及共享状态
  • 15.5 网络请求封装
  • 15.6 APP入口及主页
  • 15.7 登录页
  • 15.8 多语言和多主题

img

由于内容过多,截图展示的目录及部分内容,完整文档领取方式扫描下方二维码即可免费获取!

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值