本文介绍了 Dart 的环境搭建以及 Dart 语法,包括变量与常量、数据类型、函数、操作符、类、泛型等等。
安装
-
在 Dart for Windows (gekorm.com) 下载安装包安装 Dart SDK。
-
在 VSCode 中安装 Dart 和 Code Runner 插件。
-
新建一个
.dart
文件,输出 “Hello dart!” :
void main() {
print('Hello dart!');
}
Run Code:
Hello dart!
变量和常量
变量
在Dart中变量的声明有三个关键字:var 、dynamic 和 Object 。
像 JS,Dart 中的变量可以不预先定义变量类型,也可以在变量前加类型来声明变量:
var a = 114514;
String str = 'this is string';
使用这两种方法声明变量之后,后续不能够更改变量类型。
var a = 114514;
a = '12345'; // 报错
使用dynamic
来声明变量可以使得在编译阶段不检查类型,这与 JavaScript 一样:
dynamic str = 'this is string';
str = 114514;
print(str);
常量
使用 const
和 final
定义。
const PI = 3.14;
const
需要是编译时常量,而final
不需要:
const constDateTime = DateTime.now(); // 报错
final finalDateTime = DateTime.now();
数据类型
数值
数值类型 num
包含 int
和 double
两个子类。
String
Dart 字符串是 UTF-16 编码的字符序列。
可以在字符串中通过${expression}
语法插入表达式。
可以通过 ==
比较字符串。
bool
if
和assert
表达式里面,它们的值必须是bool
类型。默认值是null
而非false
。
List
List
类似数组,默认接收的类型是dynamic
,可以向其中添加任意对象:
List list = [1, 2, 3];
常用的属性和方法:
常用属性:
length 长度
reversed 翻转,结合 toList()
isEmpty 是否为空
isNotEmpty 是否不为空
常用方法:
add(value) 增加
addAll(list) 拼接数组
indexOf(value) 查找,传入 value 返回 index,找不到返回 -1
remove(value) 删除,传入 value
removeAt(index) 删除,传入索引值
fillRange(startIndex,endIndex,value) 修改,[参数1, 参数2)的数据修改为参数3
insert(index,value); 指定位置插入
insertAll(index,list) 指定位置插入List
toList() 其他类型转换成List
join(分隔符) List 转换成 String
split(分隔符) String 转化成 List
forEach
map
where
any
every
这些方法在 Set 和 Map 中大多也可以使用
Set
Set
表示容器中的对象唯一,按照添加的先后顺序排序。Set 是没有顺序且去重后的集合,所以没有索引值。
Set set = {'a', 'b', 'a', 'c'};
print(set); // {a, b, c}
print(set.toList()); // [a, b, c]
Map
Map
按照添加的先后顺序排序。
Map map = {'foo': 'apple', 'bar': 'banana'};
print(map); // {foo: apple, bar: banana}
print(map.keys.toList()); // [foo, bar]
print(map.values.toList()); // [apple, banana]
map.addAll({'baz':'pear'});
map.remove("baz");
print(map.containsValue("apple")); // true
循环数据的方法
forEach
、map
、where
、any
、every
List:
for、for ... in:略
forEach:
List list=["apple", "banana", "pear"];
list.forEach((value){
print(value); // apple banana pear
})
map:
List list=["apple", "banana", "pear"];
List newList = list.map((value){
return value + "1";
}).toList();
print(newList); // [apple1, banana1, pear1]
where:
List list = [1, 2, 3, 4, 5];
List newList = list.where((value){
return value > 3;
}).toList();
print(newList); // [4, 5]
any:只要 value 中有满足条件的就返回 true
bool b = list.any((value){
return value > 3;
});
print(b); // true
every:每一个 value 都要满足条件才返回 true
Set:同 List
Map:
forEach:
Map person = {
"name": "Jack",
"age": 20,
};
person.forEach((key, value){
print("$key - $value"); // name - Jack age - 20
});
函数
像JS,函数也是对象,类型为Function
。因此函数也可以作为变量的值当函数的参数或者返回值。
支持使用=>
缩写语法,不同于 ES6 的箭头函数,这里箭头只能指向函数的返回值:
String hello(var name) => 'hello $name';
// 等价于
String hello(var name){
return 'hello $name';
}
若没有返回值,默认为null
。
入口函数
入口函数即 main
函数,如果没有返回值可以省略 void。
可选参数
- 可选命名参数
命名参数要用花括号括起来,命名参数可以有默认值,不指定默认值则为 null
:
String add({var a, var b = 1}) => '$a' + '$b';
var a = add(b: 2);
print(a); // null2
- 可选位置参数
在方括号内可以设置非必填参数,默认值可加可不加,默认值为 null
:
String add(var a, [var b = 1]) => '$a' + '$b';
var a = add(1);
print(a); // 11
匿名函数
即省略函数名的函数:
var func = (a, b) => a + b;
print(func(1, 2)); // 3
闭包
在 JS 中闭包这个概念很重要,这里不再重复,后期会专门写一篇文章来说明。
操作符
条件运算符 ?.
下面的例子中,若 p 为空,则返回 null,若 p 不空,则返回 p.name :
class Person{
var name;
}
void main() {
var p = Person();
print(p?.name); // null
}
取整除 ~/
相较于/
仅仅多了取整的功能。
类型操作 as
, is
, is!
as
用来转化类型, is
、is!
用来判断类型:
num n1 = 1;
int n2 = n1 as int;
if(n2 is int){
print('n2 is int')
}
判空 ??
若 a 为空,则 b = 1;反之 b = a:
void main() {
var a;
var b = a ?? 1;
print(b);
}
级联 ..
相当于链式调用,允许对同一对象进行一系列操作。
流程控制
与其他语言一样,这里省略。
类
在 Dart 中所有对象都是某个类的实例,所有类继承了Object
类。
构造函数
import 'dart:math';
// 定义类
class Point {
num x = 0, y = 0;
// Point(this.x, this.y); // 构造器
// 或者
Point(x, y) {
this.x = x;
this.y = y;
print('这是构造函数,在实例化时触发');
}
// 实例方法
num distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
main() {
// 调用类的构造函数,new 可以省略
Point p1 = new Point(1, 2); // 这是构造函数,在实例化时触发
Point p2 = Point(3, 4); // 这是构造函数,在实例化时触发
// 调用类方法
print(p1.distanceTo(p2)); // 2.8284271247461903
}
由于 Dart 2.12 的特性,这样写构造函数会报错:
class Point {
num x, y;
Point(x, y) { // 不可为空的实例字段 'x' 必须被初始化
// 尝试添加一个初始化表达式,或者在这个构造函数中添加一个字段初始化器,或者标记它
this.x = x;
this.y = y;
}
}
由于空安全,Dart 不知道你是否为 x,y 分配了变量值。该写法在之前的版本没有问题,解决方法是dart - Non-nullable instance field must be initialized - Stack Overflow。
解决方法之一:
class Point {
num? x, y;
Point(x, y) {
this.x = x;
this.y = y;
}
}
num?
表示可空类型,表示变量值可以为空。
使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数使代码语义化,之前使用的 DateTime.now() 也是命名构造函数。
使用重定向构造函数,用冒号调用其他构造函数。
class Point {
num? x;
num? y;
Point.initX(y) { // 命名构造函数
this.x = 2;
this.y = y;
}
Point.redirctor(num x) : this(x, 0); // 重定向构造函数
}
main() {
Point p1 = Point.initX(2);
print('${p1.x}, ${p1.y}'); // 2, 2
Point p2 = Point.redirctor(1);
print('${p2.x}, ${p2.y}'); // 1, 0
}
代码越来越多导致维护性越来越差,所以我们需要把类抽离成文件,在需要的地方使用import
导入库。
// lib\Point.dart
class Point {
num x = 0, y = 0;
Point(this.x, this.y);
}
// main.dart
import 'lib/Point.dart';
main(){
//...
}
类及成员可见性
Dart 中的可见性以 library 为单位,每个Dart 文件就是一个库。
使用_
表示属性或方法的私有属性,相当于 Java 中的privite
。
class Point {
num _x = 0, _y = 0;
Point(this.x, this.y);
}
当其被抽离成一个文件时,私有属性的作用才生效。
getter,setter
每个变量都有其默认的getter
和setter
方法,final 声明的变量只可读,只有 getter
方法。
在 Dart 中,方法不能重载。
class Rect {
late num width, height;
Rect(this.width, this.height);
area() {
return this.width * this.height;
}
// getter
get area1 {
return this.width * this.height;
}
// setter
set height2(value) {
this.height = value;
}
}
main() {
Rect r1 = Rect(1, 2);
print(r1.area()); // 2
print(r1.area1); // 2
r1.height2 = 3;
print(r1.area1); // 3
}
初始化列表
初始化列表是在实例化之前进行的操作:
class Rect {
late num width, height;
// Rect(this.width, this.height); 不能同时使用
Rect()
: width = 2,
height = 3 {
print('in class');
print(this.width * this.height);
}
}
main() {
Rect r1 = Rect();
print('in main');
}
// 输出结果
// in class
// in main
// 6
static 静态成员
静态变量和静态方法可以通过类来访问,而不是类的实例。
class Person {
static String name = 'Jackie';
static void show() {
print(name);
}
}
main() {
print(Person.name); //Jackie
Person.show(); // Jackie
}
静态的方法不可以访问非静态的成员,非静态的方法可以访问静态的成员。
class Person {
static String name = 'Jackie';
static show() {
print(name);
}
showInfo() {
print(name);
show();
}
}
main() {
Person p = Person();
p.showInfo(); // Jackie Jackie
}
继承
-
子类使用
extends
关键词来继承父类; -
子类会继承父类里除构造函数的可见的属性和方法;
-
子类能复写父类的方法。
class Person {
late String name;
late int age;
Person(this.name, this.age);
work() {
print('$name is working.');
}
}
class Web extends Person {
late String sex;
// 子类的构造函数
Web(String name, int age, String sex) : super(name, age) {
this.sex = sex;
}
// 子类可以复写父类的方法
@override // 表示覆写父类的方法,选写
work() {
print('$name is working 996.');
}
}
main() {
Web w = Web('Tom', 20, 'male');
print(w.sex); // male
w.work(); // Tom is working 996.
}
抽象类
Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。
- 抽象类通过
abstract
关键字来定义。 - Dart 中没有方法体的方法称为抽象方法。
- 如果子类继承抽象类必须要实现里面的抽象方法。
- 如果把抽象类当做接口实现的话必须要实现抽象类里面定义的所有属性和方法。
- 抽象类不能被实例化,只有继承它的子类可以。
extends
抽象类和 implements
的区别:
- 如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用
extends
继承抽象类。 - 如果只是把抽象类当做标准的话就用
implements
实现抽象类。
abstract class Person {
late String name;
late int age;
Person(this.name, this.age);
work(); // 抽象方法
}
class Employee extends Person {
Employee(String name, int age) : super(name, age);
@override
work() {
print('$name is working 996.');
}
}
class Employee2 extends Person {
Employee2(String name, int age) : super(name, age);
@override
work() {
print('$name is working 669.');
}
}
main() {
Person e = Employee('Tom', 20);
e.work(); // Tom is working 996.
// Person p = Person(); //报错,抽象类不能实例化
Person e2 = Employee2('Jerry', 20);
e2.work(); // Jerry is working 669.
}
多态
是子类的实例赋值给父类的引用,允许将子类类型的指针赋值给父类类型的指针, 同一个函数调用会有不同的执行效果 。
多态就是父类定义一个方法,让继承他的子类去实现,每个子类有不同的表现。
上述例子中,Employee 和 Employee2 类都实现了多态。
接口
Dart 也有接口,但是和 Java 有区别。
Dart 的接口没有 interface 关键字,而是普通类或抽象类都可以作为接口使用 implements
关键字被实现。
如果实现的类是普通类,会将普通类和抽象类中的属性的方法全部需要覆写一遍.而因为抽象类可以定义抽象方法,普通类不可以,所以建议使用抽象类定义接口。
abstract class Db {
late String uri;
add(String data);
}
class Mysql implements Db {
@override
late String uri;
Mysql(this.uri);
@override
add(String data) {
print('This is Mysql. ' + '$data');
}
}
main() {
Mysql m = Mysql('xxx');
m.add('123');
}
接口抽离成不同的文件:
// lib\Db.dart
abstract class Db {...}
// lib\Mysql.dart
import 'lib/Db.dart';
class Mysql implements Db {...}
// main.dart
import 'lib/Mysql.dart';
main() {...}
一个类可以实现多个接口
class C implements A, B {
// A, B 接口所有的属性和方法
}
Mixins
Class 中无法实现多继承,Mixins 不同于继承和接口,可以实现类似多继承的功能:
class C extends A, B {} // 报错
class C with A, B {...} // √
- 作为 Mixins 的类只能继承自Object,不能继承其他类。
- 作为 Mixins 的类不能有构造函数。
- 一个类可以 Mixins 多个 Mixins 类。
- Mixins 绝不是继承,也不是接口,而是一种全新的特性。
class A {...}
class B extends A {...}
class C {...}
class D with B, c {...} // 报错
class D extends A with B, C {...} // √
后写的类会覆盖掉前面的类:
class C with A, B {...} // B 的方法会覆盖掉 A
class D extends A with B, C {...} // B 的方法会覆盖掉 A,
// C 的方法会覆盖掉 A 和 B
泛型
泛型对不特定数据类型的支持使方法、类、接口具有复用性。
泛型方法
T getData<T>(T value) {
return value;
}
main() {
print(getData<num>(123)); // 123
print(getData<String>('Jack')); // Jack
}
泛型类
// 调用泛型类
List list = List.filled(2, '');
list[0] = 'Tom';
list[1] = 12;
print(list); // [Tom, 12]
// 要求传入的数据是 String 类型
List list1 = List<String>.filled(2, '');
list1[0] = 'Tom';
list1[1] = 'Jerry';
print(list1); // [Tom, Jerry]
// 定义泛型类
class MyList<T> {
List list = <T>[];
add(T value) {
this.list.add(value);
}
List getList() {
return list;
}
}
泛型接口
abstract class Cache<T> {
getByKey(String key);
setByKey(String key, T value);
}
class MemoryCache<T> implements Cache<T> {
@override
getByKey(String key) {
return null;
}
@override
setByKey(String key, T value) {
print("MemoryCache key=${key} value=${value}");
}
}
void main() {
MemoryCache mc = MemoryCache<Map>();
mc.setByKey('index', {"name": "Jack", "age": 20}); // MemoryCache key=index value={name: Jack, age: 20}
}
类库
Dart中的库主要有三种:
- 我们自定义的库
import 'lib/class.dart';
- 系统内置库
import 'dart:math';
- Pub 包管理系统中的库
pub.dev/packages
pub.flutter-io.cn/packages
pub.dartlang.org/flutter
- 需要在自己想项目根目录新建一个 pubspec.yaml
- 在 pubspec.yaml 文件配置名称、描述、依赖等信息
- 在控制台运行
pub get
获取包下载到本地 - 项目中引入库
import 'package:http/http.dart' as http;
引入库的时候使用as
来重命名防止名称重复:
import 'package:http/http.dart' as http;
如果你只需要使用库中的部分功能,可以使用show
显示或hide
隐藏部分方法来部分导入:
import 'dart:math' show max;
Dart 2.12
Null Safety 空安全
Null safety 可以帮助开发者避免一些日常开发中很难被发现的错误,并且可以改善性能。
int a = 123;
a = null; // 报错,Null 类型的值不能赋值给 int 类型的变量
类型后加?
表示改变量是可空类型:
int? a = 123;
a = null;
List<int>? l=[1, 2, 3];
l=null;
print(l); // null
String? getData(value) {
if (value != null) {
return 'This is a string.';
}
return null;
}
类型断言 !
用于判断变量是否为空,若为空则抛出异常。在下面的例子中,由于 str 为空,所以抛出异常:
void main() {
String? str = 'String';
str = null;
print(str!.length); // Null check operator used on a null value
}
void printLength(String? str) {
try {
print(str!.length);
} catch (e) {
print("str is null");
}
}
void main() {
printLength(null); // str is null
}
late
关键字
late
关键字用于延迟初始化。下面的类中没有构造函数,如果没有late
关键字就会报错,加上late
关键字即可:
class Person {
late String name;
void setInfo(String name) {
this.name = name;
}
String getInfo() {
return "${this.name}";
}
}
void main(args) {
Person p = Person();
p.setInfo("Jack");
print(p.getInfo()); // Jack
}
required
关键字
required
作为内置修饰符,主要用于允许根据需要标记任何命名参数(函数或类),使其不为空。可选参数必须要有 required 或默认值。
String printInfo(String name, {int age = 20, String sex = "male"}) {
// ...
}
String printInfo2(String name, {required int age, required String sex}) {
// ...
}
最后
分享给大家一份面试题合集。
下面的题目都是在Android交流群大家在面试时遇到的,如果大家有好的题目或者好的见解欢迎分享,楼主将长期维护此帖。
参考解析:郭霖、鸿洋、玉刚、极客时间、腾讯课堂…
内容特点:条理清晰,含图像化表示更加易懂。
内容概要:包括 Handler、Activity相关、Fragment、service、布局优化、AsyncTask相关
、Android 事件分发机制、 Binder、Android 高级必备 :AMS,WMS,PMS、Glide、 Android 组件化与插件化等面试题和技术栈!
Handler 相关知识,面试必问!
常问的点:
Handler Looper Message 关系是什么?
Messagequeue 的数据结构是什么?为什么要用这个数据结构?
如何在子线程中创建 Handler?
Handler post 方法原理?
Android消息机制的原理及源码解析
Android Handler 消息机制
Activity 相关
启动模式以及使用场景?
onNewIntent()和onConfigurationChanged()
onSaveInstanceState()和onRestoreInstanceState()
Activity 到底是如何启动的
启动模式以及使用场景
onSaveInstanceState以及onRestoreInstanceState使用
onConfigurationChanged使用以及问题解决
Activity 启动流程解析
Fragment
Fragment 生命周期和 Activity 对比
Fragment 之间如何进行通信
Fragment的startActivityForResult
Fragment重叠问题
Fragment 初探
Fragment 重叠, 如何通信
Fragment生命周期
Service 相关
进程保活
Service的运行线程(生命周期方法全部在主线程)
Service启动方式以及如何停止
ServiceConnection里面的回调方法运行在哪个线程?
startService 和 bingService区别
进程保活一般套路
关于进程保活你需要知道的一切
Android布局优化之ViewStub、include、merge
什么情况下使用 ViewStub、include、merge?
他们的原理是什么?
ViewStub、include、merge概念解析
Android布局优化之ViewStub、include、merge使用与源码分析
BroadcastReceiver 相关
注册方式,优先级
广播类型,区别
广播的使用场景,原理
Android广播动态静态注册
常见使用以及流程解析
广播源码解析
AsyncTask相关
AsyncTask是串行还是并行执行?
AsyncTask随着安卓版本的变迁
AsyncTask完全解析
串行还是并行
Android 事件分发机制
onTouch和onTouchEvent区别,调用顺序
dispatchTouchEvent, onTouchEvent, onInterceptTouchEvent 方法顺序以及使用场景
滑动冲突,如何解决
事件分发机制
事件分发解析
dispatchTouchEvent, onTouchEvent, onInterceptTouchEvent方法的使用场景解析
Android View 绘制流程
简述 View 绘制流程
onMeasure, onlayout, ondraw方法中需要注意的点
如何进行自定义 View
view 重绘机制
-
Android LayoutInflater原理分析,带你一步步深入了解View(一)
-
Android视图状态及重绘流程分析,带你一步步深入了解View(二)
-
Android视图状态及重绘流程分析,带你一步步深入了解View(三)
-
Android自定义View的实现方法,带你一步步深入了解View(四)
Android Window、Activity、DecorView以及ViewRoot
Window、Activity、DecorView以及ViewRoot之间的关系
Android 的核心 Binder 多进程 AIDL
常见的 IPC 机制以及使用场景
为什么安卓要用 binder 进行跨进程传输
多进程带来的问题
-
AIDL 使用浅析
-
binder 原理解析
-
binder 最底层解析
-
多进程通信方式以及带来的问题
-
多进程通信方式对比
Android 高级必备 :AMS,WMS,PMS
AMS,WMS,PMS 创建过程
-
AMS,WMS,PMS全解析
-
AMS启动流程
-
WindowManagerService启动过程解析
-
PMS 启动流程解析
Android ANR
为什么会发生 ANR?
如何定位 ANR?
如何避免 ANR?
什么是 ANR
如何避免以及分析方法
Android 性能优化之 ANR 详解
Android 内存相关
注意:内存泄漏和内存溢出是 2 个概念
什么情况下会内存泄漏?
如何防止内存泄漏?
-
内存泄漏和溢出的区别
-
OOM 概念以及安卓内存管理机制
-
内存泄漏的可能性
-
防止内存泄漏的方法
Android 屏幕适配
屏幕适配相关名词解析
现在流行的屏幕适配方式
-
屏幕适配名词以及概念解析
-
今日头条技术适配方案
Android 缓存机制
LruCache使用极其原理
-
Android缓存机制
-
LruCache使用极其原理述
Android 性能优化
如何进行 内存 cpu 耗电 的定位以及优化
性能优化经常使用的方法
如何避免 UI 卡顿
-
性能优化全解析,工具使用
-
性能优化最佳实践
-
知乎高赞文章
Android MVC、MVP、MVVM
好几种我该选择哪个?优劣点
任玉刚的文章:设计模式选择
Android Gradle 知识
这俩篇官方文章基础的够用了
必须贴一下官方文档:配置构建
Gradle 提示与诀窍
Gradle插件 了解就好
Gradle 自定义插件方式
全面理解Gradle - 执行时序
-
Gradle系列一
-
Gradle系列二
-
Gradle系列三
RxJava
使用过程,特点,原理解析
RxJava 名词以及如何使用
Rxjava 观察者模式原理解析
Rxjava订阅流程,线程切换,源码分析 系列
OKHTTP 和 Retrofit
OKHTTP完整解析
Retrofit使用流程,机制详解
从 HTTP 到 Retrofit
Retrofit是如何工作的
最流行图片加载库: Glide
郭神系列 Glide 分析
Android图片加载框架最全解析(一),Glide的基本用法
Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
Android图片加载框架最全解析(三),深入探究Glide的缓存机制
Android图片加载框架最全解析(四),玩转Glide的回调与监听
Android图片加载框架最全解析(五),Glide强大的图片变换功能
Android图片加载框架最全解析(六),探究Glide的自定义模块功能
Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能
Android图片加载框架最全解析(八),带你全面了解Glide 4的用法
Android 组件化与插件化
为什么要用组件化?
组件之间如何通信?
组件之间如何跳转?
Android 插件化和热修复知识梳理
为什么要用组件化
- Android彻底组件化方案实践
- Android彻底组件化demo发布
- Android彻底组件化-代码和资源隔离
- Android彻底组件化—UI跳转升级改造
- Android彻底组件化—如何使用Arouter
插件化框架历史
深入理解Android插件化技术
Android 插件化和热修复知识梳理
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
好啦,这份资料就给大家介绍到这了,有需要详细文档的小伙伴,可以微信扫下方二维码免费领取哈~