您现在的位置是:网站首页> Flutter
Dart基本语法
- Flutter
- 2026-07-31
- 2458人已阅读
Dart基本语法
***与C语言语法很类似而又有扩充***
等待wait sleep(Duration(seccond:5))
对象定义赋值:Person person=Person();等同Person person=new Person();
*如有一个相当消耗时间的函数 test (int a,int b) 如何使用 Future 调用它*

创建一个Flutter项目后,打开MainActivity 出现Can't resolve symbol FlutterActivity 错误
*如有一个相当消耗时间的函数 test (int a,int b) 如何使用 Future 调用它*
语言走马观花笔记
Dart使用new和不用new是一样的
函数参数
一般参数:
命名参数:使用大括号{}括起来的参数
可选的位置参数:使用中括号[]括起来的参数
void funcname(String param1,{double param2,bool param3,...});
void printUserInfo(String name,[String from='中国',int age]);
Dart 语言支持两种类型的函数参数:位置参数和命名参数。让我们详细介绍这两种参数类型的定义和使用方法,并提供一些例子。
位置参数(Positional Parameters)
位置参数是根据它们在函数调用中的位置来传递的参数。
定义:
位置参数在函数定义时直接列在括号内。
可选的位置参数用方括号 [] 括起来。
例子:
// 必需的位置参数
void greet(String name) {
print('Hello, $name!');
}
// 混合使用必需和可选的位置参数
String formatName(String firstName, [String? lastName]) {
return lastName != null ? '$firstName $lastName' : firstName;
}
void main() {
greet('Alice'); // 输出: Hello, Alice!
print(formatName('John')); // 输出: John
print(formatName('John', 'Doe')); // 输出: John Doe
}
命名参数(Named Parameters)
命名参数允许你通过参数名来指定参数,而不是依赖于位置。
定义:
命名参数在函数定义时用大括号 {} 括起来。
默认情况下,命名参数是可选的。
使用 required 关键字可以使命名参数成为必需的。
例子:
// 可选的命名参数
void printPersonInfo({String? name, int? age}) {
print('Name: ${name ?? 'Unknown'}, Age: ${age ?? 'Unknown'}');
}
// 混合使用必需和可选的命名参数
void createUser({required String username, String? email}) {
print('Creating user: $username, Email: ${email ?? 'Not provided'}');
}
void main() {
printPersonInfo(name: 'Alice', age: 30); // 输出: Name: Alice, Age: 30
printPersonInfo(age: 25); // 输出: Name: Unknown, Age: 25
printPersonInfo(); // 输出: Name: Unknown, Age: Unknown
createUser(username: 'john_doe'); // 输出: Creating user: john_doe, Email: Not provided
createUser(username: 'jane_doe', email: 'jane@example.com'); // 输出: Creating user: jane_doe, Email: jane@example.com
}
混合使用位置参数和命名参数
你也可以在同一个函数中混合使用位置参数和命名参数。在这种情况下,位置参数应该放在前面,命名参数放在后面。
例子:
void describe(String name, {int? age, String? occupation}) {
print('Name: $name');
if (age != null) print('Age: $age');
if (occupation != null) print('Occupation: $occupation');
}
void main() {
describe('Alice'); // 输出: Name: Alice
describe('Bob', age: 30); // 输出: Name: Bob \n Age: 30
describe('Charlie', occupation: 'Engineer'); // 输出: Name: Charlie \n Occupation: Engineer
describe('David', age: 40, occupation: 'Doctor'); // 输出: Name: David \n Age: 40 \n Occupation: Doctor
}
总结:
位置参数适用于参数顺序固定且较少的情况。
命名参数提高了代码的可读性,特别是当函数有多个可选参数时。
使用 required 关键字可以使命名参数成为必需的
包使用
Flutter/Dart import导入文件关键字总结
导入文件import后面的路径有哪几种?各代表什么意思?关键字有那些?文件导入的顺序是什么?
import 'dart:xxx'; 引入Dart标准库
import 'xxx/xxx.dart';引入绝对路径的Dart文件
import 'package:xxx/xxx.dart'; 引入Pub仓库pub.dev(或者pub.flutter-io.cn)中的第三方库
import 'package:project/xxx/xxx.dart';引入自定义的dart文件
import 'xxx' show compute1,compute2 只导入compute1,compute2
import 'xxx' hide compute3 除了compute都引入
import 'xxx' as compute4 将库重命名,当有名字冲突时
library compute5; 定义库名称
part of compute6; 表示文件属于某个库
文件导入顺序(从上到下依次)
依赖
推荐使用相对路径导入依赖,如项目结果如下(Lib目录就是Dart源码目录)
my_package
|__lib
| |__src
| |__utiles.dart
|__api.dart
想要在api.dart中导入untils.dart,可以按如下方式导入
import 'src/utiles.dart'
下面的方式不推荐,因为一旦my_pakcjkage改名后,对应的所有需要导入的地方都需要修改,二相对路劲就没有这个问题
import 'package:my_package/src/utiles.dart'
dart sdk 内的库
flutter内的库
第三方库
自己的库(文件)
相对路径引用
e.g.
import 'dart:io';
import 'package:material/material.dart';
import 'package:dio/dio.dart';
import 'package:project/common/uitls.dart';
import 'xxx/xxx/xxx/xxx.dart';
变量类型
在 Dart 中,var和具体类型声明变量主要有以下区别:
一、语法和声明方式
使用var声明变量
语法简洁,无需明确指定具体的数据类型。
例如:var name = 'John';,这里name的类型由初始化的值'John'推断为String类型。
使用具体类型声明变量
明确指定变量的数据类型。
例如:String name = 'John';,直接表明name是String类型的变量。
二、类型推断和灵活性
var的类型推断
var让 Dart 根据初始化的值自动推断变量的类型。一旦推断出类型,变量的类型就固定了。
例如:var number = 10;,这里number被推断为int类型。如果之后尝试将一个非int类型的值赋给number,会导致编译错误。
优点:在一些情况下可以简化代码,特别是当类型不那么明显或者可能会发生变化的场景下,可以先快速声明变量,让编译器自动推断类型。
缺点:可能会使代码的可读性降低,尤其是在复杂的代码中,读者可能需要查看初始化的值才能确定变量的类型。
具体类型的明确性
使用具体类型声明变量可以让代码更加清晰和易于理解。读者可以在代码中直接看到变量的类型,无需推断。
例如:int age = 25;,明确表示age是一个整数类型的变量。
优点:提高代码的可读性和可维护性,有助于在开发过程中避免类型错误。可以更好地利用 Dart 的类型系统进行静态分析和错误检测。
缺点:在某些情况下可能会显得比较冗长,特别是当需要声明多个变量时。并且,如果类型选择不当,可能会导致在后续代码中需要进行类型转换。
三、可变性和常量声明
var与可变性
使用var声明的变量可以是可变的,也可以是不可变的,具体取决于初始化的值。
如果初始化的值是不可变的(如一个常量或不可变的集合),那么变量也是不可变的。如果初始化的值是可变的(如一个普通的列表或对象),那么变量也是可变的。
例如:var numbers = [1, 2, 3];,这里numbers是一个可变的列表,可以添加、删除或修改元素。var pi = 3.14;,这里pi是不可变的,因为3.14是一个常量值。
具体类型与可变性
使用具体类型声明变量时,可以通过添加final或const关键字来声明不可变的变量。
例如:final int count = 10;,这里count是一个不可变的整数变量。const List<String> names = ['John', 'Jane'];,这里names是一个不可变的字符串列表。
总的来说,var在某些情况下可以提供简洁的语法和灵活的类型推断,但具体类型声明可以提高代码的可读性和可维护性。在选择使用var还是具体类型时,可以根据具体的编程场景和需求来决定。如果需要明确变量的类型以提高代码的可读性和可维护性,或者需要声明不可变的变量,那么使用具体类型声明可能更合适。如果想要简洁的语法并且不介意让编译器自动推断类型,那么var是一个不错的选择。
dart的数据类型有哪些?详细介绍
在 Dart 中,主要的数据类型如下:
一、数值类型
int(整数类型):
用于表示整数值,例如 -10、0、42 等。
可以进行算术运算,如加、减、乘、除等。
示例:int age = 30;
double(双精度浮点类型):
用于表示带有小数部分的数值。
适用于需要更高精度的数值计算。
示例:double price = 9.99;
二、字符串类型(String)
特点:
用于表示文本数据。
可以使用单引号 ' 或双引号 " 来创建字符串。
支持插值表达式,允许在字符串中嵌入变量和表达式。例如:String name = 'John'; print('Hello, $name!');
常用方法:
length:获取字符串的长度。
contains:判断字符串是否包含指定的子字符串。
substring:提取字符串的一部分。
toUpperCase 和 toLowerCase:将字符串转换为大写或小写。
三、布尔类型(bool)
取值:
只有两个可能的值:true 和 false。
用于表示条件判断和逻辑运算。
示例:bool isActive = true;
四、列表类型(List)
特点:
也称为数组,用于存储一组有序的元素。
可以包含不同类型的元素,但通常建议保持元素类型的一致性以提高代码的可读性和可维护性。
声明方式:
使用方括号 [] 来创建列表,并可以在其中添加初始元素。例如:List<int> numbers = [1, 2, 3];
也可以使用 List 的构造函数来创建列表。例如:List<String> names = List<String>();
常用方法:
add:向列表中添加一个元素。
remove:从列表中移除一个元素。
length:获取列表的长度。
indexOf:查找指定元素在列表中的索引。
五、集合类型(Set)
特点:
用于存储一组无序的、唯一的元素。
不允许包含重复的元素。
声明方式:
使用花括号 {} 来创建集合,并可以在其中添加初始元素。例如:Set<String> fruits = {'apple', 'banana', 'orange'};
也可以使用 Set 的构造函数来创建集合。例如:Set<int> numbers = Set<int>();
常用方法:
add:向集合中添加一个元素。
remove:从集合中移除一个元素。
length:获取集合的大小。
六、映射类型(Map)
特点:
也称为字典,用于存储键值对。
键必须是唯一的,而值可以重复。
声明方式:
使用花括号 {} 来创建映射,并在其中指定键值对。例如:Map<String, int> scores = {'John': 80, 'Jane': 90};
也可以使用 Map 的构造函数来创建映射。例如:Map<String, dynamic> user = Map<String, dynamic>();
常用方法:
putIfAbsent:如果键不存在,则添加一个键值对。
remove:根据键移除一个键值对。
keys:获取映射中的所有键。
values:获取映射中的所有值。
七、动态类型(dynamic)和对象类型(Object)
dynamic:
表示动态类型,可以存储任何类型的值。
在运行时可以动态地改变其类型。
使用时需要小心,因为缺少静态类型检查可能会导致运行时错误。
Object:
是所有 Dart 对象的基类。
可以存储任何对象,但同样缺少具体的类型信息,可能会影响代码的可读性和可维护性。
总之,Dart 提供了丰富的数据类型来满足不同的编程需求。合理选择数据类型可以提高代码的效率、可读性和可维护性。
当你为变量、参数或另一个相关组件指定类型时,可以控制该类型是否允许 null 。要让一个变量可以为空,你可以在类型声明的末尾添加 ? 。
String? name // Nullable type. Can be `null` or string.
String name // Non-nullable type. Cannot be `null` but can be string.
八、Record类型
Record类型
var record = ('first', a: 2, b: true, 'last');
(int, int) swap((int, int) record) {
var (a, b) = record;
return (b, a);
}
(String, int) record;
// Initialize it with a record expression:
record = ('A string', 123);
({int a, bool b}) record;
// Initialize it with a record expression:
record = (a: 123, b: true);
({int a, int b}) recordAB = (a: 1, b: 2);
({int x, int y}) recordXY = (x: 3, y: 4);
var record = ('first', a: 2, b: true, 'last');
print(record.$1); // Prints 'first'
print(record.a); // Prints 2
print(record.b); // Prints true
print(record.$2); // Prints 'last'
(int x, int y, int z) point = (1, 2, 3);
(int r, int g, int b) color = (1, 2, 3);
print(point == color); // Prints 'true'.
content_copy
({int x, int y, int z}) point = (x: 1, y: 2, z: 3);
({int r, int g, int b}) color = (r: 1, g: 2, b: 3);
print(point == color); // Prints 'false'. Lint: Equals on unrelated types.
枚举
enum Color { red, green, blue }
基本语法
=> 粗箭头运算符 代表的是
{
return *****;
}
命名参数,使用{参数1,参数2,..}
void textStyle(String content,{double fontSize,bool bold})
textStyle("123",fontSize:12,bold:true);
可选参数 实用[]
void hello(int A,[int b,int C]);
dart 语法糖 ?.
它的意思是左边如果为空返回 null,否则返回右边的值。
A?.B
如果 A 等于 null,那么 A?.B 为 null
如果 A 不等于 null,那么 A?.B 等价于 A.B
dart 语法糖 ??
它的意思是左边如果为空返回右边的值,否则不处理。
A??B
如果 A 等于 null,那么 A??B 为 B
如果 A 不等于 null,那么 A??B 为 A
int a = null;
等价于
int? a;
级联运算符(..)
//通常是在创建对象时)的一种简单方法,而不是获得对该对象的引用并逐个更改属性
Person p = Person();
p.x = 0;
p.y = 1;
Person p = Person()
..x = 0
..y = 1;
算术运算符(~/)
//除以并返回结果的底数(整数部分)
int a = 3;
int b = 7;
int c = b ~/ a;
print(c);//2
类型检查(is和is!)和强制类型转换(as)
main() {
int number = 100;
double distance = 200.5;
num age = 18;
print(number is num);//true
print(distance is! int);//true
print(age as int);//18
}
集合(List、Set、Map)
1.List初始化方式
main() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];//直接使用[]形式初始化
var colorList = <String> ['red', 'yellow', 'blue', 'green'];
}
List常用的函数
main() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];
colorList.add('white');//和Kotlin类似通过add添加一个新的元素
print(colorList[2]);//可以类似Kotlin一样,直接使用数组下标形式访问元素
print(colorList.length);//获取集合的长度,这个Kotlin不一样,Kotlin中使用的是size
colorList.insert(1, 'black');//在集合指定index位置插入指定的元素
colorList.removeAt(2);//移除集合指定的index=2的元素,第3个元素
colorList.clear();//清除所有元素
print(colorList.sublist(1,3));//截取子集合
print(colorList.getRange(1, 3));//获取集合中某个范围元素
print(colorList.join('<--->'));//类似Kotlin中的joinToString方法,输出: red<--->yellow<--->blue<--->green
print(colorList.isEmpty);
print(colorList.contains('green'));
}
List的遍历方式
main() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];
//for-i遍历
for(var i = 0; i < colorList.length; i++) {//可以使用var或int
print(colorList[i]);
}
//forEach遍历
colorList.forEach((color) => print(color));//forEach的参数为Function. =>使用了箭头函数
//for-in遍历
for(var color in colorList) {
print(color);
}
//while+iterator迭代器遍历,类似Java中的iteator
while(colorList.iterator.moveNext()) {
print(colorList.iterator.current);
}
}
2.集合Set
集合Set和列表List的区别在于 集合中的元素是不能重复 的。所以添加重复的元素时会返回false,表示添加不成功.
Set初始化方式
main() {
Set<String> colorSet= {'red', 'yellow', 'blue', 'green'};//直接使用{}形式初始化
var colorList = <String> {'red', 'yellow', 'blue', 'green'};
}
集合中的交、并、补集,在Kotlin并没有直接给到计算集合交、并、补的API
main() {
var colorSet1 = {'red', 'yellow', 'blue', 'green'};
var colorSet2 = {'black', 'yellow', 'blue', 'green', 'white'};
print(colorSet1.intersection(colorSet2));//交集-->输出: {'yellow', 'blue', 'green'}
print(colorSet1.union(colorSet2));//并集--->输出: {'black', 'red', 'yellow', 'blue', 'green', 'white'}
print(colorSet1.difference(colorSet2));//补集--->输出: {'red'}
}
Set的遍历方式(和List一样)
main() {
Set<String> colorSet = {'red', 'yellow', 'blue', 'green'};
//for-i遍历
for (var i = 0; i < colorSet.length; i++) {
//可以使用var或int
print(colorSet[i]);
}
//forEach遍历
colorSet.forEach((color) => print(color)); //forEach的参数为Function. =>使用了箭头函数
//for-in遍历
for (var color in colorSet) {
print(color);
}
//while+iterator迭代器遍历,类似Java中的iteator
while (colorSet.iterator.moveNext()) {
print(colorSet.iterator.current);
}
}
3、集合Map
集合Map和Kotlin类似,key-value形式存储,并且 Map对象的中key是不能重复的
Map初始化方式
main() {
Map<String, int> colorMap = {'white': 0xffffffff, 'black':0xff000000};//使用{key:value}形式初始化
var colorMap = <String, int>{'white': 0xffffffff, 'black':0xff000000};
}
Map中常用的函数
main() {
Map<String, int> colorMap = {'white': 0xffffffff, 'black':0xff000000};
print(colorMap.containsKey('green'));//false
print(colorMap.containsValue(0xff000000));//true
print(colorMap.keys.toList());//['white','black']
print(colorMap.values.toList());//[0xffffffff, 0xff000000]
colorMap['white'] = 0xfffff000;//修改指定key的元素
colorMap.remove('black');//移除指定key的元素
}
Map的遍历方式
main() {
Map<String, int> colorMap = {'white': 0xffffffff, 'black':0xff000000};
//for-each key-value
colorMap.forEach((key, value) => print('color is $key, color value is $value'));
}
Map.fromIterables将List集合转化成Map
main() {
List<String> colorKeys = ['white', 'black'];
List<int> colorValues = [0xffffffff, 0xff000000];
Map<String, int> colorMap = Map.fromIterables(colorKeys, colorValues);
}
4、集合常用的操作符
dart对于集合操作的也非常符合现代语言的特点,含有丰富的集合操作符API,可以让你处理结构化的数据更加简单。
main() {
List<String> colorList = ['red', 'yellow', 'blue', 'green'];
//forEach箭头函数遍历
colorList.forEach((color) => {print(color)});
colorList.forEach((color) => print(color)); //箭头函数遍历,如果箭头函数内部只有一个表达式可以省略大括号
//map函数的使用
print(colorList.map((color) => '$color_font').join(","));
//every函数的使用,判断里面的元素是否都满足条件,返回值为true/false
print(colorList.every((color) => color == 'red'));
//sort函数的使用
List<int> numbers = [0, 3, 1, 2, 7, 12, 2, 4];
numbers.sort((num1, num2) => num1 - num2); //升序排序
numbers.sort((num1, num2) => num2 - num1); //降序排序
print(numbers);
//where函数使用,相当于Kotlin中的filter操作符,返回符合条件元素的集合
print(numbers.where((num) => num > 6));
//firstWhere函数的使用,相当于Kotlin中的find操作符,返回符合条件的第一个元素,如果没找到返回null
print(numbers.firstWhere((num) => num == 5, orElse: () => -1)); //注意: 如果没有找到,执行orElse代码块,可返回一个指定的默认值
//singleWhere函数的使用,返回符合条件的第一个元素,如果没找到返回null,但是前提是集合中只有一个符合条件的元素, 否则就会抛出异常
print(numbers.singleWhere((num) => num == 4, orElse: () => -1)); //注意: 如果没有找到,执行orElse代码块,可返回一个指定的默认值
//take(n)、skip(n)函数的使用,take(n)表示取当前集合前n个元素, skip(n)表示跳过前n个元素,然后取剩余所有的元素
print(numbers.take(5).skip(2));
//List.from函数的使用,从给定集合中创建一个新的集合,相当于clone一个集合
print(List.from(numbers));
//expand函数的使用, 将集合一个元素扩展成多个元素或者将多个元素组成二维数组展开成平铺一个一位数组
var pair = [
[1, 2],
[3, 4]
];
print('flatten list: ${pair.expand((pair) => pair)}');
var inputs = [1, 2, 3];
print('duplicated list: ${inputs.expand((number) =>[
number,
number,
number
])}');
函数类型与高阶函数
在dart函数也是一种类型Function,可以作为函数参数传递,也可以作为返回值
main() {
Function square = (a) {
return a * a;
};
Function square2 = (a) {
return a * a * a;
};
add(3, 4, square, square2)
}
num add(num a, num b, [Function op, Function op2]) {
//函数作为参数传递
return op(a) + op2(b);
}
Dart 中一切都是对象,这意味着如果我们定义一个数字,在初始化它之前如果我们使用了它,假如没有某种检查机制,则不会报错,比如:
test() {
int i;
print(i*8);
}
在 Dart 引入空安全之前,上面代码在执行前不会报错,但会触发一个运行时错误,原因是 i 的值为 null 。但现在有了空安全,则定义变量时我们可以指定变量是可空还是不可空。
int i = 8; //默认为不可空,必须在定义时初始化。
int? j; // 定义为可空类型,对于可空变量,我们在使用前必须判空。
// 如果我们预期变量不能为空,但在定义时不能确定其初始值,则可以加上late关键字,
// 表示会稍后初始化,但是在正式使用它之前必须得保证初始化过了,否则会报错
late int k;
k=9;
如果一个变量我们定义为可空类型,在某些情况下即使我们给它赋值过了,但是预处理器仍然有可能识别不出,这时我们就要显式(通过在变量后面加一个”!“符号)告诉预处理器它已经不是null了,比如:
class Test{
int? i;
Function? fun;
say(){
if(i!=null) {
print(i! * 8); //因为已经判过空,所以能走到这 i 必不为null,如果没有显式申明,则 IDE 会报错
}
if(fun!=null){
fun!(); // 同上
}
}
}
所有类型都是对象,没赋值前都是null
var i=10;
String A="1234";
const r=5; //不能重复赋值,在编译前就要赋值
final s="222222";//不能重复赋值
double z=1;
var one=int.parse('1');
String oneString=one.toString();
String piAsString=3.14159.toStringAsFixed(2);
字符串可以是单引号也可以是双引号
assert(piAsString=='3.14');
插字符串
var s='5';
var sss='${s}hello";
字符串多行用三个单引号或三个双引号
var s1='''2121224helo
eeeeee''';
var s2="""fdshfkkf
fdfdf"""
s2.runtimeType;//运行时类型
var list=[1,2,3];
assert(list.length==3);
assert(list[1]==2);
list[1]=1;
var gifs={
'first':'fff',
'ggg':'ffff'
}
var nob={
1:'1',
2:'2',
3:'3'
}
gifts=Map();
gifts["fff"]=1111;
var _nobleGases={
2:'111',
10:'1111',
18:'333'
}
bool isNble(int atomicNumber){
return _nobleGases[atomicNumber]!=null;
}
isNble(atomicNumber){
return _nobleGases[atomicNumber]!=null;
}
bool isNble(int atomicNumber)=>
_nobleGases[atomicNumber]!=null;
void enableFlags({bool bold,bool hidden}){} //命名参数
enableFlags(blod:true,hidden:false);
String Say(String from,String msg,[String Device]){ //可选位置参数
if(Device!=){
}
//代默认参数
void enableFlagsDefault({bool bold=false,bool hidden=false}){
}
enableFlagsDefault(bold:true);
String sayDefault(String from,String msg,[String device='car',String mood])
{
}
void printElement(int element){
print(element);
}
var l1=[1,2,3];
l1.forEach(printElement);
l1.forEach((item){
print('${l1.indexOf(item)}:$item');
});
l1.forEach((item)=>print('${l1.indexOf(item)}:$item'));
返回函数
Function makeAdder(num addBy){
return (num i)=>addBy+1;
}
var add2=makeAdder(2);
var add4=makeAdder(4);
assert(add2(3)==5);
assert(add4(3)==7);
//函数不返回任何值时候默认是null
foo(){}
void hello(){}
assert(foo()==null);
List<dynamic>l=[1,'a',2,'b'];
if(l[1] is String){
l[1].toUpperCase();
}
b ??=2 //如果b是null 那么赋值2
var sb=StringBuffer();
sb.write('1);
sb.write('1)..write('bar');//..函数连续调用
语句
for(var i=0;i<10;i++){
}
var collection=[0,1,2];
for(var x in collection)
{
print(x);
}
var iDone=false;
while(!isDone)
{
print('do something');
isDone=true;
}
do(
}
while(!isDone);
while(true)
{
break;
}
dart的注解
在 Dart 中,注解(Annotation)用于为代码添加元数据,以便在编译时或运行时进行额外的处理。以下是一些常见的 Dart 注解及介绍和实例:
一、@override
介绍:
用于标记子类中重写父类方法的方法。
帮助编译器检查是否正确地重写了父类的方法。
实例:
class Animal {
void makeSound() {
print('Animal makes a sound');
}
}
class Dog extends Animal {
@override
void makeSound() {
print('Dog barks');
}
}
二、@required
介绍:
在方法参数前使用,表示该参数是必需的。
通常用于构造函数或方法中,以确保调用者提供该参数。
实例:
class Person {
final String name;
Person({@required this.name});
}
void main() {
var person = Person(name: 'John');
}
三、@deprecated
介绍:
用于标记已过时的代码元素,如类、方法、字段等。
向开发者发出警告,表明该元素可能在未来的版本中被移除。
实例:
@deprecated
void oldMethod() {
print('This method is deprecated.');
}
void main() {
oldMethod();
}
四、@proxy
介绍:
用于创建代理类,可以拦截对目标对象的方法调用。
通常用于实现 AOP(面向切面编程)等功能。
实例(较为复杂,这里仅为简单示例示意):
class TargetClass {
void method() {
print('Target method called.');
}
}
class ProxyClass implements TargetClass {
final TargetClass target;
ProxyClass(this.target);
@override
void method() {
print('Before calling target method.');
target.method();
print('After calling target method.');
}
}
五、@immutable
介绍:
用于标记类是不可变的。
表示该类的实例一旦创建,其状态就不能被修改。
实例:
@immutable
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
void main() {
var point = const Point(10, 20);
}
这些只是 Dart 中一些常见的注解,Dart 还支持自定义注解,可以通过创建类并使用 @Target 和 @Retention 等元注解来定义特定的注解行为。
//异常处理
Exception是核心错误 ,Error是应用代码异常
throw rethrow 再出抛出异常
finally 必须执行的代码
try
{
}
catch(e)
{
rethrow;
}
try
{
}
on ErrorType catch(e) //on 是捕获指定类型的错误
{
}
catch(e,s) //s为栈在代码哪儿
{
}
类
class Point{
num x,y,r;
num distanceFromOrigin;
Point(this.x,this.y):distanceFromOrigin=sqrt(x*x+y*y);
num get right=>r;
set right(num value)=>r=value;
}
//抽象类
abstract class Door{
void doSomething();
}
class A extends B
{
A()
{
}
@override
void turnOn()
{
super.turnOn();
}
}
静态变量和函数
static String A;
static bool isMobile(){
return true;
}
通过函数构造类如:
class Point{
num x,y
Point.fromJson(Map<string,dynamic>json)
{
x=json["x"];
y=json["y"];
}
调用构建:
Point pt=Point.fromJson({"x":1,"y":2});
}
类派生与with使用
*with的最后那个是最高级别,页就是说同一个函数最后一个为准*
class AB extends P with A,B{
}
class BA extends P with B,A{
}
class P
{
String getMessage()=>'P';
}
class A{
String getMessage()=>'A';
}
class B{
String getMessage()=>'B';
}
void main()
{
String result='';
AB ab=AB();
result+=ab.getMessage();
BA ba=BA();
result+=ba.getMessage();
print(result);
}
执行结果'BA'
类前置转换as
泛型
abstract clsss Cache<T>{
T getbyKey(String key);
void setBykey(string key,T value);
}
方法泛型
T first<T>(List<T>s){
T tmp=s[0];
return tmp;
}
dart混入
在 Dart 中,混入(Mixins)是一种重用代码的机制,它允许一个类从多个其他类中获取功能,而无需使用传统的多重继承。
一、混入的特点
代码复用:可以将一些通用的功能提取到一个单独的类中,然后通过混入将这些功能添加到其他类中,避免了代码重复。
灵活性:可以根据需要选择要混入的类,从而实现更加灵活的代码结构。
避免多重继承的复杂性:Dart 不支持多重继承,但混入提供了一种类似的功能,同时避免了多重继承可能带来的复杂性和二义性问题。
二、混入的使用方法
定义一个混入类:
混入类通常是一个普通的类,但它使用 mixin 关键字进行声明。
混入类可以包含方法、属性和构造函数,但不能被实例化。
在其他类中使用混入:
使用 with 关键字将一个或多个混入类添加到一个类的声明中。
这样,该类就可以访问混入类中的方法和属性。
三、实例
以下是一个使用混入的例子:
// 定义一个混入类
mixin Flyable {
void fly() {
print('I can fly!');
}
}
// 定义一个混入类
mixin Swimable {
void swim() {
print('I can swim!');
}
}
// 定义一个类,使用混入
class Duck with Flyable, Swimable {
// 类的其他方法和属性...
}
void main() {
var duck = Duck();
duck.fly();
duck.swim();
}
在这个例子中:
定义了两个混入类 Flyable 和 Swimable,分别包含了 fly 和 swim 方法。
定义了一个类 Duck,使用 with 关键字将 Flyable 和 Swimable 混入到 Duck 类中。
这样,Duck 类就具有了 fly 和 swim 方法,可以在实例化后调用这些方法。
混入是一种强大的代码复用机制,可以帮助你更好地组织和管理代码。但在使用混入时,要注意避免混入类之间的冲突,并确保混入的功能与类的需求相匹配。
库:
内置库的URI前缀是dart: ,由包管理工具提供的库前缀为package:
导入多个库之间出现表示冲突,可以使用as指定引用前缀来避免
使用show和hide来导入或隐藏指定的标识符
使用deferred as来延迟加载库
import ‘dart:math' as m
m.sqrt(2);
import 'dart:math' show sqrt
import 'dart:math' hide sqrt
import 'dart:math' deferred as m //延迟加载
await m.loadLibrary(); //加载库
m.sqrt();
异步
函数里要使用await 那么调用函数必须加async
如
Future<String> lookUpVersion() async=>'1.0.01';
Future checkVersion() async{
var version=await lookUpVersion();
print(version);
}
void main() async{
var version;
try
{
version=await lookUpVersion();
}
catch (e)
{
print(e);
return;
}
print(version);
...
}
dart中的生成器函数
在 dart 中有生成器函数的语法,在很多其他的语言中也有,比如 js c#
这个语法看上去和 async await 语法很像
使用的关键字是 async* sync* yield yield*
官方对于这个语法的说明可以参考这个连接generators
其实async await也是一种生成器语法
生成器语法就是你返回的类型通常情况下和 return 的类型可能不一致
比如你return 1,但是返回值上却需要写Future<int>
sync*
在 dart 中可以使用这个便利的生成一个迭代器
如下所示
这两种写法是一样的,但是第一个写法会简洁很多
main(List<String> arguments) {
print(genList());
print(genList2());
}
Iterable<int> genList({int max = 10}) sync* {
var i = 0;
while (i < max) {
yield i;
i++;
}
}
Iterable<int> genList2({int max = 10}) {
var list = <int>[];
var i = 0;
while (i < max) {
list.add(i);
i++;
}
return list.map((i) => i);
}
async*
这个返回值是一个 Stream
main(List<String> arguments) {
print(genList());
print(genList2());
genStream().listen((data) {
print("stream1 : $data");
});
genStream2().listen((data) {
print("stream2 : $data");
});
}
Stream<int> genStream({int max = 10}) async* {
int i = 0;
while (i < max) {
yield i;
await Future.delayed(Duration(milliseconds: 300));
i++;
}
}
Stream<int> genStream2({int max = 10}) {
StreamController<int> controller = StreamController();
Future<void>.delayed(Duration.zero).then((_) async {
int i = 0;
while (i < max) {
controller.add(i);
await Future.delayed(Duration(milliseconds: 300));
i++;
}
controller.close();
});
return controller.stream;
}
两种写法达到了一样的效果,但是生成器函数代码会更加简洁一些
执行结果如下
yield*
在生成器函数中还有一个关键字 yield*
这个关键字是结合递归使用的,可以配合sync* 也可以配合async*
结合 sync*
main(List<String> arguments) {
var r = naturalsDownFrom(10);
print(r); //(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
r = naturalsDownWithNormal(10);
print(r); //(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
}
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
Iterable<int> naturalsDownWithNormal(int n) {
var list = <int>[];
if (n > 0) {
list.add(n);
var r = naturalsDownWithNormal(n - 1);
list.addAll(r);
}
return list.map((v) => v);
}
结合 async*
main(List<String> arguments){
naturalsStreamDownFrom(10).listen((data) {
print("data = $data");
});
}
Stream<int> naturalsStreamDownFrom(int n) async* {
if (n > 0) {
yield n;
yield* naturalsStreamDownFrom(n - 1);
}
}
输出结果
data = 10
data = 9
data = 8
data = 7
data = 6
data = 5
data = 4
data = 3
data = 2
data = 1
常规写法分开写
main(List<String> arguments) {
naturalsStreamDownWithNormal(10).listen((data) {
print("data2 = $data");
});
}
Stream<int> naturalsStreamDownWithNormal(int n) {
var controller = StreamController<int>();
if (n > 0) {
controller.add(n);
naturalsStreamDownWithNormal(n - 1).listen((data) {
controller.add(data);
});
}
return controller.stream;
}
data2 = 10
data2 = 9
data2 = 8
data2 = 7
data2 = 6
data2 = 5
data2 = 4
data2 = 3
data2 = 2
data2 = 1
这里常规的写法也比较复杂,而且还有 controller 不关闭的可能
还需要注意一下 streamController 的关闭
需要修改如下
Stream<int> naturalsStreamDownWithNormal(int n) {
var controller = StreamController<int>();
if (n > 0) {
controller.add(n);
naturalsStreamDownWithNormal(n - 1).listen((data) {
controller.add(data);
}, onDone: () {
print("close controller = $n");
controller.close();
});
} else {
controller.close();
}
return controller.stream;
}
这里加了一个 print 输出
close controller = 1
data2 = 10
close controller = 2
data2 = 9
close controller = 3
data2 = 8
close controller = 4
data2 = 7
close controller = 5
data2 = 6
close controller = 6
data2 = 5
close controller = 7
data2 = 4
close controller = 8
data2 = 3
close controller = 9
data2 = 2
close controller = 10
data2 = 1
可以调用类
使用call方法
class hello{
call(String a,String b,String c)=>'$a,$b,$c';
}
void main() async{
var str=hello();
}
}
类型定义
Function comp;
Sort(int f(Object a,Object b)){
comp=f;
}
//定义函数
typedef FF=int Function(Object a,Object b);
元数据 @开头
@deprecated 将要废弃的函数 @override
公有和私有变量函数
在Flutter中,没有像其他一些编程语言那样严格的公有函数和私有函数的概念。但是,Flutter遵循了一些命名约定来表示函数的可见性和预期用途。
在Flutter中,函数的可见性主要通过以下约定来控制:
以下划线 (_) 开头的函数名称被认为是私有的,只能在当前库中访问。这种函数通常被称为库私有函数。
没有以下划线开头的函数或变量名称被认为是公有的,可以在其他库中导入和使用。
这种约定是基于Dart语言的库和可见性规则。在Dart中,以下划线开头的标识符只在当前库中可见,而没有下划线开头的标识符在其他库中也可以访问。
虽然这种约定不像其他语言中的访问修饰符那样严格,但它提供了一种在Flutter项目中组织和控制函数可见性的方式。
举个例子:
// 公有函数
void publicFunction() {
// ...
}
// 私有函数(库私有)
void _privateFunction() {
// ...
}
// 公有变量 int publicVariable = 10; // 私有变量(库私有) int _privateVariable = 20;
implements 和 extends的区别
在 Dart 编程语言中,implements 和 extends 是两个用于建立类之间关系的关键字,但它们有着不同的用途和含义。
extends 关键字:
用于类的继承,表示一个类是另一个类的子类。
子类继承父类的所有属性和方法,并且可以重写父类的方法。
一个类只能继承一个父类,形成单继承关系。
示例:
class Animal {
void eat() {
print('Animal is eating.');
}
}
class Dog extends Animal {
void bark() {
print('Dog is barking.');
}
}
在上述示例中,Dog 类通过 extends 关键字继承了 Animal 类,因此 Dog 类拥有 Animal 类的 eat() 方法,同时也定义了自己的 bark() 方法。
implements 关键字:
用于接口的实现,表示一个类实现了一个或多个接口。
接口定义了一组方法的签名,类必须提供这些方法的具体实现。
一个类可以实现多个接口,用逗号分隔。
示例:
abstract class Flyable {
void fly();
}
abstract class Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable {
void fly() {
print('Duck is flying.');
}
void swim() {
print('Duck is swimming.');
}
}
在上述示例中,Duck 类通过 implements 关键字实现了 Flyable 和 Swimmable 两个接口,并提供了这些接口中定义的方法的具体实现。
区别:
extends 用于类的继承,表示 "是一个" 的关系。子类继承父类的属性和方法,并且可以添加自己的特定功能。
implements 用于接口的实现,表示 "具有某种能力" 的关系。类必须实现接口中定义的所有方法,以满足接口的契约。
一个类只能继承一个父类,但可以实现多个接口。
继承是一种 "是一个" 的关系,而接口实现是一种 "具有某种能力" 的关系。
总之,extends 用于类的继承,表示类之间的层次结构和共享的属性和行为;而 implements 用于接口的实现,表示类具有某些特定的能力或满足某些契约。它们在面向对象编程中扮演着不同但同样重要的角色。
Dart 异步处理
概念
异步函数里
await Future.delayed(Duration(seconds: 2));//该句是让出CPU时间便于执行前面有界面更新后续代码的操作
经典例子
import 'dart:async';
Future<int> addNumbers() async {
await Future.delayed(Duration(seconds: 10));
int a=1;
for(int i=0;i<99999;i++)
{
for(int j=0;j<9999;j++)
{
a=a+i;
}
}
print("over");
return 2 + 3;
}
void main() async {
await addNumbers();
//有await时输出顺序为 over 、main over
//无await时输出顺序为 main over 、over
print("main over");
}
改进代码:
import 'dart:async';
Future<int> addNumbers() async {
int a=1;
for(int i=0;i<9999;i++)
{
await Future.delayed(Duration(milliseconds: 5));
for(int j=0;j<99999;j++)
{
a=a+i;
}
}
print("over");
return 2 + 3;
}
void main() async {
await addNumbers();
//有await时输出顺序为 over 、main over
//无await时输出顺序为 main over 、over
print("main over");
}
Future是 Dart 中用于处理异步操作的核心类。它表示一个在未来某个时间点可能完成(或者可能产生错误)的操作。可以把它想象成一个承诺(promise),在未来会返回一个值或者一个错误。
异步操作在 Dart 中很常见,比如读取文件、网络请求等。这些操作通常需要花费一些时间来完成,而Future允许程序在等待这些操作完成的同时可以去做其他事情,而不是一直阻塞等待。
状态
一个Future有三种状态:未完成(uncompleted)、已完成并带有值(completed with a value)、已完成并带有错误(completed with an error)。
当一个Future被创建时,它最初处于未完成状态。随着异步操作的进行,它会转变为另外两种状态之一。一旦Future进入完成状态(无论是成功还是失败),它的状态就不能再改变。
创建 Future 的方式
使用Future构造函数:
可以直接使用Future构造函数来创建一个Future对象。例如:
import 'dart:async';
void main() {
Future<void> myFuture = Future(() {
print('This is an asynchronous operation.');
});
myFuture.then((_) {
print('The future is completed.');
});
}
在这个例子中,Future构造函数接受一个回调函数作为参数。这个回调函数就是异步操作的具体内容。当Future被调度执行时,这个回调函数会被调用。这里的myFuture是一个Future<void>类型,因为回调函数没有返回值。then方法用于在Future成功完成后执行一个操作。
使用async和await关键字(实际上也是基于Future):
当函数内部有异步操作时,可以将函数标记为async。在async函数内部,可以使用await来暂停函数的执行,直到一个Future完成并返回结果。例如:
import 'dart:async';
Future<int> addNumbers() async {
await Future.delayed(Duration(seconds: 2));
return 2 + 3;
}
void main() async {
int a=await addNumbers();
print("a: $a");
addNumbers().then((result) {
print('The result is $result');
});
print("exit");
}
在addNumbers函数中,首先使用Future.delayed来模拟一个延迟 2 秒的异步操作(这可以类比网络延迟等情况)。然后返回两个数的和。在main函数中,调用addNumbers函数会返回一个Future<int>,通过then方法来处理返回的结果。
耗时计算操作:
import 'dart:async';
Future<int> addNumbers() async {
await Future.delayed(Duration(seconds: 2));//该句是让出CPU时间便于执行前面有界面更新后续代码的操作,下面将给出完整的例子,相当于VC的自处理消息循环PeekMessage PostMessage
int a=1;
for(int i=0;i<99999;i++)
{
for(int j=0;j<999999;j++)
{
a=a+i;
}
}
return 2 + 3;
}
void main() async {
int a=await addNumbers();
print("a: $a");
addNumbers().then((result) {
print('The result is $result');
});
print("exit");
}
耗时操作界面前后提醒的例子
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
Future<int> addNumbers() async {
int a=1;
await Future.delayed(Duration(milliseconds: 200));//该句是让出CPU200毫秒时间便于执行前面有界面更新的操作,如setState等
for(int i=0;i<9999;i++)
{
for(int j=0;j<99999;j++)
{
a=a+i;
}
}
return 2 + 3;
}
void _incrementCounter() async {
setState(() {
_counter=_counter+10;
});
// 创建SnackBar
final snackBar1 = SnackBar(
content: Text('这是一个SnackBar前提示!'),
action: SnackBarAction(
label: '撤销',
onPressed: () {
// 可以在这里执行撤销操作
},
),
);
// 显示SnackBar
ScaffoldMessenger.of(context).showSnackBar(snackBar1);
int a=await addNumbers();//函数里执行了让出CPU时间便于执行setState与showSnackBar
setState(() {
_counter=_counter+10;
});
// 创建SnackBar
final snackBar2 = SnackBar(
content: Text('这是一个SnackBar后提示!'),
action: SnackBarAction(
label: '撤销',
onPressed: () {
// 可以在这里执行撤销操作
},
),
);
// 显示SnackBar
ScaffoldMessenger.of(context).showSnackBar(snackBar2);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
特别提醒:
在异步函数里使用setState改变状态或其他与界面相关的操作后,需要用await Future.delayed(Duration(milliseconds: 200));延时函数释放CPU,做界面更新操作
处理 Future 的结果
then方法:
用于在Future成功完成后获取并处理返回的值。它接受一个回调函数作为参数,这个回调函数的参数就是Future返回的值。例如:
import 'dart:async';
Future<String> getMessage() {
return Future<String>(() {
return "Hello, World!";
});
}
void main() {
getMessage().then((message) {
print(message);
});
}
这里getMessage函数返回一个Future<String>,在main函数中,通过then方法获取并打印了这个Future返回的字符串。
catchError方法:
用于处理Future执行过程中出现的错误。例如:
import 'dart:async';
Future<int> divideNumbers() {
return Future<int>(() {
throw FormatException('Cannot divide by zero');
});
}
void main() {
divideNumbers().catchError((error) {
print('An error occurred: $error');
});
}
在divideNumbers函数中,通过抛出一个异常来模拟出错的情况。在main函数中,使用catchError方法捕获并打印了这个错误。
whenComplete方法:
无论Future是成功完成还是出现错误,whenComplete方法中的回调函数都会被执行。例如:
import 'dart:async';
Future<int> multiplyNumbers() {
return Future<int>(() {
return 4 * 5;
});
}
void main() {
multiplyNumbers().whenComplete(() {
print('The future operation is finished.');
}).then((result) {
print('The result is $result');
});
}
在这里,先通过whenComplete方法在Future操作完成后(无论是成功还是失败,不过这里没有失败的情况)打印一条消息,然后通过then方法获取并打印结果
dart序列化类
在 Dart 中,可以使用 dart:convert 库中的 json 编码器和解码器将类序列化为 JSON 格式,并将其写入文件。然后,可以从文件中读取 JSON 数据,并将其反序列化为类的实例。
下面是一个详细的例子,演示了如何将类序列化到文件并从文件中反序列化:
import 'dart:convert';
import 'dart:io';
class Person {
String name;
int age;
Person(this.name, this.age);
// 将 Person 对象转换为 Map
Map<String, dynamic> toMap() {
return {
'name': name,
'age': age,
};
}
// 从 Map 中创建 Person 对象
factory Person.fromMap(Map<String, dynamic> map) {
return Person(
map['name'],
map['age'],
);
}
// 将 Person 对象转换为 JSON 字符串
String toJson() => json.encode(toMap());
// 从 JSON 字符串中创建 Person 对象
factory Person.fromJson(String source) => Person.fromMap(json.decode(source));
}
void main() async {
// 创建 Person 对象
Person person = Person('John Doe', 25);
// 将 Person 对象序列化为 JSON 字符串
String jsonString = person.toJson();
// 将 JSON 字符串写入文件
File file = File('person.json');
await file.writeAsString(jsonString);
// 从文件中读取 JSON 字符串
String fileContent = await file.readAsString();
// 将 JSON 字符串反序列化为 Person 对象
Person deserializedPerson = Person.fromJson(fileContent);
// 打印反序列化后的 Person 对象的属性
print('Name: ${deserializedPerson.name}');
print('Age: ${deserializedPerson.age}');
}
在上面的例子中:
我们定义了一个 Person 类,包含 name 和 age 两个属性。
在 Person 类中,我们实现了 toMap() 方法,将 Person 对象转换为 Map。
我们还实现了一个工厂构造函数 Person.fromMap(),用于从 Map 中创建 Person 对象。
我们实现了 toJson() 方法,将 Person 对象转换为 JSON 字符串。
我们实现了另一个工厂构造函数 Person.fromJson(),用于从 JSON 字符串中创建 Person 对象。
在 main() 函数中,我们创建了一个 Person 对象,并将其序列化为 JSON 字符串。
我们将 JSON 字符串写入文件 person.json。
然后,我们从文件中读取 JSON 字符串。
我们将读取的 JSON 字符串反序列化为 Person 对象。
最后,我们打印反序列化后的 Person 对象的属性。
通过这个例子,你可以看到如何将类序列化为 JSON 格式并写入文件,以及如何从文件中读取 JSON 数据并将其反序列化为类的实例
Flutter可以创建线程吗
在 Flutter 中,你可以使用 Dart 语言提供的异步编程特性,如 Future 和 Isolate,来实现并发和多线程。
Future: Future 允许你编写异步代码,但它并不会创建新的线程。相反,它在 Dart 的事件循环中运行,允许你在不阻塞 UI 的情况下执行异步操作。你可以使用 async 和 await 关键字来简化异步编程。
Isolate: Isolate 是 Dart 中真正的多线程解决方案。每个 Isolate 都在自己的线程中运行,有自己的内存堆,确保了不同 Isolate 之间的状态隔离。Isolate 之间通过消息传递进行通信。
下面是一个使用 Isolate 的简单示例:
import 'dart:async';
import 'dart:isolate';
void main() async {
// 创建一个 ReceivePort 用于接收来自新 Isolate 的消息
ReceivePort receivePort = ReceivePort();
// 创建一个新的 Isolate
await Isolate.spawn(isolateFunction, receivePort.sendPort);
// 监听来自新 Isolate 的消息
receivePort.listen((message) {
print('Received message from isolate: $message');
});
}
// 在新的 Isolate 中运行的函数
void isolateFunction(SendPort sendPort) {
// 向主 Isolate 发送消息
sendPort.send('Hello from the new isolate!');
}
在这个例子中,我们创建了一个新的 Isolate,并在其中运行 isolateFunction。主 Isolate 和新 Isolate 通过 SendPort 和 ReceivePort 进行通信。
需要注意的是,尽管 Flutter 支持多线程,但大多数情况下,你应该首先考虑使用 Future 和异步编程。只有在执行计算密集型任务或需要并行执行多个任务时,才应该使用 Isolate。过度使用 Isolate 可能会使你的代码复杂化,并引入不必要的开销。
Flutter 完整开发发布私有 / 公开 Package 教程
分三种包类型:
Dart Package:纯 Dart 逻辑,无原生代码(工具类、网络封装、工具函数)
Flutter Package:含 Flutter 组件、UI、页面,无 Android/iOS 原生代码
Flutter Plugin:需要 Android/iOS 原生交互(蓝牙、相机、定位、通道通信)
一、创建包项目
1. 创建 Flutter Package(UI 组件库,最常用)
bash
# 格式:flutter create --template=package 包名
flutter create --template=package flutter_common_ui
cd flutter_common_ui
2. 创建 Plugin(带原生代码)
bash
flutter create --template=plugin flutter_native_utils
3. 纯 Dart 工具包
bash
dart create dart_http_utils
二、目录结构讲解(flutter package)
plaintext
flutter_common_ui/
├── lib/
│ ├── flutter_common_ui.dart # 导出入口文件(对外暴露API)
│ └── src/ # 内部源码,不对外直接导入
│ ├── button.dart
│ ├── dialog.dart
│ └── utils.dart
├── example/ # 示例Demo,使用者参考怎么调用
│ └── lib/main.dart
├── pubspec.yaml # 包核心配置文件
└── README.md # 使用文档,必须写
规范:内部代码放 src,统一在入口导出
lib/src/button.dart
dart
import 'package:flutter/material.dart';
class CommonButton extends StatelessWidget {
final String text;
final VoidCallback onTap;
const CommonButton({super.key, required this.text, required this.onTap});
@override
Widget build(BuildContext context) {
return ElevatedButton(onPressed: onTap, child: Text(text));
}
}
lib/flutter_common_ui.dart(统一导出,用户只需要导入这一个文件)
dart
export 'src/button.dart';
export 'src/dialog.dart';
export 'src/utils.dart';
三、pubspec.yaml 完整标准配置(可直接复制)
yaml
name: flutter_common_ui
description: 通用UI组件库,包含弹窗、按钮、输入框等封装
version: 1.0.0
homepage: shturl.cc/DvUCiyVNIsIQeKbwWiG9tigCntgwt # 仓库地址
repository: shturl.cc/DvUCiyVNIsIQeKbwWiG9tigCntgwt.git
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: '>=3.10.0'
dependencies:
flutter:
sdk: flutter
# 依赖第三方包
flutter_screenutil: ^5.9.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
# 如果有图片/字体资源,在这里声明
assets:
- assets/images/
fonts:
- family: MyFont
fonts:
- asset: assets/fonts/my.ttf
关键字段说明:
version:版本号,遵循 主版本。次版本。补丁(1.0.0)
environment:限制使用者 Flutter/Dart 最低版本,避免兼容报错
description:简短说明包作用
repository:代码仓库地址(私有 / 公开都要写)
四、编写 example 示例项目
example/lib/main.dart 写完整调用示例,使用者一看就会:
dart
import 'package:flutter/material.dart';
import 'package:flutter_common_ui/flutter_common_ui.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: CommonButton(
text: "测试按钮",
onTap: () => print("点击"),
),
),
),
);
}
}
本地测试包:进入 example 文件夹执行 flutter run
五、三种分发方式(给别人使用)
方式 1:本地本地包(本地调试,无网络)
别人直接本地引入你的包文件夹
yaml
dependencies:
flutter_common_ui:
path: ../flutter_common_ui # 本地包相对路径
方式 2:Git 仓库依赖(最常用,私有 / 公开仓库都支持)
上传代码到 Gitee/GitHub,其他人 pubspec 直接拉取
yaml
dependencies:
flutter_common_ui:
git:
url: shturl.cc/DvUCiyVNIsIQeKbwWiG9tigCntgwt.git
# 指定tag版本(推荐,固定版本,避免代码变动)
tag: 1.0.0
# 或者指定分支
# ref: main
拉取更新:flutter pub get
方式 3:发布到 pub.dev(公开免费包,所有人可搜索)
检查包规范,无报错
bash
dart pub publish --dry-run
正式发布
bash
dart pub publish
注意:pub.dev 公开,不能放公司私有业务代码;私有包不建议上传。
六、包开发规范(给别人用必须遵守)
1. 导出规范
所有内部逻辑放 src,禁止使用者直接导入 src 下文件
统一在包根文件 包名.dart export 所有对外类、方法
2. 代码规范
全部添加文档注释,别人 IDE 悬浮能看到说明
dart
/// 通用圆角按钮
/// [text] 按钮文字
/// [onTap] 点击回调
class CommonButton
参数必填用 required,可选参数给默认值
不要写死业务数据,全部外部传参,保证通用性
3. 资源处理
图片、字体放在包内 assets,必须在 pubspec 声明
使用包内资源路径格式:AssetImage("packages/包名/assets/images/xx.png")
4. 版本迭代规则
修复 bug:补丁位 +1 1.0.1
新增功能、兼容旧代码:次版本 1.1.0
不兼容破坏性修改:主版本 2.0.0
每次修改打 git tag 对应 version
5. README.md 必备内容
功能介绍
安装方式(git/pub/path 三种引入代码)
最简使用示例代码
API 文档(组件、方法参数说明)
更新日志
七、插件专属补充(含 Android/iOS 原生)
Android 代码放在 android/src/main/java/包名/
iOS 代码放在 ios/Classes/
MethodChannel 通道封装,dart 层暴露简单调用方法,屏蔽原生复杂逻辑
权限说明写在 README,告知使用者需要在 AndroidManifest、Info.plist 配置权限
八、常见坑
资源图片不显示:图片路径必须加 packages/包名/
版本冲突:合理设置 environment 最低 SDK 版本,依赖库版本用 ^ 兼容
Git 更新不生效:执行 flutter pub cache repair 清理缓存
导入 src 下文件警告:严格统一入口导出规范
打包插件原生报错:Android gradle、iOS pod 版本与项目对齐
九、快速交付给他人完整流程
完成组件 / 工具代码,统一导出入口
写 example 完整 demo,自测运行无 bug
完善 README,写引入代码 + 使用示例
提交代码到 git,打 tag 版本号
发给对方 pubspec.yaml 的 git 依赖代码
对方执行 flutter pub get 即可使用
Person person=Person();
print("person,runtimeType:"+person.runtimeType.toString());
输出:
person,runtimeType:Person
Person person=Person();
dynamic dy=person;
Person tperson=dy;
示例 1:变量存储任意数据
dart
void main() {
dynamic data;
data = 100;
print(data.runtimeType); // int
print(data + 5); // 105
data = "Flutter";
print(data.runtimeType); // String
print(data.length); // 7
data = [1, 2, 3];
print(data[0]); // 1
data = false;
print(data ? "真" : "假"); // 假
}
示例 2:函数参数 / 返回值动态类型
适用于接口返回未知结构、通用工具方法
dart
// 参数、返回值都是 dynamic,接收任意类型
dynamic printValue(dynamic value) {
print("类型:${value.runtimeType},值:$value");
return value;
}
void main() {
printValue(666);
printValue("测试字符串");
printValue([10, 20]);
}
示例 3:Json 解析最常用场景(Flutter 高频)
后端返回 JSON 嵌套数据,字段类型不固定,用 dynamic 承接
dart
void main() {
// 模拟后端json
Map<String, dynamic> json = {
"id": 1,
"name": "张三",
"age": 20,
"isVip": true,
"tags": ["学生", "程序员"]
};
// Map 的 value 是 dynamic,任意类型都能存
dynamic name = json["name"];
dynamic age = json["age"];
print("$name,年龄$age");
dynamic list = json["tags"];
print(list[0]);
}
安全使用 dynamic 的配套方案
1. runtimeType 获取真实类型
dart
dynamic val = 99;
print(val.runtimeType); // int
2. is /is! 类型判断(推荐)
dart
void checkType(dynamic data) {
if (data is int) {
print("数字:${data + 10}");
} else if (data is String) {
print("字符串长度:${data.length}");
} else {
print("其他类型");
}
}
3. as 强制类型转换
dart
dynamic text = "flutter";
String s = text as String;
print(s.toUpperCase());
创建Future
几种方法创建
Future();
Future.microtask()
Future.sync() //立即执行
Future.value()
Future.delayed()
Future.error()
示例 1:基础延时 Future(模拟异步任务)
dart
// 返回Future,2秒后返回字符串
Future<String> getMsg() {
return Future.delayed(const Duration(seconds: 2), () {
return "数据加载完成";
});
}
void main() async {
// 写法1:then链式
getMsg().then((res) {
print("then接收:$res");
}).catchError((err) {
print("出错:$err");
});
// 写法2:async await(推荐)
try {
String result = await getMsg();
print("await接收:$result");
} catch (e) {
print("异常:$e");
}
}
示例 2:模拟网络请求(Flutter 最常用)
dart
import 'dart:io';
// 模拟接口请求,返回Map
Future<Map<String, dynamic>> fetchUser() async {
// 模拟网络延迟
await Future.delayed(const Duration(seconds: 1));
// 模拟http返回json
return {
"id": 1,
"name": "张三",
"age": 22
};
}
void requestDemo() async {
try {
Map<String, dynamic> user = await fetchUser();
print("用户名:${user['name']}");
} catch (e) {
print("网络请求失败:$e");
}
}
示例 3:Future 异常抛出与捕获
dart
Future<int> divide(int a, int b) async {
await Future.delayed(const Duration(milliseconds: 500));
if (b == 0) {
// 抛出异常,外部catch捕获
throw Exception("除数不能为0");
}
return a ~/ b;
}
void errorDemo() async {
try {
int res = await divide(10, 0);
print(res);
} catch (e) {
print("捕获错误:$e");
}
}
示例 4:Future.wait 并发多个异步(同时请求多个接口)
多个 Future 一起执行,全部完成再返回结果,提升速度,不用串行等待。
dart
// 两个独立异步任务
Future<String> getBanner() => Future.delayed(const Duration(seconds: 1), () => "轮播图数据");
Future<List> getGoods() => Future.delayed(const Duration(seconds: 2), () => [1,2,3]);
void batchRequest() async {
// 并发执行
List results = await Future.wait([
getBanner(),
getGoods()
]);
print("轮播:${results[0]}");
print("商品:${results[1]}");
}
示例 5:Future.any 取第一个完成的异步
多个任务,谁先执行完就取谁的结果,其余舍弃。
dart
Future<String> taskA() => Future.delayed(const Duration(seconds: 3), () => "A任务");
Future<String> taskB() => Future.delayed(const Duration(seconds: 1), () => "B任务");
void anyDemo() async {
String fastResult = await Future.any([taskA(), taskB()]);
print("最先完成:$fastResult"); // B任务
}
示例 6:Future.delayed 延时执行(弹窗倒计时、延迟操作)
dart
void delayOperate() async {
print("等待3秒...");
await Future.delayed(const Duration(seconds: 3));
print("延时结束,执行操作");
}
示例 7:Flutter 页面中 FutureBuilder(UI 渲染异步数据)
页面加载网络数据,自动显示加载中、成功、失败 UI,Flutter 核心组件
dart
import 'package:flutter/material.dart';
Future<String> loadData() async {
await Future.delayed(const Duration(seconds: 2));
return "接口返回页面数据";
}
class FutureBuilderPage extends StatelessWidget {
const FutureBuilderPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<String>(
future: loadData(), // 异步任务
builder: (ctx, snapshot) {
// 加载中
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
// 请求失败
if (snapshot.hasError) {
return Center(child: Text("出错:${snapshot.error}"));
}
// 请求成功
if (snapshot.hasData) {
return Center(child: Text(snapshot.data!));
}
return const SizedBox();
},
),
);
}
}
示例 8:串行执行多个异步(按顺序执行,上一个完成再下一个)
dart
Future step1() async {
await Future.delayed(const Duration(seconds: 1));
print("步骤1完成");
}
Future step2() async {
await Future.delayed(const Duration(seconds: 1));
print("步骤2完成");
}
Future step3() async {
await Future.delayed(const Duration(seconds: 1));
print("步骤3完成");
}
void serialTask() async {
await step1();
await step2();
await step3();
print("全部执行完毕");
}
关键总结
普通异步优先 async/await + try catch,代码线性易懂;
多接口并发用 Future.wait,节省时间;
页面渲染异步数据固定搭配 FutureBuilder;
延时操作使用 Future.delayed;
所有网络、本地存储、原生通道返回值几乎都是 Future。
*如有一个相当消耗时间的函数 test (int a,int b) 如何使用 Future 调用它*
耗时同步函数用 Future 两种方案(建议用Compute)
核心问题说明
如果 test(a,b) 是纯同步阻塞代码(大量循环、复杂计算、大数据解析,会卡住 UI),直接包 Future 没用,依然阻塞主线程;分两种场景处理:
场景 1:简单耗时、短计算(仅包装成 Future,不隔离线程)
只是把同步函数返回值套上 Future,仍在主线程运行,会卡顿 UI,仅适配轻度耗时。
dart
// 耗时同步函数
int test(int a, int b) {
int res = 0;
// 模拟重度循环计算
for (int i = 0; i < 100000000; i++) {
res += a + b;
}
return res;
}
// 方式1:Future((){}) 包装
Future<int> runTest1(int a, int b) {
return Future(() => test(a, b));
}
// 方式2:async 函数简写(推荐写法)
Future<int> runTest2(int a, int b) async {
return test(a, b);
}
// 调用
void main() async {
int result = await runTest2(10, 20);
print(result);
}
场景 2:重度耗时,防止 UI 卡顿(必须用 Isolate 新开子线程)
Flutter/Dart 主线程阻塞会造成页面卡死、ANR,重度计算必须丢到子 Isolate,结合 Future 使用:
完整可运行示例
dart
import 'dart:isolate';
// 耗时同步逻辑
int test(int a, int b) {
int res = 0;
for (int i = 0; i < 100000000; i++) {
res += a * b;
}
return res;
}
// 封装:在子线程执行test,返回Future
Future<int> computeTest(int a, int b) async {
// compute 是封装好的Isolate工具,参数1:子线程执行函数,参数2:传参
return await compute((List<int> args) {
int x = args[0];
int y = args[1];
return test(x, y);
}, [a, b]);
}
// 页面调用示例(Flutter)
void exec() async {
try {
// 不会阻塞UI
int val = await computeTest(5, 8);
print("计算结果:$val");
} catch (e) {
print("计算异常:$e");
}
}
补充:compute 来自 foundation,Flutter 项目直接导入
改成纯isolate
import 'dart:isolate';
int test(int a, int b) {
int res = 0;
for (int i = 0; i < 100000000; i++) {
res += a * b;
}
return res;
}
// 单次任务入口
void singleIsolateTask(List<Object> args) {
SendPort sendPort = args[0] as SendPort;
int a = args[1] as int;
int b = args[2] as int;
int ret = test(a, b);
sendPort.send(ret);
}
Future<int> computeTest(int a, int b) async {
ReceivePort receive = ReceivePort();
// 一次性传递 发送端口 + 参数a + 参数b
await Isolate.spawn(singleIsolateTask, [receive.sendPort, a, b]);
// 等待唯一返回值
int res = await receive.first;
receive.close();
return res;
}
void main() async {
int r = await computeTest(2, 3);
print(r);
}
和 compute 的区别
1.compute:Flutter 封装,开箱即用,内部自动管理 Isolate 生命周期,适合绝大多数场景;
2.原生 Isolate:自由度更高,支持双向多次通信(持续收发数据),适合流式计算、持续后台任务;
3.缺点:原生 Isolate 需要手动管理端口、手动销毁,模板代码更多,容易内存泄漏。
dart
import 'package:flutter/foundation.dart';
场景 3:如果 test 内部本身包含异步操作(IO、网络)
函数自带 async,直接正常调用即可,天然不阻塞 UI:
dart
// 内部有异步,本身就是Future函数
Future<int> test(int a, int b) async {
// 模拟文件/网络耗时
await Future.delayed(Duration(seconds: 2));
return a + b;
}
// 直接await调用
void call() async {
int ans = await test(1, 2);
}
关键区分避坑
纯同步循环计算:只用 Future 包装 → 主线程卡死,必须 compute/Isolate
IO / 网络延时异步逻辑:直接 async/await Future,无需开子线程
compute 传参限制:只能传可序列化简单数据(数字、字符串、List、Map,不能传 Widget、闭包、对象实例)
FutureBuilder 页面完整实战(UI 加载状态展示)
dart
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
int test(int a, int b) {
int res = 0;
for (int i = 0; i < 100000000; i++) res += a + b;
return res;
}
Future<int> calc(int a, int b) => compute((args) => test(args[0], args[1]), [a, b]);
class CalcPage extends StatelessWidget {
const CalcPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<int>(
future: calc(10, 20),
builder: (ctx, snap) {
if (snap.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snap.hasError) return Text("错误:${snap.error}");
return Center(child: Text("计算结果:${snap.data}"));
},
),
);
}
}