Flutter入门基础

官方文档


预研内容主要分为几个部分:

  1. Flutter环境搭建(技术框架, 安装,编辑器)
  2. Flutter的UI及交互(布局,交互,手势,动画,路由导航)
  3. Flutter资源文件管理(图片资源,文件资源,文件的读写)
  4. Flutter数据存储
  5. Flutter的网络请求(网络HTTP,JSON序列化)
  6. Flutter与原生平台(平台特定代码交互)
  7. Flutter开发语言(Dart语言)
  8. Flutter其他

Flutter环境搭建

  • 技术框架
15583329814371.jpg
15583330609276.jpg

在Flutter中用Widget来描述界面,Widget只是View的“配置信息”,编写的时候利用Dart语言一些声明式特性来得到类似结构化标记语言的可读性。Widget根据布局形成一个层次结构。每个widget嵌入其中,并继承其父项的属性。没有单独的“应用程序”对象,相反,根widget扮演着这个角色。在Flutter中,一切皆为Widget,甚至包括css样式。

  • Flutter环境安装

mac下终端操作:
vim ~/.bash_profile 添加路径(没有.bash_profile的时候,需要通过vim命令创建)
export PUB_HOSTED_URL=https://pub.flutter-io.cn //国内用户需要设置
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn //国内用户需要设置
export PATH=PATH_TO_FLUTTER_GIT_DIRECTORY/flutter/bin:$PATH
修改 ~/.zshrc ,在其中添加:source ~/.bash_profile (没有.zshrc的时候,需要通过vim命令创建)

  • 编辑器

Android Studio 安装

安装Flutter和Dart插件

在Flutter插件中,可使用以下模板:
前缀stless: 创建一个StatelessWidget的子类.
前缀stful: 创建一个StatefulWidget子类并且关联到一个State子类.
前缀stanim: 创建一个StatefulWidget子类, 并且它关联的State子类包括一个 AnimationController

Flutter的UI及交互

  • 布局

参考:https://flutterchina.club/tutorials/layout/

Text:该 widget 可让创建一个带格式的文本。

Row、 Column: 这些具有弹性空间的布局类Widget可让您在水平(Row)和垂直(Column)方向上创建灵活的布局

Stack: 取代线性布局 (译者语:和Android中的LinearLayout相似),Stack允许子 widget 堆叠

Container: Container 可让您创建矩形视觉元素。container 可以装饰为一个BoxDecoration

为了继承主题数据,widget需要位于MaterialApp内才能正常显示, 因此我们使用MaterialApp来运行该应用。 //Scaffold是Material中主要的布局组件.

GestureDetector widget并不具有显示效果,而是检测由用户做出的手势。 当用户点击Container时, GestureDetector会调用它的onTap回调, 在回调中,将消息打印到控制台。您可以使用GestureDetector来检测各种输入手势,包括点击、拖动和缩放。

StatefulWidgets是特殊的widget,它知道如何生成State对象,然后用它来保持状态

在StatefulWidget调用createState之后,框架将新的状态对象插入树中,然后调用状态对象的initState。 子类化State可以重写initState,以完成仅需要执行一次的工作。 例如,您可以重写initState以配置动画或订阅platform services。initState的实现中需要调用super.initState当一个状态对象不再需要时,框架调用状态对象的dispose。 您可以覆盖该dispose方法来执行清理工作。例如,您可以覆盖dispose取消定时器或取消订阅platform services。 dispose典型的实现是直接调用super.dispose。

stateless widget 没有内部状态. Icon、 IconButton, 和Text 都是无状态widget, 他们都是 StatelessWidget的子类。stateful widget 是动态的. 用户可以和其交互 (例如输入一个表单、 或者移动一个slider滑块),或者可以随时间改变 (也许是数据改变导致的UI更新)

可以使用key来控制框架将在widget重建时与哪些其他widget匹配。默认情况下,框架根据它们的runtimeType和它们的显示顺序来匹配。 使用key时,框架要求两个widget具有相同的key和runtimeType。

各种widgets的目录索引,有UI不熟悉的,可以在这里找到说明:https://flutterchina.club/widgets/
各类Widgets的地址: https://flutterchina.club/widgets/basics/
各种UI布局需要用到的控件介绍: https://flutterchina.club/widgets/material/
iOS 风格的控件集合 介绍: https://flutterchina.club/widgets/cupertino/

  • UI的一些注意事项

类MyAppBar和MyScaffold中使用了Container、Row、Column、Text、IconButton、Icon、BoxDecoration、Center、Expanded等常用Widget

Theme.of(context)将查找Widget树并返回树中最近的Theme。如果我们的Widget之上有一个单独的Theme定义,则返回该值。如果不是,则返回App主题。 事实上,FloatingActionButton真是通过这种方式找到accentColor的!

ListView的构造函数需要一次创建所有项目,但ListView.builder的构造函数不需要,它将在列表项滚动到屏幕上时创建该列表项。

new ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return new ListTile(
      title: new Text('${items[index]}'),
    );
  },
);

滑动删除有直接可用的Widget;

将响应转换为自定义Dart对象;

class Post {
  final int userId;
  final int id;
  final String title;
  final String body;

  Post({this.userId, this.id, this.title, this.body});

  factory Post.fromJson(Map<String, dynamic> json) {
    return new Post(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
}

http package提供了一种方便的方法来为请求添加headers。您也可以使用dart:iopackage来添加。

Flutter提供各种按钮和类似的交互式widget。这些widget中的大多数实现了Material Design 指南, 它们定义了一组具有质感的UI组件。可以使用GestureDetector来给任何自定义widget添加交互性。 可以在管理状态和Flutter Gallery中找到GestureDetector的示例。

如果你要构建一个 CustomButton ,并在构造器中传入它的 label?那就组合 RaisedButton 和 label,而不是扩展 RaisedButton。

Isolates 是分离的运行线程,并且不和主线程的内存堆共享内存。这意味着你不能访问主线程中的变量,或者使用 setState() 来更新 UI。正如它们的名字一样,Isolates 不能共享内存。

在 Flutter 中,最简单的方法是使用 ListView widget。它表现得既和 iOS 中的 ScrollView 一致,也能和 TableView 一致,因为你可以给它的 widget 做垂直排布:

  • 交互

参考:https://flutterchina.club/tutorials/interactive/

  • 手势

要从widget层监听手势,使用 GestureDetector.

  • 动画

参考动画的说明: https://flutterchina.club/animations/
在 Flutter 中,使用 AnimationController 。这是一个可以暂停、寻找、停止、反转动画的 Animation<double> 类型。它需要一个 Ticker 当 vsync 发生时来发送信号,并且在每帧运行时创建一个介于 0 和 1 之间的线性插值(interpolation)。你可以创建一个或多个的 Animation 并附加给一个 controller。

  • 路由

路由的介绍:https://docs.flutter.io/flutter/widgets/Navigator-class.html
在页面之间跳转,你有几个选择:
具体指定一个由路由名构成的 Map。(MaterialApp
直接跳转到一个路由。(WidgetApp)
Navigator 类不仅用来处理 Flutter 中的路由,还被用来获取你刚 push 到栈中的路由返回的结果。通过 await等待路由返回的结果来达到这点。

Map coordinates = await Navigator.of(context).pushNamed('/location');
Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

Flutter资源文件管理

  • 图片资源

iOS 把 images 和 assets 作为不同的东西,而 Flutter 中只有 assets。被放到 iOS 中 Images.xcasset 文件夹下的资源在 Flutter 中被放到了 assets 文件夹中。assets 可以是任意类型的文件,而不仅仅是图片。
例如,你可以把 json 文件放置到 my-assets 文件夹中。在 pubspec.yaml 文件中声明 assets:`assets:

  • my-assets/data.json然后在代码中使用 AssetBundle 来访问它:Future<String> loadAsset() async {
    return await rootBundle.loadString('my-assets/data.json');
    }`

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

  • 文件的读写

PathProvider 插件提供了一种平台透明的方式来访问设备文件系统上的常用位置。该类当前支持访问两个文件系统位置:

Flutter数据存储

在 Flutter 中,可以使用 Shared Preferences plugin 来达到相似的功能。它包裹了 UserDefaluts 以及 Android 上等价的 SharedPreferences 的功能。

在 iOS 中,你通过 CoreData 来存储结构化的数据。这是一个 SQL 数据库的上层封装,让查询和关联模型变得更加简单。在 Flutter 中,使用 SQFlite 插件来实现这个功能。

Flutter的网络请求

  • 网络HTTP

使用dio 来发起网络请求,它是一个强大易用的dart http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载
参见具体说明: https://flutterchina.club/networking/

  • JSON序列化

使用dart:convert库可以简单解码和编码JSON;要对简单的JSON进行编码,请将简单值(字符串,布尔值或数字字面量)或包含简单值的Map,list等传给encode方法:

Flutter与原生平台

  • 平台特定代码交互

交互的方式: https://flutterchina.club/platform-channels/

通过MethodChannel实现。在Flutter当中定义平台管道,定义平台需要捕获的方法名称->Appdelegate当中注册定义的管道->管道方法调用的时候,实现平台方法的调用(达到Flutter调用平台方法的目的)

  • Flutter页面路由 与 原生页面之间跳转实现:

Flutter 的代码并不直接在平台之下运行,相反,Dart 代码构建的 Flutter 应用在设备上以原生的方式运行,却“侧步躲开了”平台提供的 SDK。这意味着,例如,你在 Dart 中发起一个网络请求,它就直接在 Dart 的上下文中运行。你并不会用上平常在 iOS 或 Android 上使用的原生 API。你的 Flutter 程序仍然被原生平台的 ViewController 管理作一个 view,但是你并不会直接访问 ViewController 自身,或是原生框架。

我怎么访问 GPS 传感器?使用 location 社区插件。

我怎么访问摄像头?image_picker 在访问摄像头时非常常用。

Flutter开发语言

  • Dart语言一些语法特性:

所有没有初始化的变量值都是 null。
注意: 只有当名字冲突的时候才使用 this。否则的话, Dart 代码风格样式推荐忽略 this。
注意: 如果在构造函数的初始化列表中使用 super(),需要把它放到最后。 详情参考 Dart 最佳实践。
在构造函数体执行之前除了可以调用超类构造函数之外,还可以 初始化实例参数。 使用逗号分隔初始化表达式。

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Initializer list sets instance variables before
  // the constructor body runs.
  Point.fromJson(Map jsonMap)
      : x = jsonMap['x'],
        y = jsonMap['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }
}

有时候一个构造函数会调动类中的其他构造函数。 一个重定向构造函数是没有代码的,在构造函数声明后,使用 冒号调用其他构造函数。 Point.alongXAxis(num x) : this(x, 0);

如果一个构造函数并不总是返回一个新的对象,则使用 factory 来定义 这个构造函数。例如,一个工厂构造函数 可能从缓存中获取一个实例并返回,或者 返回一个子类型的实例。

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to the _ in front
  // of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) {
      print(msg);
    }
  }
}

Abstract methods(抽象函数): 实例函数、 getter、和 setter 函数可以为抽象函数, 抽象函数是只定义函数接口但是没有实现的函数,由子类来 实现该函数。如果用分号来替代函数体则这个函数就是抽象函数。

abstract class Doer {
  // ...Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // ...Provide an implementation, so the method is not abstract here...
  }
}

操作符可以被覆写。 例如,如果你定义了一个 Vector 类, 你可以定义一个 + 函数来实现两个向量相加。

如果你使用 noSuchMethod() 函数来实现每个可能的 getter 、setter、 以及其他类型的函数,你可以使用 @proxy 注解来避免警告信息:

枚举类型通常称之为 enumerations 或者 enums, 是一种特殊的类,用来表现一个固定 数目的常量。枚举的 values 常量可以返回 所有的枚举值。

Mixins 是一种在多类继承中重用 一个类代码的方法。使用 with 关键字后面为一个或者多个 mixin 名字来使用 mixin

class Musician extends Performer with Musical {
  // ...
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

如果你查看 List 类型的 API 文档, 则可以看到 实际的类型定义为 List<E>。 这个 <…> 声明 list 是一个 泛型 (或者 参数化) 类型。 通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等。T 是一个备用类型。这是一个类型占位符, 在开发者调用该接口的时候会指定具体类型。

如果你导入的两个库具有冲突的标识符, 则你可以使用库的前缀来区分。 例如,如果 library1 和 library2 都有一个名字为 Element 的类, 你可以这样使用:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element();           // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib2.

Dart 有一些语言特性来支持 异步编程。 最常见的特性是 async 方法和 await 表达式。要使用 await,其方法必须带有 async 关键字:

在一个方法上添加 async 关键字,则这个方法返回值为 Future。 例如,下面是一个返回字符串 的同步方法: String lookUpVersionSync() => '1.0.0';如果使用 async 关键字,则该方法 返回一个 Future,并且 认为该函数是一个耗时的操作。Future<String> lookUpVersion() async => '1.0.0';

在 await expression 中, expression 的返回值通常是一个 Future; 如果返回的值不是 Future,则 Dart 会自动把该值放到 Future 中返回。 Future 对象代表返回一个对象的承诺(promise)。 await expression 执行的结果为这个返回的对象。 await expression 会阻塞住,直到需要的对象返回为止。如果 await 无法正常使用,确保是在一个 async 方法中。 例如要在 main() 方法中使用 await, 则 main() 方法的函数体必须标记为 async:

异步 for 循环具有如下的形式:使用 break 或者 return 语句可以 停止接收 stream 的数据, 这样就跳出了 for 循环并且 从 stream 上取消注册了。

await for (variable declaration in expression) {
  // Executes each time the stream emits a value.
}

所有的 Dart 代码在 isolates 中运行而不是线程。 每个 isolate 都有自己的堆内存,并且确保每个 isolate 的状态都不能被其他 isolate 访问。

第三方Flutter框架Demo 及文章参考

其他

  • Flutter安装包大小的问题?

在安卓的安装包当中会根据工程的不同增加不同的大小。本地打的包和发布的包大小也可能不一样。

部分团队经验:在Release模式下,安卓端的安装包大小增加约为3~4M ,iOS端的安装包大小增加约为13~14M,实际增加的大小跟业务端代码和资源大小有关系;

其他团队的经验,引入 Flutter 之前,涨乐财富通的安装包为 94MB,引入之后大小为 100MB,发现增大了 6MB,这其中主要是引入了 Flutter 的 SDK,增加的大小在可以接受的范围。

参考: Flutter Android/iOS包大小分析

  • Flutter的组件化集成(将Flutter代码集成进现有的工程)?

对现有工程有侵入。不能完全按照官方的指导来集成。需要把Flutter编译产物放入主工程。

对混合栈的管理,参考闲鱼团队的开源方案。

参考:使用 Flutter 之后,我们的 CPU 占用率降了 50%

  • 预研结果
  1. 采用Flutter框架,能够满足日常80%以上的基础UI展示和常见需求。如果涉及复杂动画或者特定平台特性的调用,也可以使用Flutter的管道特性,和平台进行交互实现。

  2. Flutter的性能虽然不如官方宣称60FPS,但是在流畅性上也接近原生,而且主要能够在安卓和iOS平台上实现统一且支持部分定制,尽管安装包和内存上会有部分提升空间。





开发过程中遇到的实际问题及解决:

  1. 按钮做倒计时功能
  2. 导航的时候,如果用Navigator.push处理,新的路由页面如果用MaterailPageRoute创建,那么 main.dart中 run 的需要是一个MaterailApp
  3. 在编辑的时候,有的时候编辑器输入颜色的时候,会fetching Doc ,然后AS就会请求文档地址,网络阻塞造成卡死。 根据查找的资料,在mac下需要找到 ~/Library/preference/Android studio x.x下面的jav.table.xml这样的文件,替换里面JAVDOC节点下的文档来源地址url,或者有解决方式是直接删除掉这个文件。 试了一下删除,效果不是很好,但是有所改善。可以参考这个回答: Android Studio hangs at fetching documentation
  4. ListView自定义显示Item。 建立数据源 -> 自定义Adapter,处理Item显示的内容 -> list的内容设置给Adapter -> Adapter设置给ListView;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容