目录
- 参考文章
- 移动端跨平台开发的深度解析
- 最火移动端跨平台方案盘点:React Native、weex、Flutter
- 国内少有的Flutter干货分享:Flutter的原理及美团的实践
- GMTC-闲鱼Flutter实践效果访谈
- 移动开发新利器 | 一文深入了解 Flutter 界面开发
1.Flutter是什么?
- Flutter是Google papa推出的一款全新的,响应式·跨平台·高性能的移动开发框架。
2. Flutter与其他框架对比
- 在移动开发的中的跨平台中有以下几种方案:
- 基于WebView 和JavaScript(Cordova,AppCan)
- 基于HTML和JavaScript渲染映射成原生控件(ReatNative、Weex)
- 基于微信生态的小程序
- 基于原生编码无桥接(Flutter)
方案 | 优点 | 缺点 |
基于WebView和JavaScript的方案(Cordova) | 1. 可以继承Web开发的所有成果 (丰富的控件库,满足各种需求的页面框架,完全动态化,自动化测试工具等) 2.学习成本低(不需要太多的成本和迁移成本就可以开发一个app) | 1.WebView渲染效率和JavaScript的执行性能太差 2.由于Android各个系统版本和厂商的定制,很难保证在所有设备有一致的效果 |
基于HTML(vue)和JavaScript渲染映射成原生控件(ReatNative,Weex) | 1.将渲染工作交给原生,性能体验好 2.Facebook出品推出时间较长,社区资源丰富 | 随系统版本变化和API的变化,需要处理不同平台的差异,有的特性不能满足所有平台(第三方库良莠不齐,维护困难) |
基于微信生态的小程序 | 1.迭代速度快,学习成本低 2.可以实现动态无缝更新,可以利用微信强大的用户基数 | 依赖于微信产品生态,定制程度大打折扣 |
基于原生编码无桥接的Flutter | 1.热重载 2.渲染引擎依赖跨平台的Skia图形库(一个开源的2D图形库在一系列硬件和软件平台提供通用的API),可以最大程度保证不同平台和设备的体验一致性 3.使用支持AOT的Dart语言,执行效率比JavaScript好很多 4.美观可定制的组件 | 1.dart 语言的生态小,精通成本比较高 2.嵌入外部 platform view 成本高这块(基于官方代码)目前在 iOS 端有支持,android 上还没有,但相关开发者博客上有提到实现了webview,mapview 等 platform view 的嵌入。从 iOS 端的实现来看,每嵌入一层 platform view 会额外多一层 surface,内存代价比较高 3.flutter 目前提供的开箱即用的功能只有 UI framework + dart 语言的能力,平台能力需要通过 platform channel 来扩展。(目前丰富的程度有限,未来可期) |
3.Flutter的Dart架构和Dart语言
- Flutter的框架部分全部完全使用Dart语言实现,有着清晰的分层架构。分层架构使得我们可以在调用Flutter提供的便捷开发功能之外,还可以修改每一层额实现Framework的底层是flutter引擎,引擎主要负责图形绘制(skia),文字排版(libtxt)和提供Dart运行时,引擎全部使用C++实现,Framework层使我们可以用Dart语言引擎的强大能力.
- Dart部分的最低层为Foundation其中大都是非常基础的,提供给其他所有层的工具类和方法。
绘制库(Painting 封装了Flutter Engine 提供的绘制接口,主要是为了在绘制控制等固定样式时提供更直观,更方便的接口,比如绘制缩放后的位图,绘制文本,插值生成阴影以及在盒子周围绘制边框等。
Animation动画类提供了类似Android系统的ValueAnimator的功能,并且提供了丰富的内置插值器。
Gesture手势类提供了手势识别相关功能,包括触摸事件类定义和多种内置的手势识别器。GestureBinding类是Flutter中处理手势的抽象服务类,继承自BindingBase类。
Binding系列的类在Flutter中充当类似于Android中的SystemService系列(ActivityManager,PackageManager)功能,每个Binding类提供一个服务的单例对象,App最顶层的Binding会包含所有相关的Binding抽象类。如果使用Flutter提供的控件开发,则需要使用WidgetsFlutterBinding,如果不使用Flutter提供的控件而直接调用Render层,则需要使用RenderingFlutterBinding。
- 渲染库(Rendering)
- Flutter的控件在实际显示时会转换成对应的渲染对象(RenderObject)树来实现布局和绘制操作。一般情况下,我们只会在调试布局,或者需要使用自定义控件来实现某些特殊效果的时候,才需要考虑渲染对象的细节。渲染库主要提供的功能类有:
(对于mixin的Dart语言机制可以参考Flutter基础:理解Dart的Mixin继承机制)
/// The glue between the render tree and the Flutter engine.
//RendererBinding是渲染树和Flutter引擎的胶水:负责管理帧重绘,窗口尺寸和渲染相关变化的监听
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
}
/// RenderObject渲染树中所有节点的基类,定义了布局,绘制和合成相关的接口
abstract class RenderBox extends RenderObject {
}
/// An image in the render tree.
class RenderImage extends RenderBox {
}
/// A table where the columns and rows are sized to fit the contents of the cells.
class RenderTable extends RenderBox {
}
/// Displays its children in a one-dimensional array.
class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>,
DebugOverflowIndicatorMixin {
}
/// A render object that displays a paragraph of text
class RenderParagraph extends RenderBox {
}
- 在Flutter界面渲染过程分为三个阶段:布局,绘制,合成,其中布局和绘制在Flutter框架中完成,合成交由Engine负责
- UI绘制流程
- 1.运行动画,动画的结果会导致 Widget State 的改变;
- 2.State Changes 触发 Flutter 生成一棵新的 Widget 树;
- 3.Flutter 根据新/旧 Widget 树的差异更新 Render 树,重新排版更新界面布局;
- 4.Flutter 根据新的 Render 树更新 Composited Layer(合成图层)的 Display List;
- 5.输出新的图层树;
- Widgets库
- Flutter提供了非常丰富的控件,包括最基本的文件,图片,容器,输入框和动画等。在Flutter中“一切皆是控件”,通过组合,嵌套不同类型的控件可以构成任意功能,任意复杂度的界面,以下为widget库中重要的几个类:
WidgetsBinding是widgets 层和Flutter引擎的胶水层
- Widget就是所有控件的基类,它本身的所有数据是只读的。RenderingObjectWidgets所有的实现类负责提供配置信息并创建具体的RenderObjectElement。widget的属性最好都是immutable。
- Element是Flutter用来分离控件和真正渲染对象的中间层,控件用来描述对应的element属性,存放上下文,通过Element遍历视图树,Element同时持有Widget和RenderObject。控件重建后可能会复用同一个element。RenderObjectElement持有真正负责布局,绘制和碰撞测试(hit test)的RenderObject对象。
- RenderObject:根据Widget的布局属性进行layout,paint Widget传入的内容。
- 为什么widget都是immutable?
flutter界面开发是一种响应式编程,主张simple is fast,flutter设计的初衷希望数据变更时发送通知到对应的可变更节点(可能是一个StatefulWidget子节点,也可以是rootWidget),由上到下重新create widget树进行刷新,这种思路比较简单,不用关心数据变更会影响到哪些节点。
2.widget重新创建,element树和RenderObject树是否也重新创建?
widget只是一个配置数据结构,创建是非常轻量的,加上flutter团队对widget的创建/销毁做了优化,不用担心整个widget树重新创建所带来的性能问题,但是RenderObject就不一样了,RenderObject涉及到layout、paint等复杂操作,是一个真正渲染的view,整个view 树重新创建开销就比较大,所以答案是否定的。
- Dart语言
- Dart语言的重要理念
- 放在变量中的所有东西都是一个对象,每个对象都是一个类的实例。即使是数字,功能, null也是对象。所有对象都从Object类继承。
- 虽然Dart是强类型的,但类型注释是可选的,因为Dart可以推断类型。在上面的代码中,number 被推断为是类型的int。当你想明确地说没有类型时, 使用特殊类型dynamic。
- Dart支持泛型类型,如List(整数列表)或List(任何类型的对象列表)。
- Dart支持顶层函数(如main()),以及与类或对象绑定的函数(分别为静态方法和实例方法)。你也可以在函数中创建函数(嵌套函数或局部函数)。
- Dart支持顶级变量以及绑定到类或对象(静态变量和实例变量)的变量。实例变量有时称为字段或属性。与Java,dart不具备关键字public,protected和private。如果标识符以下划线(_)开头,则它的库是私有的。有关详细信息,请参阅 库和可见性。
- 标识符可以以字母或下划线(_)开头,然后是这些字符和数字的任意组合。有时候,重要的是某件事是一种表达式还是一种 声明,所以这两个词的确切含义很有帮助。
- Dart工具可以报告两种问题:warnings 和errors。警告只是表明您的代码可能无法正常工作,但它们不会阻止您的程序执行。错误可以是编译时或运行时。编译时错误导致代码无法执行; 运行时错误导致 代码执行时引发异常。
- Dart语言入门
4.Flutter 初体验
- Flutter纵使千般好,纸上得来终觉浅。按照官网下载了Flutter SDK,使用Android Studio 3.3.2,使用了Github的下列教程代码 awesome-flutter的FlutterWhatsAppClone项目
- pubspec.yaml
- 依赖配置声明文件
#在Flutter中,依赖包由Pub仓库管理,项目依赖配置在这个文件中声明即可,
#对于未发布在Pub仓库的插件可以使用git仓库地址或文件路径
name: flutterwhatsapp
description: A new Flutter project.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.0
camera:
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.io/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.io/custom-fonts/#from-packages
```
- main.dart
- app的入口文件
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutterwhatsapp/whatsapp_home.dart';
import 'package:camera/camera.dart';
List<CameraDescription> cameras;
Future<Null> main() async {
cameras = await availableCameras();
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "WhatsApp",
theme: new ThemeData(
primaryColor: new Color(0xff075E54),
accentColor: new Color(0xff25D366),
),
debugShowCheckedModeBanner: false,
home: new WhatsAppHome(cameras),
);
}
}
- whatsapp_home.dart
- app主页
import 'package:flutter/material.dart';
import 'package:flutterwhatsapp/pages/call_screen.dart';
import 'package:flutterwhatsapp/pages/camera_screen.dart';
import 'package:flutterwhatsapp/pages/chat_screen.dart';
import 'package:flutterwhatsapp/pages/status_screen.dart';
class WhatsAppHome extends StatefulWidget {
var cameras;
WhatsAppHome(this.cameras);
@override
_WhatsAppHomeState createState() => new _WhatsAppHomeState();
}
class _WhatsAppHomeState extends State<WhatsAppHome>
with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, initialIndex: 1, length: 4);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("WhatsApp"),
elevation: 0.7,
bottom: new TabBar(
controller: _tabController,
indicatorColor: Colors.white,
tabs: <Widget>[
new Tab(icon: new Icon(Icons.camera_alt)),
new Tab(text: "CHATS"),
new Tab(
text: "STATUS",
),
new Tab(
text: "CALLS",
),
],
),
actions: <Widget>[
new Icon(Icons.search),
new Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
),
new Icon(Icons.more_vert)
],
),
body: new TabBarView(
controller: _tabController,
children: <Widget>[
new CameraScreen(widget.cameras),// 相机模块
new ChatScreen(),//聊天模块
new StatusScreen(),//状态模块
new CallsScreen(),//电话模块
],
),
floatingActionButton: new FloatingActionButton(
backgroundColor: Theme.of(context).accentColor,
child: new Icon(
Icons.message,
color: Colors.white,
),
onPressed: () => print("open chats"),
),
);
}
}