跟🤡杰哥一起学Flutter (四、Dart基础语法速通🏃)

🤡 花一点时间快速过下Dart的基础语法,以便提高Flutter开发时 书写/阅读代码的流畅性 ,不至于三步一查。没啥好说的,直接冲官方文档就完事了:Introduction to Dart(英文) OR 《Dart简介》(中文)。推荐使用 Dart在线编辑器DartPad 来写和跑测试Demo。🐶对了,试试把官方文档URL中的dev ,改成cn,有惊喜哦~

1. 常识

  • main() 函数:Dart程序的运行入口,一种特殊且必须的 顶层函数
  • print() 函数:将文本输出到控制台,支持 字符串插值 ,使用 $变量名 将变量嵌入到字符串中;
  • 注释 :Dart 支持三种类型的注释:单行注释 (//)、块级注释 (/* 注释内容 */)、文档注释 (///),使用建议 :块级注释只用来临时注释一段代码,其它所有的注释都应该用单行注释文档注释 用于 dart doc 生成代码的API文档。更多注释建议可查阅《Effective Dart: Documentation》

2. 变量

  • Dart是 强类型语言 ,可以直接用 var 关键字来声明变量,如:var a = 1,Dart会 自动推导出数据类型 ,而 不用显式指定类型,如:int a = 1。
  • 但是赋值类型一旦确定了就不能更改了,如:把字符串复制给int类型的变量,编译器会直接报错。
  • 如果需要 在运行时更改类型 ,可以使用 dynamic 关键字来声明变量。

Flutter的基本数据类型如下

  • Numbers (数值)→ intdouble
  • Booleans (布尔)→ bool,只有两个值 true 或 false;
  • Strings (字符串)→ String、Runes(UTF-32编码的字符串,可以通过文字转换成表情或代表特定文字),可以用单引号(')或双引号(")来包裹字符串。
  • Lists(列表)→ Dart中的数组等于列表,var list = [] 和 List list = new List() 可以简单看做一样;】
  • Sets(集)→ 无序,元素唯一的集合。
  • Maps(字典)→ 键值对的形式表示一组值;

简单使用代码示例如下

dart 复制代码
void main() {
  /// 显式指定类型、var类型推导
  int a = 1;
  double b = 0.3;
  var c = a / b;
  print(
    "a / b = $c\t向上取整 → ${c.floorToDouble()}\t向下取整 → ${c.ceilToDouble()}\t保留两位小数 → ${double.parse(c.toStringAsFixed(2))}");
  // 输出:a / b = 3.3333333333333335	向上取整 → 3.0	向下取整 → 4.0	保留两位小数 → 3.33

  /// 声明动态类型
  dynamic d = 1;
  d = "test";
  print("d = $d"); // 输出:test

  /// 定义列表
  var numbers = [2]; // 初始化列表
  numbers.add(3); // 添加元素到列表末尾
  print(numbers); // 输出:[2, 3]
  numbers.insert(0, 1); // 添加元素到指定下标
  print(numbers); // 输出:[1, 2, 3]
  // for循环遍历
  for (var i = 0; i < numbers.length; i++) {
    print(numbers[i]);
  }
  // 增强for循环遍历
  for (var number in numbers) {
    print(number);
  }
  // forEach方法遍历
  numbers.forEach((number) {
    print(number);
  });
  // 迭代器遍历
  var iterator = numbers.iterator;
  while (iterator.moveNext()) {
    print(iterator.current);
  }
  print(numbers.length); // 获取列表长度,输出:3
  print(numbers[2]); // 获取列表元素,输出:3
  // 遍历列表
  numbers.remove(1); // 删除列表元素
  print(numbers); // 输出:[2, 3]
  numbers.removeAt(1); // 删除指定下标元素
  print(numbers); // 输出:[2]
  var temps = [5, 3, 7, 1];
  var mergeList = numbers + temps; // 合并两个列表,也可以调用其中一个的addAll()
  mergeList.sort(); // 排序
  print(mergeList); // 输出:[1, 2, 3, 5, 7]
  print(mergeList.reversed.toList()); // 列表反转转List,输出:[7, 5, 3, 2, 1]

  /// 字典
  var headers = {}; // 初始化字典
  headers['Content-Type'] = 'application/json; charset=utf-8'; // 添加/更新元素
  print(headers['Content-Type']); // 访问元素,输出:application/json; charset=utf-8
  print(headers.containsKey('User-Agent')); // 判断是否包含特定键,输出:false
  print(headers.length); // 字典中的键值对数量,输出:1
  // for循环键值
  for (var key in headers.keys) {
    print('$key: ${headers[key]}');
  }
  // 使用forEach方式遍历键值
  headers.forEach((key, value) {
    print('$key: $value');
  });
  headers.remove('Content-Type'); // 删除键值对
}

补充

  • Dart支持 扩展运算符(...) ,可以便捷地将一个可迭代对象(列表、集合或字符串)元素快速展开到另一个可迭代对象中。如:var list1 = [1,2,3]; var list2 = [4,5,6]; var combinedList = [...list1, ...list2];
  • Dart支持在集合中使用 iffor 运算符来初始化集合,如:var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet']; 和 var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];
  • 如果想定义 常量 ,可以使用 constfinal 关键字来修饰,两者有些区别:
dart 复制代码
final int x = 5;  // 可以在声明时就赋值
final String name;  // 可以延迟赋值,但只能赋值一次
name = 'test';

const int y = 10; // 编译时常量,声明时就要赋值

3. 流程控制语句

跟Java那套基本一致,没啥好说的:if-else if-else、for、while、do-while、switch、break、continue~

4. 函数/方法

4.1. 函数定义

在Dart中,函数被视为 一等公民 ,可以作为对象传递给其它参数,也可以作为返回值返回。函数定义 示例如下:

dart 复制代码
// 返回值 (不写任何返回类型也可以,但不建议)、函数名、参数列表、函数体
void printMessage(String message) {
  print(message);
}

4.2. 可选参数

Dart中的参数可以是 必须 的(默认要传),也可以是 可选 的,可选参数又分为两种:

  • 位置参数:使用方括号[]表示,按照位置传递给函数;
  • 命名参数:使用大括号表示,通过名称传递给函数;

使用代码示例如下:

dart 复制代码
// []位置参数
void greet(String name, [String? gender]) {
  // 判断gender参数是否有传递
  if (gender != null) {
    print("Hello $gender $name");
  } else {
    print("Hello $name");
  }
}

// {}命名参数,required 用于修饰可选参数,表示该参数是必须的
void introduce({required String name, int? age}) {
  print("My name is $name, and I'm $age years old");
}

上述用到的 ? 问号修饰符,简称 nullable 修饰符,用于修饰可选参数,表示此参数是可选的。在处理 空值场景 ,经常和 ????= 这两个操作符一起出现:

dart 复制代码
// ? 操作符:可选调用,如果不为null才执行,避免空指针
int? a;
int b = a?.length;

// ?? 操作符:空值合并,表达式值为空时,提供一个默认值
int b = a ?? 0;

// ??= 操作符:赋值,在变量为null时,给它赋予一个默认值
a ??= 0

另外,可选参数 还支持设置 默认值

4.3. 箭头/匿名函数

一种简洁的函数定义方式,用于定义单行函数,用 箭头符号(=>) 分隔参数和函数体,并省略了函数体的大括号:

dart 复制代码
int add(int a, int b) => a + b;

4.4. 函数作为参数

代码示例如下:

dart 复制代码
void performOperation(int a, int b, Function operation) {
  var result = operation(a, b);
  print("The result is $result");
}

4.5. 函数作为返回值

代码示例如下:

dart 复制代码
Function calculator(String operator) {
  if (operator == '+') {
    return (int a, int b) => a + b;
  } else if (operator == '-') {
    return (int a, int b) => a - b;
  } else {
    return null;
  }
}

5. 类

Dart里所有的对象都继承自 Object 类,支持 单继承 。Dart没有public、private、protected这类修饰符,即默认是 public 的,通过在属性名、方法名前加 _下划线 来定义私有。定义了私有属性和方法的类需要 抽离放到一个单独的文件/模块中,才能真正起到私有的效果!!!如:写在同一个文件中,可以访问私有属性:

dart 复制代码
class Test {
  String _name = "Test";
}

void main() {
  var test = Test();
  print(test._name);	// 输出:Test
}

Tips :Dart有 @protected 注解,但仅用于标记和文档化,不会真正限制访问。

5.1. 类定义及使用

直接使用 class 关键字定义,简单代码示例如下:

dart 复制代码
class Account {
  // 定义两个成员变量
  late String name;
  late int age;

  void showInfo() {
    print("$name → $age")
  }
}

// 可以使用new关键字创建对象,也可以省略
var account = Account("杰哥","35");
account.showInfo();

5.2. 构造函数

有四种形式的构造函数:类名构造函数命名构造函数常量构造函数工厂构造函数

定义一个类,没定义构造函数,默认会有一个 无参构造函数,如果有父类,还会调用父类的无参构造函数。

先说说 类名构造函数, 就是和类同名的函数,可以参数化,但不能有返回值,如:

dart 复制代码
Account(this.name, this.age);

上述代码用 语法糖 简化了成员变量的赋值。然后是 命名构造函数 ,就是用 类名.修饰符 定义的函数:

dart 复制代码
Account.simple(String name): this(name, 18)

可以使用命名构造函数为类提供多个构造函数,不过有一点要记住:命名构造函数不可继承

接着是 常量构造函数,如果类创建的对象永远不会改变,可以在编译期就创建这个常量实例,并定义一个常量构造函数,并且确保所有成员变量都是final的。

dart 复制代码
class Circle {
  final double radius; // 常量成员变量
  const Circle(this.radius); // 常量构造函数
  double get area => 3.14 * radius * radius; // 计算圆的面积
}

最后是 工厂构造函数 ,就是定义私有构造函数,然后使用 factory 关键字进行定义,根据不同情况创建不同的对象返回,简易代码示例如下:

dart 复制代码
lass MyClass {
  final String name;
  
  // 私有构造函数
  MyClass._(this.name);
  
  // 工厂构造函数 (没有权限访问this)
  factory MyClass(String name) {
    // 在构造对象之前可以进行一些逻辑处理
    if (name == 'invalid') {
      return null; // 返回null表示构造失败
    }
    
    // 调用私有构造函数创建对象
    return MyClass._(name);
  }
}

void main() {
  var obj1 = MyClass('John');
  print(obj1.name); // 输出: John
  
  var obj2 = MyClass('invalid');
  print(obj2); // 输出: null
}

5.3. get/set修饰符

就是可以在 获取值等号赋值时 进行一些方法调用,简单代码示例如下:

dart 复制代码
class Example {
  DateTime? launchDate;
  int _value = 0;

  // 定义一个只读的非final属性launchYear
  int? get launchYear => launchDate?.year;
  
  set value(int newValue) {
    _value = newValue;
  }
}

5.4. 对象操作符

三个主要的操作符:as类型强转is类型判断..级联操作(链式) ,简单代码示例:

dart 复制代码
var account = Account("杰哥", 35);
// 强转
(account as Account).printInfo();

// 类型判断
if(account is Account) {
  print("当前对象为Account类型")
}

// 链式调用
account
  ..name = "小猪"
  ..age = 18;

5.5. 继承

子类使用 extends 关键字来继承父类,子类会继承父类里的属性和方法 (构造方法除外),可以使用 super 关键字调用父类属性/方法,或者给父类构造方法传参,贴下官方例子:

dart 复制代码
/// 使用 extends 关键字声明继承的父类
class Orbiter extends Spacecraft {
  double altitude;
	/// 定义一个构造函数,用于初始化时从父类继承的属性
  Orbiter(super.name, DateTime super.launchDate, this.altitude);
}

5.6. 接口和抽象类

Dart里的接口跟Java有些不同,我们先思考下,引入接口是为了解决什么问题?

抽象出一类事物的功能,以便更灵活地进行扩展?

这是接口的作用,更深层次的目的是:解决多继承的二义性问题 。这里的二义性问题,指的是多继承中 方法和属性名称的冲突,使得编译器无法确定该使用哪个父类的方法和属性。Java中是这样限制的:

接口中只能定义抽象成员和方法,不进行实现,强制子类必须实现。代码示例如下:

dart 复制代码
interface A {
	public void test();
}

interface B {
	public void test();
}

class C implements A,B {
	@Override
	public void test() {
		System.out.println("Test");
	}
}

public class HelloWorld {
    public static void main(String []args) {
       C c = new C();
		c.test();
    }
}

回到Dart这边,没有 interface 关键字,所有类都被隐式定义成一个接口任何类都可以作为接口被实现 。那Dart是如何解决多继承/实现的二义性问题的?子类必须将父类中所有属性和方法全部重写。代码示例如下:

java 复制代码
class A {
  String name;
  A(this.name);
  void printTest() => print("A");
}

class B {
  String name;
  B(this.name);
  void printTest() => print("B");
}

class C implements A, B {
  @override
  String name = "C";

  @override
  void printTest() => print("C");
}

void main() {
  C c = C();
  c.printTest();
}

如果想实现Java中那种 通过接口来定义标准 的方式,可以使用 抽象类 (使用abstract修饰) 来实现。Dart中的抽象类不能被实例化,可以包含 抽象方法 (没有方法体) 和 非抽象方法 (有方法体),代码示例如下:

dart 复制代码
abstract class PlayerController {
  String playerName;
  PlayerController(this.playerName);
  void play();
  void pause();
  void destory() {
    print("播放器销毁了");
  }
}

abstract class VolumeController {
  int currentVolume = 0;
  void increase();
  void reduction();
}

class MusicPlayer implements PlayerController, VolumeController {
  @override
  int currentVolume = 30;

  @override
  String playerName = "音乐播放器";

  @override
  void play() {
    print("音乐播放器开始播放...");
  }

  @override
  void pause() {
    print("音乐播放器暂停了...");
  }

  @override
  void destory() {
    print("播放器销毁了");
  }

  @override
  void increase() {
    print("音量增加,当前音量:${++currentVolume}");
  }

  @override
  void reduction() {
    print("音量减少,当前音量:${--currentVolume}");
  }
}

void main() {
  var player = MusicPlayer();
  player.play();  // 输出:音乐播放器开始播放...
  player.increase();  // 输出:音量增加,当前音量:31
  player.reduction(); // 输出:音量减少,当前音量:30
  player.pause(); // 输出:音乐播放器暂停了...
  player.destory(); // 输出:播放器销毁了
}

不难看出上述代码并不够优雅,destory() 方法体内容一样的,应该进行 复用 ,使用 继承extends 即可解决:

dart 复制代码
class MusicPlayer extends PlayerController implements VolumeController { /* ... */ }

5.7. Mixins(混入)

Dart中允许使用 with 关键字,将其它类的功能添加到另一个类中 (该类可以复用其中的方法和属性),从而能实现多继承。使用 mixin 关键字来定义一个混入类,使用代码示例如下:

dart 复制代码
mixin Swim {
  void swim() => print("能游泳");
}

class Animal {}

// 使用 on子句 指定该mixin可以混入的类类型,只能混入到继承了Animal的类中
mixin Fly on Animal {
  void fly() => print("能飞翔");
}


class Fish with Swim {}

// 混入Fly类,需要继承Animal类
class Duck extends Animal with Swim, Fly {}

void main() {
  var fish = Fish();
  fish.swim();  // 输出:能游泳
  var duck = Duck();
  duck.fly();   // 输出:能飞翔
  duck.swim();  // 输出:能游泳
}

5.8. 枚举和密封类

Dart中使用 enum 关键字定义枚举类型,枚举中的成员都有一个对应的索引值,从0开始,简单使用示例如下:

dart 复制代码
enum TimeSlot { morning, afternoon, evening }

void main() {
  for (var element in TimeSlot.values) {
    print(element); // 输出:TimeSlot.morning
    print("${element.index}=${element.name}");  // 输出:0=morning
  }
}

枚举支持 扩展方法,如给定索引值,获取对应的枚举类型,代码示例如下:

dart 复制代码
extension TimeSlotType on TimeSlot {
  static TimeSlot getTypeFormIndex(int index) => TimeSlot.values[index];
}

void main() {
  var type = TimeSlotType.getTypeFormIndex(1);
  print(type); // 输出:TimeSlot.afternoon
}

增强型枚举,就是让枚举包含其它数据类型,代码示例如下:

dart 复制代码
enum Week {
  monday(cnStr: "周一", count: 1),
  tuesday(cnStr: "周二", count: 2),
  wednesday(cnStr: "周三", count: 3),
  thursday(cnStr: "周四", count: 4),
  friday(cnStr: "周五", count: 5),
  saturday(cnStr: "周六", count: 6),
  sunday(cnStr: "周日", count: 7);  // 最后一个成员要用分号;结尾!!!

  // 构造方法需要用const修饰
  const Week({required this.cnStr, required this.count});
  // 属性是不可变的,需要使用final修饰
  final String cnStr;
  final int count;
}

void main() {
  print(Week.friday.cnStr); // 输出:周五
}

😄 Dart竟然和Kotlin一样有 密封类限制类的结构层次,确保所有子类都被显示列出,在处理复杂状态、模式匹配和不处理不完整数据时非常有用。使用 sealed 关键字进行修饰,使用代码示例如下:

dart 复制代码
// sealed修饰的类是抽象类,无法被实例化!
sealed class RequestStatus {}

class RequestSuccess extends RequestStatus {}

class RequestFailure extends RequestStatus {}

String getStatusString(RequestStatus status) {
  return switch (status) {
    RequestSuccess() => "请求成功",
    RequestFailure() => "请求失败"
  };
}

void main() {
  print(getStatusString(RequestSuccess())); // 输出请求成功
}

6. 异步

😄 这里直接CV上一节《二、从 Android ✈ Flutter 的知识迁移》异步UI部分的内容~

6.1. Future-异步任务

Dart 为事件队列的任务提供了一层封装 → Future ,表示 一个在未来时间才会完成的任务 。把一个函数体放入 Future,就完成了 同步任务到异步任务的包装 。Future还提供 链式调用,可在异步任务完成后依次执行链路上的其它函数体。异步函数返回时,内部执行动作未结束,有两种选择:

  • 异步处理 :在返回的 Future 对象上注册一个 then,等Future执行体结束后,再进行异步处理;
  • 同步等待 :同步等待 Future 执行体结束,需在调用处使用 await ,调用处的函数体使用 async ,因为Dart里的await不是堵塞等待,而是 异步等待。

官方代码示例:

dart 复制代码
import 'package:http/http.dart' as http;

// 异步等待请求加载完毕,注意async和await关键字的位置
Future<void> loadData() async {
  var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = jsonDecode(response.body);
  });
}

6.2. Isolate-多线程

Dart 也提供了多线程机制 → Isolate ,每个 Isolate 都有自己的 EventLoopEventQueue ,Isolate 间 不共享任何资源 ,只能依靠 消息机制 (SendPort发送管道) 通信,所以 不存在资源抢占问题 。Isolate的创建非常简单,只要给定一个 函数入口 ,创建时再 传入一个参数 就可以启动了。简单代码示例如下(将数据分享给主线程来更新UI):

dart 复制代码
/// 加载数据并更新页面
Future<void> loadData() async {
	/// 创建一个ReceivePort → 用于接收来自Isolate的消息
  ReceivePort receivePort = ReceivePort();
  /// 创建一个新的Isolate,并将dataLoader 和 发送端口传给它
  /// 将在新的Isolate里执行dataLoader函数
  await Isolate.spawn(dataLoader, receivePort.sendPort);
	/// 等待从 Isolate 中获取第一个消息(SendPort),用于与 Isolate 进行通信
  SendPort sendPort = await receivePort.first;
	/// 向SendPort发消息
  List msg = await sendReceive(
    sendPort,
    'https://jsonplaceholder.typicode.com/posts',
  );
	/// 更新UI
  setState(() {
    widgets = msg;
  });
}

/// 在Isolate里执行的具体异步操作
static Future<void> dataLoader(SendPort sendPort) async {
  /// 创建一个ReceivePort → 用来接收主线程的消息
  ReceivePort port = ReceivePort();
  /// 通过 sendPort 向主线程发送当前 Isolate 的 port.sendPort,用于通信
  sendPort.send(port.sendPort);
  /// 等待从主线程接收消息,并使用 for-in 循环来处理接收到的消息
  await for (var msg in port) {
    // 获取数据的URL地址、回复信息的SendPort
    String data = msg[0];
    SendPort replyTo = msg[1];
    String dataURL = data;
    /// 发起HTTP GET请求,并等待响应
    http.Response response = await http.get(Uri.parse(dataURL));
    /// 将解析后的Json发送会主线程
    replyTo.send(jsonDecode(response.body));
  }
}

/// 向指定SendPort发送消息,并返回一个Future对象用于接收响应
Future sendReceive(SendPort port, msg) {
  ReceivePort response = ReceivePort();
  port.send([msg, response.sendPort]);
  return response.first;
}

总结一下

执行 I/O任务 ,如存储访问、网络请求,可以放心使用 async/await ,要执行 消耗CPU的计算密集型 工作,可以将其转移到一个 Isolate 上以避免阻塞事件循环。

🐶 后续会有专门章节学这个,实在感兴趣的可以先查阅官方文档:《Asynchrony support》

7. 异常

使用 throw 关键字抛出一个异常,使用 try 语句配合 on (指定要捕获的异常类型) 或 catch(捕获异常并存储在一个变量中) 关键字来捕获异常,使用代码示例如下:

dart 复制代码
class IntegerDivisionByZeroException implements Exception {
  final String message;
  IntegerDivisionByZeroException(this.message);
}

int divide(int a, int b) {
  if (b == 0) throw IntegerDivisionByZeroException("除数不能为0");
  return a ~/ b;
}

void main() {
  try {
    var result = divide(1, 0);
  } on IntegerDivisionByZeroException catch (e) {
    print("捕获到异常:${e.message}");  // 输出:捕获到异常:除数不能为0
  } finally {
    print("执行结束");  // 输出:执行结束
  }
}

🤡 感觉基础的语法就这些,过完应该可以比较流畅地开发Flutter了,实际开发中遇到不懂的知识点再突击下~

相关推荐
sun0077007 小时前
android ndk编译valgrind
android
AI视觉网奇9 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空9 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet10 小时前
android组包时会把从maven私服获取的包下载到本地吗
android
catchadmin10 小时前
PHP serialize 序列化完全指南
android·开发语言·php
星秋Eliot10 小时前
Flutter多线程
flutter·async/await·isolate·flutter 多线程
tangweiguo0305198711 小时前
Kable使用指南:Android BLE开发的现代化解决方案
android·kotlin
爱笑的眼睛1113 小时前
HarmonyOS 应用开发深度解析:基于声明式UI的现代化状态管理实践
华为·harmonyos
前端世界13 小时前
HarmonyOS 实战:如何用数据压缩和解压让应用更快更省
华为·harmonyos
农夫三拳_有点甜13 小时前
Flutter Assets & Media
flutter