跨平台移动UI框架实践--Flutter

Google 出品,Dart语言,Flutter Engine引擎,响应式设计模式,原生渲染。

目录

1.png

1. 什么是Flutter

  • 现有移动平台:

苹果的iOS SDKs发布于2008年,谷歌的Android软件开发工具包发布于2009年,这两种sdk包是基于不同的编程语言的,分别是Objective-C和Java, 如下是大概的结构图:


2.jpg
  • Flutter:
    是谷歌2018年发布的跨平台移动UI框架,与 react native 和 weex 的通过 Javascript 开发不同,Flutter 的编程语言是Drat,(谷歌亲儿子,据说是因为 Drat 项目组就在 Flutter 隔壁而被选上(◐‿◑))所以执行时并不需要 Javascript 引擎。

Flutter提供响应式的视图,提供Dart语言编译多个平台的原生代码,因此可以直接和平台通信,同时使用Skia图形引擎来完成图形、文本、图像、动画等绘制,拥有自己独立的一套图形系统,不再依赖于原生。避免由JavaScript桥接器引起的性能问题。
Skia:这个是谷歌的一个跨平台渲染框架,从目前iOS和Anrdroid来看,Skia底层最终都是调用OpenGL绘制。

3.jpg

得益于 Engine 层,Flutter 甚至不使用移动平台的原生控件, 而是使用自己 Engine 来绘制 Widget (Flutter的显示单元),而 Dart 代码都是通过 AOT 编译为平台的原生代码,所以 Flutter 可以直接与平台通信,不需要JS引擎的桥接。同时 Flutter 唯一要求系统提供的是 canvas,以实现UI的绘制。

Dart 还可以编译成 ARM 和 x86 代码直接运行在 iOS、Android 设备上。Dart 同时支持 JIT 和 AOT。 Just-in-time Compiler(运行时编译 动态编译) Ahead-of-time Compiler(运行前编译 静态编译)

2. Flutter框架架构

4.jpg

如上图,Flutter 主要分为 Framework 和 Engine,我们基于Framework 开发App,运行在 Engine 上。Engine 是 Flutter 的独立虚拟机,由它适配和提供跨平台支持。

3. 使用Flutter进行开发

5.png
6.png

在Flutter中,几乎所有东西都是一个widget - 甚至布局模型都是widget。您在Flutter应用中看到的图像、图标和文本都是widget。 甚至你看不到的东西也是widget,例如行(row)、列(column)以及用来排列、约束和对齐这些可见widget的网格。

  1. Widgets
  • Widget生命周期

    Flutter提供两种类型的Widget, StatelessWidget 和 StatefulWidget,前者为状态不可变,后者可以通过setState()改变state来改变更新UI,开发者可以根据自己的实际情况使用
    StatefulWidget:


    7.jpg

    StatelessWidget:

    8.jpeg
  • iOS风格Widget

    Cupertino (iOS风格) 类型 作用特点
    CupertinoNavigationBar An iOS-style top navigation bar
    CupertinoTabBar An iOS-style bottom tab bar
    CupertinoActivityIndicator 一个iOS风格的loading指示器。显示一个圆形的转圈菊花
    CupertinoAlertDialog 一iOS风格的alert dialog.
    CupertinoButton iOS风格的button.
    CupertinoDialog iOS风格的对话框,没有按钮
    CupertinoSlider An iOS-style slider
    CupertinoSwitch An iOS-style switch.
    CupertinoPicker An iOS-style picker control.
    CupertinoPageTransition Provides an iOS-style page transition animation.
    CupertinoFullscreenDialogTransition An iOS-style transition used for summoning fullscreen dialogs.
  • Material风格Widget

    Material类型 作用特点
    Scaffold Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet的API。
    Appbar 一个Material Design应用程序栏,由工具栏和其他可能的widget(如TabBar和FlexibleSpaceBar)组成。
    BottomNavigationBar 底部导航条,可以很容易地在tap之间切换和浏览顶级视图。
    TabBar 一个显示水平选项卡的Material Design widget。
    TabBarView 显示与当前选中的选项卡相对应的页面视图。通常和TabBar一起使用。
    MaterialApp 一个方便的widget,它封装了应用程序实现Material Design所需要的一些widget。
    WidgetsApp 一个方便的类,它封装了应用程序通常需要的一些widget。
    Drawer 从Scaffold边缘水平滑动以显示应用程序中导航链接的Material Design面板。
    Image Image.asset Image.network Image.file Image.memory
    Icon A Material Design icon.
    RaisedButton Material Design中的button, 一个凸起的材质矩形按钮
    RaisedButton Material Design中的button, 一个凸起的材质矩形按钮
    FloatingActionButton 一个圆形图标按钮,它悬停在内容之上,以展示应用程序中的主要动作。FloatingActionButton通常用于Scaffold.floatingActionButton字段。
    FlatButton 一个扁平的Material按钮
    IconButton 一个Material图标按钮,点击时会有水波动画
    PopupMenuButton 当菜单隐藏式,点击或调用onSelected时显示一个弹出式菜单列表
    ButtonBar 水平排列的按钮组
    TextField 文本输入框
    Checkbox 复选框,允许用户从一组中选择多个选项。
    Radio 单选框,允许用户从一组中选择一个选项。
    Switch On/off 用于切换一个单一状态
    Slider 滑块,允许用户通过滑动滑块来从一系列值中选择。
    Date & Time Pickers 日期&时间选择器
    SimpleDialog 简单对话框可以显示附加的提示或操作
    AlertDialog 一个会中断用户操作的对话款,需要用户确认
    BottomSheet BottomSheet是一个从屏幕底部滑起的列表(以显示更多的内容)。你可以调用showBottomSheet()或showModalBottomSheet弹出
    SnackBar 具有可选操作的轻量级消息提示,在屏幕的底部显示。
    Card 一个 Material Design 卡片。拥有一个圆角和阴影
    ListTile 一个固定高度的行,通常包含一些文本,以及一个行前或行尾图标。
    Divider 一个逻辑1像素厚的水平分割线,两边都有填充
    Placeholder 一个绘制了一个盒子的的widget,代表日后有widget将会被添加到该盒子中
    RefreshIndicator 内置下拉刷新控件
    ListView 可以有多个子 Widget。
  • 负责Layout的Widget

    Flutter 中拥有需要将近30种内置的 布局Widget,其中常用有 Container、Padding、Center、Flex、Stack、Row、Colum等,下面简单讲解它们的特性和使用。

类型 作用特点
Container 只有一个子 Widget。默认充满,包含了padding、margin、constraints、color、width、height、decoration、alignment、transform。
Padding 只有一个子 Widget。只用于设置Padding,常用于嵌套child,给child设置padding。
Center 只有一个子 Widget。只用于居中显示,常用于嵌套child,给child设置居中。
Stack 可以有多个子 Widget。 子Widget堆叠在一起。
Colum 可以有多个子 Widget。垂直布局。
Row 可以有多个子 Widget。水平布局。
Expanded 只有一个子 Widget。在 Colum 和 Row 中充满。
  1. 布局方式

在iOS系统中,我们使用frame来进行UI布局,同时可以通过addChild和removeChild添加或者移除视图。
但是在Flutter中,Widget 是不可变的,可以传入一个函数,该函数返回一个子Widget 给父 Widget。并在该函数中通过一个 bool 值来控制子 Widget 的创建。

children: <Widget>[
    new Padding(
    padding:
        const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
    child: new RaisedButton(
        textColor: Colors.black,
        child: new Text('opacity'),
        onPressed: () {
          setState(() {
            _aniIndex = 0;
          });
        }),
     ),
],
  1. 事件监听

Flutter中有两种方式来处理touch:
一是直接传递一个处理事件的方法给Widget;
或者通过GestureDetector来实现事件监听与处理。

       new Padding(
         padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
         child: new GestureDetector(
           onTapDown: (tapDown) {
             setState(() {
               tapEvent = '这是GestureDetector监听的onTapDown事件';
             });
           },
           onTapUp: (tapUp) {
             setState(() {
               tapEvent = '这是GestureDetector监听的onTapUp事件';
             });
           },
           onTapCancel: () {
             setState(() {
               tapEvent = '这是GestureDetector监听的onTapCancel事件';
             });
           },
           onDoubleTap: () {
             setState(() {
               tapEvent = '这是GestureDetector监听的onDoubleTap事件';
             });
           },
           onLongPress: () {
             setState(() {
               tapEvent = '这是GestureDetector监听的onLongPress事件';
             });
           },
           child: new BorderButton('GestureDetector onTap 分解事件按钮'),
         ),
       ),
  1. 动画
 // 创建 AnimationController 对象
 controller = AnimationController(
     vsync: this, duration: const Duration(milliseconds: 2000));

 //非线性动画
 final CurvedAnimation curvedAnimation = CurvedAnimation(
   parent: controller,curve: Curves.elasticInOut);
 
 // 通过 Tween 对象 创建 Animation 对象
 animation = Tween(begin: 50.0, end: 200.0).animate(curvedAnimation)
 //Calls the listener every time the value of the animation changes.
   ..addListener(() {
     // 注意:这句不能少,否则 widget 不会重绘,也就看不到动画效果
     setState(() {});
   })
   // Calls listener every time the status of the animation changes. 
   ..addStatusListener((status) {
     if (status == AnimationStatus.completed) {
       controller.reverse();
     } else if (status == AnimationStatus.dismissed) {
       controller.forward();
     }
   });  
 // 执行动画
 controller.forward();
 
 // 做动画的widget
 body: Center(
       child: Container(
         width: animation.value,
         height: animation.value,
         decoration: BoxDecoration(
           color: Colors.redAccent
         ),
       ),
     ),
  1. 数据交互

    Flutter 是支持原生页面和 Flutter 页面混合开发的,但是不支持原生组件在Flutter 中使用,原生端有 MethodChannel 来支持 Flutter 对原生的一些API调用。

flutter --> 原生

// flutter
Future<Null> _launchPlatformCount() async {
 final int platformCounter =
     await _methodChannel.invokeMethod('switchView', _counter);
 setState(() {
   _counter = platformCounter;
 });
}

// iOS
FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"samples.flutter.io/platform_view" binaryMessenger:controller];
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
 if ([@"switchView" isEqualToString:call.method]) {
   _flutterResult = result;
   PlatformViewController* platformViewController =
   [controller.storyboard instantiateViewControllerWithIdentifier:@"PlatformView"];
   platformViewController.counter = ((NSNumber*)call.arguments).intValue;
   platformViewController.delegate = self;
   UINavigationController* navigationController =
   [[UINavigationController alloc] initWithRootViewController:platformViewController];
   navigationController.navigationBar.topItem.title = @"Platform View";
   [controller presentViewController:navigationController animated:NO completion:nil];
 } else {
   result(FlutterMethodNotImplemented);
 }
}];

// FlutterResult是一个回调函数
  1. 网络请求
// 把方法声明为异步方法,通过await关键字等待该异步方法执行完成
 loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  // http.Response response = await http.get(dataURL);
  // setState(() {
  //   widgets = json.decode(response.body);
  // });
  Dio dio =  new Dio();
  Response response = await dio.get(dataURL);
  setState(() {
        widgets = response.data;
      });
 }
  1. 路由导航
    Flutter 中的页面跳转是通过 Navigator 实现的,路由跳转又分为:带参数跳转和不带参数跳转。不带参数跳转比较简单,默认可以通过 MaterialApp 的路由表跳转;而带参数的跳转,参数通过跳转页面的构造方法传递。常用的跳转有如下几种使用:
///不带参数的路由表跳转
Navigator.pushNamed(context, routeName);

///跳转新页面并且替换,比如登录页跳转主页
Navigator.pushReplacementNamed(context, routeName);

///跳转到新的路由,并且关闭给定路由的之前的所有页面
Navigator.pushNamedAndRemoveUntil(context, '/calendar',   ModalRoute.withName('/'));

///带参数的路由跳转,并且监听返回
Navigator.push(context, new MaterialPageRoute(builder: (context) => new NotifyPage())).then((res) {
  ///获取返回处理
  });

可以看到,Navigator 的 push 返回的是一个 Future,这个Future 的作用是在页面返回时被调用的。也就是你可以通过 Navigator 的 pop 时返回参数,之后在 Future 中可以的监听中处理页面的返回结果。

@optionalTypeArgs
static Future<T> push<T extends Object>(BuildContext context, Route<T> route) {
     return Navigator.of(context).push(route);
}

4. Flutter和原生PK

  1. 内存对比
9.png
  1. 启动速度
    10.jpg
  1. 包大小对比
    11.jpg
  1. 兼容性对比
    Flutter 所提供的所有的 Widget 动画还有事件机制都是基于skia来实现的,与平台无关,所以有很高的跨平台的兼容性。但是独立的 UI 系统导致了,很多Android/iOS 对应的工具无法使用。
    移动操作系统:Android Jelly Bean,v16,4.1.x或更新的版本以及iOS 8或更新版本。
    移动硬件:64位iOS设备(从iPhone 5S和更新的iPhone型号开始)以及ARM Android设备。
    请注意,我们目前不支持:
    ● ARM32 iOS设备(iPhone 4,iPhone 5)
    ● x86 Android设备(好像不多吧)

5. 总结

  1. Flutter 的优点:

    ● 热重载(Hot Reload),利用提供的IDE直接保存代码并重载,手机或者模拟器立马就可以看见效果,这一点调试起来很方便。
    ● Widget的理念,对于Flutter来说,手机应用里的所有组件都是Widget,通过可组合的空间集合、丰富的动画库以及分层可扩展的架构实现了富有感染力的灵活界面设计。
    ● 借助GPU加速的渲染引擎以及高性能本地代码运行时以达到跨平台设备的高质量用户体验。

  1. Flutter 的不足:

    ● 开发语言是基于Dart, 对开发者而言,增加了不少学习成本。
    ● UI布局方面,层次不够明显,不那么直接,复杂化了程序的可读性。
    ● Flutter是一种新的框架,目前市面上应用和社区不太成熟,而且支持的库不如ReactNative及原生。
    ● 目前Dart代码会AOT编译到native,不像ReactNative,支持热更新起来会很难,但从API的结构设计上来看,后期应该很快会实现热更新。
    ● 不能支持原生组件在Flutter中显示,导致很多组件需要重新开发,不如ReactNative灵活。
    ● 现在还不支持HTML
    ● bate版 0.58

总之从Flutter的设计理念来看,整体架构都是具有革命性的,相比于其他跨平台实现了真正意义的跨平台,各平台体验一致,而且让用户体验达到了最优,各种UI库和组件也在不断的增加,各种生态系统和社区在不断的完善,对于以后新的操作系统适配性会更强,如Fuchsia系统,非常值得大家了解和学习,相信不久的将来,会慢慢成熟起来,成为主流开发语言。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容