全网最全Flutter的学习文档,不可转载
Flutter学习文档—Author:Brath
由于文章内容较干,请允许Brath打一波广告…
面试记APP
Github:https://github.com/Guoqing815/interview
安卓APP下载:https://www.pgyer.com/interview_app_release
Brath的个人博客:https://brath.top
面试记官方公众号,定期分享有趣的编程知识:https://mp.weixin.qq.com/s/jWs6lLHl5L-atXJhHc4YvA
前言:如果你要学习flutter,那么你一定要会dart语言,因为flutter是基于dart来封装的一个 UI 组件包
本文使用 Typort 书写,禁止转载。
本文仅限有后端有语言基础(C/C++/C#/Java/python/Golang/PHP 都可以),前端 ( JavaScript,Html,CSS)的人来学习。如果0基础,请先学习任意一门后端语言并熟练掌握!
Dart语言学习:
安装Dart:https://github.com/GeKorm/dart-windows/releases/download/v1.6.0/Dart_x64.stable.setup.exe
安装好后配置环境变量:DART_HOME E:\dart\bin 安装路径
配置好后cmd输入 dart --version 查看环境
Dart VM version: 2.3.3-dev.0.0.flutter-b37aa3b036 (Tue Jun 11 13:00:50 2019 +0000) on "windows_x64"
注释:
/**多行注释*多行注释*//*** 文档注释 与Java相同*////文档注释 dart独有
变量定义:
dart语言特点:
自动类型转换,var声明的变量可以是任意类型!
dart拥有两种变量定义方式。
指定类型:
String name = "brath";
或者
类型推导
var name = "brath"; //推导为任意类型
final name = "brath"; //不可修改,定义常量,可以初始化
const name = "brath"; //不可修改,定义常量,不可以初始化,可以被构造修改
void main(){var name = 111111;String name1 = "brath用类型定义";print("Hello World! Brath~");print(name.runtimeType);print(name1.runtimeType); } console: Hello World! Brath~ int String
变量拼接:
与Java不同,拼接方式用 ${}
如果只拼接普通变量,可以直接 $变量名
如果拼接变量引用方法,必须要${变量名.方法名}
集合类型:
list集合: var names = ["111","222","333"];
set集合: var movies = {"111","222","333”};
map集合:var info = {"11":"1","22":"2"}
默认情况下,dart的所有class都是隐式接口!
Dart函数使用:
void main(List<String> args) {print(sum(51, 14891)); } int sum(int a,int b){return a + b; }
函数参数:
必选参数,不能有默认值,可选参数,可以有默认值
main(){sayHello("why"); } //必选参数:String name 必须传参 void sayHello(String name){print(name) } //可选参数:位置可选参数 void sayHello2(String name, [int age, String desc]){sayHello2("brath",12,"waa");//位置可选参数:用[]包围的参数可传可不传,但是位置必须对应 } //可选参数:命名可选参数 重点,多用! void sayHello3(String name, {int age, String desc}){sayHello3("brath",age: 13,desc: "212");//位置可选参数:用{}包围的参数可传可不传,但是必须指定参数名 }
函数是一等公民:
函数可以作为另外一个函数的参数!
void main(List<String> args) {// test(see); //匿名函数// test((){// print("匿名");// return 10;// }); test(() => print("箭头")); } void test(Function foo){see(); } void see(){print("see!"); }
void main(List<String> args) { // test((num1,num2){// return num1+num2;// });var num = demo();print(num(20,12)); } //将函数声明式显示,利用 typedef 声明一个函数列表,调用 typedef 声明的函数 typedef Calculate = int Function(int num1,int num2); void test(Calculate calculate){calculate(20,30); } // void test(int foo(int num1,int num2)){ // foo(20,30); // } Calculate demo(){return (num1,num2){return num1 * num2;}; }
赋值运算符:
Flutter中,有诡异的赋值运算符 比如 name ??="111"; 解释:当原来的变量有值时,不执行 当原来的变量为null时,执行 或者 var name = name ?? "11"; 解释: 当name不为空时使用name,为空使用后面的变量
级联运算符:
void main(){var p = Person()..name = "brath"..eat();..run(); } //利用 .. 连续调用对象中的方法,类似于Java中的链式调用 class Person(){String name;void eat(){print("吃");}void run(){print("跑");} }
For循环和Switch循环与JS和Java基本一致
构造函数:
class Person{String name;int age;double height;//默认构造函数Person(this.name.this.age);//命名构造函数,指定名字的构造函数Person.NameCon(this.name.this.age,this.height); }
dynamic:
dynamic代表任意类型 dynamic obj = "obj"; //可以调用 print(obj.subString(1)); Object obj = "obj"; //不能调用! print(obj.subString(1));
初始化列表:
mian(){var p = Person('brath'); } class Person{final String name;final int age;//如果传了age参数,就用age参数,如果没传age参数就用10Person(this.name,{int age}): this.age = age ?? 10; }
构造函数重定向:
mian(){} class Person{String name;int age;//默认构造函数调用内部构造函数,重定向Person(String name) : this._internal(name,0);Person._internal(this.name,this.age) }
工厂构造函数:
//相比于普通构造函数来说,工厂构造函数可以手动返回对象 class Person{String name;String color;static final Map<String,Person> _nameCache = {};static final Map<String,Person> _colorCache = {};//工厂构造函数,手动根据条件返回对象factory Person.withName(String name){if(_nameCache.containsKey(name)){return _nameCache[name];}else{_nameCache[name] = Person(name,"default");return Person(name,"default");}} }
Getter和Setter:
void main(List<String> args) {//直接访问属性final p = Person();p.name = "brath";print(p.name);//get,set访问p.setName("brath.cloud");print(p.getName); } class Person{late String name; // //get,set方法// void setName(String name) {// this.name = name;// }// String get getName{// return name;// } //get,set方法箭头函数void setName(String name) => this.name = name;String get getName => name; }
隐式接口:
//dart中没有interface关键字,默认所有类都是隐式接口 //当讲将一个类作为接口使用时,实现这个接口的类,必须实现这个接口中的所有方法
类的混入:
用class声明的类不可以混入其他类 要混入其他类,使用 mixin 声明该类,并在混入时用with关键字来连接被混入的类
类属性和类方法:
类属性:在类中不用static声明的变量,叫做成员变量,不可以被类直接调用 静态属性:在类中用static声明的变量,叫做静态属性,类属性,可以被类直接调用 类方法:在类中不用static声明的方法,叫做成员方法,不可以被类直接调用 静态方法:在类中用static声明的方法,叫做静态方法,类属性,可以被类直接调用
枚举的使用:
void main(List<String> args) {final color = Colors.bule; switch(color){case Colors.bule:print("蓝色");break;case Colors.red:print("红色");break;case Colors.yellow:print("黄色");break;} print(Colors.values); } enum Colors{red,bule,yellow }
库的使用:
//在Dart中,任何一个dart文件都是一个库,类似于Java中的包 //系统库导入: import 'dart:库名'; //自定会库导入: import '包名/类名'; //库别名:当本类引用其他库时,出现方法名冲突,可以用 as 来给导入的库起别名,再用别名引用 import 'utils/TimeUtil' as timeUtil; //默认情况下,导入一个库时,导入的是这个库中所有的内容 //dart提供两个关键字来单独导入方法或者隐藏某个方法: show hide import 'utils/TimeUtil' show timeUtil; //只导入timeUtil方法 import 'utils/TimeUtil' hide timeUtil; //只有timeUtil不会导入 //多个方法可以用逗号分割: import 'utils/TimeUtil' show timeUtil, FileUtil; //只导入timeUtil,FileUtil方法 import 'utils/TimeUtil' hide timeUtil, FileUtil; //只有timeUtil,FileUtil不会导入
抽取公共库文件:
以上方法导入库的时候总是会遇到一些问题,比如如果有100个方法,你只想用50个,那么你就要用50个show或者50个hide,但是dart提供了一种方式,就是抽取库到一个公共类中。前面提到过,dart中所有文件都是一个库,那么我们把需要导入的库,全部export到一个库中,在引用这个库,就不用担心过多引入了。公共库: util.dart export 'util/TimeUtil' export 'util/FileUtil' 我的代码: import 'util';
使用第三方库:
//dart使用第三方库需要创建一个文件 pubspec.yaml
name: 库名 desciption: 描述 dependencies: 依赖http: ^0.13.4 怎么找库? https://pub.dev/packages/http
点击installing
把dependencies内容复制到代码中
name: coderwhy desciption: a dart dependencies:http: ^0.12.0+4 environment: sdk: '>=2.10.0 < 3.0.0'
进入当前类文件夹,终端输入 pub get 就会下载对应库包
import 'package:http/http.dart' as http; //引入第三方库,必须用package来开头 void main() async {var url = 'https://www.brath.cloud:9000/esn-user-service/user/getUserInfo?id=1';var url2 = 'https://brath.cloud/image/back.png';var response = await http.get(url);print(response.body); }
异常处理:
与Java相同但是有不一样的部分:
同步处理
在一个方法中用try捕获异常,如果调用方法就捕获不到了!
异步处理
调用一个异步方法如果发生异常,可以用自异步+await来捕获异常
void main() async{try{await test1();}catch(e){print(e);} } test1() async{print(11~/0); }
接下来介绍 我们的Flutter!
最好的跨平台解决方案 Flutter
架构对比:
GUP绘制出图像,放到Buffer缓存中,手机屏幕根据刷新率来读取缓存的操作,就是展示图像。
引出了一个概念:垂直同步
为什么要有垂直同步?
来看一个例子:假设我GPU每秒帧率产生60,手机屏幕每秒也是接受60,这时可以正常显示。
如果突然每秒帧率提高到120,手机屏幕可能会来不及读取缓存导致画面重叠、撕裂
开启垂直同后,会有两块缓存区域。
垂直同步就限制了手机屏幕读取缓存和GPU产生的速度,开启垂直同步后,GPU将画面写入到第一个缓存中,第一个缓存会复制内容(地址交换)到第二个缓存中,当两份缓存都存在这一帧,就会发送一个VSync的信号,告诉GPU可以绘制下一张图,然后手机屏幕来显示第二个缓存中的内容,这样就可以避免图像撕裂。
一个简单的flutter结构:
import 'package:flutter/material.dart'; // mian() => runApp(MyApp()); void main() {runApp(const MyApp()); } //APP主体 class MyApp extends StatelessWidget{@overrideWidget build(BuildContext context) {return MaterialApp(home: BrathScaffoldPage());} } //页面主体 class BrathScaffoldPage extends StatelessWidget{@overrideWidget build(BuildContext context) {return Scaffold(//appbar:顶部标签主体appBar: AppBar(centerTitle: true,title: Text("第一个Fullter程序",style: TextStyle(fontSize: 20),),),body: BrathBodyPage());} } //内容主体 class BrathBodyPage extends StatelessWidget{@overrideWidget build(BuildContext context) {return Text("Hello Fullter");} }
开始学习:
下载Flutter SDK
配置Flutter的第一步就是下载Flutter SDK,然后进行安装,上面两个地址都有给SDK下载地址,这里的问题是有的SDK安装包有时会报 没有.git文件的错误,所以最稳妥的方法是通过git clone命令安装 在安装目录下面执行
git clone -b stable https://github.com/flutter/flutter.git
安装完成之后,可以在安装根目录,找的 flutter_console.bat 文件,双击运行
配置Flutter运行环境变量
在用户变量里面编辑或者添加 Path 条目,把Flutter的bin目录路径添加在里面
运行Flutter
在命令行运行 flutter doctor,它会下载它自己的依赖项并自行编译,一般情况是会报错提示,多半是Android SDK找不到什么的,如果出错了,就按照错误信息网上查一下就解决了。 我的已经处理完成的
编辑器设置
我用的Android Studio,上面连接里面有不同系统和编辑器的流程,详情可以前往查看
Android Studio的开发环境就不说了,需要的可以自行百度。Android Studio配置Flutter开发主要是 Flutter 和 Dart两个插件
File -- Settings -- Plugins -- Marketplace 然后在搜索里面搜索Flutter和Dart安装就可以了。 安装完插件,重启一下 Android Studio 基本就配置完成了,可以新建Flutter项目了。
新建Flutter项目
File -- New -- New Flutter Project
选择Flutter Application
然后到这个最后一步的时候,会有一点小问题
Flutter SDK path 这一栏第一次默认是空的,需要手动选择,选择我们最开始下载的Flutter SDK,选择根目录,就可以了
至此Flutter的开发环境安装完毕!
现在开始学习Flutter的基础组件,以及进阶理论!
flutter学习笔记 auther:Brath
所有的重点都在代码的注释中标注!
创建项目:
到想存储项目的文件路径,打开CMD,输入 flutter create 项目名称即可
vscode下载好插件,dart和flutter打开对应flutter文件,即可开始编写
import 'package:flutter/material.dart'; //导包 material main() {runApp(MyApp()); //运行app } class MyApp extends StatelessWidget { //继承无状态widget@overrideWidget build(BuildContext context) {return MaterialApp( //运行根节点MaterialApp);} }
Widget:flutter模块/组件
特性:
widget分为有状态(StatefulWidget)和无状态的 (StatelessWidget)
无状态的widget是静态页面
有状态的widget是动态页面
要点:
tips:flutter的main入口调用第一个widget需要该widget使用 MaterialApp()作为首个widget
因为 MaterialApp 包含了路由主题等等组件,flutter规定只能用MaterialApp当作根节点
使用MaterialApp的home属性来指定页面
class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(debugShowCheckedModeBanner: false,home: HomePage(),);} }
Container容器(相当于DIV)(widget):下面有更详细的介绍
均为可选参数
Container({Key? key,this.alignment,this.padding, //边距this.color, //颜色 使用 Clolrs枚举this.decoration, //描述this.foregroundDecoration,double? width, //宽度 使用double 常量double? height, //高度 使用double 常量BoxConstraints? constraints,this.margin, //marginthis.transform,this.transformAlignment,this.child, //子组件this.clipBehavior = Clip.none,}) : assert(margin == null || margin.isNonNegative),assert(padding == null || padding.isNonNegative),assert(decoration == null || decoration.debugAssertIsValid()),assert(constraints == null || constraints.debugAssertIsValid()),assert(clipBehavior != null),assert(decoration != null || clipBehavior == Clip.none),assert(color == null || decoration == null,'Cannot provide both a color and a decoration\n''To provide both, use "decoration: BoxDecoration(color: color)".',),constraints =(width != null || height != null)? constraints?.tighten(width: width, height: height)?? BoxConstraints.tightFor(width: width, height: height): constraints,super(key: key);
Text文本组件(widget):
Text默认传一个文本:
class TextDemo extends StatelessWidget @overrideWidget build(BuildContext context) {return Container( //容器width: double.infinity, //宽度 使用double枚举color: Colors.blue, //颜色 使用Colors枚举child: Text( //容器的子组件 文本组件 "文本" * 20, //输入文本 20个maxLines: 1, //最大行数 1textDirection: TextDirection.ltr, //从左到右textAlign: TextAlign.center, //剧中style: TextStyle( //设置文本样式fontSize: 30, //字体大小 30color: Colors.teal //字体颜色),));}}
const Text(//必传参数String this.data, //可选参数{Key? key,this.style, //文本风格,使用 TextStyle方法来指定this.strutStyle,this.textAlign, //设置文本居中 靠左 靠右,使用 TextAlign枚举this.textDirection, //文本排列:左到右 右到左 使用 TextDirection枚举this.locale,this.softWrap,this.overflow, //溢出后按照什么风格显示,使用TextOverflow的枚举this.textScaleFactor,this.maxLines, //最大行数this.semanticsLabel,this.textWidthBasis,this.textHeightBehavior,}) : assert(data != null,'A non-null String must be provided to a Text widget.',),textSpan = null,super(key: key);
Button按钮组件(widget):
flutter中有几种常用按钮组件:
在 2.0 版本后遗弃按钮 RaisedButton改为ElevatedButton , FlatButton改为TextButton
RaisedButton 已遗弃 FlatButton 已遗弃
ElevatedButton:漂浮按钮/升降按钮
class ButtonDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Column(children: [ElevatedButton(onPressed:(){//点击事件,如果为null未定义的话,按钮无法点击}, child: Text( //这里是按钮文本,可以是图片可以是文本"漂浮按钮"))],);} }
TextButton:扁平按钮/文本按钮
class ButtonDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Column(children: [TextButton(onPressed: (){//点击事件}, child: Text("扁平按钮"))],);} }
TextButton.icon:带图标的扁平按钮/文本按钮
class ButtonDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Column(children: [TextButton.icon(onPressed: (){},icon: Icon(Icons.add), //使用Icons枚举选择图标label: Text("图标按钮"))],);} }
OutlinedButton.icon:无阴影按钮
class ButtonDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Column(children: [OutlinedButton(onPressed: (){}, child: Text("无阴影按钮"))],);} }
OutlinedButton.icon:图标按钮
class ButtonDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Column(children: [IconButton(onPressed: (){}, icon: Icon(Icons.home)) //图标用 Icons 枚举选择],);} }
Image图片、图标组件(widget):
flutter提供了四种图片加载方式:
1、Image.network //从网络获取图片
2、Image.asset //从项目本地获取图片
3、Image.file //从文件路径获取图片
4、Image.memory //从手机内存,存储中获取图片
使用 asset 需要设置 pubspec.yaml 中的 assets
class ImageIconDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Column(children: [Icon(Icons.home), //普通图标IconButton(onPressed: (){}, icon: Icon(Icons.home)), //带点击事件的图标Container(width: double.infinity, //最大宽度child: Image.network( //从网络获取图片"https://brath.cloud/love/GCLK6888.JPG?versionId=CAEQNxiBgID8yJjchBgiIDUzZGFiMWU3YWVlNDQ4YmJhMzMwNDY0Mzk1OGJiOTU1",fit: BoxFit.fill, //图片填充模式),),Image.asset("images/image.jpeg"), //项目加载图片],);} }
Switch开关,CheckBox复选框组件(widget):
因为开关和复选框是动态的,有状态的,所以我们要使用 StatefulWidget 来做他们的widget
//Tips:在 onChanged 使用 setState 来改变状态
Check 复选框
class CheckDemo extends StatefulWidget {@overrideState<CheckDemo> createState() => _CheckDemoState(); } class _CheckDemoState extends State<CheckDemo> {bool _check = false;@overrideWidget build(BuildContext context) {return Column(children: [Checkbox(value: _check, onChanged: (res){ //在 onChanged 使用 setState 来改变状态setState(() {_check = res!;});}),],);} }
Switch开关
class CheckDemo extends StatefulWidget {@overrideState<CheckDemo> createState() => _CheckDemoState(); } class _CheckDemoState extends State<CheckDemo> {bool _switch = false;@overrideWidget build(BuildContext context) {return Column(children: [Switch(value: _switch, onChanged: (res){ //在 onChanged 使用 setState 来改变状态setState(() {_switch = res;});})],);} }
Progress 进度条,指示器组件(widget):
flutter为我们提供了几种进度条和指示器样式
1、LinearProgressIndicator 线性指示器
2、CircularProgressIndicator 圆圈指示器
3、CupertinoActivityIndicator IOS风格的进度指示器
可以设置的参数:
value:可以设置 0 - 1,来表示当前进度
valueColor:使用 AlwaysStoppedAnimation(Colors.red) 动画包裹颜色设置进度指示器的颜色
class ProgressDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Padding(padding: EdgeInsets.all(10),child: Column(children: [LinearProgressIndicator( //线性指示器value: .5, //进度 从0-1, .5就是一半valueColor: AlwaysStoppedAnimation(Colors.red), //设置颜色要用动画包裹),SizedBox(height: 16), //设置间隔 16 Container( //设置容器height: 100, //高 100width: 100, //宽 100child: CircularProgressIndicator( //圆圈指示器// value: .8,valueColor: AlwaysStoppedAnimation(Colors.red),),),SizedBox(height: 16),CupertinoActivityIndicator(), //IOS风格的进度指示器]),);} }
Click 点击组件(widget):
flutter为我们提供了 GestureDetector 手势检测器
class ClickDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return GestureDetector( //创建手势检测器onTap: (){ //单击print("点击");},onDoubleTap: (){ //双击print("双击");},child: Text("点击组件"),);} }
Input 输入框组件(widget):
flutter为我们提供了两种常用输入组件:
TextField:默认典型输入框,没有 validator 验证
TextFromField:特点是可以带参数校验 validator 一般用于登录注册表单验证
TextField 源码
const TextField({Key? key,this.controller, //控制器this.focusNode, //焦点this.decoration = const InputDecoration(), //装饰器TextInputType? keyboardType,this.textInputAction, //输入动作 键盘右下角(完成,搜索,下一行)this.textCapitalization = TextCapitalization.none,this.style, //样式this.strutStyle,this.textAlign = TextAlign.start, //文本格式 默认从左开始this.textAlignVertical,this.textDirection, //文本方向this.readOnly = false,ToolbarOptions? toolbarOptions,this.showCursor,this.autofocus = false,this.obscuringCharacter = '•',this.obscureText = false,this.autocorrect = true,SmartDashesType? smartDashesType,SmartQuotesType? smartQuotesType,this.enableSuggestions = true,this.maxLines = 1, //最大行数this.minLines, //最小行数this.expands = false,this.maxLength, //最大字数@Deprecated('Use maxLengthEnforcement parameter which provides more specific ''behavior related to the maxLength limit. ''This feature was deprecated after v1.25.0-5.0.pre.',)this.maxLengthEnforced = true,this.maxLengthEnforcement,this.onChanged, //当值改变this.onEditingComplete,this.onSubmitted,this.onAppPrivateCommand,this.inputFormatters,this.enabled,this.cursorWidth = 2.0,this.cursorHeight,this.cursorRadius,this.cursorColor,this.selectionHeightStyle = ui.BoxHeightStyle.tight,this.selectionWidthStyle = ui.BoxWidthStyle.tight,this.keyboardAppearance,this.scrollPadding = const EdgeInsets.all(20.0),this.dragStartBehavior = DragStartBehavior.start,this.enableInteractiveSelection = true,this.selectionControls,this.onTap,this.mouseCursor,this.buildCounter,this.scrollController,this.scrollPhysics,this.autofillHints = const <String>[],this.clipBehavior = Clip.hardEdge,this.restorationId,this.enableIMEPersonalizedLearning = true,})
TextFromField 源码
Key? key,this.controller,String? initialValue,FocusNode? focusNode,InputDecoration? decoration = const InputDecoration(),TextInputType? keyboardType,TextCapitalization textCapitalization = TextCapitalization.none,TextInputAction? textInputAction,TextStyle? style,StrutStyle? strutStyle,TextDirection? textDirection,TextAlign textAlign = TextAlign.start,TextAlignVertical? textAlignVertical,bool autofocus = false,bool readOnly = false,ToolbarOptions? toolbarOptions,bool? showCursor,String obscuringCharacter = '•',bool obscureText = false,bool autocorrect = true,SmartDashesType? smartDashesType,SmartQuotesType? smartQuotesType,bool enableSuggestions = true,@Deprecated('Use maxLengthEnforcement parameter which provides more specific ''behavior related to the maxLength limit. ''This feature was deprecated after v1.25.0-5.0.pre.',)bool maxLengthEnforced = true,MaxLengthEnforcement? maxLengthEnforcement,int? maxLines = 1,int? minLines,bool expands = false,int? maxLength,ValueChanged<String>? onChanged,GestureTapCallback? onTap,VoidCallback? onEditingComplete,ValueChanged<String>? onFieldSubmitted,FormFieldSetter<String>? onSaved,FormFieldValidator<String>? validator, //与TextFiled不同的点,增加了 validator验证方法List<TextInputFormatter>? inputFormatters,bool? enabled,double cursorWidth = 2.0,double? cursorHeight,Radius? cursorRadius,Color? cursorColor,Brightness? keyboardAppearance,EdgeInsets scrollPadding = const EdgeInsets.all(20.0),bool enableInteractiveSelection = true,TextSelectionControls? selectionControls,InputCounterWidgetBuilder? buildCounter,ScrollPhysics? scrollPhysics,Iterable<String>? autofillHints,AutovalidateMode? autovalidateMode,ScrollController? scrollController,String? restorationId,bool enableIMEPersonalizedLearning = true,})
简易登录
class InputDemo extends StatefulWidget { //创建有状态 widget@overrideState<InputDemo> createState() => _InputDemoState(); } class _InputDemoState extends State<InputDemo> {GlobalKey _key = GlobalKey<FormState>(); //key的泛型是表单状态,这样就可以通过key提交TextEditingController _rootController = TextEditingController();//账号控制器TextEditingController _passController = TextEditingController();//密码控制器FocusNode _r = FocusNode(); //账号焦点FocusNode _p = FocusNode(); //密码焦点 //当退出时销毁controller,否则占用内存@overridevoid dispose() {super.dispose(); //销毁父类_rootController.dispose(); //销毁_passController.dispose(); //销毁_r.dispose(); //销毁_p.dispose(); //销毁}@overrideWidget build(BuildContext context) {return Form( //构建表单key: _key, //构建表单提交keychild: Column(children: [TextFormField( //构建表单输入框autofocus: true, //默认焦点聚集focusNode: _r, //账号焦点controller: _rootController, //引用账号控制器decoration: InputDecoration( //输入框描述prefixIcon: Icon(Icons.add), //输入框图标labelText: "账号", //输入框标题hintText: "默认文字" //输入框默认value),validator: (v){ //只有使用 TextFormField 才可以用验证 validator 不用验证使用 TextFieldif(v == null || v.isEmpty){return "账号不能为空!";}},textInputAction: TextInputAction.next, //回车后跳转下个输入框onFieldSubmitted: (v){ //监听回车键print("brath");},),SizedBox(height: 8), //设置间隔高度TextFormField(focusNode: _p, //密码焦点controller: _passController,decoration: InputDecoration(prefixIcon: Icon(Icons.add),labelText: "密码",hintText: "输入密码"),obscureText: true,validator: (v){if(v == null || v.length < 5){return "密码不能小于5位数!";}},textInputAction: TextInputAction.send, //将小键盘右下角的回车设置图标),SizedBox(height: 16),ElevatedButton(onPressed: (){//当校验通过时输出 true 否则 falseprint((_key.currentState as FormState).validate().toString());},child: Text("提交"),),]),);} }
Flutter路由工具:
var res = await Navigator.of(context).push( //跳转路由到 MenuPage 并可以接受返回值
这段代码用异步来监听返回值,优点是,无论是否点击按钮返回,都可以接收到返回值
还可以用 .then((value) => print(value)); 的方式来获取,这样更简洁,只有返回的时候才会监听,不返回不监听
// ignore_for_file: prefer_const_constructors, use_key_in_widget_constructors import 'package:flutter/material.dart'; //新页面导包 class LoginPage extends StatelessWidget { //无状态widget@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar( //标题title: Text("登录"),elevation: 10.0,centerTitle: true,),body: ElevatedButton( //登录按钮onPressed: () async {var res = await Navigator.of(context).push( //跳转路由到 MenuPage 并可以接受返回值MaterialPageRoute(builder: (context) {return MenuPage( //传参 menuTitlemenuTitle: "菜单",);},settings: RouteSettings( //路由设置name: "参数",arguments: "我是参数", //向目标传参的数据),maintainState: false,fullscreenDialog: true,));print(res); //打印返回值},child: Text("登录"),),);} } class MenuPage extends StatelessWidget {final String menuTitle;const MenuPage({Key? key,required this.menuTitle}) : super(key: key);@overrideWidget build(BuildContext context) {//通过 ModalRoute.of(context)?.settings.arguments; 来获取传参dynamic arguments = ModalRoute.of(context)?.settings.arguments;return Scaffold(appBar: AppBar(title: Text(menuTitle + " " + arguments),),body: ElevatedButton(onPressed: (){Navigator.of(context).pop("Brath");},child: Text("返回按钮"),),);} }
Flutter中管理多个页面时有两个核心概念和类:Route
和Navigator
。 一个route
是一个屏幕或页面的抽象,Navigator
是管理route
的Widget
。Navigator
可以通过route
入栈和出栈来实现页面之间的跳转。 路由一般分为静态路由(即命名路由)和动态路由。
静态路由(即命名路由)
静态路由在通过Navigator
跳转之前,需要在MaterialApp
组件内显式声明路由的名称,而一旦声明,路由的跳转方式就固定了。通过在MaterialApp
内的routes
属性进行显式声明路由的定义。
class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(initialRoute: "/", // 默认加载的界面,这里为RootPageroutes: { // 显式声明路由//"/":(context) => RootPage(),"A":(context) => Apage(),"B":(context) => Bpage(),"C":(context) => Cpage(),},// home: LoginPage(),//当设置命名路由后,home不用设置);} } 注意:如果指定了home属性,routes表则不能再包含此属性。 如上代码中【home: RootPage()】 和 【"/":(context) => RootPage()】两则不能同时存在。
例如:RootPage
跳转Apage
即:RootPage
—>Apage
Navigator.of(context).pushNamed("A");
一般方法中带有Name
多数是通过静态路由完成跳转的,如pushNamed
、pushReplacementNamed
、pushNamedAndRemoveUntil
等。
动态路由
动态路由无需在MaterialApp
内的routes
中注册即可直接使用:RootPage —> Apage
Navigator.of(context).push(MaterialPageRoute(builder: (context) => Apage(),));
动态路由中,需要传入一个Route
,这里使用的是MaterialPageRoute
,它可以使用和平台风格一致的路由切换动画,在iOS上左右滑动切换,Android上会上下滑动切换。也可以使用CupertinoPageRoute
实现全平台的左右滑动切换。 当然也可以自定义路由切换动画,使用PageRouteBuilder
:使用FadeTransition
做一个渐入过渡动画。
Navigator.of(context).push(PageRouteBuilder(transitionDuration: Duration(milliseconds: 250), // //动画时间为0.25秒pageBuilder: (BuildContext context,Animation animation,Animation secondaryAnimation){return FadeTransition( //渐隐渐入过渡动画opacity: animation,child: Apage());}) );
到现在为止,可能对路由有了一定的认识,,下面就结合具体方法来详细说明。 在这之前有必要说明: Navigator.of(context).push
和Navigator.push
两着并没有特别的区别,看源码也得知,后者其实就是调用了前者。 of
:获取Navigator
当前已经实例的状态。
路由拦截:
flutter提供了 onGenerateRoute 来使用路由拦截器,作用于强制登录
class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(debugShowCheckedModeBanner: false,initialRoute: "/",routes: {"/" :(context) => LoginPage(),// "menu" :(context) => MenuPage(), },onGenerateRoute: (RouteSettings s){ //路由拦截器print(s.name); //路由名称if(s.name != "menu"){ //当该路由不等于 menu 强制跳转回首页return MaterialPageRoute(builder: (context){return LoginPage();},settings: s);}switch(s.name){case "menu" : //当该路由等于 menu 跳转至 menu 菜单return MaterialPageRoute(builder: (context){return MenuPage();},settings: s);break;}},// home: LoginPage(),//当设置命名路由后,home不用设置);} }
路由方法解释:
pop
返回当前路由栈的上一个界面。 Navigator.pop(context);
push / pushNamed :
见上,两者运行效果相同,只是调用不同,都是将一个page
压入路由栈中。直白点就是push
是把界面直接放入,pushNames
是通过路由名的方式,通过router使界面进入对应的栈中。 结果:直接在原来的路由栈上添加一个新的 page
。
pushReplacement / pushReplacementNamed / popAndPushNamed
替换路由,顾名思义替换当前的路由。 例如
Replacement.png
由图可知在BPage
使用替换跳转到Cpage
的时候,Bpage
被Cpage
替换了在堆栈中的位置而移除栈,CPage
默认返回的是APage
。
pushReplacement 使用的动态路由方式跳转:
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => Cpage(), ));
pushReplacementNamed 使用的静态路由方式,
Navigator.of(context).pushReplacementNamed("/C");
两者运行效果相同。
popAndPushNamed:
Navigator.of(context).popAndPushNamed("/C");
其实和上面两个方法运行的结果也是一致,区别就是动画效果不一样:BPage
—>CPage
的时候,CPage
会同时有pop
的转场效果和从BPage
页push
的转场效果。简单来说就是CPage
先pop
到BPage
,在push
到CPage
。(不知道是不是卡顿的原因,笔者看起来区别不大)
综上:3中方法结果一样,只是调用方式和过渡动画的区别,开发者自行选择。
pushAndRemoveUntil / pushNamedAndRemoveUntil
在使用上述方式跳转时,会按次序移除其他的路由,直到遇到被标记的路由(predicate
函数返回了true
)时停止。若 没有标记的路由,则移除全部。 当路由栈中存在重复的标记路由时,默认移除到最近的一个停止。
第一种
// 移除全部 Navigator.pushAndRemoveUntil(context,MaterialPageRoute(builder: (_) => CPage()), (Route router) => router == null);
或
// 移除全部 Navigator.of(context).pushNamedAndRemoveUntil("/C", (Route router) => router == null);
此时的路由栈示意图:
RemoveUntil_all.png
可知出了要push
的CPage
,当前路由栈中所有的路由都被移除,CPage
变成根路由。
第二种:移除到RootPage停止
// "/"即为RootPage,标记后,移除到该路由停止移除 Navigator.pushAndRemoveUntil(context,MaterialPageRoute(builder: (_) => CPage()), ModalRoute.withName('/')) 或 Navigator.pushAndRemoveUntil(context,MaterialPageRoute(builder: (_) => CPage()), (Route router) => router.settings.name == "/"); // 只是写法不一样
或
Navigator.of(context).pushNamedAndRemoveUntil("/C", (Route router) => router.settings.name == "/"); 或 Navigator.of(context).pushNamedAndRemoveUntil("/C", ModalRoute.withName("/"));
此时的路由栈示意图:
RemoveUntil_until.png
push
到CPage
的时候,移除到RootPage
停止,CPage
默认返回RootPage
。
popUntil
返回到指定的标记路由,若标记的路由为null
,则程序退出,慎用!!! 有时候我们需要根据业务需求判断:可能返回上一级路由,也可能返回上上级路由或是返回指定的路由等。这个时候就不能使用Replacemen
t和RemoveUntil
来替换、移除路由了。 例如:
until.png
Navigator.of(context).popUntil((route) => route.settings.name == "/"); 或 Navigator.of(context).popUntil(ModalRoute.withName("/"));
再例如:
要实现上述功能,从CPage
返回到APage
,并且不在MaterialApp
内的routes
属性进行显式声明路由。因为笔者觉得一个应用程序的界面太多了,如果每个界面都要显示声明路由,实在是不优雅。 因为需要返回APage
,还是需要标记路由,所有我们在之前跳转APage
的时候设置RouteSettings
,如下:
// 设置APage的RouteSettings Navigator.of(context).push(MaterialPageRoute(settings: RouteSettings(name:"/A"),builder: (context) => APage(), ));
在CPage
需要返回的时候,调用就行:
Navigator.of(context).popUntil(ModalRoute.withName("/A"));
这样代码看起来很优雅,不会冗余。 另:
// 返回根路由 Navigator.of(context).popUntil((route) => route.isFirst);
canPop
用来判断是否可以导航到新页面,返回的bool
类型,一般是在设备带返回的物理按键时需要判断是否可以pop
。
maybePop
可以理解为canPop
的升级,maybePop
会自动判断。如果当前的路由可以pop
,则执行当前路由的pop
操作,否则将不执行。
removeRoute/removeRouteBelow
删除路由,同时执行Route.dispose
操作,无过渡动画,正在进行的手势也会被取消。
removeRoute
removeRoute.png
BPage
被移除了当前的路由栈。 如果在当前页面调用removeRoute
,则类似于调用pop
方法,区别就是无过渡动画,所以removeRoute
也可以用来返回上一页。
removeRouteBelow
移除指定路由底层的临近的一个路由,并且对应路由不存在的时候会报错。 同上。
综上:这个两个方法一般情况下很少用,而且必须要持有对应的要移除的路由。 一般用于立即关闭,如移除当前界面的弹出框等。
路由传值
常见的路由传值分为两个方面:
向下级路由传值
返回上级路由时传值
要注意的是,我们一般说静态路由不能传值,并不是说一定不能用于传值,而是因为静态路由一般需要在MaterialApp
内的routes
属性进行显式声明,在这里使用构造函数传值无实际意义。 如:
MaterialApp(initialRoute: "/", // 默认加载的界面,这里为RootPageroutes: { // 显式声明路由"/":(context) => RootPage(),"/A":(context) => APage("title"), // 在这里传参无实际意义,一般需要传入的参数都是动态变化的"/B":(context) => BPage(),"/C":(context) => CPage(),},// home: RootPage(),);
向下级路由传值
1、构造函数传值
首先构造一个可以带参数的构造函数:
class APage extends StatefulWidget {String title;APage(this.title);@override_APageState createState() => _APageState(); }
在路由跳转的时候传值:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => APage("这是传入的参数"), ));
在APage拿到传入的值:
// 在 StatefulWidget 使用[widget.参数名] Container(child: Text(widget.title), )
2、ModalRoute 传值
在Navigator.of(context).push
的跳转方式中,MaterialPageRoute
的构造参数中 可以看到有RouteSettings
的属性,RouteSettings
就是当前路由的基本信息
const RouteSettings({this.name,this.isInitialRoute = false,this.arguments, // 存储路由相关的参数Object});
路由跳转时设置传递参数:
Navigator.of(context).push(MaterialPageRoute(settings: RouteSettings(name:"/A",arguments: {"argms":"这是传入A的参数"}),builder: (context) => APage(), )); 或使用静态路由pushName: Navigator.of(context).pushNamed("/A",arguments:{"argms":"这是传入A的参数"});
在APage
中取值:
Map argms = ModalRoute.of(context).settings.arguments; print(argms["argms"]);
返回上级路由时传值
就是在调用APage
中调用pop
返回路由的时候传参
Navigator.of(context).pop("这是pop返回的参数值");
在上一级路由获取:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => APage(), )).then((value){ // 获取pop的传值print(value); }); 或 String value = await Navigator.of(context).pushNamed('/xxx');
Flutter 布局(Layout )(Widget):
textDirection: TextDirection.ltr, //组件排列方式mainAxisSize: MainAxisSize.max, //主轴最大值mainAxisAlignment: MainAxisAlignment.spaceEvenly, //主轴布局crossAxisAlignment: CrossAxisAlignment.start, //纵轴排列方式
Column - 纵向
概念:纵轴的宽度,默认使用子组件最大宽度
此时,红色和黄色容器宽度为 100 绿色为150,整个容器就会使用 最大的子组件宽度 150 来表示自己
Column 代码演示:
class LayoutDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("布局练习"),),body: Container(color: Colors.grey,child: Column(children: [Container(width: 100,height: 100,color: Colors.red,),Container(width: 150,height: 100,color: Colors.green,),Container(width: 100,height: 100,color: Colors.yellow,),]),));} }
Row - 横向
概念:和Colunm相似,纵轴的宽度,默认使用子组件最大高度
此时,红色和黄色容器高度为 100 绿色为200,整个容器就会使用 最大的子组件高度 200 来表示自己
Row 代码演示
class LayoutDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("布局练习"),),body: Container(color: Colors.grey,child: Row(textDirection: TextDirection.ltr, //组件排列方式mainAxisSize: MainAxisSize.max, //主轴最大值mainAxisAlignment: MainAxisAlignment.spaceEvenly, //主轴布局crossAxisAlignment: CrossAxisAlignment.start, //纵轴排列方式children: [Container(width: 100,height: 200,color: Colors.red,),Container(width: 150,height: 100,color: Colors.green,),Container(width: 100,height: 100,color: Colors.yellow,),]),));} }
Flutter弹性布局(Flex):
flutter为我们提供了 Flex 这个 widget 来制造弹性布局
Flex 默认 必传方向 Axis
children使用 Expanded来包裹,可以设置 flex权重,根据数字大小来设置权重
class LayoutDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("布局练习"),),body: Container(color: Colors.grey,child: Flex(direction: Axis.vertical,children: [Expanded(child: Container(width: 100,height: 200,color: Colors.red,),flex: 2,),Expanded(child: Container(width: 100,height: 200,color: Colors.green,),flex: 2,),Expanded(child: Container(width: 100,height: 200,color: Colors.yellow,),flex: 2,),],),));} }
Flutter流式布局(Wrap):
flutter为我们提供了 Wrap 这个 widget 来制造弹性布局
使用 有状态的 StatefulWidget 来构建 wrap 布局
class WrapDemo extends StatefulWidget {@overrideState<WrapDemo> createState() => _WrapDemoState(); } class _WrapDemoState extends State<WrapDemo> {var list = <int>[];@overridevoid initState() {super.initState();for (var i = 0; i < 20; i++) { //初始化时向数组添加 20 个数据list.add(i);}}@overrideWidget build(BuildContext context) {return Wrap(direction: Axis.horizontal, //设置方向alignment: WrapAlignment.start, //布局参数spacing: 1.0, //边距runSpacing: 1.0, //边距children: list.map((e) => Container(height: 100,width: 100,child: Text(e.toString(),style: TextStyle(color: Colors.black,fontSize: 20)),color: Colors.blue,)).toList());} }
Flutter层叠布局(Stack):
flutter为我们提供了 Stack这个 widget 来制造层叠布局
我们设置了两个容器div,在层叠布局中,如果后一个容器,比前面的容器大,那么就会遮挡,原理是为什么?
flutter在绘画时,从x 0 y 0开始绘画,也就是 左上角
意味着两个容器绘画开始的坐标都是相同的,只不过宽高不一样
那么如果第一个容器宽高为 100 第二个为150 就理所应当的遮住啦!
class StackDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(color: Colors.grey,width: double.infinity,child: Stack(alignment: AlignmentDirectional.center, //居中布局children: [Container(color: Colors.green,width: 150,height: 150,),Container(color: Colors.red,width: 100,height: 100,),],),);} }
Flutter定位布局(Positioned ):
flutter为我们提供了 Positioned 这个 widget 来制造层叠布局
如果 Positioned 设置了宽高,那么子组件不生效
//如果设置了 • top: 10, • bottom: 10, • 那么就不能设置高度 height //如果设置了 • left: 10, • right: 10, • 那么就不能设置宽度 width
代码演示:
class StackDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(color: Colors.grey,width: double.infinity,child: Stack(alignment: AlignmentDirectional.center,children: [Container(color: Colors.green,width: 150,height: 150,),Container(color: Colors.red,width: 100,height: 100,),Positioned(// width: 100,// height: 100,child: Container(color: Colors.yellow,width: 300,height: 300,),top: 50,left: 150,right: 150,bottom: 50,)],),);} }
Flutter相对定位(Align):
flutter为我们提供了 Align 这个 widget 来制造层叠布局
要点:只会相对于父组件来定位,而不是屏幕
class AlignDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(width: 200,height: 200,color: Colors.green,child: Align(alignment: Alignment.center, //居中child: FlutterLogo( //flutter的logosize: 60, //宽高60),),);} }
Flutter的内外边距 Padding、Margin
flutter为我们提供了 padding 和 margin 这量个 属性来设置内外边距
内边距:当前容器内的组件对于当前容器的距离
外边距:当前容器距离父类容器的距离
代码演示:
class PaddingAndMarginDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(width: 100,height: 100,color: Colors.red,//设置外边距(当前容器距离父类容器的距离)// margin: EdgeInsets.only(left: 10),//单独设置外边距margin: EdgeInsets.all(10),//四个方向设置外边距//设置内边距(当前容器内的组件对于当前容器的距离)padding: EdgeInsets.all(20),child: Text("我有边距"),);} }
Flutter尺寸限制容器(ConstrainedBox)widget:
要点:子widget没有设置宽高的时候取自己设置的最大宽高
ConstrainedBox的特点就是可以设置最大或者最小的宽高,子组件怎么设置都不可以超过这个宽高
代码演示:
class ConstrainedBoxDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return ConstrainedBox(constraints: BoxConstraints(maxHeight: 100,maxWidth: 100,minHeight: 50,minWidth: 50,),child: Container(width: 500,height: 500,color: Colors.red,),);} }
Flutter尺寸限制容器(SizeBox)widget:
要点:如果父容器指定了宽高,那么子组件不可以修改宽高
代码演示:
class ConstrainedBoxDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return SizedBox(// width: 100,// height: 100,child: Container(color: Colors.red,width: 200,height: 200,),);} }
Flutter装饰器(BoxDecoration)widget:
flutter为我们提供了 BoxDecoration 这量个 widget 来设置样式装饰
代码演示:
class ConstrainedBoxDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(margin: EdgeInsets.all(20),width: double.infinity,child: DecoratedBox( //装饰器decoration: BoxDecoration(// color: Colors.redgradient: LinearGradient( //渐变颜色colors: [Colors.red, //从红色Colors.green, //到绿色],),borderRadius: BorderRadius.circular(10.0), //圆角度boxShadow: [BoxShadow(color: Colors.black,offset: Offset(2.0,2.0),blurRadius: 2,)],),child: Padding(padding: EdgeInsets.only(left: 100,right: 100,top: 20,bottom: 20),child: Text("渐变色~",style: TextStyle(color: Colors.white),textAlign: TextAlign.center,),),),);} }
Flutter小容器(Container)widget:
要点:当Container设置了 foregroundDecoration(前景) 的背景颜色,那么子组件将不会显示
要点:当Container设置了 decoration(背景) 的背景颜色,那么子组件将会显示
设置内边距并旋转 0.5
代码演示
class ContarinerDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(margin: EdgeInsets.all(100), //设置内边距width: 100,height: 100,child: Text("data"), decoration: BoxDecoration( //设置背景 foregroundDecoration设置前景,会遮挡color: Colors.red),transform: Matrix4.rotationZ(0.5), //旋转,可选坐标轴);} }
Flutter小容器(MateriaApp,Scaffold)widget:
1.MateriaApp是flutter的根节点,flutter规定必须要 MateriaApp 来作为根节点展示
2.在MateriaApp可以设置路由,每个子页面必须由 Scaffold 来包裹
3.每个 Scaffold 包含两个部分 appBar(头部),body(展示体)
Flutter的AppBar:
Scaffold 中的 AppBar 有很多特性:
代码演示
class PageDemo extends StatefulWidget {@overrideState<PageDemo> createState() => _PageDemoState(); } class _PageDemoState extends State<PageDemo> {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(leading: IconButton( //设置左侧图标onPressed: () {print("点击了!");},icon: Icon(Icons.home) //左边房子图片),// centerTitle: true, //设置centerTitle为true,可将标题居中title: Text("演示",style: TextStyle(fontSize: 15),),actions: [ //设置左侧图标IconButton(onPressed: () {print("点击了加!");},icon: Icon(Icons.add)),IconButton(onPressed: () {print("点击了减!");},icon: Icon(Icons.remove)),IconButton(onPressed: () {print("点击了灯!");},icon: Icon(Icons.wb_iridescent_rounded)),],elevation: 10.0,),// body: ,);} }
Flutter的顶部TabBar选项卡:
Flutter提供 顶部TabBar选项卡
代码演示:
class PageDemo extends StatefulWidget {@overrideState<PageDemo> createState() => _PageDemoState(); } class _PageDemoState extends State<PageDemo> with SingleTickerProviderStateMixin{List tabs = ["Fullter", "Andiord", "IOS"]; //选项卡数组//选项控制器late TabController _controller = TabController(length: tabs.length, vsync: this);//选项索引 int _index = 0; /*** 初始化**/@overridevoid initState() {_controller = TabController( //创建新控制器initialIndex: _index, //设置初始索引length: tabs.length, //长度为数组疮毒vsync: this);_controller.addListener(() { //监听器setState(() { //监听状态,当状态改变,把控制器索引赋值到选项索引,用来做内容切换_index = _controller.index;});});super.initState();}/*** 销毁**/@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(elevation: 10.0, //阴影bottom: TabBar(controller: _controller, //选项接收控制器tabs: tabs.map((e) => Tab( //遍历选项text: e, //文本为map中的内容)).toList(), //转为集合),),body: Text(_index.toString()), //body可以根据index来输出不同内容);} }
Flutter的顶部TabBar选项卡(进阶)
使用Flutter提供 顶部TabBarView组件来设置选项卡
代码演示:
class PageDemo extends StatefulWidget {//指定三个容器页面,在下方调用 widget.widgets 因为泛型指定了 Widget 所以都是Widget数组List<Widget> widgets = [FlutterView(),AndroidView(),IOSView()];@overrideState<PageDemo> createState() => _PageDemoState(); } class _PageDemoState extends State<PageDemo> with SingleTickerProviderStateMixin{List tabs = ["Fullter", "Andiord", "IOS"];late TabController _controller = TabController(length: tabs.length, vsync: this); @overridevoid initState() {_controller = TabController(length: tabs.length, vsync: this);super.initState();}@overridevoid dispose() {_controller.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(elevation: 10.0,bottom: TabBar(controller: _controller,tabs: tabs.map((e) => Tab(text: e,)).toList(),),),body: TabBarView( //使用 TabBarView 包裹bodychildren: widget.widgets, //内容就是widgetscontroller: _controller, //通过控制器来切换));} } class FlutterView extends StatelessWidget {@overrideWidget build(BuildContext context) {return Center(child: Text("FlutterView"),);} } class AndroidView extends StatelessWidget {@overrideWidget build(BuildContext context) {return Center(child: Text("AndroidView"),);} } class IOSView extends StatelessWidget {@overrideWidget build(BuildContext context) {return Center(child: Text("IOSView"),);} }
Flutter的侧抽屉 Drawer 样式
使用Flutter提供 侧抽屉 Drawer 组件来设置抽屉样式
要点:drawer是 Scaffold 中的属性,并不是 AppBar 的
代码演示:
class myDrawer extends StatelessWidget {@overrideWidget build(BuildContext context) {return Drawer(child: MediaQuery.removePadding( //删除边距context: context, child: Column(crossAxisAlignment: CrossAxisAlignment.start, //从左开始children: [Padding(padding: EdgeInsets.only(top: 40),child: Text("Brath"),)],),removeTop: true, //删除顶部), );} }
Flutter的底部选项卡
使用 flutter 提供的 bottomNavigationBar 来做底部选项卡,做到点击卡片切换页面
代码演示:
class BottomNavigatorBarDemo extends StatefulWidget {const BottomNavigatorBarDemo({ Key? key }) : super(key: key); @overrideState<BottomNavigatorBarDemo> createState() => _BottomNavigatorBarDemoState(); } class _BottomNavigatorBarDemoState extends State<BottomNavigatorBarDemo> {int _index = 0; //页面index@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("底部选项卡"),),bottomNavigationBar: BottomNavigationBar( //底部选项widgetitems: [//三个选项BottomNavigationBarItem(icon: Icon(Icons.add),label: "新增"),BottomNavigationBarItem(icon: Icon(Icons.home),label: "我的"),BottomNavigationBarItem(icon: Icon(Icons.remove),label: "减少"),],currentIndex: _index, //当前indexonTap: (v){ //当点击时,把当前索引状态改为点击的索引setState(() {_index = v;});},),body: Center(child: Text(_index.toString())), //展示当前索引);} }
Flutter的底部选项卡(进阶版)
使用 flutter 提供的 bottomNavigationBar 来做底部选项卡,做到按钮居中布局
要点:两种实现方式,BottomNavigationBar中如果BottomNavigationBarItem超过三个需要设置type
全网最全Flutter的学习文档,不可转载相关推荐
- 手把手教最新最全最详细Git使用教程(图文并茂,附Git命令大全学习文档)
导读 因为教程详细,所以行文有些长,新手边看边操作效果出乎你的预料.GitHub虽然有些许改版,但并无大碍. 最全Git命令学习文档下载(集合整理,非常适合新手) 一.Git是什么? Git是目前世界 ...
- 最全编程语言在线 API 文档
1.常用API文档索引 最全编程语言在线 API 文档:https://tool.oschina.net/apidocs 2.Learn X in Y minutes Learn X in Y min ...
- C和C++编程和学习文档
C和C++编程和学习文档 C和C++编程和学习文档 1 :指针变量名称以p为首字符,这是程序员通常在定义指针时的一个习惯 2 :har * p; (int *)p 把p强制转换为int型 ...
- Bootstrap学习文档(三)
Bootstrap 注意下面的组件,很多是需要用到 js 的,所以要引入 Bootstrap 的 js 文件和 jquery 文件在示例代码中,我只是没有写,注意加上哦. 字体图标 Bootstrap ...
- 安卓学习文档收集汇总
安卓学习文档收集汇总 https://www.jianshu.com/p/86aed183ce6c?utm_campaign=maleskine&utm_content=note&ut ...
- Hadoop大数据平台实践(二):Hadoop生态组件的学习文档
Hadoop基础组件学习-Yzg-2019-03-06 Hadoop基础组件学习文档.. 1 简介.. 4 HDFS. 5 HDFS读文件.. 6 HDFS写文件.. 7 Mapreduce 8 单词 ...
- linux个人学习文档
Linux系统基础 第1章 Linux简介 1.1开源的力量 1.1.1 我们已经用过的开源软件 1.1.2 开源软件领域的旗帜:Linux 1.1.3 软件开源的好处 1.2 Linux的来历 1. ...
- IntelliJ IDEA 14,15 使用教程,实战总结,倾囊相授,内附PDF学习文档
转载博文,尊重原创,感谢前辈分享,原文地址:"请叫我大师兄__"的CSDN博客主页 文章目录 前言 一.安装 二.使用 1.Debug 2.修改内存 3.一般设置 4.高级设置 5 ...
- springboot学习文档
SpringBoot学习文档 首先真的特别感谢同学的分享以及老师的整理,让我成功入门了springboot的. 一.介绍 小结: - SpringBoot并不是一个新的开发语言- Spring Boo ...
最新文章
- mysql在线教程嵌套_MySQL update嵌套
- 查看某段代码或语句的被调用路径的方法小结
- 类模板(参考《C++ Templates 英文版第二版》)
- google js cdn_「效率工具」模拟CDN的浏览器扩展程序,改善在线隐私
- Eclipse怎样把文件系统形式的项目作为工程直接导入?
- mysql知识点拾遗梳理
- wx-jq:一套完全原创的微信小程序插件集合库
- Trie图的学习过程
- mybatis xml配置
- 计算机网络数据通信部分之网络层IP报文格式解析
- 7.1 php7.0 微擎_php7.1以上微擎-人人商城小程序授权登录问题
- 第六章 用正三和弦为含跳进旋律配和声
- 浅谈设计师职业成长轨迹
- 从零点亮一个led灯
- 计算机辅助医疗未来展望,数字骨科应用与展望:更精确、个性、直观的未来前景...
- 网桥是怎么分类的?具体有哪些分类?
- numpy中的seed
- python实践报告总结_关于开发Python项目的心得总结
- level set 介绍4(水平集方法)
- 计算机键盘可以分为哪几个区,键盘分为哪几个区?分别是什么?