-
- 级联操作符
Dart 中 级联操作符 可以返回对象从而继续执行其他方法:
new Column( children: [ new Container(), ] ..addAll(List.generate(3, (index) { return Container(); })) ..addAll([ new HorizontalLine(height: 5), ]), );
-
- 一次性执行匿名回调函数
Dart 支持一次性执行匿名回调函数,如:
void test() { () { print('hi'); }(); }
也可在组件中运用并接收参数,如:
@override Widget build(BuildContext context) { return new Scaffold( body: new Text((String text) { return text; }('我是文字')), }
-
- 可选方法参数
Dart
方法可以设置 参数默认值 和 指定名称 。比如:
getDetail(Sting userName, reposName, {branch = "master"}){}
方法,这里 branch 不设置的话,默认是 “master” 。参数类型 可以指定或者不指定。调用效果:getRepositoryDetailDao(“aaa", "bbbb", branch: "dev");
。 -
- Assert (断言)
assert
只在检查模式有效,在开发过程中,assert(unicorn == null);
只有条件为真才正常,否则直接抛出异常,一般用在开发过程中,某些地方不应该出现什么状态的判断。 -
- 扩展
扩展可以给指定类型的数值增加获取方法,如( ScreenUtil 给 num 加的扩展):
extension SizeExtension on num { num get w => ScreenUtil().setWidth(this); num get h => ScreenUtil().setHeight(this); num get sp => ScreenUtil().setSp(this); num get ssp => ScreenUtil().setSp(this, allowFontScalingSelf: true); }
比如我想获取一个 ScreenUtil 适配的50宽度值:
Container(width: 50.w)
等同于:
Container(width: ScreenUtil().setWidth(50))
从而简化了代码,提升了开发效率。
-
- runZoned
可以在自己的区域中运行指定代码(根据 zoneSpecification 使用 Zone.fork 创建的区域),如果代码出现错误将抛出全局错误在 onError:
runZoned(() { runApp(MyApp()); }, onError: (Object obj, StackTrace stack) { print(obj); print(stack); });
我们通用的错误信息统计平台如 sentry,一般都是放在 runZoned 的 onError 内。
组件
1. ListView 自动内边距
ListView 会在脚手架中未写 AppBar 的情况下自动添加 padding 内边距,内边距值是状态栏高度
2. Draggable 重绘问题
Draggable 的 child 和 feedback 是同一个组件是每次拖动都会重绘组件,如果不想被重绘,在组件的 key 给个 GlobalKey即可。
3. 拿到数组模型索引
List.generate 可以获得数组模型的索引 index,length 写数组模型的长度即可。
4. 输入框内容被清空
如果出现输入框内容输入的时候突然返回到桌面,回来的时候被清空了,可以尝试使用 WidgetsBindingObserver 监听不可见生命周期,输入框焦点自动取消,从而输入框会保存原有内容。(这个问题在早期 flutter 版本出现)
5. 刷新页面的指定组件
如果只想刷新某个页面的指定组件可使用 GlobalKey 包裹此组件的 State 类,调用此组件的时候传过去,想刷新的时候就可以在外面调用这个 GlobalKey 的 setState 或其他刷新方法了。
6. 拿特殊滑动组件内控制器
特殊滑动组件如 NestedScrollView 是有内外控制器的,普通的赋值 controller 只能拿到外部的滑动值,想要拿到内控制可以在 NestedScrollView 的 body 中类在上下文获取,调用上下文的 ancestorWidgetOfExactType
class DataBody extends StatefulWidget {
@override
_DataBodyState createState() => _DataBodyState();
}
class _DataBodyState extends State {
ScrollController pageScrollController;
Type typeOf() => T;
@override
void initState() {
super.initState();
PrimaryScrollController primaryScrollController =
context.ancestorWidgetOfExactType(typeOf());
pageScrollController = primaryScrollController.controller;
}
}
不过这个在 1.12 已被废弃,新版 Flutter 使用 findAncestorWidgetOfExactType
代替。
功能
1. 无需上下文路由
自己定义个 NavigatorState 的全局 key 然后赋值到 MaterialApp 的 navigatorKey 就能使用 NavigatorState 的所有功能并无需上下文。
2. Future 执行完成错误
如果出现 Future 执行完成错误可尝试使用 Completer 的 future,如:
Future _refreshData() {
final Completer completer = new Completer();
new Future.delayed(new Duration(seconds: 2), () {
completer.complete(null);
});
return completer.future;
}
3. 调用方法出现 null 错误
dispose 销毁某对象时在调用 .dispose
前加个 ?
问号,可以避免前面的为空导致调用不到 dispose 方法报错。
4. 页面状态保存
Flutter 中可以通过 mixins AutomaticKeepAliveClientMixin
,然后重写 wantKeepAlive
保持住页面,记得在被保持住的页面 build
中调用 super.build
。
5. 手势识别范围
使用 GestureDetector 范围只有 child 的设定颜色或使用区域才可被触发,behavior 为 HitTestBehavior.translucent
时整个 child 会被触发,InkWell 组件默认整个 child 会被触发。
hitTest
得到一个包含所有待处理控件列表 HitTestResult
dispatchEvent
进行事件分发并产生竞争,最终胜利者可以得到事件响应权
如果同一个区域内有多个控件都实现了 handleEvent
,那么最后事件应该交给谁处理呢?
事件竞争只有符合以下两个条件之一的,才可以得到事件的处理权。
- 最后得到直接胜利的控件
- 活到最后的控件中排在列表第一位的控件
6. Flutter 手势事件主要是通过竞技判断
主要有 hitTest
把所有需要处理的控件对应的 RenderObject
, 从 child
到 parent
全部组合成列表,从最里面一直添加到最外层。
然后从队列头的 child 开始 for 循环执行 handleEvent
方法,执行 handleEvent
的过程不会被拦截打断。
一般情况下 Down 事件不会决出胜利者,大部分时候是在 Move 或者 up 的时候才会决出胜利者。
竞技场关闭时只有一个的就直接胜出响应,没有胜利者就拿排在队列第一个强制胜利响应。
同时还有 didExceedDeadline
处理按住时的 Down 事件额外处理,同时手势处理一般在 GestureRecognizer
的子类进行。
插件开发
1. android 获取 context 和 activity
可以在插件的 Plugin 类实现 ActivityAware,然后在 onAttachedToActivity 和 onReattachedToActivityForConfigChanges 都可以拿到 ActivityPluginBinding 的 binding,binding 调用getActivity。
public class DemoPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
static Context ctx;
static Activity activity;
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
ctx = binding.getApplicationContext();
}
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
activity = binding.getActivity();
ctx = binding.getActivity();
}
@Override
public void onDetachedFromActivityForConfigChanges() {
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
ctx = binding.getActivity();
activity = binding.getActivity();
}
}
2. PlatformView
Flutter 中通过 PlatformView
可以嵌套原生 View
到 Flutter
UI 中,这里面其实是使用了 Presentation
+ VirtualDisplay
+ Surface
等实现的,大致原理就是:
使用了类似副屏显示的技术,VirtualDisplay
类代表一个虚拟显示器,调用 DisplayManager
的 createVirtualDisplay()
方法,将虚拟显示器的内容渲染在一个 Surface
控件上,然后将 Surface
的 id 通知给 Dart,让 engine 绘制时,在内存中找到对应的 Surface
画面内存数据,然后绘制出来。实时控件截图渲染显示技术。
3. Platform Channel
Flutter 中可以通过 Platform Channel
让 Dart 代码和原生代码通信的:
BasicMessageChannel
:用于传递字符串和半结构化的信息。MethodChannel
:用于传递方法调用(method invocation)。EventChannel
: 用于数据流(event streams)的通信。
同时 Platform Channel
并非是线程安全的
更多详细可查阅闲鱼技术的 《深入理解Flutter Platform Channel》
介绍
1. 运行模式
Flutter 的 Debug 下是 JIT 模式,release 下是 AOT 模式。目前主流的语言大多数只支持其中一种编译方式,如 C 仅支持 AOT,JavaScript 仅支持 JIT,一般来说静态语言使用 AOT,动态语言使用 JIT
AOT 编译方式下,编译器必须在执行代码前将代码编译成机器执行的原生代码,在程序运行时就不需要做其他额外的操作直接快速执行,但是编译时需要区分用户机器架构,生成不同架构的二进制代码
JIT 程序运行前不需要编译代码,而是在运行时动态编译,不用考虑用户机器是什么架构,虽然缩短了开发周期,但是可能导致程序执行速度更慢