从0开始写一个基于Flutter的开源中国客户端(2)——Dart语法基础

上一篇介绍了跨平台移动开发解决方案Flutter以及Flutter开发环境的搭建,因为Flutter开发使用的是Dart语言,故本篇记录的是Dart语言的语法基础,但愿跟小伙伴们一块儿温故知新。html

索引 文章
1 从0开始写一个基于Flutter的开源中国客户端(1)
Flutter简介及开发环境搭建 | 掘金技术征文
👉2 从0开始写一个基于Flutter的开源中国客户端(2)
Dart语法基础
3 从0开始写一个基于Flutter的开源中国客户端(3)
初识Flutter & 经常使用的Widgets
4 从0开始写一个基于Flutter的开源中国客户端(4)
Flutter布局基础
5 从0开始写一个基于Flutter的开源中国客户端(5)
App总体布局框架搭建
6 从0开始写一个基于Flutter的开源中国客户端(6)
各个静态页面的实现
7 从0开始写一个基于Flutter的开源中国客户端(7)
App网络请求和数据存储
8 从0开始写一个基于Flutter的开源中国客户端(8)
插件的使用

Dart语言简介

Dart是Google推出的一门编程语言,最初是但愿取代Javascript运行在浏览器端,后来慢慢发展成能够开发Android、iOS和Web端App的一门高质量的编程语言,目前Dart的版本是Dart2,官网是:www.dartlang.org/react

在Dart官方网站上,对于Dart的描述以下:git

Developers at Google and elsewhere use Dart to create high-quality, mission-critical apps for iOS, Android, and the web. With features aimed at client-side development, Dart is a great fit for both mobile and web apps.github

Google和其余地方的一些开发者使用Dart语言为Android、iOS和web构建高质量,关键任务的应用程序,针对客户端开发的特色,Dart很是适合移动和Web应用程序。web

Dart语言的特性

Productive(丰富多产的)

Dart’s syntax is clear and concise, its tooling simple yet powerful. Sound typing helps you to identify subtle errors early. Dart has battle-hardened core libraries and an ecosystem of thousands of packages.编程

Dart的语法清晰明了,工具简单但功能强大。Sound typing有助于早期识别细微的错误。Dart拥有久经沙场的核心库和数以千计的生态系统。segmentfault

Fast(快速的)

Dart provides optimizing ahead-of-time compilation to get predictably high performance and fast startup across mobile devices and the web.浏览器

Dart提供提早优化编译,以在移动设备和Web上得到可预测的高性能和快速启动。bash

Portable(可移植的)

Dart compiles to ARM and x86 code, so that Dart mobile apps can run natively on iOS, Android, and beyond. For web apps, Dart transpiles to JavaScript.网络

Dart可编译成ARM和X86代码,这样Dart移动应用程序能够在iOS、Android和其余地方运行。对于Web应用程序,DART可编译成JavaScript。

Approachable(亲切的)

Dart is familiar to many existing developers, thanks to its unsurprising object orientation and syntax. If you already know C++, C#, or Java, you can be productive with Dart in just a few days.

Dart对于许多现有的开发人员来讲是熟悉的,这得益于其使人惊讶的对象定位和语法。若是你已经知道C++,C语言,或者Java,你能够在短短几天内用Dart来开发。

Reactive(反应式的)

Dart is well-suited to reactive programming, with support for managing short-lived objects—such as UI widgets—through Dart’s fast object allocation and generational garbage collector. Dart supports asynchronous programming through language features and APIs that use Future and Stream objects.

Dart很是适合于反应式编程,支持经过Dart的快速对象分配和代垃圾收集器来管理诸如UI小部件之类的短命对象。Dart经过使用将来和流对象的语言特征和API支持异步编程。

Dart语法简介

关于Dart的语法,若是你熟悉Java,应该很快能掌握Dart,官网上对于Dart的语法也有详细介绍,不过是全英文的,若是对英文没有什么阅读障碍,能够直接移步官方文档

为了了解Dart的语法基础,这里咱们使用Android Studio做为开发工具(你也可使用dartpad来运行代码,它是一个基于浏览器的dart运行时环境),若是你按照上一篇文章中搭建好了Flutter开发环境,那么能够直接在Android Studio中新建Flutter项目,以下图所示:

新建立的Flutter项目,Dart代码主要在 lib/main.dart文件中,因为本篇主要是讲Dart的语法,故暂时不看main.dart文件,在lib目录下咱们建立一个新的 .dart文件 demo.dart,以下图所示:
在新建的 demo.dart文件中,输入以下代码:

// Define a function.
printInteger(int aNumber) {
  print('The number is $aNumber.'); // Print to console.
}

// This is where the app starts executing.
main() {
  var number = 42; // Declare and initialize a variable.
  printInteger(number); // Call a function.
}
复制代码

而后在代码编辑区域鼠标右键,选择Run demo.dart,便可运行一个最简单的dart程序,以下图所示:

运行后控制台输出以下图:
关于上面的代码,有以下几点须要说明:

  1. Dart中单行注释使用//,Dart同时支持多行注释和文档注释,能够点击这里查看更多

  2. int是Dart中的一种数据类型,同时还有其余的一些内置数据类型如String List bool

  3. 控制台输出使用print语句

  4. 字符串使用单引号或双引号都可,如'hello', "hello"

  5. 字符串插入可使用相似$name${name}的语法,好比下面的代码:

    var name = 'zhangsan';
      print("hello, I am $name");
      int a = 10, b = 20;
      print("$a + $b = ${a + b}");
    复制代码

    若是使用${name}这种方式,大括号中能够是表达式

  6. 你可能已经注意到了,Dart的变量类型是可选的,你能够为某个变量指定类型,或者使用var定义变量,Dart会自动推断变量的类型

重要概念

当你在学习Dart语言时,下面的这些事实和概念请牢记于心:

  • 在Dart中,一切都是对象,一切对象都是class的实例,哪怕是数字类型、方法甚至null都是对象,全部的对象都是继承自Object
  • 虽然Dart是强类型语言,但变量类型是可选的由于Dart能够自动推断变量类型
  • Dart支持范型,List<int>表示一个整型的数据列表,List<dynamic>则是一个对象的列表,其中能够装任意对象
  • Dart支持顶层方法(如main方法),也支持类方法或对象方法,同时你也能够在方法内部建立方法
  • Dart支持顶层变量,也支持类变量或对象变量
  • 跟Java不一样的是,Dart没有public protected private等关键字,若是某个变量如下划线(_)开头,表明这个变量在库中是私有的,具体能够看这里
  • Dart中变量能够以字母或下划线开头,后面跟着任意组合的字符或数字
  • 有时重要的是某事是一个表达仍是一个陈述,因此这两个词的精确性是有帮助的
  • Dart工具能够报告两种问题:警告和错误。警告只是指示代码可能没法工做,但它们不会阻止程序执行。错误能够是编译时,也能够是运行时发生。编译时错误根本不容许代码执行;运行时错误致使代码执行时引起异常。

变量

变量定义

如下代码是Dart中定义变量的方法:

main() {
  var a = 1;
  int b = 10;
  String s = "hello";
  dynamic c = 0.5;
}
复制代码

你能够明确指定某个变量的类型,如int bool String,也能够用vardynamic来声明一个变量,Dart会自动推断其数据类型。

变量的默认值

注意:没有赋初值的变量都会有默认值null

final和const

若是你毫不想改变一个变量,使用finalconst,不要使用var或其余类型,一个被final修饰的变量只能被赋值一次,一个被const修饰的变量是一个编译时常量(const常量毫无疑问也是final常量)。能够这么理解:final修饰的变量是不可改变的,而const修饰的表示一个常量。

注意:实例变量能够是final的但不能是const的

下面用代码说明:

final String name = 'zhangsan';
  name = 'lisi'; // 编译不经过,被final修饰的是常量,不可从新赋值
  const a = 0;
  a = 1; // 错误
复制代码

finalconst的区别:

  • 区别一: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
复制代码

因为Dart中的类没有访问控制权限,因此你能够直接用obj.var的方式访问一个对象的成员变量。

类除了有跟类名相同的构造方法外,还能够添加命名的构造方法,以下代码所示:

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)

JavaC++语言都有泛型,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操做,文件选择等都须要用到异步的知识。 asyncawait每每是成对出现的,若是一个方法中有耗时的操做,你须要将这个方法设置成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);
  });
}
复制代码

关于Dart异步操做,能够查看这篇文章了解更多。

结束语

本篇博客较长,主要是对官方文档的一个翻译(大部分),若是你对英文阅读没有太大障碍,建议直接查看官方的英文文档,但愿各位都能愉快的学习DartFlutter

参考

个人开源项目

  1. 基于Google Flutter的开源中国客户端,但愿你们给个Star支持一下,源码:
  1. 基于Flutter的俄罗斯方块小游戏,但愿你们给个Star支持一下,源码:
上一篇 下一篇
从0开始写一个基于Flutter的开源中国客户端(1)
——Flutter简介及开发环境搭建
从0开始写一个基于Flutter的开源中国客户端(3)——初识Flutter & 经常使用的Widgets
相关文章
相关标签/搜索