Dart Flutter教程_Dart Flutter3.x入门实战视频教程-16讲后是Flutter教程
https://www.bilibili.com/video/BV1S4411E7LY/
2
P1 01 Dart介绍Win Mac上面分别搭建Dart...
Dart是由谷歌开发的计算机编程语言,它可以被用于web、服务器、移动应用 和物联网等领域的开发。
Dart诞生于2011年,号称要取代JavaScript。但是过去的几年中一直不温不火。直到Flutter的出现现在被人们重新重视要学Flutter的话我们必须首先得会Dart.
官网:https://dart.dev/
www.gekorm.com/dart-windows/
使用flutter 就不用安装了
P2 02 Dart入口方法介绍Dart打印Dart注释D..
/**/
///
//
dart是一个强大的脚本类语言,可以不预先定义变量类型,自动会类型推倒
dart中定义变量可以通过var关键字可以通过类型来申明变量
var str=''
String str=''
int num=2;
1、变量名称必须由数字、字母、下划线和美元符($)组成。
2.注意:标识符开头不能是数字
3.标识符不能是保留字和关键字。
4,变量的名字是区分大小写的如:age和Age是不同的变量。在实际的运用中,也建议,不要用一个5、标识符(变量名称)一定要见名思意:变量名称建议用名词,方法名称建议用动词
Dart 常量:final 和 const修饰符
const值不变 一开始就得赋值
final可以开始不赋值只能赋一次;而final不仅有const的编译时常量的特性,最重要的它是运行时
永远不改量的量,请使用final或const修饰它,而不是使用var或其他变量类型。
P3 03 Dart的数据类型详解 int double String b.
常用数据类型:
Numbers(数值):
int double
Strings(字符串)String
Booleans(布尔)
bool
List(数组)在Dart中,数组是列表对象,所以大多数人只是称它们为列表
Maps(字典)
通常来说,Map 是一个键值对相关的对象。键和值可以是任何类型的对象。
项目中用不到的数据类型:
Runes Rune是UTF-32编码的字符串。它可以通过文字转换成符号表情或者代表特定的文字。
Symbols Symbol对象表示在Dart程序中声明的运算符或标识符。您可能永远不需要使用符号,
单双引号 都行了
String str='''多行显示'''
print("$str $str2"); 加${}已行了
var list=[];
list.add('hello');
var x=new List();//在新版本的dart里面没法使用这个方法了
var x=List.filled(2,'');///创建一个固定长度的集合 只能添加2个
可以加泛型
maps
var p={
"name":'koo'
}
var p=new Map();可以的
if(str is String){}
P4 04 Dart运算符条件表达式Dart类型转换
1、Dart运算符:
算术运算符
~/(取整)
%(取余)
关系运算符
逻辑运算符
I
&&
赋值运算符
基础赋值运算符
复合赋值运算符
b??=23;表示如果b为空的话把 23赋值给b
int.parse(str);
double.parse(str);
num.isNaN
if..else if...else
switch
P5 05 Dart循环语句for while do..while break.
for(int i=1;i<=110,i++){}
while(){}
do{}while()
P6 06 Dart List Set Map详解以及循环语句for..
Dart集合类型List Set Map详解以及循
环语句 forEach map where any every
List里面常用的属性和方法:
常用属性:
length
长度
reversed
is Empty
翻转
是否为空
isNotEmpty
是否不为空
常用方法:
add增加
addAll拼接数组
indexof查找 传入具体值
remove删除 传入具体值removeAt删除 传入索引值
fillRange修改
insert(index,value);insertAll(index,list)
指定位置插入
指定位置插入List toList()
join()
split()forEach其他类型转换成List List转换成字符串字符串转化成List map
map
常用属性:
keys
values
isEmpty
获取所有的key值
获取所有的value值
是否为空
isNotEmpty
是否不为空
常用方法:
remove(key)
删除指定key的数据
addAl1({...})
合并映射 给映射内增加属性
containsValue
查看映射内的值 返回true/false
forEach
map
where
any
every
for(var item in myList){}
.forEach()
.map()
.where() 相当js array filter
.any() 相当js array some
P7 07 Dart中的函数函数的定义可选参数默...
返回类型 方法名称(参数1,参数2,.I
}
方法体
return 返回值;
ا
int get(int n){ 参数也可以不写类型
return 21
}
void String List bool 类型也可以不写
String get(String name,[int age=88]){} 默认
get('anme',899)
get('xx') int是可选参数
String get(String name,{int age=88}){} 默认
get('name',age:88)
P8 08 Dart中的函数 箭头函数 匿名函数 闭包….
Dart中的函数 函数的定义 可选参数 默
认参数 命名参数 箭头函数 匿名函数 闭包
等
list.forEarch((value)=>{}) 箭头函数只能写一行 不能加分号
var fn=(){};
((){})();
fn(){
return(){
}
}
P9 09 Dart中的对象类类的创建构造函数命...
面向对象编程(OOP)的三个基本特征是:封装、继承、多态封装:封装是对象和类概念的主要特性。封装,把客观事物封装成抽象的类,并且把自己的部分属性和方法提供给其他对继承:面向对象编程(OOP)语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的功能,并在多态:允许将子类类型的指针赋值给父类类型的指针,同一个函数调用会有不同的执行效果。
Dart所有的东西都是对象,所有的对象都继承自Object类。
Dart是一门使用类和单继承的面向对象语言,所有的对象都是类的实例,并且所有的类都是Object的子类一个类通常由属性和方法组成。
Dart和其他面向对象语言不一样,Data中没有 public private protected这些访问修饰符合但是我们可以使用_把一个属性或者方法定义成私有 还要再单独文件 才起作用
P10 10 Dart中的类 静态成员 操作符 类的继承
Dart中的静态成员:
1、使用static 关键字来实现类级别的变量和函数
2、静态方法不能访问非静态成员,非静态方法可以访问静态成员
Dart中的对象操作符:
?条件运算符(了解)
as类型转换
is类型判断
..级联操作(连缀)
面向对象的三大特性:封装、继承、多态
Dart中的类的继承:
1、子类使用extends关键词来继承父类
2、子类会继承父类里面可见的属性和方法 但是不会继承构造函数
I
3、子类能复写父类的方法 getter和setter
void main() {
var p = new Person('name', 88);
p.printInfo();
Person p1 = new Person.now('koo', 28);
// p1.printInfo();
p1
..name = 'hot'
..age = 88
..printInfo();
}
class Person {
String name;
int age;
static int scope = 88;
// Person():name='koo',age=88{}
Person(this.name, this.age); //构造函数简写
// Person(String name,int age){
// this.name=name
// this.age=age
// }
Person.now(this.name, this.age) {
print('我是命名构造函数');
}
Person.setInfo(this.name, this.age) {
print('我是命名构造函数');
}
void printInfo() {
print('${this.name}----${this.age}');
}
//计算属性
get nameInfo {
return this.name;
}
set ageInfo(value) {
this.age = value;
}
}
class Student extends Person {
int id;
// Student(super.name, super.age,this.id);
Student(String name, int age, this.id) : super(name, age) {}
//可以写也可以不写 建议在覆写父类方法的时候加上@override
@override
void printInfo() {
super.printInfo();
}
}
P11 11 Dart中的抽象类 多态 以及接口
Dart中抽象类:Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。
1、抽象类通过abstract 長键字来定义
2、Dart中的抽象方法不能用abstract声明,Dart中没有方法体的方法我们称为抽象方法。
3、如果子类继承抽象类必须得实现里面的抽象方法
4、如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法。
5、抽象类不能被实例化,只有继承它的子类可以
extends抽象类 和 implements的区别1、如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用extends继承抽象类2、如果只是把抽象类当做标准的话我们就用implements实现抽象类
当做接口
接口:就是约定、规范
//抽象类 继承
void main() {
Student s1 = new Student();
s1.run();
Person s2 = new Student();
s2.eat(); //先上转型 没有run方法 把student引用指向Perosn
}
abstract class Person {
eat();
}
class Student extends Person {
@override
eat() {}
run() {}
}
//实现接口
void main() {
Student s1 = new Student();
}
abstract class Person {
late String name;
eat();
}
class Student implements Person {
@override
String name = 'koo';
@override
eat() {
// TODO: implement eat
throw UnimplementedError();
}
}
P12 12 Dart中一个类实现多个接口以及Dart中….
mixins的中文意思是混入,就是在类中混入其他功能。在Dart中可以使用mixins实现类似多继承的功能
因为mixins使用的条件,随着Dart版本一直在变,这里讲的是Dart2.x中使用mixins的条件:
1、作为mixins的类只能继承自Object,不能继承其他类
2、作为mixins的类不能有构造函数
3、一个类可以mixins多个mixins类
4、mixins绝不是继承,也不是接口,而是一种全新的特性 I
void main() {
Student s1 = new Student();
}
abstract class Person {
late String name;
eat();
}
abstract class Animate {
juam();
}
class Student implements Person, Animate {
@override
String name = 'koo';
@override
eat() {}
@override
juam() {}
}
void main() {
Student s1 = new Student();
}
class Person {
void eat() {}
}
class Animate {
void juam() {}
}
//旧版可以 实现多继承 混入
class Student with Person, Animate {}
P13 13 Dart中的泛型 泛型方法 泛型类 泛型接口
T getDate<T>(T value) {
return value;
}
abstract class Fn<T>{}
P14 14 Dart中的库自定义库、系统库、第三…..
Dart中的库 自定义库 系统库 第三方库
library指令可以创建一个库,每个Dart文件都是一个库,即使没有使用library指令来指定。
Dart中的库主要有三种:
1、我们自定义的库
import'lib/xxx.dart';
2、系统内置库
import'dart:math';import'dart:io';import'dart:convert';
3、Pub包管理系统中的库
https://pub.dev/packages https://pub.flutter-io.cn/packages https://pub.dartlang.org/flutter/
1、需要在自己想项目根目录新建一个pubspec.yaml
2、在pubspec.yaml文件 然后配置名称、描述、依赖等信息3、然后运行pub get获取包下载到本地
4、项目中引入库 import'package:http/http.dart'as http;看文档使用
dart pub get
P15 15 Dart 2.13之后的一些新特性Null safety...
Null safety翻译成中文的意思是空安全。
null safety 可以帮助开发者避免一些日常开发中很难被发现的错误,并且额外的好处是可以改善性能。
Flutter2.2.0(2021年5月19日发布)之后的版本都要求使用null safety。
I
?可空类型?!类型断言
String?username='张三'
username=null;//String?表示username是一个可空类型
String? Fn(){
return null
}
String? str='hhh';
str=null;
print(str!.length); //类型断言
Null safety翻译成中文的意思是空安全。
late 关键字主要用于延迟初始化。
class Person{
late String name;
}
required关键词:
最开始@required 是注解
现在它已经作为内置修饰符。
主要用于允许根据需要标记任何命名参数(函重或类),使得它们不为空。因为可选参数中必须有个r
required
P16 16 Dart性能优化之常量、identical函数..
var o1=new Object();
var o2=new Object();
print(identical(o1,o2)); //false
var o1=Object();
var o2=Object();
print(identical(o1,o2)); //false
var o1=const Object();
var o2=const Object();
print(identical(o1,o2)); //true
常量构造函数总结如下几点:
1、常量构造函数需以const关键字修饰
2、const构造函数必须用于成员变量都是final的类3、如果实例化时不加const修饰符,即使调用的是常量构造函数,实例化的对象也不是常量实4、实例化常量构造函数的时候,多个地方创建这个对象,如果传入的值相同,只会保留一个对5、Flutter中const 修饰不仅仅是节省组件构建时的内存开销,Flutter 在需要重新构建组
P17 17 Flutter介绍-Flutter Windows Android环
Flutter是谷歌公司开发的一款开源、免费的UI框架,可以让我们快速的在Android和iOS上构建高质量App.它最大的特点就是跨平台、以及高性能。目前 Flutter 已经支持iOs、Android、Web、Windows,macOS,Linux等
市面上已经有很多的混合App开发框架了,但是有些混合APP开发框架主要是针对前端开发者的:比如ReactNative(基于React)、lonic(基于Angular,Vue,React)。有些则是针对.Net平台针对.Net开发者的比如:Xamarin
P18 18 Flutter Android真机器调试、虚拟机...
flutter devices
flutter run -d all
Flutter Widget Snippets
r 热加载
R 热重启
常用的快捷键
r:点击后热加载,也就算是重新加载吧。
R键:热重启项目,
p 键:显示网格,这个可以很好的掌握布局情况,工作中很有用。
o键:切换android和ios的预览模式.
q 键:退出调试预览模式。
P19 19 Mac电脑搭建Flutter los环境以及让Flut...
P20 20 Flutter目录结构介绍、入口、自定义Wi...
Flutter目录结构介绍、入口、Widget、
Center组件、Text组件、MaterialApp
组件、Scaffold组件
flutter create flutter02
1,MaterialApp MaterialApp是一个方便的Widget,它封装了应用程序实现Material Design所需要的一些Widget。一般作为顶层widget使用。
常用的属性:
home(主页)title(标题)color(颜色)theme(主题)routes(路由)
2、Scaffold Scaffold是Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet的API Scaffold 有下面几个主要属性:
appBar-显示在界面顶部的一个AppBar.
body-当前界面所显示的主要内容Widget drawer-抽屉菜单控件。
在Fluster中自定义组件其实就是一个类,这个类需要继承StatelessWidget/StatefulWidget前期我们都继承StatelessWidget,后期给大家讲StatefulWidget的使用。
StatelessWidget 是无状态组件,状态不可变的widget StatefulWidget 是有状态组件,持有的状态可能在widget生命周期改变
P21 21 Flutter Container组件、Text组件详解
P22 22 Flutter图片组件Image、本地图片、
Flutter图片组件Image、本地图片、远
程图片、图片剪切、圆形图片
10.1、图片组件介绍
Flutter 中,我们可以通过Image组件来加载并显示图片 Image的数据源可以是asset、文件、内存以及网络。
这里我们主要给大家讲两个
Image.asset,本地图片
Image.network 远程图片
Image组件的常用属性:
图片地址:
https://www.itying.com/images/201906/goods_img/1120_P_1560842352183.png
https://www.itying.com/themes/itying/images/ionic4.png.
CliOval weiget
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(title: Text('Flutter')),
body: Page(),
),
);
}
}
class Page extends StatefulWidget {
const Page({super.key});
@override
State<Page> createState() => _PageState();
}
class _PageState extends State<Page> {
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
var screenWidth = MediaQuery.of(context).size.width;
return Container(
height: 500.0,
width: screenWidth,
decoration: BoxDecoration(
color: Colors.green,
image: new DecorationImage(
image: new AssetImage('images/code.png'), fit: BoxFit.cover),
borderRadius: BorderRadius.circular(50.0))
// child: Image.network(
// 'https://www.itying.com/images/201906/goods_img/1120_P_1560842352183.png',
// fit: BoxFit.cover,
// ),
// child: Image.asset(
// 'images/code.png',
// fit: BoxFit.cover,
// ),
);
}
}
P23 23 Flutte自带图标组件和 自定义图标
Material Design所有图标可以在其官网查看:https://material.io/tools/icons/
11.2 Flutter中借助阿里巴巴图标库自定义字体图标我们也可以使用自定义字体图标。阿里巴巴图标库官网iconfont.cn上有很多字体图标素材,我们可以选择自己需要的图标打包下载后,会生成一些不同格式的字体文件,在Flutter中,我们使用ttf格式即假设我们项目中需要使用一个书籍图标和微信图标,我们打包下载后导入:
1,导入字体图标文件;这一步和导入字体文件相同,假设我们的字体图标文件保存在项目根目录下,路径为“fonts/iconfont.tt
配置 yaml
child: Icon(
MyIcon.weixin,
color: Colors.green,
),
import 'package:flutter/material.dart';
class MyIcon {
static const IconData weixin =
IconData(0xe73b, fontFamily: 'myIcon', matchTextDirection: true);
}
P24 24 Flutte ListView列表组件普通列表Ico..
十二、Flutter 列表组件
列表布局是我们项目开发中最常用的一种布局方式.Flutter中我们可以通过Listview来定义列表项,支持垂直和水平方向展示,通过一个属性就可以控制列表的显示方向,列表有以下分类:
1、垂直列表
2、垂直图文列表
3、水平列表
4、动态列表
列表组件常用参数;名称类型说明
scrollDirection Axis Axis.horizontal水平列表Axis.vertical垂直列表padding EdgelnsetsGeometry内边距
resolve bool组件反向排序
children List列表元素
ListView 列表
配合ListTile列表
Divider()
SizedBox()
P25 25 Flutter ListView动态列表组件以及循...
listview.builder
P26 26 Flutter GridView组件 动态GridView详解
GridView网格布局在实际项目中用的也是非常多的,当我们想让可以滚动的元素使用矩阵方式排列的时候,此时我们可以用网格列表组件GridView实现布局。
GridView创建网格列表主要有下面三种方式
1、可以通过GridView.count 实现网格布局2、可以通过GridView.extent 实现网格布局3、通过GridView.builder实现动态网格布局
GirdView.count 配置主轴数量
GirdView.extent 配置item的width
P27 27 Futter页面布局Paddiing Row Colum..
Flutter页面布局Paddiing Row
Column Flex Expanded组件详解
width:double.infinity
maxFinite
14.3,double.infinity和double.maxFinite double.infinity和double.maxFinite可以让当前元素的width或者height达到父元素的尺寸底层代码
十五、弹性布局(Flex Expanded)
Flex组件可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用Row或Column会方便一些,因为Row和 Column都继承自1ex,参数基本相同,所以能使用Flex的地方基本上都可以使用Row 或 Column.Flex 本身功能是很强大的,它也可以和 Expanded 组件配合实现弹性布局
Flex(); weiget
P28 28 Flutter页面布局 Stack层叠组件 Stack..
Flutter 页面布局 Stack层叠组件 Stack
与Align Stack与Positioned实现定位布
局
四、FlutterMediaQuery获取屏幕宽度和高度final size =MediaQuery.of(context).size;
Align 组件可以调整子组件的位置,Stack组件中结合Align组件也可以控制每个子元素的显示位置
P29 29 Flutter AspectRatio Card CircleAvatar..
Flutter页面布局AspectRatio Card
CircleAvatar组件
AspectRatio的作用是根据设置调整子元素child的宽高比.
AspectRatio首先会在布局限制条件允许的范围内尽可能的扩展,widget的高度是由宽度和比率决定的,类似于BoxFit中的contain,按照固定比率去尽量占满区域。
如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio最终将会去优先适应布局限制条件,而忽略所设置的比率。
属性说明
aspectRatio宽高比,最终可能不会根据这个值去布局,具体则要看综合因素,外层是否允许按照这种比率进行布局,这只是一个参考值
child子组件
AspectRatio //需求:页面上显示一个容器,宽度是屏幕的宽度,高度是容器宽度的一半
Card() weiget
Container实现圆形图片
ClipOval实现圆形图片
CircleAvatar实现圆形图片
P30 30 Flutter按钮组件ElevatedButton TextB.
Flutter 按钮组件
ElevatedButton 普通按钮 有icon属性 label属性
TextButton 文本按钮 TextButton.icon
Outlined Button 边框的按钮 OutlinedButton.icon
IconButton icon按钮
child shap圆角 结合sizebox/container expand
P3131 Flutter Wrap组件使用Wrap组件实
Flutter Wrap组件使用Wrap组件实现
电商App搜索页面布局
Wrap可以实现流布局,单行的Wrap跟Row表现几乎一致,单列的Wrap则跟Row表现几乎一致,但Row与Column都是单行单列的,Wrap则突破了这个限制,mainAxis上空间不足时,则向crossAxis上去扩展显示。
P32 32 StatefulWidget有状态组件
二十一、Flutter StatelessWidget、StatefulWidget在Flutter中自定义组件其实就是一个类,这个类需要继承StatelessWidget/StatefulWidget.
StatelessWidget是无状态组件,状态不可变的widget StatefulWidget是有状态组件,持有的状态可能在widget生命周期改变。通俗的讲:如果我们想改变页面中的数据的话这个时候就需要用到StatefulWidget
P33 33 Flutter Scaffold属性BottomNavigation
22.1、BottomNavigationBar 组件介绍
BottomNavigationBar是底部导航条,可以让我们定义底部Tab切换,bottomNavigationBar是Scaffold组件的参数。
Scaffold.bottomnavigationbar.bottomnavigationbar.bottomnavigationbarItem
type:BottomNavigationBarType.fixed,如果底部有4个或者4个上的菜单的时候就需要配置这个参数
P34 34 Flutter Scaffold属性FloatingActionBut
FloatingActionButton实现类似闲鱼App 在 Scaffold
底部导航凸起按钮
FloatingActionButtonLocation.centerDocked
P3535Flutter Scaffold属性 Drawer侧边栏、
Drawer侧边栏、不同设备调试Flutter程序
在Scaffold组件里面传入drawer参数可以定义左侧边栏,传入endDrawer可以定义右侧边栏,侧边栏默认是隐藏的,我们可以通过手指滑动显示侧边栏,也可以通过点击按钮显示侧边栏
二、Flutter DrawerHeader
常见属性:
https://www.itying.com/images/flutter/2.png
、 Flutter UserAccountsDrawerHeader
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(title: const Text('Flutter')),
body: const Text('1111'),
drawer: const Drawer(
child: Column(
children: [
Row(
children: [
Expanded(
flex: 1,
child: DrawerHeader(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
'https://www.itying.com/images/flutter/2.png',
),
fit: BoxFit.cover)),
child: Text('data')))
],
)
],
),
),
endDrawer: const Drawer(
child: Column(
children: [
Row(
children: [
Expanded(
flex: 1,
child: UserAccountsDrawerHeader(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
'https://www.itying.com/images/flutter/2.png',
),
fit: BoxFit.cover)),
accountName: Text('dataname'),
accountEmail: Text('data@'),
otherAccountsPictures: [
CircleAvatar(
backgroundImage: NetworkImage(
'https://www.itying.com/images/flutter/2.png'),
),
CircleAvatar(
backgroundImage: NetworkImage(
'https://www.itying.com/images/flutter/1.png'),
),
],
currentAccountPicture: CircleAvatar(
backgroundImage: NetworkImage(
'https://www.itying.com/images/flutter/2.png'),
),
))
],
)
],
),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'home'),
BottomNavigationBarItem(
icon: Icon(Icons.message), label: 'message'),
BottomNavigationBarItem(
icon: Icon(Icons.settings), label: 'setting'),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: SizedBox(
height: 60.0,
width: 60.0,
child: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
),
),
);
}
}
P36 36 Flutter AppBar TabBar TabBarView实
Flutter AppBar TabBar TabBarView实现
类似头条顶部滑动导航
25.3 Tabbar TabBarView实现类似头条顶部导航
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
backgroundColor: Colors.deepOrange,
bottom: TabBar(controller: _tabController, isScrollable: true, tabs: [
Tab(
child: Text('data'),
),
Tab(
child: Text('data1'),
),
Tab(
child: Text('data2'),
),
]),
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.menu_book),
onPressed: () {},
),
],
title: const Text('Flutter')),
body: TabBarView(
controller: _tabController,
children: [
Text('data'),
Text('data1'),
Text('data2'),
],
),
);
}
}
P3737 Flutter底部Tab页面中使用TabBarTa
Flutter AppBar TabBar TabBarView实现
类似头条顶部滑动导航
with AutomaticKeepAliveClientMixin 缓存组件
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
backgroundColor: Colors.deepOrange,
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.menu_book),
onPressed: () {},
),
],
title: const Text('Flutter')),
body: TabDiy());
}
}
class TabDiy extends StatefulWidget {
const TabDiy({super.key});
@override
State<TabDiy> createState() => _TabDiyState();
}
class _TabDiyState extends State<TabDiy> with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(50),
child: AppBar(
title: TabBar(controller: _tabController, isScrollable: true, tabs: [
Tab(
child: Text('data'),
),
Tab(
child: Text('data1'),
),
Tab(
child: Text('data2'),
),
]),
),
),
body: TabBarView(
controller: _tabController,
children: [
Text('data'),
Text('data1'),
Text('data2'),
],
),
);
;
}
}
P38 38 Flutter中的路由普通路由、普通路由..
26.1、Flutter 路由介绍
Flutter中的路由通俗的讲就是页面跳转,在Flutter中通过Navigator组件管理路由导航.
并提供了管理堆栈的方法。如:Navigator.push和Navigator.pop Flutter中给我们提供了两种配置路由跳转的方式:1、基本路由2、命名路由
widget.title
P39 39Flutter中的路由命名路由命名路由传
import 'package:app/new.dart';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
Map routes = {
'/new': (contxt, {arguments}) => MyWidget(
arguments: arguments,
)
};
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
// routes: {'/new': (contxt) => const MyWidget()},
// initialRoute: "/",
onGenerateRoute: (RouteSettings settings) {
final String? name = settings.name;
final Function? pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(builder: (context) {
return pageContentBuilder(context,
arguments: settings.arguments);
});
return route;
} else {
final Route route = MaterialPageRoute(
builder: (context) => pageContentBuilder(context));
return route;
}
}
return null;
},
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Navigator.of(context).push(MaterialPageRoute(builder: (context) {
// return MyWidget();
// }));
Navigator.of(context)
.pushNamed('/new', arguments: {'title': 'hello'});
// Navigator.pushNamed(context, '/new');
},
child: const Text('btn'),
),
));
}
}
import 'package:flutter/material.dart';
class MyWidget extends StatelessWidget {
final Map arguments;
const MyWidget({super.key, required this.arguments});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('back'),
),
));
}
}
P40 40 Flutter路由跳转路由替换返回到根路.
26.8、Flutter 中替换路由
比如我们从用户中心页面跳转到了registerFirst页面,然后从registerFirst页面通过pushReplacementNamed跳转到了registerSecond页面,这个时候当我们点击registerSecond的返回按钮的时候它会直接返回到用户中心。
//替换路由
Navigator.of(context).pushReplacementNamed('/registersecond');
//返回到根页面
Navigator.of(context).pushAndRemoveUntil MaterialPageRoute(builder:(BuildContext context)f return const Tabs();
})//MaterialPageRoute
,(route)=> false);
26.10、Flutter Android 和los使用同样风格的路由跳转Material组件库中提供了一个MaterialPageRoute组件,它可以使用和平台风格一致的路由切换动画,如在iOS上会左右滑动切换,而在Android上会上下滑动切换,CupertinoPageRoute是Cupertino组件库提供的iOS风格的路由切换组件如果在Android上也想使用左右切换风格,可以使用CupertinoPageRoute.
import ' package: flutter/cupertino. dart';
P41 41 Dialog AlertDialog、SimpleDialog、s..
pub.dev/packages/fluttertoast/versions
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: () async {
var result = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('tips'),
content: const Text('content'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop('result');
},
child: const Text('confirm'))
],
);
});
print(result);
},
child: const Text('btn'),
),
ElevatedButton(
onPressed: () async {
var result = await showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return SimpleDialog(
title: const Text('select'),
children: [
SimpleDialogOption(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('confirm'),
)
],
);
});
print(result);
},
child: const Text('btn'),
),
ElevatedButton(
onPressed: () async {
showModalBottomSheet(
context: context,
builder: (context) {
return Text('this is bottom tips');
});
},
child: const Text('btn'),
),
ElevatedButton(
onPressed: () {
Fluttertoast.showToast(
msg: "This is Center Short Toast",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
},
child: Text("Flutter Toast Context"),
),
],
)));
}
}
P42 42更多实战访问IT营官网(itying.com)F.
不加() 是注册方法 加是调用方法
InkWell组件
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: () async {
var result = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('tips'),
content: const Text('content'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop('result');
},
child: const Text('confirm'))
],
);
});
print(result);
},
child: const Text('btn'),
),
ElevatedButton(
onPressed: () async {
var result = await showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return SimpleDialog(
title: const Text('select'),
children: [
SimpleDialogOption(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('confirm'),
)
],
);
});
print(result);
},
child: const Text('btn'),
),
ElevatedButton(
onPressed: () async {
showModalBottomSheet(
context: context,
builder: (context) {
return Text('this is bottom tips');
});
},
child: const Text('btn'),
),
ElevatedButton(
onPressed: () {
Fluttertoast.showToast(
msg: "This is Center Short Toast",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
},
child: Text("Flutter Toast Context"),
),
ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return MyToast();
});
},
child: Text("div toast"),
),
],
)));
}
}
class MyToast extends Dialog {
const MyToast({super.key});
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Container(
height: 240,
width: 240,
color: Colors.red,
),
),
);
}
}
P43 43 Flutter PageView仿抖音滑动切换页面
Flutter中的轮动图以及抖音上下滑页切换视频功能等等,这些都可以通过 PageView 轻松实现
pageview.builder 动态 pageview-children 可以实现轮播图功能
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
body: Center(
child: PageView(
children: const [
Text('1'),
Text('2'),
Text('3'),
Text('4'),
Text('1'),
Text('1'),
Text('1'),
Text('1'),
],
)));
}
}
P44 44 PageView实现动态轮插图PageContr..
Timer.periodic() //定时器
AutomaticKeepAliveClientMixin keep-alive
P45 45 Flutter Key以及通过Flutter Key获取子..
二十九、Flutter Key详解
英
我们平时一定接触过很多的Widget,比如Container,Row,Column等,它们在我们绘制界面的过程中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个Widget的构造函数中,都有一个共同的参数,它们通常在参数列表的第一个,那就是Key.
在Flutter中,Key是不能重复使用的,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保存主要是通过判断组件的类型或者key值是否一致。因此,当各组件的类型不同的时候,类型已经足够用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候,此时类型已经无法作为区分的条件了,我们就需要使用到key
29.1、没有 Key 会发生什么奇怪现象
如下面例:定义了一个StatefulWidget的Box,点击Box的时候可以改变Box里面的数字,当我们重新对Box排序的时候Flutter就无法识别到Box的变化了,这是什么原因呢?
29.2、Flutter key:LocalKey.GlobalKey英
湖北众猿腾网络科技有限公司
IT营(www.itying.com)在Flutter中,Key是不能重复使用的,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保存主要是通过判断组件的类型或者key值是否一致。因此,当各组件的类型不同的时候,类型已经足够用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候,此时类型已经无法作为区分的条件了,我们就需要使用到key。
Flutter key子类包含 Localkey 和 Globalkey.
局部键(LocalKey):ValueKey,ObjectKey,UniqueKey全局键(GlobalKey):GlobalKey,GlobalObjectKey
P46 46 Flutter AnimatedList 实现动态列表
30.1、AnimatedList实现动画
AnimatedList 和 ListView 的功能大体相似,不同的是,AnimatedList 可以在列表中插入或删除节点时执行一个动画,在需要添加或删除列表项的场景中会提高用户体验。
AnimatedList 是一个StatefulWidget,它对应的State类型为AnimatedListState,添加和删除元素的方法位于 AnimatedListState 中:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
final List list = ['koo', 'john'];
bool flag = true;
final _globlekey = GlobalKey<AnimatedListState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
list.add('new name');
_globlekey.currentState?.insertItem(list.length - 1);
});
},
child: Icon(Icons.add),
),
body: Center(
child: AnimatedList(
key: _globlekey,
initialItemCount: list.length,
itemBuilder: (context, index, animation) {
// ScaleTransition
return FadeTransition(opacity: animation, child: _buildItem(index));
},
)));
}
Widget _buildItem(index) {
return ListTile(
key: UniqueKey(),
title: Text(list[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
_remove(index);
},
),
);
}
void _remove(index) {
if (flag) {
flag = false;
var removeWidget = _buildItem(index);
setState(() {
list.removeAt(index);
_globlekey.currentState?.removeItem(index, (context, animation) {
return FadeTransition(
opacity: animation,
child: removeWidget,
);
});
});
}
//防止快速删除
Timer.periodic(Duration(milliseconds: 1), (timer) {
flag = true;
timer.cancel();
});
}
}
P47 47 Flutter动画Flutter隐式动画详解
31.1、动画基本原理以及Flutter动画简介
31.1.1 动画原理
在任何系统的UI框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变UI外观;由于人眼会产生视觉暂留,所以最终看到的就是一个"连续"的动画,这和电影的原理是一样的。我们将UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second),即每秒的动画帧数。很明显,帧率越高则动画就会越流畅!一般情况下,d对于人眼来说,动画帧率超过16 FPS,就基本能看了,超过 32 FPS就会感觉相对平滑,而超过32
FPS,大多数人基本上就感受不到差别了,由于动画的每一帧都是要改变U输出,所以在一个时间段内连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧率是重要的性能指标,而在Flutter中,理想情况下是可以实现 60FPS的,这和原生应用能达到的帧率是基本是持平的。
31.1.2 Flutter动画简介
FLutter中的动画主要分为:隐式动画、显式动画、自定义隐式动画、自定义显式动画、和 Hero动画31.2、隐式动画
通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以叫隐式动画,FLutter中提供的AnimatedContainer,AnimatedPadding,AnimatedPositioned,AnimatedOpacity、AnimatedDefaultTextStyle、AnimatedSwitcher都属于隐式动画。
隐式动画中可以通过duration配置动画时长、可以通过curve(曲线)来配置动画过程31.2.1.AnimatedContainer
31.2、隐式动画
通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以叫隐式动画,FLutter中提供的AnimatedContainer,AnimatedPadding,AnimatedPositioned,AnimatedOpacity、AnimatedDefaultTextStyle、Animated:witcher都属于隐式动画隐式动画中可以通过 duration 配置动画时长、可以通过curve(曲线)来配置动画过程
Duration(seconds: 1,milliseconds: 500) 1.5s
3121 AnimatedContainbr
AnimatedContainer,AnimatedPadding,AnimatedPositioned,AnimatedOpacity、AnimatedDefaultTextStyle、AnimatedSwitcher
CircularProgressIndicator 加载组件 widget
动画可以叠加 child+
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: Icon(Icons.add),
),
body: Demo());
}
// Widget Demo() {
// return AnimatedContainer(
// duration: Duration(seconds: 1),
// height: flag ? 200 : 500,
// width: flag ? 200 : 500,
// color: Colors.red,
// );
// }
// Widget Demo() {
// return AnimatedPadding(
// padding: EdgeInsets.only(top: flag ? 0 : 100),
// duration: Duration(milliseconds: 500),
// child: Container(
// height: 200,
// width: 200,
// color: Colors.red,
// ),
// );
// }
// Widget Demo() {
// return Stack(
// children: [
// AnimatedPositioned(
// curve: Curves.easeOut,
// left: flag ? 0 : 10.0,
// bottom: flag ? 0 : 500.0,
// child: Container(
// height: 200,
// width: 200,
// color: Colors.red,
// ),
// duration: Duration(milliseconds: 500))
// ],
// );
// }
// Widget Demo() {
// return AnimatedOpacity(
// opacity: !flag ? 1.0 : 0.0,
// duration: Duration(milliseconds: 500),
// child: Container(
// height: 200,
// width: 200,
// color: Colors.red,
// ),
// );
// }
// Widget Demo() {
// return AnimatedDefaultTextStyle(
// duration: Duration(milliseconds: 500),
// style: TextStyle(
// fontSize: flag ? 10 : 30, color: flag ? Colors.black : Colors.blue),
// child: Container(
// height: 200,
// width: 200,
// color: Colors.red,
// child: Text('hello'),
// ),
// );
// }
Widget Demo() {
//使用子组件动画生效
return Container(
height: 200,
width: 200,
color: Colors.red,
child: AnimatedSwitcher(
duration: Duration(milliseconds: 500),
child: Text(
flag ? 'hello' : 'koo',
key: UniqueKey(),
style: TextStyle(fontSize: flag ? 20 : 50),
),
));
}
}
P48 48 Flutter动画 Flutter显式动画详解
31.3、显式动画
常见的显式动画有RotationTransition,FadeTransition,ScaleTransition,SlideTransition、Animatedicon,在显示动画中开发者需要创建一个AnimationController,通过AnimationController控制动画的开始、暂停、重置、跳转、倒播等。
31.3.1,RotationTransition,AnimationController常见的显式动画有RotationTransition,FadeTransition,ScaleTransition,SlideTransition、Animatedicon,在显示动画中开发者需要创建一个AnimationController,通过AnimationController控制动画的开始、暂停、重置、跳转、倒播等。
31.3.1,RotationTransition,AnimationController
FlutterLogo()
RotationTransition FadeTransition ScaleTransition
SlideTransition
with SingleTickerProviderStateMixin
/让程序和手机的刷新频率统一
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
//vsync: this 让程序和手机的刷新频率统一
_controller =
AnimationController(vsync: this, duration: const Duration(seconds: 1));
// AnimationController(vsync: this, duration: const Duration(seconds: 1))..repeat();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller.repeat();
},
child: Icon(Icons.add),
),
body: Demo());
}
// Widget Demo() {
// return Column(
// children: [
// RotationTransition(
// turns: _controller,
// child: Container(
// height: 200.0,
// width: 200.0,
// color: Colors.red,
// ),
// ),
// Row(
// children: [
// ElevatedButton(
// onPressed: () {
// _controller.repeat();
// },
// child: const Text('repeat')),
// ElevatedButton(
// onPressed: () {
// _controller.stop();
// },
// child: const Text('stop')),
// ElevatedButton(
// onPressed: () {
// _controller.reverse();
// },
// child: const Text('rerevse')),
// ElevatedButton(
// onPressed: () {
// _controller.reset();
// },
// child: const Text('reset')),
// ],
// )
// ],
// );
// }
// Widget Demo() {
// return Column(
// children: [
// FadeTransition(
// opacity: _controller,
// child: Container(
// height: 200.0,
// width: 200.0,
// color: Colors.red,
// ),
// ),
// Row(
// children: [
// ElevatedButton(
// onPressed: () {
// _controller.repeat();
// },
// child: const Text('repeat')),
// ElevatedButton(
// onPressed: () {
// _controller.stop();
// },
// child: const Text('stop')),
// ElevatedButton(
// onPressed: () {
// _controller.reverse();
// },
// child: const Text('rerevse')),
// ElevatedButton(
// onPressed: () {
// _controller.reset();
// },
// child: const Text('reset')),
// ],
// )
// ],
// );
// }
// Widget Demo() {
// return Column(
// children: [
// ScaleTransition(
// scale: _controller,
// child: Container(
// height: 200.0,
// width: 200.0,
// color: Colors.red,
// ),
// ),
// Row(
// children: [
// ElevatedButton(
// onPressed: () {
// _controller.repeat(reverse: true);
// },
// child: const Text('repeat')),
// ElevatedButton(
// onPressed: () {
// _controller.stop();
// },
// child: const Text('stop')),
// ElevatedButton(
// onPressed: () {
// _controller.reverse();
// },
// child: const Text('rerevse')),
// ElevatedButton(
// onPressed: () {
// _controller.reset();
// },
// child: const Text('reset')),
// ],
// )
// ],
// );
// }
Widget Demo() {
return Column(
children: [
SlideTransition(
// position: _controller.drive(Tween(
// begin: Offset(
// 0.0,
// 0.0,
// ),
// end: Offset(0.5, 0.0))),
// position: Tween(
// begin: Offset(
// 0.0,
// 0.0,
// ),
// end: Offset(0.5, 0.0))
// .animate(_controller),
position: Tween(
begin: Offset(
0.0,
0.0,
),
end: Offset(0.5, 0.0))
.chain(CurveTween(curve: Curves.bounceInOut))
.chain(CurveTween(curve: Interval(0.2, 0.8)))
.animate(_controller),
child: Container(
height: 200.0,
width: 200.0,
color: Colors.red,
),
),
Row(
children: [
ElevatedButton(
onPressed: () {
_controller.repeat(reverse: true);
},
child: const Text('repeat')),
ElevatedButton(
onPressed: () {
_controller.stop();
},
child: const Text('stop')),
ElevatedButton(
onPressed: () {
_controller.reverse();
},
child: const Text('rerevse')),
ElevatedButton(
onPressed: () {
_controller.reset();
},
child: const Text('reset')),
],
)
],
);
}
}
P49 49 Animated动画以及交错式动画
31.3.5,AnimatedIcon Animatedicon顾名思义,是一个用于提供动画图标的组件,它的名字虽然是以Animated开头,但是他是一个显式动画组件,需要通过progress属性传入动画控制器,另外需要由Icon属性传入动画图标数据
import 'dart:ffi';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
late AnimationController _controller;
bool flag = true;
@override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: const Duration(seconds: 1));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {
flag ? _controller.forward() : _controller.reverse();
flag = !flag;
},
child: Icon(Icons.add),
),
body: Demo());
}
// Widget Demo() {
// return AnimatedIcon(
// icon: AnimatedIcons.ellipsis_search,
// progress: _controller,
// size: 50.0,
// );
// }
// Widget Demo() {
// return Stack(
// children: [
// FadeTransition(
// opacity: _controller.drive(Tween(begin: 1.0, end: 0.0)
// .chain(CurveTween(curve: Interval(0.0, 0.5)))),
// child: Icon(Icons.search),
// ),
// FadeTransition(
// opacity: _controller.drive(Tween(begin: 0.0, end: 1.0)
// .chain(CurveTween(curve: Interval(0.5, 1.0)))),
// child: Icon(Icons.close),
// ),
// ],
// );
// }
Widget Demo() {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SlideTransition(
position: _controller.drive(
Tween(
begin: Offset(
0.0,
0.0,
),
end: Offset(1.0, 0.0))
.chain(CurveTween(curve: Interval(0.0, 0.2))),
),
child: Container(
width: 200,
height: 100,
color: Colors.blue[100],
),
),
SlideTransition(
position: _controller.drive(
Tween(
begin: Offset(
0.0,
0.0,
),
end: Offset(1.0, 0.0))
.chain(CurveTween(curve: Interval(0.2, 0.4))),
),
child: Container(
width: 200,
height: 100,
color: Colors.blue[200],
),
),
SlideTransition(
position: _controller.drive(
Tween(
begin: Offset(
0.0,
0.0,
),
end: Offset(1.0, 0.0))
.chain(CurveTween(curve: Interval(0.4, 0.8))),
),
child: Container(
width: 200,
height: 100,
color: Colors.blue[500],
),
),
],
);
}
}
P50 50 Flutter动画Hero动画以及Hero动画结…
Hero动画结合photo_view实现类似朋友圈
的图片预览、滑动、放大、缩小
Hero
Hero(tag: value[' imageUrl'], child: Image. network(value[' imageUrl)
31.7.1、photo_view预览单张图片
photo_view : ^0.14.0
pub.dev/packages?q=photo_view+
多ds
单v
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
List listData = [
'https://www.itying.com/images/flutter/2.png',
'https://www.itying.com/images/flutter/3.png'
];
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
body: Demo());
}
Widget Demo() {
return GridView.builder(
itemCount: listData.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 0.7),
itemBuilder: (context, index) {
return Hero(
tag: listData[index],
child: GestureDetector(
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return ImgDetail(
url: listData[index],
);
}));
},
child: Image.network(listData[index]),
));
});
}
}
class ImgDetail extends StatelessWidget {
final url;
const ImgDetail({super.key, this.url});
@override
Widget build(BuildContext context) {
return Hero(
tag: url,
child: Center(
child: PhotoView(
imageProvider: NetworkImage(url),
),
));
}
}
P51 51【Getx试听】Flutter Getx状态管理介…..
get x
1.1、状态管理
通俗的讲:当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用Flutter中的状态管理来管理统一的状态(数据),实现不同组件之间的传值和数据共享。
现在Flutter的状态管理方案很多,redux,bloc、state、provider、Getx provider是官方提供的状态管理解决方案,主要功能就是状态管理。Getx是第三方的状态管理插件不仅具有状态管理的功能,还具有路由管理、主题管理、国际化多语言管理、Obx局部更新、网络请求、数据验证等功能,相比其他状态管理插件Getx 简单、功能强大并且高性能。
1.2、Flutter Getx介绍
1.2、Flutter Getx介绍
GetX 是 Flutter 上的一个轻量且强大的解决方案,Getx为我们提供了高性能的状态管理、智能的依赖注入和便捷的路由管理。
GetX 有3个基本原则:
性能:GetX专注于性能和最小资源消耗。GetX 打包后的apk占用大小和运行时的内存占用与其他状态管理插件不相上下。
效率:GetX的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长。
结构:Getx 可以将界面、逻辑、依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护
GetX 并不臃肿,却很轻量。如果你只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到你的代码中。它拥有众多的功能,但这些功能都在独立的容器中,只有在使用后才会启动。
Getx有一个庞大的生态系统,能够在Android,iOS,Web,Mac,Linux,Windows和你的服务器上用同样的代码运行。通过Get Server可以在你的后端完全重用你在前端写的代码。
官网:https://pub.dev/packages/get中文文档:https://github.com/jonataslaw/getx/blob/master/README.zh-cn.md
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:get/get.dart';
List listData = [
'https://www.itying.com/images/flutter/2.png',
'https://www.itying.com/images/flutter/3.png'
];
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
body: Demo());
}
Widget Demo() {
return Column(
children: [
ElevatedButton(
onPressed: () async {
Get.defaultDialog(
title: 'hello',
content: const Text('content'),
onConfirm: () {
print('ok');
// Navigator.of(context).pop();
Get.back();
},
);
},
child: const Text('GetToast'),
),
ElevatedButton(
onPressed: () async {
Get.snackbar('current miss 5w', 'kooteam');
},
child: const Text('snackbar'),
),
ElevatedButton(
onPressed: () async {
Get.bottomSheet(Row(
children: [
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Get.isDarkMode ? Colors.black : Colors.white)),
onPressed: () async {
Get.changeTheme(ThemeData.dark());
},
child: const Text('dark'),
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Get.isDarkMode ? Colors.white : Colors.black)),
onPressed: () async {
Get.changeTheme(ThemeData.light());
},
child: const Text('light'),
),
],
));
},
child: const Text('bottomSheet'),
),
],
);
}
}
P52 52【Getx试听】Flutter Getx路由管理
三、Flutter Getx路由管理
Getx为我们封装了Navigation,无需context可进行跳转,使用Getx进行路由跳转非常的简单,只需要调用Get.to0即可进行路由跳转,Getx路由跳转简化了跳转动画设置、动画时长定义、动画曲线设置。
Flutter Getx 路由管理
3.1 Get.to0实现普通路由跳转一、设置应用程序入口
二、调用to方法切换路由A二、调用Get.toNamed0跳…..
月三、Get.back0;返回到上...A 四、Get.offAllO;返回到根五、Get.off(NextScreen0);.
github.com/jonataslaw/getx/blob/master/README.zh-cn.md#redirect
今天结合自带路由 onxx onGenerateRoute
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:get/get.dart';
List listData = [
'https://www.itying.com/images/flutter/2.png',
'https://www.itying.com/images/flutter/3.png'
];
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'flutter',
defaultTransition: Transition.leftToRightWithFade,
getPages: [
GetPage(
name: '/my',
middlewares: [MyMiddleware()],
transition: Transition.rightToLeft,
page: () => const My())
],
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
body: Demo());
}
Widget Demo() {
return Column(
children: [
ElevatedButton(
onPressed: () async {
Get.to(My());
},
child: const Text('GoMy'),
),
],
);
}
}
class My extends StatelessWidget {
const My({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: () async {
Get.back();
},
child: const Text('Back'),
),
);
}
}
//抽成 目录 middleware/mymiddle
class MyMiddleware extends GetMiddleware {
RouteSettings? redirect(String? route) {
print('myMiddleware');
return null;
//在这重定向 没有权限
return RouteSettings(name: '/home');
}
}
P53 53[Getx试听】Flutter Getx状态管理响.
4.2、响应式状态管理器
响应式编程可能会让很多人感到陌生,因为它很复杂,但是GetX将响应式编程变得非常简单。
你不需要创建StreamControllers.
你不需要为每个变量创建一个StreamBuilder.
你不需要为每个状态创建一个类。
湖北众猿腾网络科技有限公司
IT营(www.itying.com)
你不需要为一个初始值创建一个get,使用Get的响应式编程就像使用setState一样简单.
让我们想象一下,你有一个名称变量,并且希望每次你改变它时,所有使用它的小组件都会自动刷新。
响应式变量
final RxString name=RxString('');
final name=Rx<string>('');
final name=''.obs; 多数
类的成员属性也可以 实例化时 加 classname().obs
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:get/get.dart';
List listData = [
'https://www.itying.com/images/flutter/2.png',
'https://www.itying.com/images/flutter/3.png'
];
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'flutter',
defaultTransition: Transition.leftToRightWithFade,
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
RxInt _counter = 0.obs;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {
// setState(() {});//经常build 耗性能
_counter++;
},
child: Icon(Icons.add),
),
body: Demo());
}
Widget Demo() {
return Center(
child: Obx(() => Text("$_counter")),
);
}
}
P54 54【Getx试听】Flutter Getx状态管理依…..
4.3、Flutter Getx简单的状态管理(依赖管理)
GetxController
4.3.1、Getx 依赖管理简介
Get有一个简单而强大的依赖管理器,它允许你只用1行代码就能检索到与你的Bloc或Controller相同的类,无需Provider context,无需inheritedWidget.
Controller controller = Get.put(ControllerO);
/不是 Controller controller = ControllerO;想象一下,你已经浏览了无数条路由,现在你需要拿到一个被遗留在控制器中的数据,那你需要一个状态管理器与Provider或Get_it一起使用来拿到它,对吗?用Get则不然,Get会自动为你的控制器找到你想要的数据,而你甚至不需要任何额外的依赖关系。
Controller controller =Get.findO;
//是的,它看起来像魔术,Get会找到你的控制器,并将其提供给你。你可以实例化100万个控制器,Get总会给你正确的控制器。
最后 _counter.value 加value 才是真正的值
第四步:Category.dart执行dec方法
你可以让Get找到一个正在被其他页面使用的Controller,并将它返回给你。
final countController= Get.find
final CountController countController = Get.find();
4.4、GetX Binding需求:所有页面都要使用状态管理
在我们使用Getx状态管理器的时候,往往每次都是用需要手动实例化一个控制器,这样的话基本页面都需要实例化一次,这样就太麻烦了,而Binding能解决上述问,可以在项目初始化时把所有需要进行状态管理的控制器进行统一初始化,接下来看代码演示:
在前面的文章中,我们经常使用Get.put(MyController)来进行控制器实例的创建,这样我们就算不使用控制器实例也会被创建,其实Getx还提供很多创建实例的方法,可根据不同的业务来进行创建,接下来我们简单介绍一下几个最常用的
Get.put():不使用控制器实例也会被创建
Get.lazyPut():懒加载方式创建实例,只有在使用时才创建Get.putAsync0:Get.putO的异步版版本Get.create):每次使用都会创建一个新的实例
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'flutter',
initialBinding: AllController(),
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
// CounterController counterController = Get.put(CounterController());
CounterController counterController = Get.find();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {
counterController.inc();
},
child: Icon(Icons.add),
),
body: Demo());
}
Widget Demo() {
return Center(
child: Column(
children: [
Obx(() => Text("${counterController._counter.value}")),
ElevatedButton(
onPressed: () {
Get.to(My());
},
child: Text('My'))
],
),
);
}
}
class AllController implements Bindings {
@override
void dependencies() {
Get.lazyPut(() => CounterController());
}
}
class My extends StatelessWidget {
CounterController counterController = Get.find();
My({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Center(
child: Text('${counterController._counter.value}'),
),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Back')),
),
);
}
}
class CounterController extends GetxController {
RxInt _counter = 0.obs;
void inc() {
_counter++;
update();
}
void dec() {
_counter--;
update();
}
}
P55 55 Getx GetView GetxController GetX Bi...
Getx GetView GetxController Binding
以及GetxController生命周期函数
九、GetView介绍以及GetxController生命
周期
Getview只是对已注册的 Controller有一个名为 controller 的getter的 const Stateless 的Widget,如果我们只有单个控制器作为依赖项,那我们就可以使用Getview,而不是使用Statelesswidget,并且避免了写Get.FindO).
GetView如何使用
GetView的使用方法非常简单,只是要将你的视图层继承自Getview并传入需要注册的控制器并Get.put(即可,我们来看下代码演示
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'flutter',
initialBinding: HomeControllerBinding(),
getPages: [
GetPage(
name: '/', binding: HomeControllerBinding(), page: () => Home()),
GetPage(
name: '/my', binding: HomeControllerBinding(), page: () => My())
],
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends GetView<CounterController> {
@override
Widget build(BuildContext context) {
// Get.put(CounterController());
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.inc();
},
child: Icon(Icons.add),
),
body: Demo());
}
Widget Demo() {
return Center(
child: Column(
children: [
Obx(() => Text("${controller._counter.value}")),
ElevatedButton(
onPressed: () {
// Get.to(My());
Get.toNamed('/my');
},
child: Text('My'))
],
),
);
}
}
class HomeControllerBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut(() => CounterController());
}
}
class My extends GetView<CounterController> {
My({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Center(
child: Text('${controller._counter.value}'),
),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Back')),
),
);
}
}
class CounterController extends GetxController {
RxInt _counter = 0.obs;
@override
void onInit() {
// TODO: implement onInit
super.onInit();
}
//等等生命周期函数
void inc() {
_counter++;
update();
}
void dec() {
_counter--;
update();
}
}
P56 56 Flutter结合Getx实现多语言配置、Get...
Getx国际化多语言配置、GetX GetUtils
工具类
github.com/jonataslaw/getx/blob/master/README.zh-cn.md#国际化
十、GetX GetUtils Getutils是getx为我们提供一些常用的工具类库,包括值是否为空、是否是数字、是否是视频、图片、音频、PPT,Word,APK、邮箱、手机号码、日期、MD5,SHA1等等.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'flutter',
translations: Messages(), // 你的翻译
locale: Locale('zh', 'CN'), // 将会按照此处指定的语言翻译
fallbackLocale: Locale('en', 'US'), // 添加一个回调语言选项,以备上面指定的语言翻译不存在
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Get.put(CounterController());
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
body: Demo());
}
Widget Demo() {
return Center(
child: Column(
children: [
Text("hello".tr),
ElevatedButton(
onPressed: () {
var locale = Locale('zh', 'CN');
Get.updateLocale(locale);
},
child: Text('chinese')),
ElevatedButton(
onPressed: () {
var locale = Locale('en', 'US');
Get.updateLocale(locale);
},
child: Text('english'))
],
),
);
}
}
class Messages extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'zh_CN': {
'hello': '你好 世界',
},
'en_US': {
'hello': 'Hallo World',
}
};
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: Home());
}
}
class Home extends StatelessWidget {
TextEditingController editingController = TextEditingController();
@override
Widget build(BuildContext context) {
// Get.put(CounterController());
return Scaffold(
appBar: AppBar(title: const Text('Flutter')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
body: Demo());
}
Widget Demo() {
return Center(
child: Column(
children: [
TextField(
controller: editingController,
),
ElevatedButton(
onPressed: () {
if (GetUtils.isEmail(editingController.text)) {
Get.snackbar('success', 'success',
backgroundColor: Colors.greenAccent);
} else {
Get.snackbar('fail', 'fail', backgroundColor: Colors.red);
}
},
child: Text('confirm')),
],
),
);
}
}
P57 57[APP实战第14讲试听】IT营 Flutter G..
Flutter 仿小米商城APP 首页热销臻选布局
主讲教师:(大地)
我的专栏:https://www.itying.com/category-79-b0.html
合作网站:www.itying.com(IT营官网)
ScreenAdapter 响应式 适配不同尺寸手机
https://github.com/feisher/ScreenAdapter
https://github.com/tal-tech/flutter_screen_adapter
https://blog.csdn.net/winni_a/article/details/137038254
https://book.flutterchina.club/
标签:教程,const,Text,Dart,context,child,return,Flutter From: https://www.cnblogs.com/KooTeam/p/18606824