Flutter试用报告


目录

1、Flutter 为什么使用Dart开发语言 2、Flutter的UI系统 1.特色 2.架构简介 2.1 Flutter Engine 2.2 Framework(Dart) 3.Flutter如何经过widget构建UI 4.Flutter是响应式的框架,可是推崇能不变就不变 5.庞大的widget体系,带来方便的同时也带来了高昂的学习成本 6.套娃UI代码,揭开一层还有一层,喝完这杯还有三杯 7.优秀的跨平台UI框架必需要有优秀的UI调试工具 3、Flutter与Native的交融 1.混编依赖方案的抉择 2.通不通且看武功 2.1 打通事件通信:平台通道(Platform Channel) 2.2 打通跨层渲染:外接纹理(Texture)html


1、Flutter 为什么使用Dart开发语言

  • Dart运行时和编译器支持Flutter的两个关键特性:在开发阶段采用,采用JIT模式,改动无需编译,极大的节省了开发时间;发布时能够经过AOT生成高效的ARM代码以保证应用性能。
  • 另外Dart还支持静态类型检查,相比JavaScript在开发时有很大优点。
  • Flutter框架使用函数式流,这使得它在很大程度上依赖于底层的内存分配器,而Dart使用Chrome V8引擎来作内存分配,使得内存分配能够获得保证。
  • Dart使Flutter不须要单独的声明式布局语言,如JSX或XML,或单独的可视化界面构建器,由于Dart的声明式编程布局易于阅读和可视化。全部的布局使用一种语言,汇集在一处,Flutter很容易提供高级工具,使布局更简单
  • 因为Flutter应用程序被编译为本地代码,所以它们不须要在领域之间创建缓慢的桥梁(例如,RN须要在JavaScript和Native之间通讯),它的启动速度也快得多。

2、Flutter 的UI系统

1. 特色

  • Flutter不使用webView,也不使用操做系统的原生控件android

  • Flutter使用本身的高性能渲染引擎Skia来绘制widget。 这样不只能够保证在Android和iOS上UI的一致性,并且也能够避免对原生控件依赖而带来的限制及高昂的维护成本。web

  • 组合大于继承 控件自己一般由许多小型、单用途的控件组成,结合起来产生强大的效果,类的层次结构是扁平的,以最大化可能的组合数量编程

2. 架构简介

Flutter架构图

2.1 Flutter Engine

Flutter引擎是托管Flutter应用程序的可移植运行时。它实现了Flutter的核心库,包括动画和图形、文件和网络I/O、可访问性支持、插件架构以及Dart运行时和编译工具链。大多数开发人员将经过Flutter框架与Flutter进行交互,该框架提供了一个现代的、可响应的框架,以及一组丰富的平台、布局和基础小部件。数组

2.2 Framework(Dart)

这是一个纯 Dart实现的 SDK,它实现了一套基础库,自底向上,咱们来简单介绍一下:xcode

  • 底下两层(Foundation和Animation、Painting、Gestures) 在Google的一些视频中被合并为一个dart UI层,对应的是Flutter中的dart:ui包,它是Flutter引擎暴露的底层UI库,提供动画、手势及绘制能力。安全

  • Rendering层 这一层是一个抽象的布局层,它依赖于dart UI层,Rendering层会构建一个UI树,当UI树有变化时,会计算出有变化的部分,而后更新UI树,最终将UI树绘制到屏幕上,这个过程相似于React中的虚拟DOM。Rendering层能够说是Flutter UI框架最核心的部分,它除了肯定每一个UI元素的位置、大小以外还要进行坐标变换、绘制(调用底层dart:ui)。bash

  • Widgets层是Flutter提供的的一套基础组件库 在基础组件库之上,Flutter还提供了 Material 和Cupertino两种视觉风格的组件库。而咱们Flutter开发的大多数场景,只是和这两层打交道。微信

在Flutter中,几乎一切都是widget。应用程序,页面,布局,视图,事件,通知,甚至是具体的文本样式。统一化为widget的方式,使得Flutter的代码更加统一。网络

Flutter的widget是对页面UI的一种描述,相似于web中的html,iOS中的xib,android中的xml。Flutter在构建UI过程当中也是造成一个widget树,就如iOS的视图树。可是不一样的是这个树并非最终渲染的树。

3. Flutter如何经过widget构建UI

先来看一下Flutter的渲染管道:

Rendering Pipeline

在这个渲染过程当中经历了widget树转化成element树再到最终渲染的renderObject树的过程,以下:

树的转化

  • Widget:存放渲染内容、视图布局信息,widget的属性最好都是immutable(如何更新数据呢?查看后续内容)

  • Element:存放上下文,经过Element遍历视图树,Element同时持有Widget和RenderObject

  • RenderObject:根据Widget的布局属性进行layout,paint Widget传人的内容

element相比于widget增长了上下文的信息。element是对应widget,在渲染树的实例化节点。同一个widget能够对应渲染树中的多个element,就像是一个视图模板。

widget都是不可变的,初始状态设置之后就不可再变化。也就是说,每次视图的更新都会从新构建widget自己和子widget(具体表现为从新执行widget的build方法)。

针对视图在运行时可能变化的状况,Flutter引入了State来管理视图的状态。在修改数据以后,须要主动调用setState()来触发视图状态的更新。不像普通的双向绑定,数据一修改就会触发视图的变化,容易形成视图在短期内屡次更新渲染。从这里也能看得出Flutter的设计者并不但愿你频繁地去更新视图状态,毕竟从新构建widget树的代价也是蛮大,尤为是相对复杂的页面。

另外,Flutter在视图描述widget和真实渲染的RenderObject的中间设计的Element层,对某一时刻的事件作了汇总和比对,只对真正须要修改的部分同步到真实渲染的RenderObject树上面,作到最小程度的修改,以提升渲染效率。

4. Flutter是响应式的框架,可是推崇能不变就不变

拥有响应式框架的如下特色

  • 不直接操做UI,改成经过修改数据而后更新视图的状态来驱动视图变化
  • 经过视图事件的绑定来操做数据并最终将结果副作用于视图

Flutter在页面渲染上面的核心思想是simple is fast,因此相对于可变状态的StatefulWidget还设计了状态不可变的StatelessWidget,也就更增强调了能不变就不变的理念。

5. 庞大的widget体系带来方便的同时也带来了高昂的学习成本

Flutter有一个庞大的组件体系,有不少iOS风格(Cupertino)和安卓风格(Material)的现成widget可使用,使得UI的构建变得相对容易。可是,庞大的组件体系带来方便的同时也带来了高昂的学习成本(单单记下这些widget的大致功能都要花很多时间)。

不过值得庆幸的是,你经常使用的widget并不会这么多。上面说过widget只是界面描述,同一个界面实现的方式都会有不少种,每一个人都会使用本身熟悉和擅长的方式去构建界面,可是通过转换成Element树,最终到达的RenderObject树多是同样的。有一种异曲同工的感受。

下图是Flutter常见的widget,体会一下吧。

flutter widgets (图片来自网上)

6. 套娃UI代码,揭开一层还有一层,喝完这杯还有三杯

因为Flutter基本上都是由widget实现,因此也就难以免一层套一层的代码风格,很有HTML风范。

  • 控件套一层
  • 容器修饰套一层(圆角,着色等)
  • 事件套一层
  • 布局套N层
  • 父级控件套N层 ……
  • 页面也来套一层

有些抽象,咱们来看个实际的例子。

实现这样的一个cell

如下是Cell的视图代码:

Column(//纵向分栏
            children: <Widget>[
                Padding(//边距
                  padding: EdgeInsets.all(10),
                  child: Row(//横向分栏
                    children: <Widget>[

                      ClipRRect(//切圆角
                        borderRadius: BorderRadius.circular(10.0),
                        child: Image.asset('images/icon.png',width: 80,height: 80),
                      ),

                      Padding(//边距
                        padding: const EdgeInsets.only(left: 10),
                        child: Column(//纵向分栏
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            Text(//文本控件
                              '香草拿铁',
                              style: TextStyle(//文本风格
                                  fontSize: 18,
                                  color: Colors.black
                              ),
                            ),

                            Text(
                              'Vanilla Latte',
                              style: TextStyle(
                                  fontSize: 14,
                                  color: Color(0xffcccccc)
                              ),
                            ),

                            Text(
                              '默认:大/单糖/热',
                              style: TextStyle(
                                  fontSize: 14,
                                  color: Color(0xffcccccc)
                              ),
                            ),

                            Text(
                              '¥27',
                              style: TextStyle(
                                  fontSize: 17,
                                  color: Colors.black
                              ),
                            ),

                          ],
                        ),
                      ),
                      Expanded(//充满父容器剩余空间
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.end,//右对齐
                          children: <Widget>[
                            GestureDetector(
                              onTap: (){//点击事件触发
                              },
                              child: ClipRRect(//切圆角
                                borderRadius: BorderRadius.circular(10.0),
                                child: Container(//容器修饰,用于添加蓝色背景
                                    color:Colors.blue ,
                                    child: Icon(//图标
                                      Icons.add,
                                      size: 20,
                                      color: Colors.white
                                    )
                                ),
                              ),
                            )
                          ],
                        ),
                      )
                    ],
                  ),
                ),
                Container(
                    color: Color(0xffcccccc),
                    height: 0.5
                )
              ],
            );

复制代码

在IDE中有个辅助线和尾部的备注会稍微好一点,可是已经习惯iOS代码风格的笔者确实有些适应不过来(虽然已经磨合一段时间了):

image.png

也许,你和我同样想到了封装,好,咱们就封装一下。

//cell总体
Column(
  children: <Widget>[
    getContent(),/*cell内容*/
    getBottomLine()/*分割线*/
  ],
);

/*cell内容*/
Widget getContent(){
  return Padding(
    padding: EdgeInsets.all(10),
    child: Row(
      children: <Widget>[
        getHeadIcon(),/*头像*/
        Padding(/*中间文本列*/
          padding: const EdgeInsets.only(left: 10),
          child: getMiddleWidget(),
        ),
        Expanded(/*充满父容器剩余空间*/
            child: getRightButton()/*按钮*/
        )
      ],
    ),
  );
}

/*分割线*/
Widget getBottomLine(){
  return Container(
      color: Color(0xffcccccc),
      height: 0.5
  );
}

/*头像*/
Widget getHeadIcon(){
  return ClipRRect(//切圆角
    borderRadius: BorderRadius.circular(10.0),
    child: Image.asset('images/icon.png',width: 80, height: 80),
  );
}

/*中间的文本列*/
Widget getMiddleWidget() {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      getText('香草拿铁', 18, Colors.black),
      getText('Vanilla Latte', 14, Color(0xffcccccc)),
      getText('默认:大/单糖/热', 14, Color(0xffcccccc)),
      getText('¥27', 17, Colors.black)
    ],
  );
}

/*右侧按钮*/
Widget getRightButton(){
  return Row(
    mainAxisAlignment: MainAxisAlignment.end,//右对齐
    children: <Widget>[
      GestureDetector(
        onTap: (){//点击事件触发

        },
        child: ClipRRect(//切圆角
          borderRadius: BorderRadius.circular(10.0),
          child: Container(
              color:Colors.blue ,
              child: Icon(
                  Icons.add,
                  size: 20,
                  color: Colors.white
              )
          ),
        ),
      )
    ],
  );
}

/*文本*/
Text getText(text, double fontSize, Color color){
  return Text(
    text,
    style: TextStyle(
        fontSize: fontSize,
        color: color
    ),
  );
}
复制代码

以上已是较为详细的封装了,固然由于层级不少,要再继续拆分下去也不是不能够,这就涉及封装粒度以及封装的最终成果能不能成正比了。看一下结构:

  • cell总体
  • cell内容
    • 头像
    • 文本列
      • 文本
    • 按钮
  • 分割线

以上,拆分后相对好一些,可是一层套一层的诟病仍是没法避免。 (好比,cell内容这个函数,右侧按钮这个函数不考虑,由于这个是很是规的按钮实现方式,通常用Flutter的按钮widget便可)

笔者认为,形成这个结果的最主要缘由便是其最大的特色:一切皆widget,widget包widget。

若是要让代码够优雅,布局粒度的划分是值得思量的。

7.优秀的跨平台UI框架必需要有优秀的UI调试工具

在Flutter Inspector之中提供了,可视化视图树查看工具,虽然与xcode的 界面调试工具相比是2D的有些遗憾,不过也已经挺强大。以下图:

Flutter Inspector select widget mode

更多工具
  • 性能监控(Performance Overlay)能够查看GPU和UI的帧率

    Performance Overlay

  • 绘制基线(Paint Baselines)

    image.png

  • Debug Paint 展现全部控件的绘制边界 绿色箭头表示可滚动内容,以及可滚动内容的初始到结束的方向

    Paint

由于widget树并非最终绘制的UI树,因此Flutter 监察器还提供了真正的绘制树查看工具,以下Render Tree分栏。

Render Tree

Flutter新版本插件(32.0.1)提供了代码的同步定位功能,愈来愈好了呢

代码同步定位

这些工具在日常的开发中已经够用,其余细节留给读者们本身探索吧。

3、Flutter 与原生交互

1.混编依赖方案的抉择

1.1 Flutter默认的工程构建方式,native工程彻底是Flutter构建产物

Flutter工程目录
Flutter默认
默认的方式,没法在已有原生工程的基础上引入Flutter,必须完整从新建立整个工程,这个是致命的。问题还有不少,好比:

  • native反向依赖Flutter父目录,耦合严重
  • 代码库难以拆分管理
  • 对纯native开发的团队成员形成入侵,须要完备的Flutter开发环境,和相应的构建步骤

1.2 三个代码库独立,修改 Flutter 构建流程将构建产物直接提供给native做为依赖

本地依赖

这个方式中,以iOS为例,将Flutter.framework及相关插件等作成本地的pod依赖,资源也复制到本地进行维护。这样Flutter就被打包成了pod库, 在native团队成员那边,Flutter就是黑盒,只管用就好了。Flutter pod库的引用内容须要各个团队成员走Flutter构建流程去生成。虽然说代码仓库比较好分开了,可是缺点仍是有的:

  • 须要对Flutter原有的构造流程进行稍嫌复杂的改动
  • Native工程与Flutter的内容仍是耦合在本地
  • Native开发者仍然须要完备的Flutter开发环境

1.3 将Flutter本地依赖修改为远程依赖,native开发彻底脱离Flutter

这个方案是将Flutter全部依赖内容都放在独立的远端仓库中,native如同引用公开三方库同样去引用Flutter。这时候,native就不须要Flutter开发环境了。 要说这个方案的缺点就是同步的流程变得更繁琐,Flutter内容的变更须要先同步到远程仓库再同步到native依赖。极端状况,在native与Flutter频繁交互的时候,就须要频繁更新依赖库。Flutter依赖库的版本和native代码版本的对应管理也是须要额外耗费精力。不过,这个不算大问题,与以往的H5与原生的混编相似,沿用便可。

Flutter远程依赖

2 通不通且看武功

Flutter基于SKIA使用Dart搭建了本身的UI框架,而底层最终都是调用OpenGL绘制,在Native和Flutter Engine上实现了UI的隔离。那么开发者书在写UI代码时就不用再关心平台实现,从而实现了跨平台。这层隔离在Flutter Engine和Native之间竖立了一座大山,想要实现通信就得另辟蹊径。

2.1 打通事件通信:平台通道(Platform Channel)

Flutter与原生之间的通讯依赖灵活的消息传递方式:平台通道(Platform Channel)。 平台指的就是指Flutter运行的平台,如Android或IOS,能够认为就是应用的原生部分,平台通道正是Flutter和原生之间通讯的桥梁。

平台通道消息传递
当在Flutter中调用原生方法时,调用信息经过平台通道传递到原生,原生收到调用信息后方可执行指定的操做,如需返回数据,则原生会将数据再经过平台通道传递给Flutter。值得注意的是消息传递是异步的,这确保了用户界面在消息传递时不会被挂起。

平台通道的能力

  • 传递小量数据:基本数据类型,数组,字典,二进制数据;
  • 经过定制可传递大数据块,可是用于如图像,视频等大数据的传输必然引发内存和CPU的巨大消耗
  • 非线程安全,native的回调必须在主线程执行,故应该在Native端的Handler中处理耗时操做

平台通道的设计初衷并非用来传递大数据的,从本质上说是提供了一个消息传送机制。

2.2 打通图像渲染:外接纹理(Texture)

纹理(Texture):能够理解为GPU内表明图像数据的一个对象。 Flutter提供了一个Texture控件,这个控件上显示的数据,须要由Native提供。

image.png
Flutter和Native传输的数据载体是PixelBuffer,Native端的数据源(摄像头、播放器等)将数据写入PixelBuffer,Flutter拿到PixelBuffer之后转成OpenGLES Texture,交由Skia绘制。 经过这个方式,Flutter就能够容易的绘制出一切Native端想要绘制的数据,除了摄像头播放器等动态图像数据,也给其余诸如地图等视图的展现提供了另外一种可能。


笔者和朋友作了淘宝优惠券公众号,购物领券省钱,帮忙关注一下。

微信公众号
相关文章
相关标签/搜索