Flutter开发中的一些Tips(四)

news2024/11/17 12:40:31

在这里插入图片描述
最近接手了一个flutter项目,整体感觉代码质量不高,感觉有些是初学者容易犯的问题。几年前写的前三篇,我是站在我自己开发遇到问题的角度,这篇是站在别人遇到问题的角度,算是一种补充。下面我整理一下遇到的小问题,大家可以当作开发中的Tips。

1.使用“平替”Widget

Spacer

有时候在Row或者Column中需要占位会有如下写法:

Expanded(child: Container())
/// 或
const Expanded(child: SizedBox())

上面两种相比我更推荐第二种,因为可以加const。另外第一种方式,因为Container没有child,所以它的大小会撑满父布局。在Stack中使用需要注意,别问我为什么突然这么说,因为我就遇到这样写的,然后出了问题。。。

当然Flutter还有自带的Spacer,源码如下:

class Spacer extends StatelessWidget {
  const Spacer({super.key, this.flex = 1})
    : assert(flex > 0);
  final int flex;

  
  Widget build(BuildContext context) {
    return Expanded(
      flex: flex,
      child: const SizedBox.shrink(),
    );
  }
}

等于是上面第二种方法封装了一层,使用起来也更方便,const Spacer()就搞定了。

Nil

有时会根据一个条件来判断显示什么widget。当我们无法返回null时,我们会返回类似const SizedBox()的东西。

这很好,但自从SizedBox创建RenderObject,它有一些性能影响。RenderObject位于渲染树中,并在上面执行一些计算,即使它在屏幕上没有绘制任何东西。
所以我们可以有一个不创建RenderObject的Widget,同时仍然有效。Nil小部件是它的实现。它只创建一个Element,在构建时什么也不做。

代码如下:

/// A widget which is not in the layout and does nothing.
/// It is useful when you have to return a widget and can't return null.
class Nil extends Widget {
  /// Creates a [Nil] widget.
  const Nil({super.key});

  
  Element createElement() => _NilElement(this);
}

class _NilElement extends Element {
  _NilElement(Nil super.widget);

  
  void mount(Element? parent, dynamic newSlot) {
    assert(parent is! MultiChildRenderObjectElement, """
        You are using Nil under a MultiChildRenderObjectElement.
        This suggests a possibility that the Nil is not needed or is being used improperly.
        Make sure it can't be replaced with an inline conditional or
        omission of the target widget from a list.
        """);
    super.mount(parent, newSlot);
  }

  
  bool get debugDoingBuild => false;

  
  void performRebuild() {
    super.performRebuild();
  }
}

目前Nil不支持RowColumn这类(MultiChildRenderObjectElement)多个子节点的组件。

ColoredBox/SizedBox/DecoratedBox等

如果只是设置背景色,可以完全使用ColoredBox替代Container;同理如果只是设置大小,可以使用SizedBox;只设置边框圆角渐变这类,可以使用DecoratedBox。类似的还有PaddingTransform等。如果你需要以上的多种组合,这个时候推荐Container ,因为它就是以上实现的封装。

为什么如此?Container是一个比ColoredBox等更重的小部件,里面的实现不都是我们需要的。同时也是为了尽可能的使用const声明。const的问题,我在很早之前的Flutter性能优化实践 —— UI篇中就有说明过:

有人测试一个页面上构建1000个重复图标,结果使用const构造函数的,FPS大约高8.4%,内存使用量降低约20%。
当然,实际一个页面上有1000个Widget也不现实。其实说这个点的原因也是希望大家能养成一个好习惯。

如果你的linter启用了use_colored_box或是sized_box_for_whitespace,当你有上述“错误”写法时,编译器也会给你相应的警告。

2.嵌套

Flutter的地狱嵌套一直被许多人诟病,觉得代码看起来很乱。但是实际上很好解决这个问题。

  • Widget封装。封装公共组件,实现拆分。
  • 善用ThemeData,可以避免重复设置属性。
  • 可以使用flutter_constraintlayout 库,它和 Android 下的 ConstraintLayout ,iOS 下的 AutoLayout 相似。可以将“阶梯状”的代码变得“扁平”。我自己在日常开发中优先就使用的它,可以让我无脑布局。
  • 换一种嵌套方式。大家可以看下Flutter的源码,学习一下代码风格。例如Container 源码:
 
  Widget build(BuildContext context) {
    Widget? current = child;

    if (child == null && (constraints == null || !constraints!.isTight)) {
      current = LimitedBox(
        maxWidth: 0.0,
        maxHeight: 0.0,
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
      );
    } else if (alignment != null) {
      current = Align(alignment: alignment!, child: current);
    }

    final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
    if (effectivePadding != null) {
      current = Padding(padding: effectivePadding, child: current);
    }

    if (color != null) {
      current = ColoredBox(color: color!, child: current);
    }
	...

    if (margin != null) {
      current = Padding(padding: margin!, child: current);
    }

    if (transform != null) {
      current = Transform(transform: transform!, alignment: transformAlignment, child: current);
    }

    return current!;
  }

可以看到是一种从上到下嵌套的方式。比如我们写一个控件,可以先写内容,然后再套大小间距样式,再套点击事件这种。这样代码就会清晰很多。下面是一个简单的例子:

 
  Widget build(BuildContext context) {
    Widget child = Row(
      children: <Widget>[
        Text(title),
        const Spacer(),
        ...
      ],
    );
    
    child = Container(
      margin: const EdgeInsets.only(left: 15.0),
      padding: const EdgeInsets.fromLTRB(0, 15.0, 15.0, 15.0),
      constraints: const BoxConstraints(
        minHeight: 50.0,
      ),
      width: double.infinity,
      decoration: BoxDecoration(
        border: Border(
          bottom: Divider.createBorderSide(context, width: 0.6),
        ),
      ),
      child: child,
    );
    
    return InkWell(
      onTap: onTap,
      child: child,
    );
  }

3.Linter规则

使用linter可以帮助我们识别Dart代码中可能存在的问题,当有“问题”时,编译器会有警告,同时里面会有文档链接详细解释说明。另一方面在团队开发中也可以统一代码规范。

一般新建项目后,会自动添加flutter_lints库。这个里面有lints库中有关dart的core规则集,和recommended规则集,加上flutter_lints中的flutter规则集。我数了一下大概八十多条规则,但我觉得还远远不够。比如上面提到有关ColoredBoxuse_colored_box这条就没有。

所以我个人推荐使用Flutter项目中的analysis_options.yaml内容来替换flutter_lints,然后可以基于此规范再做调整。目前Flutter项目中的analysis_options.yaml大致有两百条规则左右。更多的规则可以给到更多代码建议,同时统一标准,长远看是有利于团队的。

比如最近我看见analysis_options.yaml中新增了一条规则strict-inference,它是当无法确定静态类型时,类型推断永远不会选择动态类型。
在这里插入图片描述
加上这条后,我就发现了我之前的“错误”写法。

在这里插入图片描述
就是因为我没有声明方法的返回类型,那么以前编译器就会推断为dynamic。所以修改很简单,前面加上void就好了。

其实看下flutter源码,你就会发现都是很标准的写法:

在这里插入图片描述

所以对于初学者我更是建议学的时候就直接上高标准,学的时候就严要求自己。否则“不良”的代码习惯一旦养成,后面再改就比较痛苦了。

4.SafeArea

SafeArea内部通过Padding添加间距来避免我们的widget和状态栏/刘海/底部的安全区域重叠。但是使用时需要注意,默认情况下SafeArea的上下左右四个属性都是true。所以如果包裹的控件在上方显示,注意将bottom改为false,否则如果设备bottom不为0时,包裹的控件下方会有高度占用,影响下方排列的widget。

另外就是横屏使用时,注意考虑左右方向。

5.Mixin

Mixin(混入)是Dart语言的一个特性。有别于extends的单继承,它是一种在多类层次结构中复用代码的一种方式。我个人认为是必须要掌握的,使用起来方便,也是封装功能的首选。我们平时使用的SingleTickerProviderStateMixinAutomaticKeepAliveClientMixin都是利用Mixin实现的。

下面我举个小例子说明一下用法。比如平时会使用各种Controller来监听动画,滑动之类的,页面销毁时我们要将这些Controller释放掉。就会有类似下面的代码:

class TestPageState extends State<TestPage> {
   final TextEditingController _controller = TextEditingController();
   final FocusNode _nodeText = FocusNode();
   
   
   void initState() {
     _controller.addListener(callback);
     super.initState();
   }

   
   void dispose() {
     _controller.removeListener(callback);
     _controller.dispose();
     _nodeText.dispose();
     super.dispose();
   }
 }

如果一个页面上有许多Controller时,会写许多这样的代码。说实话挺麻烦的,一个不留神可能还会漏掉。那我们完全可以把这些相似的操作封装起来。

mixin ChangeNotifierMixin<T extends StatefulWidget> on State<T> {

  Map<ChangeNotifier?, List<VoidCallback>?>? _map;

  Map<ChangeNotifier?, List<VoidCallback>?>? changeNotifier();
  
  
  void initState() {
    _map = changeNotifier();
    /// 遍历数据,如果callbacks不为空则添加监听
    _map?.forEach((changeNotifier, callbacks) { 
      if (callbacks != null && callbacks.isNotEmpty) {

        void addListener(VoidCallback callback) {
          changeNotifier?.addListener(callback);
        }

        callbacks.forEach(addListener);
      }
    });
    super.initState();
  }

  
  void dispose() {
    _map?.forEach((changeNotifier, callbacks) {
      if (callbacks != null && callbacks.isNotEmpty) {
        void removeListener(VoidCallback callback) {
          changeNotifier?.removeListener(callback);
        }

        callbacks.forEach(removeListener);
      }
      changeNotifier?.dispose();
    });
    super.dispose();
  }
}

在页面执行initStatedispose方法时,就会自动执行Mixin中的这两个方法,就像是方法“混入”其中一样。

修改后,使用方法:

class TestPageState extends State<TestPage> with ChangeNotifierMixin<TestPage> {
   final TextEditingController _controller = TextEditingController();
   final FocusNode _nodeText = FocusNode();

   
   Map<ChangeNotifier, List<VoidCallback>?>? changeNotifier() {
     return {
       _controller: [callback],
       _nodeText: null,
     };
   }
 }

这样处理后,代码是不是简洁了许多。而且使用起来只需要添加ChangeNotifierMixin实现changeNotifier方法就行了。如果用抽象去封装,效果可以达到一样,但是无法多个继承,都放在一个base下会越来越臃肿。Mixin有种即插即用的感觉,既可以像继承一样方便,又可以像接口一样实现多个,非常好用。

同理,Dart的扩展方法,枚举扩展这些语法特性,都是需要掌握的。


本篇到此结束,同时推荐阅读前三篇。后面有补充内容也会更新到这里。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1201711.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux安装微信

Linux安装微信 环境&#xff1a;ubuntu 20.04 https://archive.ubuntukylin.com/ubuntukylin/pool/partner/weixin_2.1.4_amd64.deb sudo dpkg -i weixin_2.1.4_amd64.deb完成 参考文章

【Spring进阶系列丨第一篇】初识Spring框架

前言 小伙伴们大家好&#xff0c;我是陈橘又青&#xff0c;今天起 《Spring进阶系列》 开始更新。本专栏将涵盖Spring框架的核心概念、配置管理、Web开发、AOP、Boot、Security、Data、Integration和Batch等多个主题。通过理论讲解和实际案例的剖析&#xff0c;帮助读者深入理解…

k8s的Init Containers容器实现代码版本升级发布和deployment版本回退:实战操作版

Pod中的初始化容器&#xff1a;Init Containers initContainers实现理论前提:同一个Pod内的容器共享 网络、volume等资源 Init Containers 在Kubernetes中&#xff0c;init容器是在同一个Pod中的其他容器之前启动和执行的容器。它的目的是为Pod上托管的主应用程序执行初始化…

【C++】STL容器适配器——priority_quene(堆/优先级队列)类的使用指南(含代码使用)(19)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.priority_quene的文档介绍二、prior…

爬虫实战:基于urllib和mysql爬取苏州公交线路信息

文章目录 写在前面实验环境实验描述实验目标实验内容1. 确定并分析目标网页结构2. 编写urllib代码爬取公交信息3. 保存公交数据到csv文件中4. 保存公交数据到mysql数据库中 写在后面 写在前面 本文将基于python的urllib模块&#xff0c;爬取北京公交线路的信息&#xff0c;最后…

web基础和http协议(粗糙版)

服务部署&#xff0c;集训&#xff0c;分布式&#xff0c;数据库&#xff0c;日志系统&#xff0c;等二阶段 web基础和http协议&#xff1a; web的相关基础知识&#xff0c;包括域名 dns解析 网页的概念以及http协议 1.网络当中通信&#xff1a;端口 ip 协议 tcp/ip 传输过程…

CAD转换器:CAD Exchanger SDK --Crack

转换器 目录 概述读取文件 增量加载写入文件格式特定的详细信息进度状态支持例子 读取和写入多种 CAD 和 BIM 文件格式。 概述 读取&#xff08;导入&#xff09;和写入&#xff08;导出&#xff09;文件是使用 CAD Exchanger SDK 时的主流场景。支持的格式列表可在此处获取。 …

Misc | bucket 第二届“奇安信”杯网络安全技能竞赛

题目描述&#xff1a; 解密Base全家桶。 密文&#xff1a; 下载附件&#xff0c;解压得到一个txt文本&#xff0c;打开如下。 3441344134363435344435323442344534423441343635353334353333323442343935413442353434393535354135333441344534353536353535333332353534413436…

酷柚易汛ERP-自定义打印整体介绍

1、产品介绍 每种单据系统预设常用模板&#xff0c;提供A4纸张、三等分、二等分&#xff0c;销货单额外提供80mm、58mm供用户选择&#xff1b;每张单据可设置一个默认模板和多个常用模&#xff1b;除默认模板外&#xff0c;其他模板都允许删除&#xff0c;用户可以根据公司业务…

ArcGIS实现矢量区域内所有要素的统计计算

1、任务需求&#xff1a;统计全球各国所有一级行政区相关属性的总和。 &#xff08;1&#xff09;有一个全球一级行政区的矢量图&#xff0c;包含以下属性&#xff08;洪灾相关属性 province.shp&#xff09; &#xff08;2&#xff09;需要按照国家来统计各个国家各属性的总值…

前端前沿技术

文章目录 网站静态化PWA - Progressive Web APP, 渐进式 Web 应用PWA 简介解决了哪些问题?PWA 的优势浏览器支持情况参考文档 Weex 是一个可以使用现代化的 Web 技术开发高性能原生应用的框架。高性能跨平台贴近前端生态被大规模的使用 GraphQL[一种用于 API 的查询语言](http…

并发事务下,不同隔离级别可能出现的问题

并发事务下&#xff0c;不同隔离级别可能出现的问题 1、事务的 ACID2、并发事务下&#xff0c;不同隔离级别可能出现的问题2.1、脏写2.2、脏读2.3、不可重复读2.4、幻读 3、SQL 中的四种隔离级别 1、事务的 ACID 原子性&#xff08;Atomicity&#xff09;&#xff1a;原子性意味…

数据结构 | 栈的实现

数据结构 | 栈的实现 文章目录 数据结构 | 栈的实现栈的概念及结构栈的实现 Stack.h初始化栈入栈出栈获取栈顶元素获取栈中有效元素个数检测栈是否为空销毁栈 Stack.c 栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。…

勘察设计考试公共基础之数学篇

1、数学 向量点积&#xff1a;向量叉积&#xff1a;平面的法向量为n&#xff08;A&#xff0c;B&#xff0c;C&#xff09;&#xff0c;则该平面的点法式方程为&#xff1a; A&#xff08;x-x0&#xff09;B&#xff08;y-y0&#xff09;C&#xff08;z-z0&#xff09;0 两平…

爬虫,TLS指纹 剖析和绕过

当你欲爬取某网页的信息数据时&#xff0c;发现通过浏览器可正常访问&#xff0c;而通过代码请求失败&#xff0c;换了随机ua头IP等等都没什么用时&#xff0c;有可能识别了你的TLS指纹做了验证。 解决办法&#xff1a; 1、修改 源代码 2、使用第三方库 curl-cffi from curl…

【T690 之十一】基于方寸EVB2开发板,结合 Eclipse+gdb+gdbserver 调试 CCAT 的流程总结

目录 1. 准备工作1.1 Eclipse1.2 工程编译1.3 烧写固件 2. 创建工程2.1 搭建调试工程2.2 配置Dbug调试信息 3. 调试4. 手动调试过程4. 总结 备注&#xff1a; 1&#xff0c;假设您已对方寸微电子的T690系列芯片的使用方式都有了一定的了解&#xff0c;可以根据此文的配置进行Li…

3D模型人物换装系统二(优化材质球合批降低DrawCall)

3D模型人物换装系统 介绍原理合批材质对比没有合批材质核心代码完整代码修改总结 介绍 本文使用2018.4.4和2020.3.26进行的测试 本文没有考虑法线贴图合并的问题&#xff0c;因为生成法线贴图有点问题&#xff0c;放在下一篇文章解决在进行优化 如果这里不太明白换装的流程可以…

基于物理的多偏置射频大信号氮化镓HEMT建模和参数提取流程

标题&#xff1a;Physics-Based Multi-Bias RF Large-Signal GaN HEMT Modeling and Parameter Extraction Flow 来源&#xff1a;JOURNAL OF THE ELECTRON DEVICES SOCIETY 摘要 本文展示了一种一致的Al镓氮化物&#xff08;AlGaN&#xff09;/氮化镓&#xff08;GaN&#x…

CSS省略号n行公式

记得改图中的n&#xff0c;这是你需要的几行省略号&#xff01;复制中间的5行就行了。 .text {overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: n; //n为你想省略的行数&#xff0c;需要改-webkit-box-orient: vertical; } 这是…

卷积神经网络(1)

目录 卷积 1 自定义二维卷积算子 2 自定义带步长和零填充的二维卷积算子 3 实现图像边缘检测 4 自定义卷积层算子和汇聚层算子 4.1 卷积算子 4.2 汇聚层算子 5 学习torch.nn.Conv2d()、torch.nn.MaxPool2d()&#xff1b;torch.nn.avg_pool2d()&#xff0c;简要介绍使用方…