flutter簡易教程

吳先鋒Flutter發表於2019-02-19

推薦一個小工具:ui.flutterdart.cn/ 可以像as,xcode一樣拖拽生成widget,只支援pc,chrome系列safri瀏覽器。

注意:

跟Java等很多語言不同的是,Dart沒有public protected private等關鍵字,如果某個變數以下劃線 _ 開頭,代表這個變數在庫中是私有的。Dart中變數可以以字母或下劃線開頭,後面跟著任意組合的字元或數字

變數

變數定義

以下程式碼是Dart中定義變數的方法:

main() {
  var a = 1;
  int b = 10;
  String s = "hello";
  dynamic c = 0.5;
}
複製程式碼

你可以明確指定某個變數的型別,如int bool String,也可以用var或 dynamic來宣告一個變數,Dart會自動推斷其資料型別。

變數的預設值

注意:沒有賦初值的變數都會有預設值null

final和const

如果你絕不想改變一個變數,使用final或const,不要使用var或其他型別,一個被final修飾的變數只能被賦值一次,一個被const修飾的變數是一個編譯時常量(const常量毫無疑問也是final常量)。可以這麼理解:final修飾的變數是不可改變的,而const修飾的表示一個常量。

注意:例項變數可以是final的但不能是const的

下面用程式碼說明:

var count = 10; 
final Num = count;  // final 只能賦值一次
const Num1 = 10; // const賦值必須是編譯時常量
複製程式碼

final和const的區別:

區別一:final 要求變數只能初始化一次,並不要求賦的值一定是編譯時常量,可以是常量也可以不是。而 const 要求在宣告時初始化,並且賦值必需為編譯時常量。

區別二:final 是惰性初始化,即在執行時第一次使用前才初始化。而 const 是在編譯時就確定值了。

內建資料型別

Dart有如下幾種內建的資料型別:

  • numbers
  • strings
  • booleans
  • lists(或者是arrays)
  • maps
  • runes(UTF-32字符集的字元)
  • symbols 下面用一段程式碼來演示以上各類資料型別:
main() {
  // numbers
  var a = 0;
  int b = 1;
  double c = 0.1;

  // strings
  var s1 = 'hello';
  String s2 = "world";

  // booleans
  var real = true;
  bool isReal = false;

  // lists
  var arr = [1, 2, 3, 4, 5];
  List<String> arr2 = ['hello', 'world', "123", "456"];
  List<dynamic> arr3 = [1, true, 'haha', 1.0];

  // maps
  var map = new Map();
  map['name'] = 'zhangsan';
  map['age'] = 10;
  Map m = new Map();
  m['a'] = 'a';

  //runes,Dart 中 使用runes 來獲取UTF-32字符集的字元。String的 codeUnitAt and codeUnit屬性可以獲取UTF-16字符集的字元
  var clapping = '\u{1f44f}';
  print(clapping); // 列印的是拍手emoji的表情

  // symbols
  print(#s == new Symbol("s")); // true
}
複製程式碼

函式

函式的返回值

Dart是一個物件導向的程式語言,所以即使是函式也是一個物件,也有一種型別Function,這就意味著函式可以賦值給某個變數或者作為引數傳給另外的函式。雖然Dart推薦你給函式加上返回值,但是不加返回值的函式同樣可以正常工作,另外你還可以用=>代替return語句,比如下面的程式碼:

// 宣告返回值
int add(int a, int b) {
  return a + b;
}

// 不宣告返回值
add2(int a, int b) {
  return a + b;
}

// =>是return語句的簡寫
add3(a, b) => a + b; 

main() {
  print(add(1, 2)); // 3
  print(add2(2, 3)); // 5
  print(add3(1, 2)); // 3
}
複製程式碼

命名引數、位置引數、引數預設值

命名引數

sayHello({String name}) {
  print("hello, my name is $name");
}

sayHello2({name: String}) {
  print("hello, my name is $name");
}

main() {
  // 列印 hello, my name is zhangsan
  sayHello(name: 'zhangsan');

  // 列印 hello, my name is wangwu
  sayHello2(name: 'wangwu');
}
複製程式碼

可以看到,定義命名引數時,你可以以 {type paramName} 或者 {paramName: type} 兩種方式宣告引數,而呼叫命名引數時,需要以 funcName(paramName: paramValue) 的形式呼叫。

命名引數的引數並不是必須的,所以上面的程式碼中,如果呼叫sayHello()不帶任何引數,也是可以的,只不過最後列印出來的結果是:hello, my name is null,在Flutter開發中,你可以使用@required註解來標識一個命名引數,這代表該引數是必須的,你不傳則會報錯,比如下面的程式碼:

const Scrollbar({Key key, @required Widget child})
複製程式碼

位置引數

使用中括號[]括起來的引數是函式的位置引數,代表該引數可傳可不傳,位置引數只能放在函式的引數列表的最後面,如下程式碼所示:

sayHello(String name, int age, [String hobby]) { // 位置引數可以有多個,比如[String a, int b]
  StringBuffer sb = new StringBuffer();
  sb.write("hello, this is $name and I am $age years old");
  if (hobby != null) {
    sb.write(", my hobby is $hobby");
  }
  print(sb.toString());
}

main() {
  // hello, this is zhangsan and I am 20 years old
  sayHello("zhangsan", 20);
  // hello, this is zhangsan and I am 20 years old, my hobby is play football
  sayHello("zhangsan", 20, "play football");
}
複製程式碼

引數預設值

你可以為命名引數或者位置引數設定預設值,如下程式碼所示:

// 命名引數的預設值
int add({int a, int b = 3}) { // 不能寫成:int add({a: int, b: int = 3})
  return a + b;
}

// 位置引數的預設值
int sum(int a, int b, [int c = 3]) {
  return a + b + c;
}
複製程式碼

main()函式

不論在Dart還是Flutter中,必須都需要一個頂層的main()函式,它是整個應用的入口函式,main()函式的返回值是void,還有一個可選的引數,引數型別是List<String>。

函式作為一類物件

你可以將一個函式作為引數傳給另一個函式,比如下面的程式碼:

printNum(int a) {
  print("$a");
}

main() {
  //  依次列印:
  //  1
  //  2
  //  3
  var arr = [1, 2, 3];
  arr.forEach(printNum);
}
複製程式碼

你也可以將一個函式賦值給某個變數,比如下面的程式碼:

printNum(int a) {
  print("$a");
}

main() {
  var f1 = printNum;
  Function f2 = printNum;
  var f3 = (int a) => print("a = $a");
  f1(1);
  f2(2);
  f3(6);
}
複製程式碼

匿名函式

大多數函式都是有名稱的,比如main() printName()等,但是你也可以寫匿名函式,如果你對Java比較熟悉,那下面的Dart程式碼你肯定也不會陌生:

test(Function callback) {
  callback("hello");
}

main() {
  test((param) {
    // 列印hello
    print(param);
  });
}
複製程式碼

匿名函式類似於Java中的介面,往往在某個函式的引數為函式時使用到。

函式返回值

所有的函式都有返回值,如果沒有指定return語句,那麼該函式的返回值為null。

運算子

Dart中的運算子與Java中的類似,比如++a a == b b ? a : b,但是也有一些與Java不太一樣的運算子,下面用程式碼說明:

main() {
  // 與Java相同的運算子操作

  int a = 1;
  ++a;
  a++;
  var b = 1;
  print(a == b);  // false
  print(a * b); // 3
  bool real = false;
  real ? print('real') : print('not real'); // not real
  print(real && a == b); // false
  print(real || a == 3); // true
  print(a != 2); // true
  print(a <= b); // false
  var c = 9;
  c += 10;
  print("c = $c"); // c = 19
  print(1<<2); // 4

  // 與Java不太一樣的運算子操作

  // is運算子用於判斷一個變數是不是某個型別的資料
  // is!則是判斷變數不是某個型別的資料
  var s = "hello";
  print(s is String); // true
  var num = 6;
  print(num is! String); // true

  // ~/才是取整運算子,如果使用/則是除法運算,不取整
  int k = 1;
  int j = 2;
  print(k / j); // 0.5
  print(k ~/ j); // 0

  // as運算子類似於Java中的cast操作,將一個物件強制型別轉換
  (emp as Person).teach();

  // ??=運算子 如果 ??= 運算子前面的變數為null,則賦值,否則不賦值
  var param1 = "hello", param2 = null;
  param1 ??= "world";
  param2 ??= "world";
  print("param1 = $param1"); // param1 = hello
  print("param2 = $param2"); // param2 = world
  
  // ?.運算子
  var str1 = "hello world";
  var str2 = null;
  print(str1?.length); // 11
  print(str2?.length); // null 
  print(str2.length); // 報錯
}
複製程式碼

…運算子(級聯操作)

如果你對Java中的建造者模式比較熟悉的話,Dart中的…運算子也很好理解,先看下面的程式碼:

class Person {
  eat() {
    print("I am eating...");
  }

  sleep() {
    print("I am sleeping...");
  }

  study() {
    print("I am studying...");
  }
}

main() {
  // 依次列印
  //  I am eating...
  //  I am sleeping...
  //  I am studying...
  new Person()..eat()
      ..sleep()
      ..study();
}
複製程式碼

可以看到,使用…呼叫某個物件的方法(或者成員變數)時,返回值是這個物件本身,所以你可以接著使用…呼叫這個物件的其他方法,這不就類似於Java中的建造者模式,每次build某個屬性時,都返回一個this物件嗎。

控制流程

if / else switch for /while try / catch語句跟Java中都類似,try / catch語句可能稍有不同,下面用一段程式碼說明:

main() {
  // if else語句
  int score = 80;
  if (score < 60) {
    print("so bad!");
  } else if (score >= 60 && score < 80) {
    print("just so so!");
  } else if (score >= 80) {
    print("good job!");
  }

  // switch語句
  String a = "hello";
  // case語句中的資料型別必須是跟switch中的型別一致
  switch (a) {
    case "hello":
      print("haha");
      break;
    case "world":
      print("heihei");
      break;
    default:
      print("WTF");
  }

  // for語句
  List<String> list = ["a", "b", "c"];
  for (int i = 0; i < list.length; i++) {
    print(list[i]);
  }
  for (var i in list) {
    print(i);
  }
  // 這裡的箭頭函式引數必須用圓括號擴起來
  list.forEach((item) => print(item));

  // while語句
  int start = 1;
  int sum = 0;
  while (start <= 100) {
    sum += start;
    start++;
  }
  print(sum);

  // try catch語句
  try {
    print(1 ~/ 0);
  } catch (e) {
    // IntegerDivisionByZeroException
    print(e);
  }
  try {
    1 ~/ 0;
  } on IntegerDivisionByZeroException { // 捕獲指定型別的異常
    print("error"); // 列印出error
  } finally {
    print("over"); // 列印出over
  }
}
複製程式碼

類(Class)

類的定義與構造方法

Dart中的類沒有訪問控制,所以你不需要用private, protected, public等修飾成員變數或成員函式,一個簡單的類如下程式碼所示:

class Person {
  String name;
  int age;
  String gender;
  Person(this.name, this.age, this.gender);
  sayHello() {
    print("hello, this is $name, I am $age years old, I am a $gender");
  }
}
複製程式碼

上面的Person類中有3個成員變數,一個構造方法和一個成員方法,看起來比較奇怪的是Person的構造方法,裡面傳入的3個引數都是this.xxx,而且沒有大括號{}包裹的方法體,這種語法是Dart比較獨特而簡潔的構造方法宣告方式,它等同於下面的程式碼:

Person(String name, int age, String gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}
複製程式碼

要呼叫Person類的成員變數或成員方法,可以用下面的程式碼:

var p = new Person("zhangsan", 20, "male");
  p.sayHello(); // hello, this is zhangsan, I am 20 years old, I am a male
  p.age = 50;
  p.gender = "female";
  p.sayHello(); // hello, this is zhangsan, I am 50 years old, I am a female
複製程式碼

類除了有跟類名相同的構造方法外,還可以新增命名的構造方法,如下程式碼所示:

class Point {
  num x, y;
  Point(this.x, this.y);
  // 類的命名構造方法
  Point.origin() {
    x = 0;
    y = 0;
  }
}

main() {
  // 呼叫Point類的命名構造方法origin()
  var p = new Point.origin();
  var p2 = new Point(1, 2);
}
複製程式碼

Dart中使用extends關鍵字做類的繼承,如果一個類只有命名的構造方法,在繼承時需要注意,如下程式碼:

class Human {
  String name;
  Human.fromJson(Map data) {
    print("Human's fromJson constructor");
  }
}

class Man extends Human {
  Man.fromJson(Map data) : super.fromJson(data) {
    print("Man's fromJson constructor");
  }
}
複製程式碼

由於Human類沒有預設構造方法,只有一個命名構造方法fromJson,所以在Man類繼承Human類時,需要呼叫父類的fromJson方法做初始化,而且必須使用Man.fromJson(Map data) : super.fromJson(data)這種寫法,而不是像Java那樣將super寫到花括號中。

有時候你僅僅只是在某個類的構造方法中,呼叫這個類的另一個構造方法,你可以這麼寫:

class Point {
  num x, y;
  Point(this.x, this.y);
  // 命名構造方法呼叫了預設的構造方法
  Point.alongXAxis(num x) : this(x, 0);
}
複製程式碼

類的成員方法

一個類的成員方法是一個函式,為這個類提供某些行為。上面的程式碼中已經有了一些類的成員方法的定義,這些定義方式跟Java很類似,你可以為某個類的成員變數提供getter/setter方法,如下程式碼:

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

  // 構造方法傳入left, top, width, height幾個引數
  Rectangle(this.left, this.top, this.width, this.height);

  // right, bottom兩個成員變數提供getter/setter方法
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}
複製程式碼

抽象類和抽象方法

使用abstract修飾一個類,則這個類是抽象類,抽象類中可以有抽象方法和非抽象方法,抽象方法沒有方法體,需要子類去實現,如下程式碼:

abstract class Doer {
  // 抽象方法,沒有方法體,需要子類去實現
  void doSomething();
  // 普通的方法
  void greet() {
    print("hello world!");
  }
}

class EffectiveDoer extends Doer {
  // 實現了父類的抽象方法
  void doSomething() {
    print("I'm doing something...");
  }
}
複製程式碼

運算子過載

Dart中有類似於C++中的運算子過載語法,比如下面的程式碼定義了一個向量類,過載了向量的+ -運算:

class Vector {
  num x, y;
  Vector(this.x, this.y);
  Vector operator +(Vector v) => new Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => new Vector(x - v.x, y - v.y);
  printVec() {
    print("x: $x, y: $y");
  }
}

main() {
  Vector v1 = new Vector(1, 2);
  Vector v2 = new Vector(3, 4);
  (v1 - v2).printVec(); // -2, -2
  (v1 + v2).printVec(); // 4, 6
}
複製程式碼

列舉類

使用enum關鍵字定義一個列舉類,這個語法跟Java類似,如下程式碼:

enum Color { red, green, blue }
複製程式碼

mixins

mixins是一個重複使用類中程式碼的方式,比如下面的程式碼:

class A {
  a() {
    print("A's a()");
  }
}

class B {
  b() {
    print("B's b()");
  }
}

// 使用with關鍵字,表示類C是由類A和類B混合而構成
class C = A with B;

main() {
  C c = new C();
  c.a(); // A's a()
  c.b(); // B's b()
}
複製程式碼

靜態成員變數和靜態成員方法

// 類的靜態成員變數和靜態成員方法
class Cons {
  static const name = "zhangsan";
  static sayHello() {
    print("hello, this is ${Cons.name}");
  }
}

main() {
  Cons.sayHello(); // hello, this is zhangsan
  print(Cons.name); // zhangsan
}
複製程式碼

泛型(Generics)

Java和C++語言都有泛型,Dart語言也不例外,使用泛型有很多好處,比如:

正確指定泛型型別會產生更好的生成程式碼。 泛型可以減小程式碼的複雜度

Dart內建的資料型別List就是一個泛型資料型別,你可以往List中塞任何你想的資料型別比如整型、字串、布林值等 關於Dart更多的泛型知識點,可以檢視這裡。

Dart庫(Libraries)

Dart目前已經有很多的庫提供給開發者,許多功能不需要開發者自己去實現,只需要匯入對應的包即可,使用import語句來匯入某個包,比如下面的程式碼:

import 'dart:html';
複製程式碼

如果你想匯入自己寫的某個程式碼檔案,使用相對路徑即可,例如當前有一個demo.dart檔案,跟該檔案同級目錄下有個util.dart檔案,檔案程式碼如下:

// util.dart檔案內容

int add(int a, int b) {
  return a + b;
}
複製程式碼

在demo.dart檔案中如果要引用util.dart檔案,使用下面的方式匯入:

// demo.dart

import './util.dart';

main() {
  print(add(1, 2));
}
複製程式碼

你可以使用as關鍵字為匯入的某個包設定一個字首,或者說別名,比如下面的程式碼:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
複製程式碼

你也可以在匯入包時使用show hide關鍵字來匯入某個包中的部分功能,比如下面的程式碼:

// 只匯入foo
import 'package:lib1/lib1.dart' show foo;

// 匯入除了foo的所有其他部分
import 'package:lib2/lib2.dart' hide foo;
複製程式碼

匯入包時使用deferred as可以讓這個包懶載入,懶載入的包只會在該包被使用時得到載入,而不是一開始就載入,比如下面的程式碼:

import 'package:greetings/hello.dart' deferred as hello;
複製程式碼

非同步

Dart提供了類似ES7中的async await等非同步操作,這種非同步操作在Flutter開發中會經常遇到,比如網路或其他IO操作,檔案選擇等都需要用到非同步的知識。 async和await往往是成對出現的,如果一個方法中有耗時的操作,你需要將這個方法設定成async,並給其中的耗時操作加上await關鍵字,如果這個方法有返回值,你需要將返回值塞到Future中並返回,如下程式碼所示:

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}
複製程式碼

下面的程式碼使用Dart從網路獲取資料並列印出來:

import 'dart:async';
import 'package:http/http.dart' as http;

Future<String> getNetData() async{
  http.Response res = await http.get("http://www.baidu.com");
  return res.body;
}

main() {
  getNetData().then((str) {
    print(str);
  });
}
複製程式碼

相關文章