我是 TrueFace 的高级软件工程师 Adel Boussaken。今天我要给大家介绍 Flutter 开发。欢迎加入 Dart 阵营。
我将跳过所有有关安装和运行 Flutter 应用程序的部分,直接进行操作,如果您不确定 Flutter 是什么,并且有许多问题,请阅读 关于 Flutter 的演进之路 。
Dart
如果你认为 Kotlin 是 Java 的替代,等到你看到 Dart 后,你会发现它是直截了当,没有样板代码的:
- 默认为public,通过前缀“_” 进行私有化;
- 集合; 点击试用
- 一切都是对象 - 没有“primitives”
- 命名参数,可选参数,默认值: 点击试用
- 属性 - 无需在任何地方写“get”方法
- 级联 - 一切都是构建者
- 使用 strong mode 类型推导 - 只需要在本地写“var”
- 命名构造函数,自动分配到字段; 点击试用
- 字符串插值,几种类型的字符串; 点击试用
- dartfmt - 如果不想格式化 也可以不用这个工具
这里有一个 Dart 代码的例子,一个获取 IP 地址的私有方法:
_getIPAddress() async { String url = 'https://httpbin.org/ip'; var httpClient = createHttpClient(); var response = await httpClient.read(url); Map data = JSON.decode(response); String ip = data['origin']; }
设计模式
我很确定你已将你的 Android 技巧从无序代码扩展到 MVC、MVP、MVVM,Dagger2、DataBinding 和 Android 架构组件 ,Flutter 基本上就是一个 V(View),响应式视图,它可以是无状态或有状态的 widget,就连 AppCompatActivity 也是一个 widget。
让我们创建一个非常简单的 Activity,我们需要一个文本视图和一个更新该文本的按钮,下面是 Activity 代码(注意没有 XML):
public class Click extends Activity { int count = 0; protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.main); final Button button = (Button) findViewById(R.id.button); final TextView text = (TextView) findViewById(R.id.counter); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { count++; text.setText("Button clicked " + counter + " times"); } }); } }
在 Flutter,你不需要操纵视图,就像 setText() 中的代码,你可以使用 state 来替代,并且 Flutter 将会处理好屏幕更新,在 Flutter 中和上述代码等价的是:
import 'package:flutter/material.dart';class Activity extends StatefulWidget { @override ActivityState createState() => new ActivityState(); }class ActivityState extends State<Activity> { int counter = 0; @override Widget build(BuildContext context) { return new Scaffold( body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( 'Button clicked ${counter} times', ), new FlatButton( child: new Text('Increment'), onPressed: () { setState(() { counter++; }); }, ), ], ), ), ); } }
你也注意到,我们在 setState 中增加了 counter 的值,一旦 onPressed 被触发(之后是 setState),Flutter 将会使用新的 counter 值来重新渲染整个 widget 和 text 对象。
分离 widget 和 state(对于 Android 开发人员来说)的最大优点是,如果你在 Java 代码中旋转设备或调整窗口大小(支持多窗口),则计数器的值将被重置为零,你必须编写大量重复代码来管理 Activity 的生命周期。而在 Flutter 中,这是不需要的,现成的功能,无需额外的代码。
虽然 Dart 最终编译为 native 代码,但它不会访问硬件设备,例如,如果你要获取用户位置,你必须使用 平台 通道编写特定平台的代码,与此同时,确实有很多插件来处理这些情况,但你依然希望登录Android Studio(或 Xcode)在代码之间完成该事项。
Async/Await
在 Android 开发中,UI 线程非常重要,完成速度稍慢的事情工作,会显示以下神秘行:
I/Choreographer(1378): Skipped 55 frames! The application may be doing too much work on its main thread.
这种情况你只能使用线程来处理,如下所示:
public void onClick(View v) { new Thread(new Runnable() { public void run() { // a potentially time consuming task final Bitmap bitmap = processBitMap("image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start(); }
上述代码可以很简单的使用 Dart 完成:
onClick() async { Bitmap bitmap = await processBitMap("image.png");
// you don't want to update non-existent widget if (!mounted) return;
setState(() { imageView = bitmap; }); }
这里发生了什么? async 修饰符标记的方法会安排稍后执行,该方法变成非阻塞的,因此你的应用程序不会丢失任何帧,await 修饰符挂起方法的执行,等待 processBitMap 完成,然后执行将会恢复。
Widget
目前有大量在 Android 之上运行的各种 OEM 软件和皮肤,这都是可以理解的,因为每个制造商都希望为其设备建立自己的附加值,我不能计算我究竟说了多少次“Funck S*ms*ng”,OEM 定制对 Android 开发人员引入了很多难题.。
Flutter,为了实现 120FPS 的目标,已决定开始在一个空的画布上,使用 Skia 2D 图形库绘制每个 widget,而不是重用 OEM widget,并实现大多数 Material Designa 和通用 layout。
这种方法,无法保证你的应用程序在多种设备上看起来是一样的,尽管并不是所有的 Flutter widget 都经过优化,可以在平板电脑上也可以在较小屏幕的设备上正常工作。
无论如何,这种方法也为 UI 设计的创新打开了新的大门,例如,以下的代码可以使得用户改变 Activity 的大小:
class HeroScreen extends StatefulWidget { @override HeroState createState() => new HeroState(); }class HeroState extends State<HeroScreen> with SingleTickerProviderStateMixin { bool _scaling = false; Matrix4 _transformScale = new Matrix4.identity(); double scalePos = 1.0; set _scale(double value) => _transformScale = new Matrix4.identity()..scale(value, value, 1.0); @override Widget build(BuildContext context) { return new Scaffold( body: new GestureDetector( onScaleStart: (details) { setState(() { _scaling = true; }); }, onScaleUpdate: (details) { var n = details.scale; setState(() { scalePos = n; _scale = n; }); }, onScaleEnd: (details) { setState(() { _scaling = false; _scale = scalePos < 1 ? 1.0 : scalePos; }); }, child: new Container( color: Colors.black26, child: new Transform( transform: _transformScale, child: new Center( child: new Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text(_scaling ? "Scaling" : "Scale me up"), ])))))); } }
这让我们得到一个非常有趣的小部件,这样去写可重复使用的小部件就太容易了, 我可以将以上不太友好的代码转换成可重用且干净的小部件:
class Scale extends StatefulWidget { final Widget child; final GestureScaleStartCallback onStart; final GestureScaleEndCallback onEnd; final GestureScaleUpdateCallback onUpdate; Scale({Key key, this.child, this.onStart, this.onUpdate, this.onEnd}) : super(); @override ScaleState createState() => new ScaleState(); }class ScaleState extends State<Scale> with SingleTickerProviderStateMixin { Matrix4 _transformScale = new Matrix4.identity(); double scalePos = 1.0; set _scale(double value) => _transformScale = new Matrix4.identity()..scale(value, value, 1.0); @override Widget build(BuildContext context) { return new GestureDetector( onScaleStart: widget.onStart, onScaleUpdate: (details) { widget.onUpdate(details); var n = details.scale; setState(() { scalePos = n; _scale = n; }); }, onScaleEnd: (details) { widget.onEnd(details); setState(() { _scale = scalePos < 1 ? 1.0 : scalePos; }); }, child: new Container( color: Colors.black26, child: new Transform( transform: _transformScale, child: widget.child))); } }
重构后的代码如下:
class HeroScreen extends StatefulWidget { @override HeroState createState() => new HeroState(); }class HeroState extends State<HeroScreen> with SingleTickerProviderStateMixin { bool _scaling = false; @override Widget build(BuildContext context) { return new Scaffold( body: new Scale( onStart: (details) => setState(() => _scaling = true), onUpdate: (details) {}, onEnd: (details) => setState(() => _scaling = false), child: new Container( color: Colors.black26, child: new Center( child: new Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text(_scaling ? "Scaling" : "Scale me up"), ]))), )); } }
Etc
- 即时运行称为热重新加载,需要不到一秒钟
- Flutter 使用 CPU 绘制图形、使用 GPU 渲染, 但是一些关于 3D 的 API 是不可用的
- 不用 XML 来设计布局
- 布局只需一次性计算树的大小
- 即使有语法错误,您的 APP 也不会崩溃
- Flutter 小部件可以嵌入到 Android Activity 中
- 您不能在 Flutter 小部件中嵌入 Android 库
- 至少需要 60 FPS,除非你的机器正在做别的事情
- 受到 React 的启发设计 了API,但不是像 React 那样运行的
- 不需要任何 POJO
原文发布时间为:2017-10-10
本文作者:佚名
本文来自合作伙伴“51CTO”,了解相关信息可以关注。