写在前面
本文以官网为学习路线。辅助一些注意点。了解Flutter的整体环境
Flutter官网
快速学习flutter,首先需要知道flutter官网:
https://flutter.dev/
同时还有翻译版的flutter 中文网
https://flutterchina.club/
Flutter基本知识
由dart语言编写的跨平台UI框架。
Flutter 的特性
- 跨平台
- 高性能
- 丰富的 UI 组件
- 学习成本低
- 开发效率高
Flutter Windows环境搭建
Flutter网站有安装:
https://flutter.dev/docs/get-started/install
英文不好可以看中文
Flutter中文网:
https://flutterchina.club/setup-windows/
- Flutter SDK
- Flutter IDE
查看文档时有几点注意:
1.中国镜像地址
1.计算机 -> 属性 -> 高级系统设置 -> 环境变量,打开环境变量设置框。
2.在用户变量下,选择新建环境变量,添加如下的两个环境变量和值:
变量名:FLUTTER_STORAGE_BASE_URL
变量值:storage.flutter-io.cn
变量名:PUB_HOSTED_URL
变量值:pub.flutter-io.cn
2.搭建 Android 开发环境
1.打开 Android Studio
2.File > Settings > Plugins
3.点击 Browse repositories, 搜索 Flutter 并安装 重启 Android Studio。
4.Android Studio 重启后,点击 File > New ,如果看到了 New Flutter Project… ,说明 Flutter 插件已经安装完成。
3.Flutter SDK
https://flutter.dev/docs/development/tools/sdk/releases?tab=windows
最好下载稳定版本
Flutter SDK 的环境变量:
1.计算机 -> 属性 -> 高级系统设置 -> 环境变量,打开环境变量设置框。
2.在用户变量下,选择 Path,点击编辑:
- 如果已经存在 Path变量,则在原有的值后面先加 ;,然后将 Flutter SDK 的完整路径 E:\src\flutter\bin 添加上。
- 如果没有 Path 变量,则新建一个名为 Path 的用户变量,然后将 Flutter SDK 的完整路径 E:\src\flutter\bin 添加上。
Flutter是如何运转的
Flutter重写了底层渲染逻辑和上层渲染开发。
图片来自
Flutter 架构是采用的分层设计。从下到上依次为:Embedder(嵌入器)、Engine、Framework。
Embedder 是嵌入层,做好这一层的适配 Flutter 基本可以嵌入到任何平台上去; Engine 层主要包含 Skia、Dart 和 Text。Skia 是开源的二位图形库;Dart 部分主要包括 runtime、Garbage Collection、编译模式支持等;Text 是文本渲染。Framework 在最上层。我们的应用围绕 Framework 层来构建,因此也是本文要介绍的重点。
不是特别了解可以以后再看。
Flutter 实现原理及在马蜂窝的跨平台开发实践
Flutter知识体系
因为Flutter是跨平台开发。整体其实按照客户端路线即可构建整个知识体系。
Dart语言
Dart官网:
https://dart.dev/
Dart 语言优势
- 面向对象
- 支持 JIT(Just-in-time)(动态编译),也可以支持 AOT(Ahead Of Time)(静态编译)。热重载就是基于JIT编译。
- 单线程模型:dart中没有线程,只有Isolate(隔离区)。Isolate之间不会共享内存。通过时间循环机制(Event looper)在时间队列(Event Queue)上传递消息。
- 内存分配与垃圾回收: dart来及回收采用多声带算法。新生代在回收内存时采用“半空间机制”,触发垃圾你回收时,dart会将前半空间的活跃对象拷贝到备用空间,然后整体释放当前空间的所有内存。回收过程中,Dart 只需要操作少量的“活跃”对象,没有引用大量的死亡对象则被忽略。很适合Widget销毁重建场景。
如何区分一门语言究竟是AOT还是JIT?
通常来说,看代码在执行前是否需要编译即刻,如果需要编译属于AOT;如果不需要,属于JIT.
- AOT代表:C/C++ 执行前编译机器码
- JIT代表:JavaScript .Python 等脚本语言。
为什么Flutter会选择 Dart ?
Dart基础语法
官网中:
https://dart.dev/guides/language/language-tour
中文网:
http://dart.goodev.org/guides/language/language-tour
Dart语法:
Dart语法如果会java,javascript。上手会很快。
关键字56个
var name = 'Bob';
重点是Final and const
- const 是编译时常量,在编译的时候就初始化了,但是 final 变量是当类创建的时候才初始化。
- 有String 字符串 还可以写成 var(声明变量而不指定类型。)
数据类型
//基本
String abc= 'abcdefg';
// 单引号嵌套双引号
String abcs= '$singleString a "bbb" ${doubleString}';
- booleans
- lists (也被称之为 arrays)
//创建一个int类型的list
List list = [1, 2, 3];
// 使用List的构造函数
var vegetables = new List();
// 添加list
fruits.add('kiwis');
// 添加多个
fruits.addAll(['grapes', 'bananas']);
// 获取list的长度
assert(fruits.length == 5);
// 移出一个条目
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
assert(fruits.length == 4);
// 移除所有
fruits.clear();
assert(fruits.length == 0);
定义 map 对象
// 经常使用字符串作为key。
var hawaiianBeaches = {
'Oahu' : ['Waikiki', 'Kailua', 'Waimanalo'],
'Big Island': ['Wailea Bay', 'Pololu Beach'],
'Kauai' : ['Hanalei', 'Poipu']
};
// 构造函数也可以
var searchTerms = new Map();
//键值对
var nobleGases = new Map<int, String>();
//添加
var nobleGases = {54: 'xenon'};
//移除
nobleGases.remove(54);
Functions(方法)
- 函数可以被定义为变量,甚至可以被定为参数传递给另一个函数。
- 函数可以不用写返回值:
// 声明返回值
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
// 不声明返回值
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
//对于只有一个表达式的方法,你可以选择 使用缩写语法来定义:
// => expr 语法是 { return expr; } 形式的缩写
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
流程控制语句
和其他语言基本类似。
Typedefs
在 Dart 语言中,方法也是对象。 使用 typedef, 或者 function-type alias 来为方法类型命名, 然后可以使用命名的方法。 当把方法类型赋值给一个变量的时候,typedef 保留类型信息。
typedef int Compare(Object a, Object b);
class SortedCollection {
Compare compare;
SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
main() {
SortedCollection coll = new SortedCollection(sort);
assert(coll.compare is Function);
assert(coll.compare is Compare);
}
异常
和java基本相似
try {
breedMoreLlamas();
} catch(e) {
print('Error: $e'); // Handle the exception first.
} finally {
cleanLlamaStalls(); // Then clean up.
}
类
和java基本相似,也有构造方法,成员变量,有抽象类,可以实现
class Point {
num x;
num y;
Point(num x, num y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
以上代码不免有点java语言的感觉,构造方法可以简化,简化后就有dart语言的风格了。
class Point {
num x;
num y;
Point(this.x, this.y);
}
需要注意的是:
没有public private等关键字。在变量前面加入“_”,表示私有。
父子类构造函数顺序:
子类构造函数调用父类非命名,无参构造函数。先父后子。
1.初始化列表
2.父类的无参构造函数
3当前类的无参构造函数
通过named constructors可以使父类有多个构造函数,但是子类是不能继承父类的构造函数的。如果使用父类的构造函数,子类需要实现父类的构造函数。
多个构造函数
可以通过Named constructors使类有多个构造函数:
使用:
class Point {
num x, y;
Point(this.x, this.y);
// Named constructor
Point.origin() {
x = 0;
y = 0;
}
}
通过named constructors可以使父类有多个构造函数,但是子类是不能继承父类的构造函数的。如果使用父类的构造函数,子类需要实现父类的构造函数。
继承和接口的差别
从下面代码中,我们也可以看到一些dart语言的语法风格。
class Point {
num x = 0, y = 0;
void printInfo() => print('($x,$y)');
}
//Vector 继承自 Point
class Vector extends Point{
num z = 0;
@override
void printInfo() => print('($x,$y,$z)'); // 覆写了 printInfo 实现
}
//Coordinate 是对 Point 的接口实现
class Coordinate implements Point {
num x = 0, y = 0; // 成员变量需要重新声明
void printInfo() => print('($x,$y)'); // 成员函数需要重新声明实现
}
var xxx = Vector();
xxx
..x = 1
..y = 2
..z = 3; // 级联运算符,等同于 xxx.x=1; xxx.y=2;xxx.z=3;
xxx.printInfo(); // 输出 (1,2,3)
var yyy = Coordinate();
yyy
..x = 1
..y = 2; // 级联运算符,等同于 yyy.x=1; yyy.y=2;
yyy.printInfo(); // 输出 (1,2)
print (yyy is Point); //true
print(yyy is Coordinate); //true
子类 Coordinate 采用接口实现的方式,仅仅是获取到了父类 Point 的一个“空壳子”,只能从语义层面当成接口 Point 来用,但并不能复用 Point 的原有实现。
类继承,接口实现和混入(minxin)的理解
父类继承:和java类似,继承了父类的实例变量和各种方法。但是不能用一个普通方法重写getter。
抽象类:抽象类不能实例化,会报出AbstractClassInstantiationError错误。
接口:成员变量,成员函数需要重新声明实现。和java不一样的是,没有接口声明,可以通过抽象类来描述接口。
mixin:使一个类有多个父类。例如:在Flutter中常见的我们需要继承state。如果需要页面保持状态,我们还需要AutomaticKeepAliveClientMixin来保持页面状态。这时就需要通过with来使用mixin.
泛型
和java类似
T first<T>(List<T> ts) {
// ...Do some initial work or error checking, then...
T tmp ?= ts[0];
// ...Do some additional checking or processing...
return tmp;
}
Asynchrony support(异步支持)
和java不同的是,java使用多线程,dart提供的是异步支持,通过async和await
checkVersion() async {
var version = await lookUpVersion();
if (version == expectedVersion) {
// Do something.
} else {
// Do something else.
}
}
Flutter示例
让我们来看一下dart语法在Flutter中
//导包
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
//程序的起始入口
//箭头符号 支持lambada表达式
void main() => runApp(MyApp());
//extends 可以继承
//StatelessWidget 是无状态的
//StatefulWidget是有状态的。有状态的就是数据频繁变动的,我们可以用setstate()方法来进行刷新UI
class MyApp extends StatelessWidget {
//通过build来进行构建
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
home: RandomWords(),
);
}
}
class RandomWordsState extends State<RandomWords> {
//Dart 没有 public、 protected、 和 private 关键字
//变量定义可以为final
//_suggestions :如果一个标识符以 (_) 开头,则该标识符 在库内是私有的。
//数组 []
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
Widget _buildSuggestions() {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
});
}
//一个简单的函数
Widget _buildRow(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Startup Name Generator'),
),
body: _buildSuggestions(),
);
}
}
class RandomWords extends StatefulWidget {
@override
RandomWordsState createState() => RandomWordsState();
}
MaterialApp 与 Scaffold
- Flutter中有两种风格安卓对应Material Design,MaterialApp ,iOS对应Cupertino.
- MaterialApp 经常是 Flutter Widget 树里的第一个元素,就是 Root Widget。
- Scaffold 实现了 Material Design 的基本布局结构,例如 AppBar、Drawer、SnackBar 等,所以为了使用这些布局,也必须要使用 Scaffold,所以一个 Flutter App 的 基本结构就是:Root Widget 是 MaterialApp ,然后 MaterialApp 的 子Widget 就是 Scaffold,然后我们在 Scaffolfd 的 子Widget 里写UI。
Widgets
小拓展:widgets中的key:
key这块仅仅是一个小的知识点。如果不是特别理解,入门的时候可以不看。
官网:
https://api.flutter.dev/flutter/widgets/Widget/key.html
控件一个小部件如何替换树中的另一个小部件。
key分类:
官网中 youtube视频 ,可以点击字幕选项,这样听起来可能会更好。
https://www.youtube.com/watch?v=kn0EOS-ZiIc
Flutter中key的作用
https://www.jianshu.com/p/57debb89a24f?tdsourcetag=s_pctim_aiomsg
Flutter中Widget之key原理探索
https://www.jianshu.com/p/e9f48141218d?tdsourcetag=s_pctim_aiomsg
Widget 的分类
- StatefulWidget
- 可以变化的 Widget,通过setState刷新
- StatelessWidget
基本布局构建
常用的widgets有
- 文本框
- 图片
- 输入框(TextField)
- 对话框(showDialog)
flutter中有几种布局:
我们需要注意的是,大多数widget都是有padding,居中等自带属性。
如果一开始不熟悉布局,我们可以在每个控件之上加上矩形边框(BoxDecoration)来查看每个布局的大小,熟悉之后将其去除。
盒子模型
三种:
- 尽可能大的。例如,Center和ListView使用的框。
- 那些试图和他们的孩子一样大的孩子。例如, Transform 和 Opacity。
- 那些试图成为特定尺寸的。例如,图像(image)和文本框(Text)
container也是尽可能大,但是给宽度,就遵循这个值。
路由跳转
界面之间跳转通过路由
第一个页面跳转到第二个页面。例如:
Navigator.push(
context, MaterialPageRoute(builder: (context) => SecondPage()));
返回:
Navigator.pop(context);
flutter第三方插件
pub.dartlang.org/
搜索第三方插件
例如搜索webview
里面会有相应的用法
在github中可以看到flutter中官方的插件
https://github.com/flutter/plugins/tree/master/packages
flutter_webview_plugin 插件是先于官方出的。
Flutter 的状态管理
Flutter 在设计声明式 UI 上借鉴了不少 React 的设计思想。
状态管理简单理解就是一个页面数据改变之后,其他页面也会改变相同的数据。
全局状态的例子:
用户的数据信息
App 的已读、未读状态数据
常用的框架:
网络
我们可以使用基本http进行网络请求。
还可以使用dio第三方库进行网络请求。
dio使用
yaml 文件配置
yaml相当于Android中的gradle,用来配置项目相关脚本。
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.2
json_annotation: ^1.2.0
dio: ^1.0.6
dev_dependencies:
build_runner: ^1.0.0
json_serializable: ^1.5.0
为了便利使用 json_serializable库
https://caijinglong.github.io/json2dart/index_ch.html
选1.xx 文件名写上
注意
part 'Email.g.dart';
类名如果大写的时候 需要修改,默认是小写的
flutter packages pub run build_runner watch
监听之后 会自动生成。
Flutter dio结合json_serializable请求数据并解析Demo
本地数据
官方shared_preferences
pub网址
https://pub.dev/packages/shared_preferences#-installing-tab-
基本shared_preferences工具类将会是这样。
class LocalStorage {
//保存
static save(String key, value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
}
//获取
static get(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.get(key);
}
//移除
static remove(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove(key);
}
}
Flutter 与 Native 通信
Flutter毕竟是UI框架,有的时候避免不了使用原生的一些功能或者代码。
这个时候UI和Native之间就需要通信。例如我们的Flutter需要推送功能,我们就需要自己封装一个插件代码。
PlatformChannel
PlatformChannel 用于 Flutter 与 Native(Android、iOS) 之间的消息传递。
PlatformChannel 是双向的
MethodChannel
EventChannel
BasicMessageChannel
官网中介绍了一种写法:
https://flutter.dev/docs/development/platform-integration/platform-channels
github中有相应的demo。
官方的video_player 源码中用到了event channel和methodChannel。
详细请看 flutter/plugins github
BasicMessageChannel大多数情况下是不用的。
Flutter 性能监控
https://flutterchina.club/debugging/
Flutter打包
Android
build.gradle android下
android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
....
}
在android\app同级目录下建立local.properties文件并且填入
storePassword=你的
keyPassword=你的
keyAlias=你的
storeFile=哪个盘的jks 如: D:/myproject/lalala.jks
ios打包和正常一样
持续交付
Travis CI 是在线托管的持续交付服务,网页上点几下就好,非常方便。
Travis CI官网
Flutter 深入原理
美团
https://tech.meituan.com/2018/08/09/waimai-flutter-practice.html
Flutter混合遇到的问题
如果是新建立一个项目,可以重新建立一个Flutter App。而在大多数已经有的项目中,部分引入flutter会遇到很多的问题。
闲鱼对这部分探索是最多的。闲鱼技术掘金
混合Flutter主要问题:
混合栈管理,减少包大小,flutter音视频,内存, Flutter工具,Flutter webview,业务架构
混合栈管理:flutter_boost 闲鱼的一个框架
减少包大小:带有flutter引擎,资源目录,以及代码实现。
flutter音视频:
有两种方法:
一platfomeview
如果有原生的音视频代码,迁移到flutter上面,platfomeview是一个很好的选择。
二 TextureWidget
platfomeview在内存上有一定的劣势。
google video_player的demo推荐的方式就是TextureWidget
解码的纹理特征给TextureWidget,通过textureWidget调用Opengl来进行底层渲染,通过textureid来传递。
内存
flutter工具
flutter webview
业务架构
fish_redux