Flutter探究

Flutter探究

背景

为什么是flutter

flutter的优势:

  • 跨平台多端一致:Flutter 从设计上就天然支持多平台开发,它的底层基于 Skia 跨平台图形引擎,向上构建出了一整套平台无关的渲染体系和事件处理体系,一套代码可以无差异的同时跑在 iOS 与 Android 两端
  • 性能体验:Flutter底层是通过C++实现的,性能几乎与原生一模一样,帧频率达到到60帧/秒是基本操作
  • 开发效率:由于跨平台的原因相同技术水平的情况下一个开发人员的输出会是之前的两倍,hot reload(热重载)修改代码后,不用重新编译效果会实时显示到正在运行的设备上

[TOC]

一、环境配置

按照官网配置步骤配置

可能遇到的问题

  • 需要配置环境变量:配置方法
  • 新工程创建成功后报错:需要跑一下flutter packages get(跟pod update类似) 这个命令可能会很慢,网上有解决方法
  • 需要更新pod到1.8:
  • 需要安装Android Studio(只跑ios端的话可能不用安装)
  • 真机调试:在VS Code右下角点击 No Devices 然后选择已连接的设备,如果无法正常启动可能是证书问题,当前 Flutter 项目目录下运行 open ios/Runner.xcworkspace 命令来打开默认的 Xcode 工程配置证书

环境配置好后在终端执行flutter doctor命令检查是否所有的依赖都已经安装成功
VSCode

二、VSCode Flutter项目目录简单介绍

VSCode创建flutter的项目目录结构是长这样的

123456.png

目录结构介绍

目录 描述 备注
.gitignore git管理文件忽略配置文件
android 里面放的对应的是android工程
ios 里面放的对应的是ios工程
pubspec.lock 记录已安装第三方依赖库的版本信息 类似POD的Podfile.lock
pubspec.yaml 第三方依赖库的配置文件,或者指定本地资源 类似于Pod中的Podfile
lib flutter开发工作目录,我们主要编写的代码就在这个文件夹
.dart_tool 记录了一些dart工具库所在的位置和信息 不需要关注
.idea 记录了项目的一些信息文件 做了哪些更改
.vscode 暂时不太清楚
build 放的是一些生成文件
test 测试代码目录
.metadata 主要是对flutter版本做的一个记录 自动生成的,不要手动更改
.packages 开发相关工具路径配置文件
README.md 项目说明文件
startup_namer.iml 也是记录了对某些文件的一些配置 暂时不清楚有啥作用

程序入口

lib目录里面的 main.dart 文件就是 flutter项目 的入口文件(类似于main.m),
其中的 main 方法是 dart 的入口方法。runApp 方法是 flutter 的入口方法。 MyApp 是自定义的一个组件。

void main() => runApp(MyApp());

runApp函数接受给定的Widget并使其成为widget树的根

编程语言介绍

flutter采用dart语言,dart也是Google推出的一种编程语言,在Google的未来操作系统Fuchsia中,Dart被指定为官方的开发语言。
dart语言变量是强类型,可以类型推断,缩进会影响代码运行结果,因此写代码时要注意缩进(代码格式化快捷键shift + option + f)

三、flutter基本组件介绍

  • flutter使用响应式编程,这里有一份关于响应式编程,或者说 声明式编程 和传统的命令式编程有什么不同之处的文章,可以浏览 声明式 UI 介绍。
  • flutter 提供了一套安卓风格的组件Material,和iOS风格的Cupertino,但是Material 中的组件比Cupertino多很多

1、Flutter Widget

框架概述
介绍
Flutter Widget采用现代响应式框架构建,这是从 React 中获得的灵感,中心思想是用widget构建你的UI。 Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改(类似于React/Vue中虚拟DOM的diff算法)。
类比iOS组件
给 iOS 开发者的 Flutter 指南

基础 Widget
  • Text:该 widget 可让创建一个带格式的文本。类似于UILabel
  • Row、 Column:这些具有弹性空间的布局类Widget可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。其设计是基于web开发中的Flexbox布局模型。
  • Stack:取代线性布局 (和Android中的LinearLayout相似),Stack允许子 widget 堆叠, 你可以使用Positioned 来定位他们相对于Stack的上下左右四条边的位置。Stacks是基于Web开发中的绝度定位(absolute positioning )布局模型设计的。
  • Container:Container 可以创建矩形视觉元素。container 可以装饰为一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)

2、dart 简单介绍

2.1变量声明

1.var

类似于JavaScript中的var,它可以接收任何类型的变量,但最大的不同是Dart中var变量一旦赋值,类型便会确定,则不能再改变其类型,如:

var t;
t = "hi world";
// 下面代码在dart中会报错,因为变量t的类型已经确定为String,
// 类型一旦确定后则不能再更改其类型。
t = 1000;
2.dynamic和Object

Object 是Dart所有对象的根基类,也就是说所有类型都是Object的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object声明的对象. dynamic与var一样都是关键词,声明的变量可以赋值任意对象。 而dynamic与Object相同之处在于,他们声明的变量可以在后期改变赋值类型。

dynamic t;
 Object x;
 t = "hi world";
 x = 'Hello Object';
 //下面代码没有问题
 t = 1000;
 x = 1000;

dynamic与Object不同的是,dynamic声明的对象编译器会提供所有可能的组合, 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错。如:

dynamic a;
 Object b;
 main() {
     a = "";
     b = "";
     printLengths();
 }   

 printLengths() {
     // no warning
     print(a.length);
     // warning:
     // The getter 'length' is not defined for the class 'Object'
     print(b.length);
 }

变量a不会报错, 变量b编译器会报错

dynamic的这个特性与Objective-C中的id作用很像. dynamic的这个特点使得我们在使用它时需要格外注意,这很容易引入一个运行时错误.

3.final和const

如果您从未打算更改一个变量,那么使用 final 或 const,不是var,也不是一个类型。 一个 final 变量只能被设置一次,两者区别在于:const 变量是一个编译时常量,final变量在第一次使用时被初始化。被final或者const修饰的变量,变量类型可以省略,如:

//可以省略String这个类型声明
final str = "hi world";
//final String str = "hi world"; 
const str1 = "hi world";
//const String str1 = "hi world";
2.2函数

Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。这意味着函数可以赋值给变量或作为参数传递给其他函数,这是函数式编程的典型特征。

1.函数声明
bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

Dart函数声明如果没有显式声明返回值类型时会默认当做dynamic处理,注意,函数返回值没有类型推断:

typedef bool CALLBACK();

//不指定返回类型,此时默认为dynamic,不是bool
isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

void test(CALLBACK cb){
   print(cb()); 
}
//报错,isNoble不是bool类型
test(isNoble);
2.对于只包含一个表达式的函数,可以使用简写语法
bool isNoble (int atomicNumber)=> _nobleGases [ atomicNumber ] != null ;
3.函数作为变量
var say = (str){
  print(str);
};
say("hi world");
4.函数作为参数传递
void execute(var callback) {
    callback();
}
execute(() => print("xxx"))
5.可选的位置参数

包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面:

String say(String from, String msg, [String device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  return result;
}

例子:

say('Bob', 'Howdy'); //结果是: Bob says Howdy
say('Bob', 'Howdy', 'smoke signal'); //结果是:Bob says Howdy with a smoke signal
6.可选的命名参数

定义函数时,使用{param1, param2, …},放在参数列表的最后面,用于指定命名参数。例如:

//设置[bold]和[hidden]标志
void enableFlags({bool bold, bool hidden}) {
    // ... 
}
//调用函数时,可以使用指定命名参数。例如:paramName: value
enableFlags(bold: true, hidden: false);

注意,不能同时使用可选的位置参数和可选的命名参数

2.3异步支持

1.Future

Future表示一个异步操作的最终完成(或失败)及其结果值的表示。简单来说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future只会对应一个结果,要么成功,要么失败。Future 的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。

Future.then
为了方便示例,在本例中我们使用Future.delayed 创建了一个延时任务(实际场景会是一个真正的耗时任务,比如一次网络请求),即2秒后返回结果字符串"hi world!",然后我们在then中接收异步结果并打印结果,代码如下:

Future.delayed(new Duration(seconds: 2),(){
   return "hi world!";
}).then((data){
   print(data);
});

Future.catchError
如果异步任务发生错误,我们可以在catchError中捕获错误,我们将上面示例改为:

Future.delayed(new Duration(seconds: 2),(){
   //return "hi world!";
   throw AssertionError("Error");
}).then((data){
   //执行成功会走到这里 
   print(data);
}).catchError((e){
   //执行失败会走到这里   
   print(e);
}).whenComplete((){
   //无论成功或失败都会走到这里
});

then方法还有一个可选参数onError,我们也可以它来捕获异常:

Future.delayed(new Duration(seconds: 2), () {
    //return "hi world!";
    throw AssertionError("Error");
}).then((data) {
    print("success");
}, onError: (e) {
    print(e);
});

Future.wait
有些时候,我们需要等待多个异步任务都执行结束后才进行一些操作可以使用Future.wait,它接受一个Future数组参数,只有数组中所有Future都执行成功后,才会触发then的成功回调,只要有一个Future执行失败,就会触发错误回调。

Future.wait([
  // 2秒后返回结果  
  Future.delayed(new Duration(seconds: 2), () {
    return "hello";
  }),
  // 4秒后返回结果  
  Future.delayed(new Duration(seconds: 4), () {
    return " world";
  })
]).then((results){
  print(results[0]+results[1]);
}).catchError((e){
  print(e);
});

Async/await
Dart中的async/await 和JavaScript中的async/await功能和用法是一模一样的,为了解决回调地狱问题

回调地狱(Callback Hell)
如果代码中有大量异步逻辑,并且出现大量异步任务依赖其它异步任务的结果时,必然会出现Future.then回调中套回调情况。举个例子,比如现在有个需求场景是用户先登录,登录成功后会获得用户ID,然后通过用户ID,再去请求用户个人信息,获取到用户个人信息后,为了使用方便,我们需要将其缓存在本地文件系统,代码如下:
//先分别定义各个异步任务

Future login(String userName, String pwd){
    ...
    //用户登录
};
Future getUserInfo(String id){
    ...
    //获取用户信息 
};
Future saveUserInfo(String userInfo){
    ...
    // 保存用户信息 
};

接下来,执行整个任务流:

login("alice","******").then((id){
 //登录成功后通过,id获取用户信息    
 getUserInfo(id).then((userInfo){
    //获取用户信息后保存 
    saveUserInfo(userInfo).then((){
       //保存用户信息,接下来执行其它操作
        ...
    });
  });
})

目前我们项目中应该也存在回调七八层的情况
使用Future消除Callback Hell

login("alice","******").then((id){
      return getUserInfo(id);
}).then((userInfo){
    return saveUserInfo(userInfo);
}).then((e){
   //执行接下来的操作 
}).catchError((e){
  //错误处理  
  print(e);
});

“Future 的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用” ,如果在then中返回的是一个Future的话,该future会执行,执行结束后会触发后面的then回调,这样依次向下,就避免了层层嵌套。

使用async/await消除callback hell
通过Future回调中再返回Future的方式虽然能避免层层嵌套,但是还是有一层回调,有没有一种方式能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式?答案是肯定的,这就要使用async/await了,下面我们先直接看代码,然后再解释,代码如下:

task() async {
   try{
    String id = await login("alice","******");
    String userInfo = await getUserInfo(id);
    await saveUserInfo(userInfo);
    //执行接下来的操作   
   } catch(e){
    //错误处理   
    print(e);   
   }  
}
  • async用来表示函数是异步的,定义的函数会返回一个Future对象,可以使用then方法添加回调函数。
  • await 后面是一个Future,表示等待该异步任务完成,异步完成后才会往下走;await必须出现在 async 函数内部。

其实,无论是在JavaScript还是Dart中,async/await都只是一个语法糖,编译器或解释器最终都会将其转化为一个Promise(Future)的调用链。

Stream
Stream 也是用于接收异步事件数据,和Future 不同的是,它可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。

Stream.fromFutures([
  // 1秒后返回结果
  Future.delayed(new Duration(seconds: 1), () {
    return "hello 1";
  }),
  // 抛出一个异常
  Future.delayed(new Duration(seconds: 2),(){
    throw AssertionError("Error");
  }),
  // 3秒后返回结果
  Future.delayed(new Duration(seconds: 3), () {
    return "hello 3";
  })
]).listen((data){
   print(data);
}, onError: (e){
   print(e.message);
},onDone: (){

});

上面的代码依次会输出:

I/flutter (17666): hello 1
I/flutter (17666): Error
I/flutter (17666): hello 3

待处理问题

1、android studio跟VS Code对比
2、dart基本语法介绍
3、flutter基本组件介绍 对比iOS组件
4、flutter打包发布
5、dart常用三方网络请求库介绍
6、flutter和oc混编

参考链接

让阿里告诉你, iOS开发者为什么要学 Flutter !
1个人,100天业余时间,用Flutter开发完一个商业APP
在flutter的选择上可以参考宗心同学在云栖社区的分享
Flutter 项目介绍及体验
Flutter for iOS 开发者

你可能感兴趣的:(Flutter探究)