作为忠实与较资深的 Android 汪, 最近抽出了一些时间研究了一下 Google 的亲儿子 Flutter, 尚属皮毛, 只能算是个简单的记录吧.
Google 自 2017 年第一次提出 Flutter, 到 2018 年 Beta, 再加之 RN 的各种风波与问题, 使得 Flutter 的热度不断上升, 国内不少公司都公布 Flutter 在其产品中的应用, 如美团, 闲鱼等.
前言
Flutter 作为跨平台框架, 常常被人拿出来与 React Native, 以及 Xamarin 进行对比, 除了大家都是跨平台框架之外且能达到近乎 Native 的体验之外, Flutter 与这两者的原理大不相同.
让我们来看看这三者的结构图吧.
可能有一些复杂, 咱大致解释一下.
React Native 跟 Xamarin 都是基于 mapping native 代码来实现所谓的 Native 体验的框架, 只是 RN 基于 JS 引擎 + Bridge 与 native 打交道, 并且在运行时进行绑定, 而 Xamarin 是基于微软的基于 Linux 的 C# 虚拟机 mono + JNI 与 native 进行通信.
这里 Android 与 iOS 还是有差别的, 如 RN 在 iOS 上 JS 引擎不支持 JIT, 会一定程度影响效率, Xamarin 在 iOS 上可以直接编译成 iOS 平台可以执行的程序, 所以在实际运行起来的性能是一样的, 唯一的差别就是微软得更快的支持 API 同步.
对于 Flutter 来说, 由于他的渲染引擎使用了 Skia 直绘, 加上基于 C++ 的 Dart 引擎, 所以在不同平台上没有差别, 加之其实现了 Android Material Design 与 iOS Cupertino 两套 UI 组件, 所以即便是自绘组件, 看起来还是跟原生的一个样子.
通过对三种跨平台引擎的大致了解, 我们可以看出来, 他们都达到了一定程度的 Native 体验, 然则各自都有一定的性能损耗, 比如 RN 的 JS 引擎加载 JS, 以及 Bridge 通信的损耗, Xamarin Mono 虚拟机与 Java 通信的损耗, 以及 Flutter Skia 渲染与 Native Android 渲染的差异等.
Flutter 笔记
如何启动一个 App
Android 需要在 Manfest 里面指定带有 MAIN action 与 LAUNCHER category 的 Activity 声明, 而 Flutter 只需要一行.
void main() => runApp(MyApp());
其中 MyApp 就是一个普通的 Widgets(View).
View vs Widgets
Flutter 没有 View, 与之对应的是 Widget, 并且分为 StatelessWidgets 与 StatefulWidgets, 前者是个静态 View, 后者是动态通过 Data 来更新的 View.
- Stateless
- Text(
- 'I like Flutter!',
- );
- Stateful
- class StatefulText extends StatefulWidget {
- @override
- State<StatefulWidget> createState() => _TextState();
- }
- class _TextState extends State<StatefulText> {
- // Default placeholder text
- String textToShow = "I Like Flutter";
- void _updateText() {
- setState(() {
- // update the text
- textToShow = "Flutter is Awesome!";
- });
- }
- @override
- Widget build(BuildContext context) {
- ...invoke _updateText
- }
- }
实际上是因为 StatefulWidgets 通过调用 State 的 setState 方法来触发整个 Widgets 树的重绘, 并且在重绘之前会调用传进去的(){ ... }block.
怎么写 Layout, xml 到哪里去了.
实际上 Flutter 没有 xml 了, 并且是通过 Widgets 的嵌套来实现一个布局的.
如:
Center 是一个可以把子 View 放置在中央的容器.
Row 对应的就是 LinearLayout + Horizontal, Column 对应的就是 LinearLayout + Vertical, 他们都具备一个属性叫做 crossAxisAlignment, 有点类似 gravity, 来控制子 View 相对于父 View 的位置.
Expanded 支持一个类似 weight 的属性, 叫 flex.
Container 是一个具有 decoration 属性的容器, 可以用来控制背景色, border, margin 等等.
Stack 有点像是一个特殊的 RelatetiveLayout 或者 ConstraintLayout, children 属性指定了它的子 View, 第一个是 Base View, alignment 属性指定了后面的子 View 相对于 BaseView 的位置, 如 alignment: const Alignment(0.6, 0.6)指定了位于 BaseView 右下角的位置.
ListTile 是一个特殊的 ListItem, 有三个属性, 分别是左边的 Icon (leading), 文字 (title), 以及右边的 Icon (trailing).
还有诸如 ListView, GridView, Card 等等比较熟悉的 Widgets.
另外有一个类似于我们 Activity 的 Widgets:
叫做 MaterialApp, 可以指定 theme, title, 以及子 View home, 还有更重要的页面跳转 routes.
- MaterialApp(
- title: 'Welcome to Flutter',
- home: ...,
- routes: <String, WidgetBuilder> ...,
- theme: ThemeData(
- primaryColor: Colors.white
- ),
- )
还有一个类似于 Fragment 的:
叫做 Scaffold, 中文意思是脚手架, 它包含一个 appBar (ActionBar)与一个 body, appBar 可以指定 title 与 actions (类似于 action button 的点击事件).
- Scaffold(
- appBar: AppBar(
- title: Text(widget.title),
- actions: <Widget>[...],
- ),
- body: ...,
- )
如何从父 View 中 Remove 一个元素
答案是没有... 因为在 Flutter 看来, Widgets 的树结构是不可以被更改的, 但是如果想更改, 则是通过 StatefulWidgets 的方法, 通过 setState 来更改 Data, 触发 Widgets 重绘, 从而替换掉之前的 Widgets.
喜欢画 Canvas 的同学怎么办?
Flutter 同样支持, CustomPaint 作为一个 Widgets 就支持传入一个实现 CustomPainter 抽象类的参数, 而 CustomPainter 的抽象方法也类似于 Android View 的 onDraw
- void paint(Canvas canvas, Size size)
- bool shouldRepaint(CustomPainter oldDelegate)
如何自定义 View
不用继承, 而使用类似 Android ViewGroup 的办法, 通过组合 (composing) 与封装的方法来实现, 通过小 Widgets 组合成需要的新 Widgets.
页面跳转怎么办, 四大组件之一的 Intent 跑哪里去了
貌似在讲类似于 Activity 的 MaterialApp 的时候剧透了...
就是使用 Navigator 与 Routes 来实现界面跳转, 实际上是整个 Widgets 的替换.
- routes: <String, WidgetBuilder> {
- '/a': (BuildContext context) => MyPage(title: 'page A'),
- '/b': (BuildContext context) => MyPage(title: 'page B'),
- '/c': (BuildContext context) => MyPage(title: 'page C'),
- }
- Navigator.of(context).pushNamed('/b');
如何处理外部的 Intent
实际上还是需要在 Flutter App 的 Android 壳子中注册这个 filter, 然后在 FlutterActivity 中拿到存下来,
FlutterView 初始化后再通过 Bridge, 官方叫 MethodChannel 从 Java 里获取, 进行下一步逻辑.
可以看个简单的例子.
- new MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler(
- new MethodCallHandler() {
- @Override
- public void onMethodCall(MethodCall call, MethodChannel.Result result) {
- if (call.method.contentEquals("getSharedText")) {
- result.success(sharedText);
- sharedText = null;
- }
- }
- });
- getSharedText() async {
- var sharedData = await platform.invokeMethod("getSharedText");
- if (sharedData != null) {
- setState(() {
- dataShared = sharedData;
- });
- }
- }
** 常用的 startActivityForResult 怎么办 **
```.
这个 Flutter 有完全对应的办法, 而且用起来很方便, 结合之前说的页面跳转:
- Map xxx = await Navigator.of(context).pushNamed('/xxx');
- Navigator.of(context).pop({
- xxx
- });
** 异步怎么办, runOnUiThread()哪里去了 **
Flutter 有点像 JS, 是一个单线程模式, 所以只是通过模拟来构建简单的异步, 关键字就是类似于 kotlin coroutines 一样, 通过 await+async 来处理.
如:
- loadData() async {
- response = await http.get(xxx);
- setState(() {
- xxx
- });
- }
但是由于它的单线程, 所以无法做很长的阻塞操作, 像 http 请求的延迟正常情况可能都是毫秒级的, 但是数据的处理等, 可能就得秒级了.
这也是 RN 在线程方面的做 Android 程序的一个痛点, Flutter 采用了比较容易想到的曲线救国的办法, 提供了一个叫 Isolate 的对象, 它实际是一个基于 socket 的数据通道, 相当于把数据放在一个独立的进程进行处理, 然后再通过 socket 发送回程序进程, 还记得进程间通信办法之一的管道吗...
**Flutter 替代 OkHttp 的网络库 **
自带了 http 库, 直接 http.get(url), 在线程部分的代码实例里也有涉及.
通过类似 gradle 的文件 pubspec.YAML 引入.
- dependencies:
- ...
- http: ^0.12
^ 表示不升大版本, 并取最新版本, 比 gradle 的 + 要范围更小.
** 常见的 LCE(Loading Content Error)里面的 Loading 怎么 show**
Flutter 有一个 widget 叫做 ProgressIndicator, 比如我们期望有一个转圈圈的 Loading 界面在数据加载出来之前.
我们就可以通过 StatefulWidgets, 根据数据, 或者 List Widgets 的个数 (如果是显示一个 List 的话)来判断是否显示 Loading, 使用子类 CircularProgressIndicator, 来替换页面的 Widgets.
当然也是通过 setState(() {...})来触发界面刷新的, 可以在 initState()内触发加载数据的异步操作.
** 不同分辨率的图片资源怎么放 **
这个有点像 iOS 了, 即有 1x,2x,3x:
- images/my_icon.PNG // Base: 1.0x image
- images/2.0x/my_icon.PNG // 2.0x image
- images/3.0x/my_icon.PNG // 3.0x image
不一样的一点还需要添加到类似 gradle 的文件 pubspec.YAML 里.
- assets:
- images/my_icon.jpeg
** 字符串怎么存储 **
Flutter 没有像 Android 的 string.xml 的东西, 目前来说最好的就就是存成静态字符串.
- class Strings {
- static String welcomeMessage = "Welcome To Flutter";
- }
- Text(Strings.welcomeMessage)
**Gradle 变成什么了 **
前面说网络库, 图片资源的时候提到过, 提供了一个叫 pubspec.YAML 的文件
**Fragment 与 Activity 呢?**
之前做过类比, 如 MaterialApp 有点类似于 Activity, 而 Scaffold 都点类似 Fragment, 实际上他们两个都是 Flutter 的 Widgets, 也就是说其实只有 View 的概念了.
** 还有生命周期吗?**
Flutter 有一个叫做 WidgetsBinding 的可以提供类似生命周期的回调.
四种状态 inactive (iOS 专用), paused(相当于 onPause, 退后台), resumed(相当于 onPostResume, 到前台), suspending(Android 专用, 相当于 onStop).
一般在 StatefulWidgets 的 State 中注册与反注册.
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance.addObserver(this);
- }
- @override
- void dispose() {
- WidgetsBinding.instance.removeObserver(this);
- super.dispose();
- }
- **ScrollView vs ListView**
Flutter 没有 ScrollView, 合并到了 ListView, 通过 ListView.builder 创建的 ListView 提供了 View 复用的逻辑.
- ListView.builder(
- itemCount: widgets.length,
- itemBuilder: (BuildContext context, int position) {
- return Text(xxx);
- }))
其中 itemBuilder 有点像 Android ListView 的 getView, 官方文档说它会自动回收 Element 给你, 但是事实上每次你都需要根据 position 生成新的 Widgets, 所以呢应该是 Flutter 在内部回收了之前的 Widgets 并在你重新创建的时候又用上了.
BTW, 通过 ListView 构造来显示就不具备这种特性, 所以大量数据需要用 Builder.
**Flutter 横竖屏怎么玩.**
因为它实际上还是借助了 Android 程序的壳子, 所以如果 AndroidManifect 定义了 Android:configChanges="orientation|screenSize", 则 Flutter 会自己 hanlde.
** 怎么处理 Gesture**
Flutter 提供了 GestureDetector, 它相当于一个 Container, 将我们期望接收手势的 Widgets 放进去, 再实现事件回调就行了.
- GestureDetector(
- child: FlutterLogo(
- size: 200.0,
- ),
- onTap: () {
- print("tap");
- },
- )
它同样支持其他的手势, 如 onDoubleTap 等等等.
** 字体怎么弄 **
首先需要在 pubspec.YAML 里面配置需要的字体库:
- fonts:
- family: MyCustomFont
- fonts:
- asset: fonts/MyCustomFont.ttf
- style: italic
然后在 Text 的 style 属性进行配置.
- Text(
- 'This is a custom font text',
- style: TextStyle(fontFamily: 'MyCustomFont'),
- )
Hint 哪里去了, 错误信息怎么输出
对于输入框的 Hint 基本一致, 可能就是换了个名字, 一看便知.
- TextField(
- decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
- )
总结
Flutter 在视图渲染上另辟蹊径, 性能优势凸显, 在跨平台框架属于一匹黑马, 又有 Google 撑腰, 值得在 Mobile 勤耕多年的同学入手.
由于作者曾经从事过 2 年的 webkit 开发工作, 拜读了 Flutter 的渲染模式, 很像是 Webkit/Chrome/Blink 的思路, 通过查证, 起草者确实有大批同样的人, 如果你还没有入坑 RN, 或许 Flutter 可以作为跨平台方案学习的首选哦.
同样 Google 自己也有很多 Plugin 去支持更多扩展功能, 如 GPS, Camera, SharePreference, Database. 还例如 Firebase 这种亲儿子级的服务也是全面支持 Flutter.
当然也可以自己去开发需要的 Plugin 来适配需要的功能, 基于的技术就是上面有提的 MethodChannel, NDK 的支持也是同样的道理.
来源: http://blog.51cto.com/13983283/2315551