第一章 Flutter环境搭建
在第一章,我们将学习如下内容
1.1 配置Flutter环境 1.下载Flutter SDK 打开Flutter中文官网:https://flutterchina.club/setup-macos/
选择下载SDK,我们选择稳定版:Stable channel (macOS)
下载速度比较慢,我们把链接复制到迅雷里面下载
2.配置Flutter路径: 1 export PATH=$PATH:/Library/dev/flutter/bin
3.配置Flutter国内镜像: 打开终端,输入:
配置镜像地址:https://flutter.dev/community/china
1 2 3 #Flutter export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
5.检查Flutter环境
如果提示:Some Android licenses not accepted. To resolve this, run: flutter doctor –android-licenses,则允许一下:
1 flutter doctor --android-licenses
启动IOS模拟器:
4.创建项目
1.2 Flutter for Desktop 官方文档:https://flutter.dev/desktop
Windows版 环境:Visual Studio 2019,添加C++支持
添加桌面支持,打开控制台:
1 2 3 flutter config --enable-windows-desktop flutter config --enable-macos-desktop flutter config --enable-linux-desktop
查看是否有支持的桌面:
1 2 3 4 5 6 7 flutter devices 1 connected device: Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.18362.1082] macOS (desktop) • macos • darwin-x64 • macOS 11.2 20D64 darwin-x64 Linux (desktop) • linux • linux-x64 • Linux
检测环境是否正常,以下是Windows
1 2 3 4 5 6 7 8 9 PS > flutter doctor Doctor summary (to see all details, run flutter doctor -v): [√] Flutter (Channel beta, 1.27.0-1.0.pre, on Microsoft Windows [Version 10.0.19042.782], locale en-AU) [√] Android toolchain - develop for Android devices (Android SDK version 30.0.3) [√] Chrome - develop for the web [√] Visual Studio - develop for Windows (Visual Studio Community 2019 16.7.7) [√] Android Studio (version 4.1.0) [√] VS Code (version 1.51.1) [√] Connected device (3 available)
支持已有项目:
1 flutter create --platforms=windows,macos,linux .
运行:
1 2 3 flutter run -d windows flutter run -d macos flutter run -d linux
修改桌面窗体标题:
1 2 3 4 5 6 dependencies: window_size: git: url: git://github.com/google/flutter-desktop-embedding.git path: plugins/window_size ref: fd519be1e8398c6b6c2062c2447bac960a71bc02
在代码中:
1 2 3 import 'package:window_size/window_size.dart'; setWindowTitle("Custom window title");
1.3 Flutter for Web 官方文档:https://flutter.dev/docs/get-started/web
检测是否有chrome环境:
1 2 3 flutter devices 1 connected device: Chrome (web) • chrome • web-javascript • Google Chrome 88.0.4324.150
支持已有项目:
运行:
1 2 普通运行: flutter build web 指定端口和ip运行:flutter run -d chrome --web-port=8080 --web-hostname=127.0.0.1
1.4 响应式App 官网文档:https://flutter.dev/docs/development/ui/layout/adaptive-responsive
第二章 Dart基础 Dart入门参考学习资料:https://www.dartcn.com/guides/language/language-tour
1.基础数据类型
1.数字类型 num是数字类型的父类,有两个子类,int和double
求绝对值:abs
类型转换:toInt(),round()
示例代码:
1 2 3 4 5 6 7 8 9 10 num num1 = -1.5; int num2 = 1; double num3 = 1.4; print("num1=$num1,num2=$num2,num3=$num3"); //求绝对值 print("$num1的绝对值是${num1.abs()}"); //数据类型转换 print("$num1的转为整数是${num1.toInt()}"); print("$num1四舍五入是${num1.round()}");
转String,保留小数位数
1 toStringAsFixed(位数),如:保留1位小数 如:5 -> "5.0"
其他知识点:
1 2 3 4 5 var a = 10 /5; print(a); //2.0 var b = 9.1~/5; print(b); //1
是否是偶数:isEven(),是否是奇数:isOdd()
round:四舍五入 floor:向下取整 ceil:向上取整
2.字符串 使用String定义,字符串拼接类似kotlin,也可以使用Java方式拼接
1 2 3 4 5 String str1 = '字符串',str2="双引号"; String str3 = "str1:$str1 str2:$str2"; print("str1=$str1");//演示字符串拼接 print("str2=$str2"); print("str3=$str3");
可以使用单引号,双引号,三引号定义字符串,以及使用r’字符串’定义一个不转义的字符串
1 2 3 4 var str1 = "hello \n world"; var str2 = r'hello \n world'; print(str1); print(str2); // 不转义,直接输出,结果:hello \n world
常用方法,自己尝试,类似Java的用法
1 2 3 4 5 String str = "Hello WROLD"; print(str.toUpperCase()); print(str.toLowerCase()); print(str.substring(0,5)); print(str.indexOf('e'));
字符串乘法:
1 2 var str3 = "love you " * 5; print(str3);//love you love you love you love you love you
==比较的是内容
[]取字符串索引位置的字符
1 2 3 var str4 = "hello"; print(str4[0]);//h print(str4.length);//5
判断内容是否为空:
1 2 String str5 = ""; print(str5.isEmpty);//只能判断内容是否为空,不能判断是否为null
split:拆分字符串,拆分结果是列表,这里和Java不太一样
1 2 3 String str6 = "this is my lover"; print(str6.split(" "));//[this, is, my, lover] print(str6.split(" ").runtimeType);//List<String>
3.布尔 dart中定义布尔类型,使用bool
1 2 3 4 bool success = true; bool failure = false; print(success || failure); print(success && failure);
4.List集合 在Dart中,list和数组是一个概念
1 2 3 4 5 创建list:var list = [1,2,3] 创建不可变lsit: var list = const[1,2,3] 构造创建:var list = new List()
使用List定义集合
1 2 3 4 List list = [1,2,3,"哈哈"];//不限制泛型,泛型就是dynamic print(list); List<int> list2 = [1,3,5,7,9];//限制泛型 print(list2);
注意:泛型不同,最好不要赋值,编译会报错
1 2 list = list2;//编译正常,int是dynamic的子类型 list2 = list;//编译错误,type 'List<dynamic>' is not a subtype of type 'List<int>'
集合的另一种生成方式:
1 2 List list4 = List.generate(3, (index) => index * 2); print(list4);//[0, 2, 4]
常用操作:
length:获取长度
remove(),clear()
indexof(),lastIndexOf()
sort():排序 sublist()
shuffle():打乱 asMap():转成map forEach()
添加:add(),addAll(),insert()
1 2 3 4 List list3 = []; list3.add("hello"); list3.add("world"); list3.addAll(list);
集合遍历:
方式一:
1 2 3 for (var value in list4) { print("$value"); }
方式二:
1 2 3 list4.forEach((value){ print(value); });
方式三:
1 2 3 4 for(int i=0; i<list4.length;i++){ int a = list4[i]; print("$a"); }
5.Map集合 创建map:var language = {‘first’:’Dart’,’sencond’:’Java’};
创建不可变Map:var language2 = const{‘first’:’Dart’,’sencond’:’kotlin’};
构造创建:var language3 = new Map();
定义map:
1 Map map1 = {"1":"小明","2":"小王"};
遍历map
1 2 3 map1.forEach((k,v){ print("$k -- $v"); });
常用操作:
[] ,length
isEmpty(), isNotEmpty()
keys() , values()
containsKey(),containsValue()
remove()
forEach()
6.dynamic,var,Object Daynamic:动态数据类型,编译时不会揣测数据类型,但是运行时会推断
是这样的坏处就是会让dart的语法检查失效,所以有可能会造成混乱而不报错
所以不要直接使用dynamic
1 2 3 4 5 6 7 dynamic x = "xxx"; print(x);//xxx print(x.runtimeType);//String x = 1; print(x);//1 print(x.runtimeType);//int
var:自动推断数据类型,一旦定义,不可修改数据类型
1 2 var s = "hello"; s = 1;//编译错误,不能再次修改数据类型
Object:所有dart对象的基类
区别在于:dynamic 不检查语法,如:调用不存在的方法也不会报错,运行时才报错,
Object 在编译时就会校验,如:调用不存在的方法,无法通过编译检测
1 2 3 4 5 6 7 8 9 void main(){ // var a;//无法推断出数据类型,那么类型就是dynamic,所以才能修改数据类型 // a = 10; // a = "hello"; var a = 10; // a = "hello";//报错,一开始自动推断出数据类型int,不能修改数据类型 }
2.运算符 算术运算符
加减乘除: + , - , * , / , ~/ , %
递增递减:++ , – ,
关系运算符
注意:==是用于判断内容是否相等
逻辑运算符:
赋值运算符:
+= , -= , *= , /= , ~/=
??= 如果没有赋值,则赋值
1 2 3 var a; a ??= "hello"; print("a = $a"); //a = hello
条件运算符
三目运算符:同Java
表达式1 ?? 表达式2 :表达式1没有值则使用表达式2的值
1 2 int gender = 1; String str = gender == 1 ? "male" : "female";
3.语句 条件语句:
循环语句:
for
while do while,break continue
switch语句同Java
4.方法
1 2 3 4 5 var str = printPerson("Jack", 18); print(str); ///定义方法,返回值类型可以省略,方法参数类型可以省略 printPerson(name,age) => "$name and $age";
方法参数,可选参数可以使用{},并且可选参数必须放在最后边
1 2 3 4 5 6 7 ///定义方法,两个参数都是可选参数 printPerson2({String name,int age}){ print("name = $name, age = $age"); } ///调用方法 printPerson2();//name = null, age = null printPerson2(name: "lucy",age:18 );//name = lucy, age = 18
1 2 3 4 ///默认参数 printPerson3({String name = "Jack",int age = 100}){ print("name = $name, age = $age"); }
闭包
闭包是一个方法
闭包是定义在 其他方法内部的方法
闭包会持有外部方法的局部变量(这个概念不太好理解)
面向对象 类和对象
使用关键字class声明一个类
使用关键字new创建一个对象,new可省略
所有对象都继承Object
1.属性和方法
属性默认会生成getter和setter方法
使用final声明的属性只有getter方法
方法不能重载,如:work() work(int n) 这样两个方法会报错
2.类和成员的可见性:
3.计算属性 :概念:计算属性的值是通过计算得来的,而不是本身存储的,举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Rectangle { ///定义两个属性 num width, height; ///定义计算属性,它的值是长和宽的乘积 num get area { return width * height; } ///简写形式 num get area2 => width * height;//num返回值 get表示是getter are2属性名 } void main() { var rect = new Rectangle(); rect.width = 20; rect.height = 10; print(rect.area);//200 print(rect.area2);//200 }
4.构造方法 :
如果没有定义构造方法,则会有一个默认的构造方法
如果定义了构造方法,则默认构造方法无效
构造方法不能重载
1 2 3 4 5 6 7 8 9 10 11 12 13 class Person{ String name; int age; ///类似Java的写法 // Person(String name,int age){ // this.name = name; // this.age = age; // } ///dart中提供的简化写法,方法体可以省略 Person(this.name, this.age){}; }
疑问:构造方法不能重载,那么如何实现多个构造方法呢?这里可以使用命名构造方法
5.命名构造方法 :使用类名.方法的形式实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Person{ String name; int age; //第一个构造方法 Person(this.name, this.age); ///命名构造方法 Person.withName(this.name); work(){ print("$name is working"); } } var p = new Person.withName("lucy"); p.work();//lucy is working
6.常量构造方法 :
如果一个类是不可变状态,可以把对象定义为编译时常量
使用const声明构造方法,并且把所有变量都定义为final
1 2 3 4 5 6 7 8 9 10 11 class Person{ ///使用final修饰 final String name; final String age; ///构造方法使用const const Person(this.name, this.age); } void main(){ const p = const Person("toM", "18");//const Person()的const可以省略 }
7.工厂构造方法:
工厂构造方法类似于设计模式中的工厂模式
在构造方法前添加关键字factory 实现一个工厂构造方法
在工厂构造方法中可以返回对象
8.初始化列表
初始化列表会在构造方法体执行之前执行
使用逗号分隔初始化表达式
初始化列表常用于设置final变量的值
1 Person.withName(this.name) : gender = "male";
9.静态成员
使用static关键字来实现类级别的常量和函数
静态成员不能访问非静态成员,非静态成员可以访问静态成员
类级别的常量需要使用static const声明
10.对象操作符:
条件成员访问: ?. 这个感觉类似kotlin中的?. 可以防止空指针报错
1 2 3 Person p; // p.work();//The method 'work' was called on null. p?.work();
类型转换:as
是否是指定类型:is 和 !is,并且if判断后,类型会自动转换
级联操作:.. 类似于链式编程
1 2 3 4 5 6 7 ///普通赋值 // p.name = "lucy"; // p.age = 18; ///级联操作 p..name = "lucy" ..age = 18;
对象call方法:如果一个类实现了call方法,则该类的对象可以作为方法使用
继承 基础知识:
使用关键字extends继承一个类
子类会继承父类可见的属性和方法
子类能够重写父类的方法,getter,setter
单继承,多态
抽象类
基础知识:
抽象类使用abstract关键字修饰
抽象方法不需要用abstract修饰
接口: 基础知识
在Dart中,类就是接口
实现接口使用关键字:implements
建议使用抽象类来作为接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ///定义了一个接口类 abstract class FlyAble{ ///包含一个抽象方法fly void fly() } ///鸟实现飞的接口,可以飞 class Bird implements FlyAble{ @override void fly() { print("鸟可以飞"); } } ///鸟人实现飞的接口,可以飞 class FlyMan implements FlyAble{ @override void fly() { print("鸟人可以飞"); } } void main(){ var p1 = Bird(); p1.fly(); var p2 = FlyMan(); p2.fly(); }
Mixins: 基础知识:
在dart中实现多继承的方式,使用with
1 2 3 4 ///D继承A,同时也拥有C和B的方法 class D extends A with C,B{ }
这里的B,C就称为Mixin类
Mixin类不能显示声明构造方法
作为Mixin的类,只能继承自Object
操作符: 基础知识:
可以自定义一些操作符,举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { int age; Person(this.age); ///操作符重载 bool operator >(Person p) { return this.age > p.age; } } var p1 = Person(21); var p2 = Person(20); print(p1 > p2);//true
枚举
基础知识:
1 2 3 4 5 6 enum Season{ spring, summer, autumn, winter }
dart枚举有一个属性:index,从0开始
dart中枚举比较简单,不能定义方法
泛型: 和Java中泛型差不多的用法
编程技巧:
使用?.避免空指针
使用??设置默认值 如: a = str??”暂无值”
第三章 Flutter快速入门 3.1 快速入门 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 ///使用import 类似Java中的导包 import 'package:flutter/material.dart'; ///程序的主入口main函数,执行了runApp()方法 void main() => runApp(App()); ///StatelessWidget:无状态的组件 ///App是我们程序入口的第一个组件,在build方法中,返回了一个MaterialApp(材料设计)的组件 class App extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( ///theme:页面的主题 ///ThemeData.dark ThemeData.light theme: ThemeData( ///primarySwatch主色版,即设置标题栏颜色 primarySwatch: Colors.yellow ), home: Scaffold(///Scaffold 脚手架,包含了appBar,底部导航,侧面抽屉栏等 appBar: AppBar( title: Text('Ivan'), elevation: 0,//设置顶部阴影 ), body: Hello(), ), ); } } ///这里是我们自定义的一个小组件 ///在屏幕中央输出一段话:Hello World class Hello extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: Text('Hello World', style: TextStyle( fontSize: 40, fontWeight: FontWeight.bold, color: Colors.black87 ), ), ); } }
我们再来快速实现一个列表,首先准备数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Post{ final String title; final String author; final String imageUrl; const Post({ this.title, this.author, this.imageUrl }); } final List<Post> posts = [ Post( title: 'Candy Shop', author: 'Mohamed Chahin', imageUrl: 'https://resources.ninghao.org/images/candy-shop.jpg', ), Post( title: 'Childhood in a picture', author: 'Mohamed Chahin', imageUrl: 'https://resources.ninghao.org/images/childhood-in-a-picture.jpg', ), Post( title: 'Contained', author: 'Mohamed Chahin', imageUrl: 'https://resources.ninghao.org/images/contained.jpg', ), Post( title: 'Dragon', author: 'Mohamed Chahin', imageUrl: 'https://resources.ninghao.org/images/dragon.jpg', ), Post( title: 'Free Hugs', author: 'Mohamed Chahin', imageUrl: 'https://resources.ninghao.org/images/free_hugs.jpg', ), ];
然后编写列表界面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import 'package:flutterdart/ninghao/model/Post.dart'; import 'package:flutter/material.dart'; ///ListView的使用 ///使用ListView.builder来生成每一项 ///标题使用的属性:Theme.of(context).textTheme.title ///作者使用的属性:Theme.of(context).textTheme.subhead class Home extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[100], appBar: AppBar( title: Text("ListView的使用"), ), body: ListView.builder(itemBuilder: _itemBuilder, itemCount: posts.length,), ); } ///生成每一个item Widget _itemBuilder(BuildContext context,int index){ return Container( color: Colors.white, margin: EdgeInsets.all(8.0), child: Column( children: <Widget>[ Image.network(posts[index].imageUrl), SizedBox(height: 16.0,), Text(posts[index].title,//标题 style: Theme.of(context).textTheme.title,), Text(posts[index].author,//作者 style: Theme.of(context).textTheme.subhead,), SizedBox(height: 16,) ], ), ); } }
3.2 路由 跳转页面:
1 2 3 //跳转页面 Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) => Page('About')));
返回上一页:
1 Navigator.pop(context);//返回到上一页
带名字的路由:
1 2 3 4 //定义路由 routes: { '/about': (context) => Page('About'), },
1 Navigator.pushNamed(context, '/about');
设置初始路由:initialRoute,注意和home不能同时出现
1 2 3 4 5 6 7 // home: NavigatorDemo(), initialRoute: '/', //定义路由 routes: { '/': (context) => NavigatorDemo(), //根路由 '/about': (context) => Page('About'), },
3.3 输入 TextField
一:属性样式
二:监听事件
三:获取表单数据
定义变量,并在触发保存的时候,存储数据
1 2 3 4 5 6 String username, password; TextFormField( decoration: InputDecoration(labelText: 'Password'), onSaved: (value)=> password = value,//存储数据 ),
定义key,获取数据的时候,调用save()
1 2 3 4 5 6 7 8 //定义key final key = GlobalKey<FormState>(); //key设置到表单上 Form( key: key, } //保存 key.currentState.save(); //保存表单
四:验证表单数据
给TextField添加验证器
1 2 3 4 5 6 7 8 9 validator: validatorPassword, //验证数据,如果数据通过验证,返回null String validatorPassword(String value) { if(value.isEmpty){ return "请填写密码"; } return null; }
保存的时候,调用验证器
1 key.currentState.validate();//验证表单数据
自动验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool isAutoValidate = false; TextFormField( autovalidate: isAutoValidate, ), void submitFromData() { //验证表单数据 if (key.currentState.validate()) { debugPrint("username = $username ,password = $password"); key.currentState.save(); //保存表单 } else { setState(() => isAutoValidate = true); } }
第四章 基础控件 1.AppBar
Title:标题
Leading:标题前面的小部件,通常和抽屉栏关联,可以使用IconButton
1 leading: IconButton(icon: Icon(Icons.menu), onPressed: null),
Actions:一个widget数组,在标题栏的右边,比如我们可以放一个搜索图标:
1 2 3 actions: <Widget>[ IconButton(icon: Icon(Icons.search), onPressed: null) ],
通常使用iconButton,对于不常见的操作,使用PopupMenuButton
1 2 3 4 5 6 7 8 9 10 11 12 13 actions: <Widget>[ IconButton(icon: Icon(Icons.search), onPressed: () {}), PopupMenuButton( onSelected: (int value){ print(value);//获取到选中的标识 }, itemBuilder: (BuildContext ctx) => [ PopupMenuItem(child: Text('A'),value: 1,), //需要使用value来作为唯一标识 PopupMenuItem(child: Text('B'),value: 2,), ], icon: Icon(Icons.more_horiz), ) ],
bottom:标题栏的底部,一般可以放TabBar:类似于Android中的TabLayout,必须设置控制器
1 2 3 4 5 6 7 8 9 10 11 12 bottom: TabBar(tabs: [ Tab(icon: Icon(Icons.local_florist),), Tab(icon: Icon(Icons.change_history),), Tab(icon: Icon(Icons.directions_bike),) ]), ///可以使用默认控制器:DefaultTabController,length必须和TabBar中的个数一致 TabBarView:用于显示指定Tab的内容,这里我们展示到Scaffold的body里面 body: TabBarView(children: [ Icon(Icons.local_florist,size: 128,color: Colors.black12,), Icon(Icons.change_history,size: 128,color: Colors.black12,), Icon(Icons.directions_bike,size: 128,color: Colors.black12,), ]),
2.TabBar
unselectedLabelColor:未选中时候标签颜色
indicatorColor:指示器的颜色
indicatorSize:指示器的样式,有两种选择:tab,label
indicatorWeight:指示器的高度
3.theme:主题 themeData:
primarySwatch标题栏的颜色
highlightColor按钮点击时候的高亮颜色
splashColor:水波纹颜色
3.drawer:属于Scaffold里面的标签
左部抽屉使用drawer,右边则使用endDrawer
4.Drawer: 一般使用listview来排版布局,
顶部可以使用DrawerHeader,下边使用ListTitle来布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 drawer: Drawer( child: ListView( padding: EdgeInsets.zero,//这里必须设置,顶部padding才为零,看起来正常 children: <Widget>[ DrawerHeader( child: Text("header".toUpperCase()), decoration: BoxDecoration(color: Colors.grey[100]), ), ListTile( title: Text("Message",textAlign: TextAlign.center,), trailing: Icon(Icons.message), ), ListTile( title: Text("Favourite",textAlign: TextAlign.center,), trailing: Icon(Icons.favorite), ), ListTile( title: Text("Settings",textAlign: TextAlign.center,), trailing: Icon(Icons.settings), ) ], ), ),
抽屉的关闭:onTap: () => Navigator.pop(context),
使用系统提供的一个header:UserAccountsDrawerHeader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 UserAccountsDrawerHeader( accountName: Text("Ivan"), accountEmail: Text("loabc24@163.com"), currentAccountPicture: CircleAvatar( backgroundImage: NetworkImage( "https://thirdqq.qlogo.cn/g?b=sdk&k=dcdm22zvv9c7pLsOp0LFFw&s=100&t=1556967668"), ), decoration: BoxDecoration( color: Colors.yellow[400], image: DecorationImage( fit: BoxFit.cover, image: NetworkImage( "https://resources.ninghao.org/images/childhood-in-a-picture.jpg"))), ),
这里使用到了BoxDecoration
BoxDecoration:一个背景装饰对象,相当于Android中的shape.xml,定制各种各样的背景(边框、圆角、阴影、形状、渐变、背景图像)
5.基础控件: 1.Text
textAlign 对齐方式,如:textAlign: TextAlign.center,
style 设置样式,属性比较多,就不细说,举例:TextStyle(fontSize: 16);
maxLines 最大行数
overflow: 超过最大行数如何处理:举例:overflow: TextOverflow.ellipsis(省略号)
RichText:富文本
RichText可以设置多种样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 RichText(text: TextSpan( text: "我爱中国", style: TextStyle( color: Colors.deepPurpleAccent, fontSize: 34, fontStyle: FontStyle.italic, fontWeight: FontWeight.w100 ), children: [ TextSpan( text: 'I love China', style: TextStyle( fontSize: 26, color: Colors.redAccent ) ) ] ));
RichText的children定义子类,默认继承父类的样式,在子类中再定义自己的样式,这样就能在一行里组合成富文本
2.Container 属性:
Child 内容
Color:背景色
Padding:内边距
Margin:外边距
Width:宽度 height:高度
Decoration:装饰,如可以使用boxdecoration,如果Decoration加了背景色,color属性就要去掉
3.Row 行 属性:
mainAxisAlignment:主轴对齐方式 如:mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment:交叉轴,和主轴对称
4.BoxDecoration:盒子装饰
具有指定大小的盒子,指定大小后,可限制子widget,如果不指定,则自适应子控件
SizedBox.expand() :创建一个父类允许最大的盒子
6.Alignment 矩形内的一个点,Alignment(x,y),其中x,y的取值范围是-1.0 - 1.0 ,表示矩形的一端到另一端
Alignment(0.0, 0.0)代表举行的中心
Alignment(-1.0, -1.0)表示左上角
Alignment(1.0, 1.0):表示右下角
可以使用内置的一些属性,方便定位,如:Alignment.center,Alignment.topLeft
7.stack 类似于Android布局中的帧布局Framelayout
stack可以指定alignment属性
8.Positioned 控制[Stack]的子元素放置位置的小部件,举例:
1 2 3 4 5 6 ///在左上角(10,10)位置 Positioned( child: Icon(Icons.shopping_cart), left: 10, top: 10, )
9.AspectRatio 一个小部件,可以限制子widget的宽高比,宽度由父控件决定,举例:
1 2 3 4 5 6 7 8 9 Container( width: 100,//宽度为100 child: AspectRatio( aspectRatio: 16/9, //宽高比16:9 child: Container( color: Colors.red, ), ), )
10.ConstrainedBox 一个小部件,可以限制子控件的最大宽高,最小宽高,举例:
1 2 3 4 5 6 7 8 ConstrainedBox( child: Container( color: Colors.red, ), constraints: BoxConstraints( minHeight: 200, maxWidth: 200, ))
11.PageView 类似于安卓中的ViewPager
PageView.builder:按需构造页面
常用属性:
scrollDirection:滑动方向
onPageChanged:滑动监听
viewportFraction:页面比例分数
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 PageView( children: <Widget>[ _pageView_item(Colors.brown[900],'One'), _pageView_item(Colors.grey[900],'Two'), _pageView_item(Colors.blueGrey[900],'Three'), ], ); ///生成PageView的每一页 Widget _pageView_item(Color color,String text){ return Container( color: color, alignment: Alignment.center, child: Text(text,style: TextStyle(fontSize: 32,color: Colors.white),), ); }
12.GridView 使用GridView.count来生成
crossAxisSpacing:交叉轴间距
mainAxisSpacing:主轴间距
crossAxisCount:交叉轴上item的个数
scrollDirection:滚动方向
说明:类似于Android中的scrollview
参考:https://book.flutterchina.club/chapter6/custom_scrollview.html
SliverAppBar 类似于安卓中的CollapsingToolbarLayout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const SliverAppBar({ Key key, this.leading,//左侧的图标或文字,多为返回箭头 this.automaticallyImplyLeading = true,//没有leading为true的时候,默认返回箭头,没有leading且为false,则显示title this.title,//标题 this.actions,//标题右侧的操作 this.flexibleSpace,//可以理解为SliverAppBar的背景内容区 this.bottom,//SliverAppBar的底部区 this.elevation,//阴影 this.forceElevated = false,//是否显示阴影 this.backgroundColor,//背景颜色 this.brightness,//状态栏主题,默认Brightness.dark,可选参数light this.iconTheme,//SliverAppBar图标主题 this.actionsIconTheme,//action图标主题 this.textTheme,//文字主题 this.primary = true,//是否显示在状态栏的下面,false就会占领状态栏的高度 this.centerTitle,//标题是否居中显示 this.titleSpacing = NavigationToolbar.kMiddleSpacing,//标题横向间距 this.expandedHeight,//合并的高度,默认是状态栏的高度加AppBar的高度 this.floating = false,//滑动时是否悬浮 this.pinned = false,//标题栏是否固定 this.snap = false,//配合floating使用 })
类似于Android中的ScrollView,主要用于一屏展示不下,需要滑动展示,如果是很长的列表,则应该使用如ListView,因为可以延迟加载,SingleChildScrollView是直接加载所有内容
15.OverflowBox 6.按钮 构建方式:
1 2 FloatingActionButton.extended( onPressed: null, label: Text('add'), icon: Icon(Icons.add));
构建方式
方式一:默认构建
方式二:使用FlatButton.icon()构建,可以添加一个小图标
和FlatButton的区别是,RaisedButton有一个背景和溅墨效果
构建方式同上
1 shape: StadiumBorder(),//两端半圆矩形
描边按钮
1 2 3 4 5 6 7 8 9 OutlineButton( onPressed: () {}, child: Text('RaisedButton'), splashColor: Colors.grey, borderSide: BorderSide( color: Colors.black87, ), shape: StadiumBorder(), ),
5.Expanded 展开,将剩余可用空间填满,如放在Row中,会填满可用空间,可设施flex,设置比例
PopupMenuItem每个item项
弹出式按钮,每个item
onSelected:回调每个点击的选项,需要给每个item设置value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem( child: Text('Home'), value: 'Home', ), PopupMenuItem( child: Text('Search'), value: 'Search', ), PopupMenuItem( child: Text('me'), value: 'me', ) ], onSelected: (value) { setState(() => text = value); }, )
7.checkbox 常用属性:
单选框
属性:
1 2 3 4 5 6 const Radio({ Key key, @required this.value,//单选按钮表示的值 @required this.groupValue,//一组单选按钮的当前选定值,如果和value相同,则表示选中 @required this.onChanged,//变化回调
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var _groupValue = 1; void _handleValueChange(int value) { setState(() { _groupValue = value; }); } Radio( value: 1, groupValue: _groupValue, onChanged: _handleValueChange), Radio( value: 2, groupValue: _groupValue, onChanged: _handleValueChange),
9.RadioListTile 类似于Radio,但是效果更多,可以设置标题,子标题,icon
10.Switch 开关,使用比较简单
属性:
Value:布尔值
onChanged:
举例:
1 2 3 4 5 6 7 8 9 var isSwitch = false; Switch( value: isSwitch, onChanged: (value) { setState(() { isSwitch = value; }); })
11.Slider 滑动选择器:
1 2 3 4 5 6 7 8 9 10 11 12 13 Slider( min: 0.0,//最小值 max: 10.0,//最大值 divisions: 10,//等分成多少份,需要和label一起使用 label: '${_sliderValue.toInt()}',//显示每份 activeColor: Theme.of(context).accentColor, //选中部分的颜色 inactiveColor:Theme.of(context).accentColor.withOpacity(0.3) ,//未选中部分颜色 value: _sliderValue,//当前的值double类型 onChanged: (value) { setState(() { _sliderValue = value; }); }),
12.SimpleDialog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void _openSimpleDialog() async { final option = await showDialog(context: context, builder: (BuildContext context) { return SimpleDialog( title: Text('温馨提示'), children: <Widget>[ SimpleDialogOption( child: Text('确定'), onPressed: ()=>Navigator.of(context).pop(Option.A), ), SimpleDialogOption( child: Text('退出'), onPressed: ()=>Navigator.of(context).pop(Option.B), ) ], ); });
13.AlertDialog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void _openAlertDialog() async{ var result = await showDialog(context: context,builder: (BuildContext context){ return AlertDialog( title: Text('warm tips'), content: Text('Are you sure about this?'), actions: <Widget>[ FlatButton(onPressed: ()=>Navigator.pop(context,Option.B), child: Text('Cancel',style: TextStyle(color: Colors.indigoAccent),)), FlatButton(onPressed: ()=>Navigator.pop(context,Option.A), child: Text('Ok',style: TextStyle(color: Colors.indigoAccent))) ], ); }); setState(() { switch(result){ case Option.A: _selectChoice = "ok"; break; case Option.B: _selectChoice = "cancel"; break; } }); }
14.BottomSheet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 final _key = GlobalKey<ScaffoldState>(); //key需要赋值给Scaffold的key ///打开BottomSheet void _openBottomSheet() { _key.currentState.showBottomSheet((BuildContext context){ return BottomAppBar( child: Container( color: Colors.red.withOpacity(0.2), padding: EdgeInsets.all(16), child: Row( children: <Widget>[ Icon(Icons.pause_circle_outline), SizedBox(width: 16,), Text('01:30 / 03:30'), Expanded(child: Text('Fix you - Coldplay',textAlign: TextAlign.right,)) ], ), ), ); }); }
15ModalBottomSheet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void _openModalBottomSheet() { showModalBottomSheet(context: context, builder: (context){ return Container( height: 200, child: Column( children: <Widget>[ ListTile(title: Text('Opton A'),),//处理点击事件onTap() ListTile(title: Text('Opton B'),), ListTile(title: Text('Opton C'),), ], ), ); }); }
16.SnackBar 1 2 Scaffold.of(context).showSnackBar(SnackBar(content: Text('正在处理中...'), action: SnackBarAction(label: 'Ok', onPressed: () {}),));
17ExpansionPanel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 bool _isExpanded = false; ExpansionPanelList( expansionCallback: (int panelIndex, bool isExpanded){ setState(() { _isExpanded = !isExpanded; }); }, children: [ ExpansionPanel( isExpanded: _isExpanded, headerBuilder: (BuildContext context, bool isExpanded) { return Container( padding: EdgeInsets.all(16.0), child: Text( 'Panel A', style: Theme.of(context).textTheme.title, ), ); }, body: Container( width: double.infinity, child: Text('content for panel A'), padding: EdgeInsets.all(16.0), )) ], )
18Chip 属性:
deleteIconColor 删除图标颜色
deleteIcon 删除图标,设置onDelete,默认带删除图标,可不设置
onDeleted:删除动作
deleteButtonTooltipMessage:长按删除图标提示信息
1 2 3 4 5 6 7 8 9 10 11 Chip(label: Text('Life')), Chip(label: Text('Sunset'),backgroundColor: Colors.orange,),//设置背景色 Chip(label: Text('Sunset'),avatar: CircleAvatar(backgroundColor: Colors.grey,child: Text('帆'),),),//设置文字头像 //设置网络图片 Chip(label: Text('Ivan'),avatar: CircleAvatar(backgroundImage: NetworkImage('https://thirdqq.qlogo.cn/g?b=sdk&k=dcdm22zvv9c7pLsOp0LFFw&s=100&t=1556967668'),),), //设置删除图标 Chip(label: Text('删除'),onDeleted: (){},)//带删除图标 Chip(label: Text('删除'),onDeleted: (){},deleteIcon: Icon(Icons.delete_forever),),//设置删除图标
actionChip:动作碎片,类似Chip
FilterChip:过滤碎片
1 2 3 FilterChip(label: Text('过滤碎片'), onSelected: (value){setState(() { _filterSelected = value; });},selected: _filterSelected,)
ChoiceChip:
属性:
label 文本
selectedColor 选中的颜色
selected:设置是否选中
onSelected:选中回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 List<String> _tags = [ 'Apple', 'Banana', 'Lemon', ]; String _choice = 'Apple'; Container( width: double.infinity, child: Text('ChoiceChip:$_choice'), ), Wrap( // mainAxisAlignment: MainAxisAlignment.spaceEvenly, spacing: 8.0,//水平间隔 runSpacing: 8.0,//垂直间隔 children: _tags.map((tag){ return ChoiceChip(label: Text(tag), selectedColor:Colors.black,selected: _choice == tag,onSelected: (value){ setState(() { _choice = tag; }); },); }).toList() ),
19.Wrap 自适应布局,就类似于流式布局那样
属性:
1 2 spacing: 8.0,//水平间隔 runSpacing: 8.0,//垂直间隔
20.Divider 分隔符
1 Divider(color: Colors.grey,)
21.DataTable columns:定义列
DataColumn:每列的名称
rows:定义行
sortColumnIndex:排序索引位置
sortAscending:布尔值,升序还是降序
selected:选中状态
onSelectChanged:选中回调
DataRow:表示一行数据,每个单元格DataCell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 DataTable(columns: [ DataColumn(label: Text('国家')),//定义了两列数据 DataColumn(label: Text('首都')), ], rows: [ DataRow(cells: [ DataCell(Text('中国')), DataCell(Text('北京')), ]), DataRow(cells: [ DataCell(Text('美国')), DataCell(Text('华盛顿')), ]), DataRow(cells: [ DataCell(Text('俄罗斯')), DataCell(Text('莫斯科')), ]) ])
22.Card 卡片布局
ClipRRect
裁剪:可以裁剪子部件的圆角等
23.Stepper 流程
主要使用两个类,Stepper,Step,每一个Step就是流程中的节点
currentStep:当前流程的位置,从零开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 child: Stepper( currentStep: _currentStep, //当前打开哪个步骤 onStepTapped: (step) { setState(() { _currentStep = step; }); }, steps: [ Step( title: Text('Register'), isActive: _currentStep == 0, //是否激活 subtitle: Text('Register First'), content: Text( 'When toasting sliced zucchinis, be sure they are room temperature.')), Step( title: Text('Login'), isActive: _currentStep == 1, subtitle: Text('Login First'), content: Text( 'Simmer iced eggs in a jar with whiskey for about an hour to enhance their consistency.')), Step( title: Text('Share'), isActive: _currentStep == 2, subtitle: Text('Share data'), content: Text( 'Combine mackerel, blood oranges and peanut butter. mash up with shredded butterscotch and serve flattened with melon. Enjoy!')), ]),
7.1 AnimatedCrossFade
作用:该组件让2个组件在切换时出现交叉渐入的效果
1 2 3 4 5 AnimatedCrossFade( firstChild: xxx1, secondChild: xxx2, crossFadeState: _crossFadeState, duration: xxx;
crossFadeState有两个状态,CrossFadeState.showSecond和CrossFadeState.showFirst
实战:在小说或者电影APP中,会有介绍,一般会有多行文字,默认展示两行,点击图标,会展示全部文字,这个时候,我们可以使用 AnimatedCrossFade来切换显示两行文字,或者多行文字,当然你也可以有其他的实现方式
1 2 3 4 5 return AnimatedCrossFade( firstChild: _twoLineText(widget.content), secondChild: Text(widget.content), crossFadeState: _crossFadeState, duration: Duration(milliseconds: 500));
7.2 transitions RotationTransition
作用:实现动画旋转
1 2 3 4 5 6 RotationTransition({ Key key, @required Animation<double> turns, //动画 this.alignment = Alignment.center, this.child, })
7.3 ValueListenableBuilder
作用:局部刷新
1 2 3 4 ValueListenableBuilder( valueListenable: _notifier, builder: (context, value, child){}, child: xxx,)
我们需要提供一个valueListenable:类型是 ValueNotifier
1 ValueNotifier _notifier = ValueNotifier<int>(0); //参数就是需要监听的值,这里是Int类型
1 当我们需要刷新的时候,就可以去改变ValueNotifier的value值,就会重新构建builder,而其他不变的内容,我们可以放在child中,每次重新构建的的时候,会回调给builder(builder的第三个参数child)
实战:
第五章 实战开发 5.1 打包发布 https://flutter.dev/docs/deployment/android
第一步:生成签名文件
第二步:配置key.properties
key.properties需要放到android目录下,同时添加到git忽略文件
1 2 3 4 storePassword=tingche2021 keyPassword=tingche2021 keyAlias=key storeFile=/Users/yons/Ivan/jks/key.jks
忽略文件
1 2 # Android ignore /android/key.properties
第三步:配置build.gradle
首页以下代码配置到android{}之前
1 2 3 4 5 def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) }
然后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] } } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.release } }
第四步:发布前,配置网络权限,否则release包无法访问网络
1 2 3 4 <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
第五步:打包
1 flutter build apk --split-per-abi
5.2 冷启动优化 针对IOS和Android需要各自做冷启动优化,主要是在启动页的主题,加上过渡图片
5.3 获取屏幕宽度 使用 MediaQuery.of(context).size.width
5.4 数据加解密 使用了几个rsa加密的库,都没能加密解密,最后换成Java端去做数据加密
总结一下,数据加密方案:
flutter端数据加密,使用flutter端的插件来做秘钥的生成和加解密,如:rsa_util 0.1.1
Java端数据加密,使用methodChannel和Java端通信
以下是flutter端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class RsaPlugin { static MethodChannel _methodChannel = MethodChannel("rsa_plugin"); ///RSA加密 static Future<String> encrypt(String str) async { return _methodChannel.invokeMethod('encrypt', str); } ///RSA加密 static Future<String> decode(String str) async { return _methodChannel.invokeMethod('decode', str); } ///AES解密 static Future<String> decodeAES(String str, String aesIv) async { return _methodChannel .invokeMethod('decodeAes', {'data': str, 'aesIv': aesIv}); } }
注意:如果需要传递多个参数,使用map的方式,在安卓端如何接收参数呢?
一个参数:call.arguments as String
两个参数:call.argument(“data”), httpKey, call.argument(“aesIv”)
以下是Java端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class RsaPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { private lateinit var methodChannel: MethodChannel override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { ENCRYPT -> { val data = RSAUtil.encrypt(call.arguments as String, RSAKey.PRIVATE_KEY_ANDROID_STR) result.success(data) } DECODE -> { val data = RSAUtil.decrypt(call.arguments as String, RSAKey.PUBLIC_KEY_PHP_STR) result.success(data) } AES_DECODE -> { val data = AESUtil.decrypt(call.argument<String>("data"), httpKey, call.argument<String>("aesIv")) result.success(data) } } } override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { methodChannel = MethodChannel(binding.binaryMessenger, TAG) methodChannel.setMethodCallHandler(this) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { methodChannel.setMethodCallHandler(null) } }
最后别忘记在FlutterActivity中注册插件
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) //rsa加解密 flutterEngine.plugins.add(RsaPlugin()) }
5.5 单例模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Dart单例模式 class SingleTon { static SingleTon _instance; static SingleTon getInstance() { if (_instance == null) { _instance = new SingleTon(); } return _instance; } } //测试Dart单例模式 main(List<String> args) { print(SingleTon.getInstance().hashCode); print(SingleTon.getInstance().hashCode); }
5.6 Toast 使用插件 fluttertoast
引入插件:
代码封装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ///toast工具类 class ToastUtil { ///弹出提示 static void showTip(String msg) { Fluttertoast.showToast( msg: msg, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM); } static void showError(String msg) { Fluttertoast.showToast( msg: msg, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.red, textColor: Colors.white); } }
使用插件
1 ToastUtil.showTip(DString.exit_tip);
5.7 连按两次返回 使用WillPopScope这个widget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 //定义上次点击时间 DateTime _lastTime; //使用 body: WillPopScope(child: null, onWillPop: _onWillPop) ///连按两次退出APP Future<bool> _onWillPop() async { if(_lastTime == null || DateTime.now().difference(_lastTime) > Duration(seconds: 2)){ _lastTime = DateTime.now(); ToastUtil.showTip(DString.exit_tip); return false; }else { SystemNavigator.pop(); return true; } }
5.8图片处理 模糊处理 BackdropFilter和ImageFilter 使用BackdropFilter和ImageFilter可以将图片模糊处理
具体分为模糊背景和模糊前景,别搞错了,一般我们用模糊前景比较多,用一个Stack来做相对布局即可
模糊前景:
1 2 3 4 5 6 7 8 9 10 11 12 13 return Stack( children: <Widget>[ _contentBackgroundImage(context), //这里是一张图片,需要模糊它,所以用stack BackdropFilter( filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100), child: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height / 2, color: AppColor.black.withOpacity(0.3), ), ) ], );
https://juejin.cn/post/6844903850449584142
5.9回调 flutter中 无参数回调用voidcallback,一个参数回调用valuechanged,多个参数回调用typedef
看一下VoidCallback、ValueChanged
的源码可以知道
1 2 typedef VoidCallback = void Function() typedef ValueSetter<T> = void Function(T value)
本质上VoidCallback、ValueChanged
就是typedef
的一种写法而已,只是Flutter帮我们做了一层封装
5.10 GlobalKey
GlobalKey的文章太多了,不想看,这里只想介绍它的一个作用,局部刷新
https://www.wanjunshijie.com/note/111.html
5.11 JSON转Dart 网址:https://javiercbk.github.io/json_to_dart/
第六章 开源组件 6.1 旋转切换 toggle_rotate
该组件的封装思路:张风捷特烈:https://www.imooc.com/article/301631
第七章 Flutter杂七杂八 5.1 常用命令
命令
说明
flutter create
创建项目,如:flutter create -i swift -a kotlin xxapp
flutter pub deps
依赖关系查看
5.2 几个小技巧 赋值运算符??=
a ??= b :表示如果a为null的时候 将b赋值给a
5.3 Flutter 异步编程知识 Dart单线程模型 :
1 2 3 4 5 6 /// Dart如何处理IO密集型任务和CPU密集型任务 /// io密集型任务:使用 async/await,Future /// CPU密集型任务:使用isolate,类似多进程 /// Dart单线程模型:一个消息循环机制(Event Loop),两个消息队列:微任务队列(microTask queue)和事件队列(Event Queue) /// 在Dart的单线程模型中,入口main函数执行后,消息循环机制就开始了,首先执行微任务队列(microTask),然后执行事件队列(Event Queue), /// 小知识插播:Future.microtask(…)方法,该方法可以向微任务队列插入一个任务
isolate 和compute
1 2 3 4 5 6 ///知识点:Json解析(CPU密集型任务)可能比较耗时,所以这里使用compute,什么是compute? compute是flutter中对Isolate的封装 ///那什么是Isolate呢?Isolate翻译为隔离,可以理解为开启了新的线程,不阻塞UI线程 ///Isolate比较重量级,因为在Isolate和UI线程之间传输数据,比较复杂,代码量多 ///compute使用须知: /// 1. compute中运行的函数,必须是顶级函数或者是static函数 /// 2. 二是compute传参,只能传递一个参数,返回值也只有一个
5.4 Flutter生命周期 第八章 常见问题 7.1 创建项目一直卡在creating Flutter Project
发现文件目录已经创建,关闭AS,重新打开AS,打开项目,编译,此时会发现存在什么问题
7.2 Flutter App Run卡在Running Gradle task ‘assembleDebug’…
用AS打开Android部分代码,编译,此时就会看到是什么问题,一般来说,我们需要配置gradle版本,kotlin版本,gradle插件版本,另外还有一些必要的第三方依赖,以上这些,下载都可能比较耗时,需要梯子、解决办法:添加阿里云仓库
7.3 android studio导入flutter项目报错:Dart SDK is not configured
1 2 3 4 5 在Android studio中导入flutter项目时报错:Dart SDK is not configured,这是因为在android studio里面没有配置Dart SDK的问题,可以通过下面步骤进行配置: ##### 1.打开File =》Setting =》Language & Frameworks => Dart ##### 2.勾选 “enable Dart support for the project”,并且选择Dart SDK path,路径为 D:\install\android\flutter\bin\cache\dart-sdk ,其中D:\install\android\flutter是flutter SDK路径
7.4 解决flutter项目在AndroidStudio4.0的logcat中出现Please configure Android SDK
https://blog.csdn.net/w815878564/article/details/106802471
解决办法:先打开一个传统的as项目,然后再新窗口打开flutter项目
7.5 报错:Waiting for another flutter command to release the startup lock…
1 2 3 4 关闭Android Studio 打开flutter安装目录/bin/cache 删除lockfile文件 此时可在命令行再执行flutter相关命令,完美解决
7.6 打开flutter项目,工具栏选择设备栏一直处于loading状态,但是flutter devices又能发现连接了设备
1 2 1. 关闭AS,删除bin/cache下的lockfile 2. 执行flutter devices,有设备后,打开as即可