Flutter 空安全的糖果罐,行业寒冬
(默认放置在 “lib” 下面)-r, --ruleconsts 的名字的命名规范“lwu”(小写带下划线) : “assets_images_xxx_jpg”“uwu”(大写带下划线) : “ASSETS_IMAGES_XXX_JPG”“lcc”(小驼峰): “assetsImagesXxxJpg”(默认 “lwu”)-c, --classconst 类的名字(默认 “Assets”)–cons
(默认放置在 “lib” 下面)
-r, --rule consts 的名字的命名规范
“lwu”(小写带下划线) : “assets_images_xxx_jpg”
“uwu”(大写带下划线) : “ASSETS_IMAGES_XXX_JPG”
“lcc”(小驼峰) : “assetsImagesXxxJpg”
(默认 “lwu”)
-c, --class const 类的名字
(默认 “Assets”)
–const-ignore 使用正则表达式忽略一些const(不是全部const都希望生成)

图片
ExtendedImage,集众多功能为一体的图片组件,包括以下主要功能:
- 缓存网络图片
- 加载状态(正在加载,完成,失败)
- 拖拽缩放图片
- 图片编辑(裁剪,旋转,翻转)
- 图片预览(跟微信掘金一样)
- 滑动退出效果(跟微信掘金一样)
- 设置圆角,边框
- 支持进度显示
- 图片预览上滑显示详情(跟图虫一样)
- 减少内存占用
![]() |
![]() |
![]() |
| [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEG8cdWN-1637309523707)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/440c95ef3f284123bb23e5a33c6c4391~tplv-k3u1fbpfcp-watermark.image)] | ![]() |
![]() |
列表扩展
ExtendedList,针对官方 Listview 和 GirdView 做的扩展组件,包括以下主要功能:
- 监听元素回收
- 监听
Viewport中元素变化 - 为最后一个元素设置特殊布局
- 列表倒序特殊布局,类聊天列表
![]() |
![]() |
嵌套滚动视图扩展
ExtendedNestedScrollView,主要解决官方 NestedScrollView 存在的2个问题。
- 解决
NestedScrollView的 Header 中不能处理多个pinned为true的元素的问题。github.com/flutter/flu… - 解决
NestedScrollView的 Body 中列表滚动会互相影响的问题。github.com/flutter/flu…
Sliver 扩展
ExtendedSliver,对 Sliver 组件的扩展,主要包括以下功能:
-
SliverPinnedPersistentHeader,跟官方的SliverPersistentHeader(pinned: true)一样的效果, 不同的是你不需要去设置minExtent和maxExtent。因为大部分场景下面,我们是无法提前知道minExtent和maxExtent。 -
SliverPinnedToBoxAdapter,可以通过它轻松创建一个置顶的元素,当child没有layout之前,你没法知道child的实际大小,这将是非常有用的组件。 -
ExtendedSliverAppbar,你可以创建一个跟SliverAppbar一样效果的组件,而不用去关心expandedHeight。

TabBarView 扩展
ExtendedTabs,对 TabBarView 组件的扩展,主要包括以下功能:
- 解决多级
TabBarView嵌套的时候,无法连贯切换的问题 - 垂直方向滚动
- 设置缓存页面数量
- 提供
CarouselIndicator和ColorTabIndicator
![]() |
![]() |
文本
ExtendedText,针对 Text 组件的扩展,主要包括以下功能:
- 方便快速生成特殊文本,将字符串转换成特定的
InlineSpan。 -
BackgroundTextSpan自定文字背景,处理圆角或者中英文背景高度不一致的问题。 -
ExtendedWidgetSpan支持选择和复制, github.com/flutter/flu… . -
TextOverflowWidget自定义文本溢出效果, github.com/flutter/flu… 。
![]() |
![]() |
|  |
|
| | |
输入框
ExtendedTextField,针对 TextField 组件的扩展,主要包括以下功能:
- 方便快速生成特殊文本,原理很简单,就是把字符串转换成特定的
InlineSpan。 -
ExtendedWidgetSpan支持输入框中插入任何Widget,比如表情图片。 -
ExtendedWidgetSpan支持选择和复制, github.com/flutter/flu… 。
![]() |
![]() |
![]() |
![]() |
路由注解
ff_annotation_route,通过注解生成路由映射,统一处理路由,支持 Navigator 1.0 和 Navigator 2.0。
- 激活工具
pub global activate ff_annotation_route - 增加引用
dependencies:
在子模块中引入
ff_annotation_route_core: any
在根项目引入,包括一些帮助类以及 ff_annotation_route_core
ff_annotation_route_library: any
- 添加注释
工具会自动处理带参数的构造,不需要做特殊处理。唯一需要注意的是,你需要设置 argumentImports 来为 class/enum 的参数提供 import 地址。现在你也可以使用 @FFArgumentImport() 注释来替代.
@FFArgumentImport(‘hide TestMode2’)
import ‘package:example1/src/model/test_model.dart’;
@FFArgumentImport()
import ‘package:example1/src/model/test_model1.dart’ hide TestMode3;
import ‘package:ff_annotation_route_library/ff_annotation_route_library.dart’;
@FFRoute(
name: ‘flutterCandies://testPageE’,
routeName: ‘testPageE’,
description: ‘Show how to push new page with arguments(class)’,
// argumentImports are still work for some cases which you can’t use @FFArgumentImport()
// argumentImports: [
// ‘import ‘package:example1/src/model/test_model.dart’;’,
// ‘import ‘package:example1/src/model/test_model1.dart’;’,
// ],
exts: <String, dynamic>{
‘group’: ‘Complex’,
‘order’: 1,
},
)
class TestPageE extends StatelessWidget {
const TestPageE({
this.testMode = const TestMode(
id: 2,
isTest: false,
),
this.testMode1,
});
factory TestPageE.deafult() => TestPageE(
testMode: TestMode.deafult(),
);
factory TestPageE.required({@required TestMode testMode}) => TestPageE(
testMode: testMode,
);
final TestMode testMode;
final TestMode1 testMode1;
}
- 执行命令生成路由,
ff_route <command> [arguments],全部命令如下:
-h, --[no-]help 帮助信息。
-p, --path 执行命令的目录,默认当前目录。
-o, --output route 和 helper 文件的输出目录路径,路径相对于主项目的 lib 文件夹。
-n, --name 路由常量类的名称,默认为 Routes。
-g, --git 扫描 git 引用的 package,你需要指定 package 的名字,多个用 , 分开
–routes-file-output routes 文件的输出目录路径,路径相对于主项目的lib文件夹
–const-ignore 使用正则表达式忽略一些const(不是全部const都希望生成)
–[no-]route-constants 是否在根项目中的 xxx_route.dart 生成全部路由的静态常量
–[no-]package 这个是否是一个 package
–[no-]supper-arguments 是否生成路由参数帮助类
-s, --[no-]save 是否保存命令到本地。如果保存了,下一次就只需要执行 ff_route 就可以了。
–[no-]null-safety 是否支持空安全,默认 true
- 设置
MaterialApp的onGenerateRoute回调
import ‘package:ff_annotation_route_library/ff_annotation_route_library.dart’;
import ‘package:flutter/material.dart’;
import ‘example_route.dart’;
import ‘example_routes.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: ‘ff_annotation_route demo’,
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: Routes.fluttercandiesMainpage,
onGenerateRoute: (RouteSettings settings) {
return onGenerateRoute(
settings: settings,
getRouteSettings: getRouteSettings,
routeSettingsWrapper: (FFRouteSettings ffRouteSettings) {
if (ffRouteSettings.name == Routes.fluttercandiesMainpage ||
ffRouteSettings.name ==
Routes.fluttercandiesDemogrouppage.name) {
return ffRouteSettings;
}
return ffRouteSettings.copyWith(
widget: CommonWidget(
child: ffRouteSettings.widget,
title: ffRouteSettings.routeName,
));
},
);
},
);
}
}
- 打开一个页面
Navigator.pushNamed(
context,
Routes.flutterCandiesTestPageE.name,
arguments: Routes.flutterCandiesTestPageE.requiredC(
testMode: const TestMode(
id: 100,
isTest: true,
),
),
);
可拖拽容器
DraggableContainer,可拖拽容器,支持元素移动动画效果,主要包括以下功能:
- 可拖动子元素
- 可删除子元素
- 可固定子元素
- 元素移动动画效果

图片编辑
ImageEditor,强大的原生图片处理库,主要包括以下功能:
- 裁剪
- 翻转
- 旋转
- 缩放
- 色彩矩阵变化
- 添加文字
- 混合图片
- 添加任意图形

Dialog
SmartDialog,一种更优雅的Dialog 解决方案,主要解决了系统自带的Dialog的一些问题:
- 必须传 BuildContext。
- 无法穿透暗色背景,点击 Dialog 后面的页面。
- 解决系统自带 Dialog 写成的 Loading 弹窗,在网络请求和跳转页面的情况,会存在路由混乱的情况。

资源选择器
AssetPicker,对标微信的多选资源选择器,99%接近于原生微信的操作,主要包括以下功能:
- ♻️ 支持基于代理重载的全量自定义
- 💚 99% 的微信风格
- 📷 图片资源支持
- 🔬HEIC 格式图片支持
- 🎥 视频资源支持
- 🎶 音频资源支持
- 1️⃣ 单资源模式
- 💱 国际化支持
- ➕ 特殊 widget 构建支持(前置/后置)
- 🗂 自定义路径排序支持
- 📝 自定义文本构建支持
- ⏳ 自定义筛选规则支持(
photo_manager) - 🎏 完整的自定义主题
- 💻 支持 MacOS
![]() |
![]() |
![]() |
|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
相机资源选择器
CameraPicker,对标微信的视频资源选择器,99%接近于原生微信的操作,主要包括以下功能:
- 🔐 支持健全的空安全
- 💚 99% 的微信风格
- 📷 支持拍照
- ☀️ 支持设置曝光参数
- 🔍️ 支持捏合缩放
- 🎥 支持录像
- ⏱ 支持限制录像时间
- 🔍 支持录像时缩放
- 🖾 支持自定义前景 widget 构建
![]() |
![]() |
|---|---|
![]() |
![]() |
JsonToDart
JsonToDart,强大的 JsonToDart 工具,主要包括以下功能:
- 空安全
- 编辑类名,属性名
- 去重复类
- Merge 类属性
- 数据数组保护
- 属性命名规范化,只读,排序
- 国际化
- 全平台
- 智能可空
| 平台 | 描述 | 地址 |
|---|---|---|
| Windows | Flutter for Windows | gitee.com/zmtzawqlp/J… |
| Macos | Flutter for Macos | gitee.com/zmtzawqlp/J… |
| Web | Flutter for Web | zmtzawqlp.gitee.io/jsontodart/ |
| 微软商店 | 功能未同步,以后会替换成 Flutter for UWP | www.microsoft.com/store/apps/… |

点赞按钮
LikeButton,仿推特点赞效果,支持数字动画效果。

增量加载列表
LoadingMoreList,支持各种布局的增量加载列表,主要包括以下功能:
- ListView
- GridView
- 瀑布流
- 多个 Sliver 布局
- 自定义加载状态 UI
- 监控进入
Viewport元素 - 类聊天列表布局
- 监控元素回收
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
下拉刷新
PullToRefreshNotification,灵活的自定义下拉刷新组件,可以创造出任意的下拉刷新样式。
![]() |
![]() |
![]() |
![]() |
底部扩散模糊动画
RippleBackdropAnimatePage,骚气十足的模糊动画,只需要几行代码就能帮你实现。

弹出菜单
WPopupMenu,目前最好用的仿微信聊天长按弹出框。

瀑布流
WaterfallFlow,高性能的瀑布流布局,github.com/flutter/flu… 。
- 高性能
- 易上手,跟
GridView一样的api - 可监控进入
Viewport元素 - 可监控元素回收
| [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E7vLEBU9-1637309523724)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a15ef8d1833a4bdb999edd8a926b3a47~tplv-k3u1fbpfcp-watermark.image)] | ![]() |
![]() |
![]() |
迁移
指南
感谢 Flutter & Dart 文档中国本地化 全球遮天团 为我们提供了完整准确的文档,dart.cn/null-safety… ,空安全 迁移大概有下面几个步骤:
-
执行
flutter pub outdated --mode=null-safety,检查自己项目依赖的库是否都支持空安全。 -
如果都支持了,执行
dart migrate --apply-changes。不加--apply-changes的话,会有一个浏览器地址,你打开之后,可以在浏览器中进行修改。我一般还是习惯在直接--apply-changes之后直接在vscode中进行修改。执行完毕之后,你的Dart SDK版本会自动改为大于2.12.0。(注意,执行 dart migrate 命令必须确保SDK是小于2.12.0的)
environment:
sdk: ‘>=2.12.0 ❤️.0.0’
- 工具不是万能的,会有一些
错误,请先查看完 dart.cn/null-safety 之后,根据自己的业务场景对代码进行更正。
问题
空安全对非空 List<T> 的影响是非常大的。
不能对非空的列表设置更大的长度
List 的 length getter 也有一个对应的 setter,这一点鲜为人知。您可以对列表设置一个较短的长度,从而截断它。您也可以对列表设置一个更长的长度,从而使用未初始化的元素填充它。
如果您对一个非空的列表做了这样的操作,在访问未初始化的元素时,就与空安全的健全性发生了冲突。为了防止意外发生,现在对一个非空类型的数组调用 length setter, 并且 准备设置一个更长的长度时,会在运行时抛出一个异常。您仍然可以对任何类型的列表进行截断,也可以对一个可空类型的列表进行填充。
如果您自定义了列表的类型,例如继承了 ListBase 或者混入了 ListMixin,那么这项改动可能会造成较大的影响。以上的两种类型都提供了 insert() 的实现,通过设置长度,为插入的元素提供空间。在空安全中这样做可能会出现错误,所以我们将它们的 insert() 实现改为了 add()。现在您自定义的列表应该继承 add() 方法 方法。
下面我们来跟一波可空列表在做 add 操作时候的流程,来理解下文档所说的意思。
| 类 | 位置 |
|---|---|
list.dart |
bin/cache/dart-sdk/lib/collection/list.dart |
growable_array.dart |
bin/cache/dart-sdk/lib/_internal/vm/lib/growable_array.dart |
array.dart |
bin/cache/dart-sdk/lib/_internal/vm/lib/array.dart |
ListMixin.add (dart:collection/list.dart:278)
// List interface.
void add(E element) {
// This implementation only works for lists which allow null
// as element.
this[this.length++] = element;
}
List.length= (dart:core-patch/growable_array.dart:227)
void set length(int new_length) {
rt|bin/cache/dart-sdk/lib/_internal/vm/lib/growable_array.dart| |array.dart|bin/cache/dart-sdk/lib/_internal/vm/lib/array.dart` |
ListMixin.add (dart:collection/list.dart:278)
// List interface.
void add(E element) {
// This implementation only works for lists which allow null
// as element.
this[this.length++] = element;
}
List.length= (dart:core-patch/growable_array.dart:227)
void set length(int new_length) {
更多推荐












































所有评论(0)