Skip to content

Flutter 基础—— Dart 入门 #15

Description

@zxiaohong

Flutter 基础—— Dart 入门


Flutter简介

Flutter是Google公司2018年2月27日发布的第一个开源跨平台软件开发工具包 (SDK),支持Android、iOS两个平台,可实现高性能、高保真的应用程序开发。开发者借助Flutter开发平台可轻松实现自己的应用,其开发框架默认支持了Material(类似Google设计风格)和Cupertion(类似iOS设计风格)两种风格,且无论从UI样式、用户体验上都非常接近原生应用。
经过近7个月的优化改进2018年9月19日Google公司在上海正式发布非常接近1.0正式版本的Flutter Release Preview 2。基于其优越性能Flutter有望成为未来应用开发的主流工具。

Flutter类似且优于Html、React Native、Weex等跨平台解决方案。ReactNative将JSX生成的代码最终都渲染成原生组件,JS与原生通过JSBridge传递数据。而Flutter则采用完全不同的设计,底层是一套独立的渲染引擎–Skia,所有组件也都是独立于平台的Widget组件,可以在最大程度上保证了跨平台体验的一致性。

image

Dart

Flutter 使用 Dart 作为开发语言,在深入flutter框架前应该先对dart语言有所了解。据说,dart语言里的API 90%以上与 Java 相同,如果你有 Java 基础,几乎可以很快上手,没有 Java 基础也不用担心,它的 API 很简单,学过 JavaScript 也可以轻松get✅;

几个重要的点:

  • 能赋值给变量的所以东西都是对象,包括 numbers, null, function, 都是继承自 Object 内置类
  • 尽管 dart 是强类型的,但类型注释是可选的,因为 dart 可以类型推断,如果要明确说明不需要任何类型,请使用特殊类型dynamic。
  • 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下会类型会是 dynamic(动态的)
  • dart支持泛型类型,如List <int>(整数列表)或List <dynamic>(任何类型的对象列表)。
  • 支持最外层函数定义,也支持类的静态方法与对象的实例方法,函数中可定义内部函数
    (Dart支持顶级函数(例如main()),以及绑定到类或对象的函数(分别是静态和实例方法)。您还可以在函数内创建函数(嵌套函数或本地函数))。
  • dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度
  • dart 没有 public、private、protected 这些关键字,变量名以"_"开头意味着对它的 lib 是私有的
  • 标识符以字母或“_”开头,后面可随意组合

这里简单介绍下:

  • Hello World

  • 变量声明

  • 数据类型

  • 函数

  • 流程控制

  • 异常处理

  • 泛型

  • 异步操作

1. Hello World

	main(List<String> args){
		print("Hello World");
	}

和 Java 程序一样,dart程序也有一个入口函数 main ,把上面的程序保存为hello_world.dart, 在命令行运行:

dart hello_world.dart

安装之后需要配置环境变量

启动 dart 程序

// 定义一个函数
	printNumber(num aNumber) {
		print("The number is `${aNumber}`."); //控制台打印
	}

	// 启动
	main() {
		var number = 42; 
		printNumber(number); 
	}

2. 变量声明

变量声明关键字: varconstfinalintStringdouble(浮点型)、 bool

2.1 var 声明变量 —— 不指定类型

和 JavaScript 一样, 可以用 var 声明变量

main(List<String> args) {
  var number = 18;
  var name ="xiao ming";
  var salary = 150300.56;
  var isDoorOpen = true;
}

不一样的是,dart自带类型推断,一旦给变量赋值,它的类型就不可变了。

尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下会类型会是 dynamic(动态的)
dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。

//类似于 typescript, 可以推导出 name 为字符串类型
var name = "Bob";
// 如果不想 推导出类型,下边两种写法:
dynamic name = "Bob";
Object name= "Bob";

2.2 intStringdouble(浮点型)、 bool声明变量——指定变量类型

数值类型num,有两个子类int,double,int可进行按位运算,没有基本类型的概念,由于int,double都是num的子类,定义为num的变量可以在int、double间多态

main(List<String> args) {
  int number = 18;
  String name = "xiaoming";
  double salary = 150300.56;
  bool isDoorOpen = true;
}

2.3 constfinal声明常量

main(List<String> args) {
  final int number = 42;
  const String name = "xiao ming";
 
  //Omit explicitly defining data types
  final salary = 150300.56;
  const isDoorOpen = true;
}

const是编译时常量,在编译时确定,不可定义为实例变量;final是运行时常量,在第一次使用时确定;一个常量是const类型也暗示必然是final类型
const也可定义为其它const的算式结果,在类中定义需与static一起
const可写在变量或构造函数前用来定义常值,常量表示引用关系不可变,常值表示值本身不可变

3. 数据类型

3.1 dart提供基本的数据类型:

  • Numbers
  • Strings
  • Booleans
  • Lists
  • Maps
  • Runes(字符)
  • Symbol(符号)
main(List<String> args) {
  //Numbers
  int x = 100;
  double y = 1.1;
  int z = int.parse('10');
  double d = double.parse('44.4');
 
  //Strings
  String s = "This is a string";
  String backslash = "I can't speak";
  //String interpolation
  String interpolated = "Value of x is $x";   //Prints: Value of x is 100
  String interpolated2 = "Value of s is ${s.toLowerCase()}"; 
	//Prints: Value of s is this is a string
  
  //Booleans
  bool isDoorOpen = false;
}

3.2 常用方法

3.2.1 number 取值范围:-2^53 to 2^53
// String -> int
var one = int.parse('1');

// String -> double
var onePointOne = double.parse('1.1');

// int -> String
String oneAsString = 1.toString();

// double -> String 注意括号中要有小数点位数,否则报错
String piAsString = 3.14159.toStringAsFixed(2);

3.2.2 string
  • '''...''',"""..."""表示多行字符串(连续三个单引号或双引号)
  • r'...',r"..."表示“raw”字符串(字符串前加字母 r 则不处理转义)
  • 可用${}引用表达式,或直接$引用标识符

示例代码:

 String s = "FRONT END DEVELOPER";

  print ("A Commpany has a $s, which is good idea." ==
      "A Commpany has a Front End Developer," +
          " which is good idea.");
  print("I am a " +
      "${s.toUpperCase()} is very hornor!" ==
      "I am a " +
          "FRONT END DEVELOPER is very hornor!");
3.2.3 bool

Dart 是强 bool 类型检查,只有bool 类型的值是true 才被认为是true

3.2.4 List

声明 List 用 new List() 或者简单的赋值

下面是一些基本操作

main(List<String> args) {
	// 声明
	var vegetables = new List();
	
	//或者简单赋值声明
	var fruits = ["apples", "peaches"];
	
	// 添加元素
	fruits.add("bananas");
	
	// 添加多个元素
	fruits.addAll(["grapes","oranges"]);
	
	// 获取第一元素
	fruits.first
	
	// 获取最后一个元素
	fruits.last
	
	// 查找某个元素的索引号
	assert(fruits.indexOf("apples") === 0);
	
	// 删除指定位置的元素,返回删除的元素
	fruits.removeAt(index);
	
	// 删除指定元素,成功返回 true ,失败返回 false
	fruits.remove("oranges");
	
	// 删除最后一个元素,返回删除的元素
	fruits.removeLast
	
	// 删除指定范围的元素,含头不含尾。成功返回 null
	fruits.removeRange(start, end);
	
	// 删除指定条件的元素,成功返回null
	fruits.removeWhere((item) => item.length > 6);
	
	//删除所有元素
	fruits.clear();
	
	// sort()排序 传入一个函数作为参数,return <0 表示有小到大,>0 表示由大到小;
	fruits.sort((a, b) => a.compareTo(b));
	
	//常量 List
	var list = const [1,2,3,4];    //Cannot alter elements of this list
}
3.2.5 map 散列表

基本操作

// 声明
// 简单赋值
var smartHomeProducts = {
	"largeAppliances": ["air conditioning", "television", "fridge"],
	"homeDevices": ["air purifier","Sweeping robot", "humidifier"]
}

var searchTerms = new Map();


// 指定键值对的参数类型
var smartHomeSkills = new Map<int, String>();

// 取值
print(smartHomeProducts["largeAppliances"]); // Output: ["air conditioning", "television", "fridge"];
// 不存在的 key 返回 null
print(smartHomeProducts["kitchenAppliances"]); // Output: null;


// Map的赋值,中括号中是Key,这里可不是数组
smartHomeSkills[1] = "chart";

//Map中的键值对是唯一的
//同Set不同,第二次输入的Key如果存在,Value会覆盖之前的数据
smartHomeSkills[1] = "sing";
assert(smartHomeSkills[1] == "sing");

// 检索Map是否含有某Key
assert(smartHomeSkills.containsKey(1));

//删除某个键值对
smartHomeSkills.remove(1);
assert(!smartHomeSkills.containsKey(1));

// 获取所有值

 var entries = smartHomeProducts.entries;
 var values = smartHomeProducts.values;
 print(entries); // Output: (MapEntry(largeAppliances: [air conditioning, television, fridge]), MapEntry(homeDevices: [air purifier, Sweeping robot, humidifier]))
 print(values); // Output: ([air conditioning, television, fridge], [air purifier, Sweeping robot, humidifier])
 
 
// 常量map

var squares = const {    //Cannot change the content of this map
    2: 4,
    3: 9,
    4: 16,
    5: 25
  };

4. 函数

Dart是一种真正的面向对象语言,因此即使是函数也是对象并且具有类型Function。这意味着函数可以分配给变量或作为参数传递给其他函数。您也可以像调用函数一样调用Dart类的实例。

函数声明无需 function 关键字;

函数名前可标注函数返回类型,没有返回值的函数返回类型可设为void;
函数的参数可设定类型也可以不设

main(List<String> args) {
  var name = fullName("John", "Doe");
  print(name);
}
 
String fullName(String firstName, String lastName) {
  return "$firstName $lastName";
}

//或忽略返回类型
fullName(String firstName, String lastName) {
  return "$firstName $lastName";
}


支持箭头函数

fullName(String firstName, String lastName) => "$firstName $lastName";

命名参数

顾名思义:调用命名参数的函数时,必须指定参数的名字;

  • 命名参数的启用,只需把所有参数用大括号包裹起来即可;
 
fullName({String firstName, String lastName}) {
  return "$firstName $lastName";
}
  • 命名参数的调用
main(List<String> args) {
  var name = fullName(firstName: "John", lastName: "Doe");
  print(name);
}

调用命名参数的函数时,如果没有指定参数的名字,程序会崩溃报错。

参数默认值

为命名参数的某个参数指定默认值,这个参数就会变成可选参数。如上面的函数指定默认参数后可以这样调用:

main(List<String> args) {
  var name = fullName(firstName: "John");
  print(name);
}

fullName({String firstName, String lastName = "Doe"}) {
  return "$firstName $lastName";
}

5. 流程控制

  • if...else if... else...
  • 三元表达式
  • for
  • while/do...while
  • switch...case..
    ...

这个流程控制语句使用和 JavaScript 没什么差异

6. 异常处理

try...catch...finaly
也跟 JavaScript 差不多。。。

7. 类 Class

7.1 类的定义

7.1.1 一个简单的类
//一个简单的类
class Point {
  num x;
  num y;
}

void main() {
  var point = Point();
  point.x = 4; // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

还记得吗: dart中一切未赋值的变量值都是 null;

7.1.2 构造函数

通过创建一个与类同名的函数来声明构造函数;

class Cat {
	String name;
	int age;
	
	Cat(String name, int age) {
		this.name = name;
		this.age = age;
	}
}

void main() {
  Cat Tom = new Cat("Tom", 2);
	print(Tom.name);
}

dart 提供了语法糖,以更简单的方式来生成构造函数;

class Cat {
	String name;
	int age;
	
	Cat(this.name ,this.age) 
}

void main() {
  Cat Tom = new Cat("Tom", 2);
	print(Tom.name);
}

上面的代码只用一行代码就完成了构造函数,第一个参数,对应到name,第二个参数对应age;

实际上,dart 会为没有声明构造函数的类创建默认的构造函数,默认构造函数没有参数,并在超类中调用无参数构造函数。

命名构造函数(构造函数标识符)

class Cat {
	String name;
	int age;
	
	Cat(this.name ,this.age) 
	
	Cat.newBorn(){
		name = "Cherry";
		age = 0;
	}
}

void main() {
  Cat Cherry = new Cat.newBorn();
	print(Cherry.name);
}

上面的代码,构造函数有了一个名字(newBorn), 这样在初始化时更清楚使用了哪个类

7.2 继承

dart 的类通过 extends 关键字实现继承

如果没有特别指明,子类的构造函数中第一步会调用父类的默认构造函数
子类构造函数的执行顺序是:初始化字段表->父类构造函数->子类构造函数

调用父类构造函数

// 调用父类构造函数
class Cat {
	String name;
	int age;
	
	Cat(this.name ,this.age) 
	
	Cat.newBorn(){
		name = "Cherry";
		age = 0;
	}
}

class Garfield extends Cat{
	Garfield(String name, int age): super(name,age)
}

void main() {
  Cat Garfield = new Garfield("jiafei", 1);
	print(Garfield.name);
}

Garfield类继承了Cat类,并且在Garfield的构造函数里用SUPER关键字调用了Cat类的构造函数,

重定向构造函数:有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。重定向构造函数的主体是空的,构造函数调用出现在冒号(:)之后。

main(List<String> args) {
  Pug p = new Pug.small('Duffy');
  print(p.name);
}
 
class Dog {
  String name;
  int age;
 
  Dog(this.name, this.age);
 
  Dog.newBorn() {
    name = 'Doggy';
    age = 0;
  }
}
 
class Pug extends Dog {
  Pug(String name, int age): super(name, age);
 
  Pug.small(Stirng name): this(name, 1);
 
  Pug.large(Stirng name): this(name, 3);
}

上面定义了两个命名构造函数, small 调用了自身的构造函数,而自身又调用了dog的构造函数。

任何构造函数都不会被继承,这意味着父类的命名构造函数不会被子类继承。如果希望使用父类中定义的命名构造函数创建子类,则必须在子类中用冒号手动指明调用父类构造函数。

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

7.3 类中的函数

  • 定义函数:在类里定义函数和单独定义函数是一样的;
main(List<String> args) {
  Cat Tom = new Cat("Tom", 1);
  Tom.eat();
}
 
class Cat {
	String name;
	int age;
	
	Cat(this.name ,this.age) 
	
	Cat.newBorn(){
		name = "Cherry";
		age = 0;
	}
	eat(){
		print("I like eating fish!");
	}
}
  • 函数重写
main(List<String> args){
	Cat Cherry = new Garfield.small("jiafei");
	Cherry.eat();
}
// 父类 -- 猫
class Cat{
	String name;
	int age;
	
	Cat(this.name, this.age);
	
	Cat.newBorn(){
		name="dahua";
		age=0;
	}
	
	eat(){
		print("I like eating fish!");
	}
}
// 子类 -- 加菲猫
class Garfield extends Cat{

	Garfield(String name, int age): super(name,age);
	
	Garfield.small(String name, int age): this(name, 1);
	
	Garfield.large(String name, int age): this(name, 3);
	
	@override
	eat(){
		print("I like eating small can!");
	}
	
}

7.4 getter setter

默认的情况下类里定义的任何变量都是可以访问的,如 dog.name,变量的值也可以直接读写。但是有时候不希望直接读写而是通过getter setter。dart里 是通过get set 关键字实现的;

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

7.5 抽象方法和抽象类

抽象类使用 abstract 关键字定义,抽象方法只能存在于抽象类中;
除非定义了工厂构造函数,抽象类不能被实例化
抽象方法以(;)结尾,不写函数体,即不用实现它

abstract class Doer {
  // 实例的变量和方法...

	// 定义一个抽象方法,以(;)结尾,没有函数体.
  void doSomething(); 
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // 提供了函数体,所以这不是一个抽象方法
  }
}

7.6 隐式接口

每个类都隐式定义一个接口,这个接口包含该类的所有实例成员及其实现的任何接口。
如果要在不继承B实现的情况下,创建支持B类API的A类,则A类应该实现B类的接口。

一个类通过implements语句实现接口

class Person{
	final _name; // 这是一个接口,但只能在本类中访问
	
	Person(this.name); // 这不是接口,因为它是构造函数
	
	String greet(String who) => "Hello $who, I am $name";	// 这是接口
}

Class Imposter Implements Person{
 get _name => ""
 
 String greet(String who) => "Hi $who, do you know who I am ?";
}

String greetBob(Person person) => person.greet("Bob");

void main(){
	print(greetBob(Person("Kathy")));// Output: Hello, Bob. I am Kathy.
	print(greetBob(Imposter()));   // Output: Hi Bob. Do you know who I am?
}

7.7 noSuchMethod

检测是否使用了不存在的变量或方法,可以重写noSuchMethod方法,否则会抛出NoSuchMethodError这个异常;

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

7.8 静态方法

在字段或方法前增加static关键字就变成了静态,如:

 
class Dog {
  String name;
  int age;
 
  Dog(this.name, this.age);
 
  static bark() {
    print('Bow Wow');
  }
}

main() {
  Dog.bark(); 
}

静态变量在使用之前不会初始化。
静态方法(类方法)不对实例进行操作,因此无法访问它.
例如:

import 'dart:math';

class Point {
  num x, y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);//2.8284271247461903

}

7.9 枚举类型Enum

枚举类型(通常称为枚举或枚举)是一种特殊类,用于表示固定数量的常量值。

使用enum关键字声明枚举类型:

enum Color { red, green, blue }

枚举中的每个值都有一个索引getter,它返回枚举声明中值的从零开始的位置。例如,第一个值具有索引0,第二个值具有索引1。

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

可以通过.values获取枚举类中所有值的列表

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

7.10 mixins 为类添加功能

mixins是一种在多层级结构类中重用代码的方法。

定义mixin:使用 mixin 关键字创建一个没有构造函数的扩展类来实现一个mixin。

使用mixin:通过with关键字后跟多个类名来使用mixin;

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}


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

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

8. 泛型

Dart 支持泛型,在定义 ListMap 类型时,可以手动指定其元素类型,类型一旦确定便不可更改,也不可向其中添加其他类型的变量。
如:

// 构造函数的参数化类型
var names = List<String>();// 代表字符串列表
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

var gifts = Map<String, String>();
gifts['first'] = 'partridge';
gifts['second'] = 'turtledoves';
gifts['fifth'] = 'golden rings';
gifts[17] = 'argon'; //Error

// 使用字面量集合
var name = <String>['Seth', 'Kathy', 'Lars'];
var gifts = <String>,<String>{
    'first': 'partridge',
    'second': 'turtledoves',
    'fifth': 'golden rings'
}

同时,使用字面量定义变量时,Dart 也可以进行类型推断,如var name = ['Seth', 'Kathy', 'Lars'],可以推断该 List 为 List<String>()类型;

不限定参数类型
如有一个类,管理一个数据,希望这个数据是任何类型。

main(List<String> args) {
  DataHolder<String> dataHolder = new DataHolder('Some data');
  print(dataHolder.getData());
  dataHolder.setData('New Data');
  print(dataHolder.getData());
}
 
class DataHolder<T> {
  T data;
 
  DataHolder(this.data);
 
  getData() {
    return data;
  }
 
  setData(data) {
    this.data = data;
  }
}

9. 异步支持

Dart 支持异步操作。
使用异步首先要导入异步库import: "dart/sync";

Dart 有丰富的库支持,这里先不讲了,开发 Flutter 时,用到再去查就行。

异步操作(函数)有两种返回类型: FutureStreams

Future基于观察者模式。类似于JavaScript里的Rx和Promise,如果熟悉这两个,就很容易理解Future。
简单来说,Future定义了未来将要发生的事,例如:未来我们会得到一个值。
Future 是泛型,Future,需要指定未来返回值的类型。如:

import 'dart/async';
Future<String> getStory(){
    return new Future<String>.delay(new Duration(milliseconds:2000), (){
        return 'Here is the story';
    })
};

main(){
    getStory().then((value) => {
        print(value);
    }).catchError((error)=> {
        print(error);
    })
    print('Another print statement.');
}

使用async/await 关键字执行异步操作,使用try,catchfinally来处理使用await的代码中的错误;
async 关键字修饰函数体就声明了该函数是异步函数;

import 'dart/async';
Future<String> getStory(){
    return new Future<String>.delay(new Duration(milliseconds:2000), (){
        return 'Here is the story';
    })
};

main() async{
    try{
        String result = await getStory();
        print(result);
    }catch(e){
        print(e);
    }
    print('Another print statement.');
}

处理流 Stream
可用await for等待遍历stream的所有值,但要小心使用:

Future main() async {
  // ...
  await for (var request in requestServer) {
    handleRequest(request);
  }
  // ...
}

表达式的值必须具有Stream类型。执行过程如下:

  1. 等待流发出值。
  2. 执行for循环的主体,并将变量设置为发出的值。
  3. 重复1和2,直到流关闭。
    要停止侦听流,您可以使用break或return语句,该语句将跳出for循环,并从流中取消订阅。

保证awaitawait for要处在异步操作中

参考文档:

  1. dart 官方文档:https://www.dartlang.org/guides/language/language-tour
  2. dart 中文文档:https://www.kancloud.cn/marswill/dark2_document
  3. 学习flutter必会的dart知识系列:https://zhuanlan.zhihu.com/p/38847885
  4. Dart 学习备忘录: https://zhuanlan.zhihu.com/p/39961580

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions