Flutter入门

前言

Flutter是Google开发的一套全新的跨平台开源UI框架,使用Dart语言开发,支持IOS、Android、Web、桌面以及嵌入式平台,是未来Google新操作系统Fuchsia的默认开发框架。
2017年5月发布第一个版本,并且在2018年12月初发布1.0稳定版,目前最新版本是2020年10月发布的1.22

Flutter优势

  • 提高开发效率

    • 同一份代码开发iOS和Android
    • 热重载
      • 页面每次改动,不需要重新编译APK,可快速刷新。即支持开发过程中热重载。
  • 统一的UI,创建美观,高度定制的用户体验

    • 受益于使用Flutter框架提供的丰富的Material Design和Cupertino(iOS风格)的widget
    • 实现定制、美观、品牌驱动的设计,而不受原生控件的限制
热重载演示

Flutter架构

Flutter架构

Flutter的架构主要分成三层:Framework,Engine和Embedder。

  1. Framework使用dart实现,包括Material Design风格的Widget,Cupertino(针对iOS)风格的Widgets,文本/图片/按钮等基础Widgets,渲染,动画,手势等。此部分的核心代码是:flutter仓库下的flutter package。

  2. Engine使用C++实现,主要包括:Skia,Dart和Text。Skia是开源的二维图形库,C++ 的2D绘图引擎,调用GPU来完成渲染,提供了适用于多种软硬件平台的通用API。

  3. Embedder是一个嵌入层,即把Flutter嵌入到各个平台上去,这里做的主要工作包括渲染Surface设置,线程设置,以及插件等。从这里可以看出,Flutter的平台相关层很低,平台(如iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。

Dart语言介绍

Flutter选用Dart作为其开发语言,除了其语法开源等方面的因素外,主要还关乎Dart语言的编译方式,Dart语言有两种编译时:

  • JIT(Just in time,即时编译):可以动态下发和执行代码,开发测试效率高,但运行速度和执行性能则会因为运行时即时编译受到影响。
  • AOT (Ahead of time,运行前编译): 在开发时就将代码编译成机器码,运行速度快,但编译耗时。

在开发模式下,Flutter会使用JIT的编译方式,支持热重载,能更快速、高效的做UI开发。而在打包APK的时候,使用AOT的编译方式,确保APK的性能跟原生媲美。

Dart基础语法

内置数据类型


数据类型

在Dart中,所有能够使用变量引用的都是对象,每个对象都是一个类的实例。数字、函数和 null 也都是对象。所有的对象都继承于Object类。

要注意,没有初始化的变量默认值为 null。数值类型变量的默认值也是 null。

数值类型num有两个具体子类,分别为int和double,其中int为整数值,范围是-253至253之间;double则是64位的双精度浮点数。

变量与常量

定义变量

// 1.通过显式指定类型来定义变量
String name = "张三";
int age = 18;

// 2.使用关键字var,不指定类型
var address = "李雷";
var id = 100;

/* 使用var定义变量,即使未显式指定类型,一旦赋值后类型就被固定
 * 因此使用var定义的变量不能改变数据类型
 */
var number = 19;
// 以下代码错误,无法运行,number变量已确定为int类型
number = "2019";

如想动态改变变量的数据类型,应当使用dynamic或Object来定义变量。

// dynamic声明变量
dynamic var1 = "hello";
var1 = 19;
print(var1);    // 19

// Object声明变量
Object var2 = 20;
var2 = "world";
print(var2);    // world

定义常量

Dart中定义常量也有两种方式,一种使用final关键字,同Java中的用法, 一个 final 变量只能赋值一次;另一种是Dart的方式,使用const关键字定义。

// 1.使用final关键字定义常量
final height = 10;

// 2.使用const关键字定义常量
const pi = 3.14;

需要注意,final定义的常量是运行时常量,而const常量则是编译时常量,也就是说final定义常量时,其值可以是一个变量,而const定义的常量,其值必须是一个字面常量值。

final time = new DateTime.now(); // 正确
const time = new DateTime.now(); // 错误


const list = const[1,2,3];       // 正确
const list = [1,2,3];            // 错误

字符串

Dart中提供的字符串插值表达式使字符串格式化变得异常方便且丰富

// 1.Dart可以使用单引号或双引号来创建字符串
var s1 = "hello";
var s2 = 'world';

// 2.类似Python,Dart可以使用三引号来创建包含多行的字符串
var multiLine1 = """你可以像这样,创建一个
包含了多行的字符串内容
""";

var multiLine2 = '''你也可以使用三个单引号,创建一个
包含了多行的字符串内容
''';

// 3.类似Python,还可以在字符串字面值的前面加上`r`来创建原始字符串,则该字符串中特殊字符可以不用转义
var path = r'D:\workspace\code';

// 4.Dart支持使用"+"操作符拼接字符串
var greet = "hello" + " world";

// 5.Dart提供了插值表达式"${}",也可以用于拼接字符串
var name = "王五";
var aStr = "hello,${name}";
print(aStr);    // hello,王五

// 当仅取变量值时,可以省略花括号
var aStr2 = "hello,$name"; // hello,王五

// 当拼接的是一个表达式时,则不能省略花括号
var str1 = "link";
var str2 = "click ${str1.toUpperCase()}";
print(str2);   // click LINK

// 6. 与Java不同,Dart使用"=="来比较字符串的内容
print("hello" == "world");

列表

Dart中列表操作与JavaScript中的数组相似。

// 创建列表
var list = [1, 2, 3];
// 下标从0开始。使用length可以访问list的长度
print(list[0]);
print(list.length);

// 可以使用add添加元素
list.add(5);

// 可在list字面量前添加const关键字,定义一个不可改变的 列表(编译时常量)
var constantList = const [1, 2, 3];
constantList[1] = 1;     // 报错

映射

又称为关联数组,相当于Java中的HashMap

// 1.通过字面量创建Map
var gifts = {
  'first' : 'partridge',
  'second': 'turtledoves',
  'fifth' : 'golden rings'
};

// 2.使用Map类的构造函数创建对象
var pic = new Map();
// 往Map中添加键值对
pic['first'] = 'partridge';
pic['second'] = 'turtledoves';
pic['fifth'] = 'golden rings';

// 3.获取Map的长度
print(pic.length);

// 4.查找Map
pirnt(pic["first"]);
print(pic["four"]);    // 键不存在则返回 null

类和函数

Dart中类和函数的定义跟java类似

// Dart中定义一个类
class Person {
  /**
  * Dart中没有private、public修饰符
 * 默认是可以直接被外部访问的,在变量前加上 “_”表示是私有的,不能被外部直接访问
  **/
  String name;
  int _age;

  // 构造函数写法一
  Person(String name, int age) {
    this.name = name;
    this._age = age;
  }

 // 在构造方法中初始化成员变量时,可使用如下写法简化
   Person(this.name, this.age);

  // 定义函数
  String greet(String name){
    return "hello,$name";
  }

  // 在Dart中,类型是可选,可以省略显式的类型,但仍然建议显式指定类型。
  greet(name){
      return "hello,$name";
  }



}

注意:
  • Dart中没有构造方法的重载,不能写两个同名的构造方法。

  • 函数也是对象,所有函数都有返回值。当没有指定返回值的时候,函数会返回null。当然,如果你强行使用void来修饰函数,则函数真的没有返回值,这种情况就另当别论了。

命名构造方法

上面已经说过,Dart类中两个同名构造方法不能重载,但是Dart语言为类新增了一种称为命名构造方法的东西。

class  Person {
    String userName;
    int age;

    Person(this.userName, this.age);

    // 命名构造方法
    Person.fromData(Map data) {
        this.userName = data['name'];
        this.age = data['age'];
    }
}

   // 可以存在多个命名构造方法
    Person.now(int age){
          this.age = age;
    }

void  main() {
    // 使用命名构造方法创建对象
    var p = new Person.fromData({
        "name":"Bob",
        "age":19
    });
    print(p.userName);
}

函数的参数

Dart中支持两种可选参数

  • 命名可选参数
  • 位置可选参数

在Java中通常使用方法重载来实现同名方法的不同参数调用,Dart中则可以通过可选参数来实现相同效果。

命名可选参数

命名可选参数是使用花括号来定义参数列表

// 定义一个函数,参数列表用花括号包裹
show({bool bold, bool hidden}) {
    // do something
}

// 调用方式,传参时使用"参数名:值"的形式
show(hidden:true,bold:false);

如果在定义函数时,给参数列表中的参数设置默认值,则该参数就是可选的,函数调用时可以忽略该参数,使用默认的值。

// 定义add函数
add({int x, int y=1, int z=0}){
    print(x + y + z);
}

// 调用
add(x:18);              // 19
add(x:18, y:2, z:10);   // 30

位置可选参数

位置可选参数使用中括号来定义参数列表,中括号中的参数是可选的

// 定义add函数
add(int x, [int y, int z]){
    int result = x;
    if (y !=  null){
        result = result + y;
    }

    if (z !=  null){
        result = result + z;
    }
    print(result);
}

// 调用
add(18);           // 18
add(18,12);        // 30
add(18, 12, 15);   // 45

当然,你也可以给位置可选参数设置默认值

// 定义add函数
add(int x, [int y=0, int z=0]){
    print(x +y+z);
}

级联运算符

我们通常使用.操作符调用对象的方法,这在Dart中也是支持的,但是Dart另外增加了一种级联运算符..,用两个点表示。

级联运算符可以在同一个对象上连续调用多个方法以及访问成员变量。 使用它可以避免创建临时变量, 写出更流畅的代码。

new Person()..setName("Bob")..setAge(20)..save();

异步编程

Dart与JavaScript一样,是一个单线程模型。但这并不意味着Dart中不能进行异步编程,只是这种异步编程区别于传统的多线程异步方式。简单说就是在某个单线程中存在一个事件循环和一个事件队列,事件循环不断的从事件队列中取出事件来执行,这里的事件就好比是一段代码,每当遇到耗时的事件时,事件循环不会停下来等待结果,它会跳过耗时事件,继续执行其后的事件。当不耗时的事件都完成了,再来查看耗时事件的结果。因此,耗时事件不会阻塞整个事件循环,这让它后面的事件也会有机会得到执行。

Dart 是事件驱动的体系结构,该结构基于具有单个事件循环和两个队列的单线程执行模型。 Dart虽然提供调用堆栈。 但是它使用事件在生产者和消费者之间传输上下文。 事件循环由单个线程支持,因此不需要同步和锁定。

20190505100632250.jpg

Dart 的两个队列分别是

  • MicroTask queue 微任务队列

  • Event queue 事件队列

Dart队列处理

Dart事件循环执行如上图所示

  1. 先查看MicroTask队列是否为空,不是则先执行MicroTask队列
  2. 一个MicroTask执行完后,检查有没有下一个MicroTask,直到MicroTask队列为空,才去执行Event队列
  3. 在Evnet 队列取出一个事件处理完后,再次返回第一步,去检查MicroTask队列是否为空

我们可以看出,将任务加入到MicroTask中可以被尽快执行,但也需要注意,当事件循环在处理MicroTask队列时,Event队列会被卡住,应用程序无法处理鼠标单击、I/O消息等等事件。

Dart中使用async和await关键字来实现异步任务

// 导入io库,调用sleep函数
import 'dart:io';

// 模拟耗时操作,调用sleep函数睡眠2秒
doTask() async{
  await sleep(const Duration(seconds:2));
  return "Ok";
}

// 定义一个函数用于包装
test() async {
  var r = await doTask();
  print(r);
}

void main(){
  print("main start");
  test();
  print("main end");
}

运行结果

main start
main end
Ok

需要注意,async 不是并行执行,它是遵循Dart 事件循环规则来执行的,它仅仅是一个语法糖,简化Future API的使用。

Isolate

如果将非常耗时的任务添加到事件队列后,仍然会拖慢整个事件循环的处理,甚至是阻塞。可见基于事件循环的异步模型仍然是有很大缺点的,这时候我们就需要Isolate,这个单词的中文意思是隔离。

简单说,可以把它理解为Dart中的线程。但它又不同于线程,更恰当的说应该是微线程,或者说是协程。它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。

Flutter 中创建Isolate

import 'package:flutter/foundation.dart';
import  'dart:io';

// 创建一个新的Isolate,在其中运行任务doWork
create_new_task() async{
  var str = "New Task";
  var result = await compute(doWork, str);
  print(result);
}


String doWork(String value){
  print("new isolate doWork start");
  // 模拟耗时5秒
  sleep(Duration(seconds:5));

  print("new isolate doWork end");
  return "complete:$value";
}

创建 Flutter app

flutter项目结构

项目结构

一个简单的页面

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          child: new Text('Hello World'),
        ),
      ),
    );
  }
}

运行应用程序,你应该看到如下界面

hello-world-screenshot.png
  • main函数使用了箭头函数(=>), 这是Dart中单行函数或方法的简写。

  • 该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个widget。 在Flutter中,大多数东西都是widget,包括对齐(alignment)、填充(padding)和布局(layout)

  • Scaffold 是 Material library 中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。widget树可以很复杂。

  • widget的主要工作是提供一个build()方法来描述如何根据其他较低级别的widget来显示自己。

  • 本示例中的body的widget树中包含了一个Center widget, Center widget又包含一个 Text 子widget。 Center widget可以将其子widget树对其到屏幕中心。

添加一个有状态的widget(StatefulWidget)

import 'package:flutter/material.dart';
import 'pan.dart';

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {


  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Test"),
        ),
        body: Center(child: Text("Hello World")
    ));
  }
}


Flutter中文官网

你可能感兴趣的:(Flutter入门)