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中管理多个页面时有两个核心概念和类:RouteNavigator。 一个route是一个屏幕或页面的抽象,Navigator是管理routeWidgetNavigator可以通过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多数是通过静态路由完成跳转的,如pushNamedpushReplacementNamedpushNamedAndRemoveUntil等。

动态路由

动态路由无需在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).pushNavigator.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的时候,BpageCpage替换了在堆栈中的位置而移除栈,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的转场效果和从BPagepush的转场效果。简单来说就是CPagepopBPage,在pushCPage。(不知道是不是卡顿的原因,笔者看起来区别不大)

综上: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

可知出了要pushCPage,当前路由栈中所有的路由都被移除,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

pushCPage的时候,移除到RootPage停止,CPage默认返回RootPage

popUntil

返回到指定的标记路由,若标记的路由为null,则程序退出,慎用!!! 有时候我们需要根据业务需求判断:可能返回上一级路由,也可能返回上上级路由或是返回指定的路由等。这个时候就不能使用Replacement和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,在层叠布局中,如果后一个容器,比前面的容器大,那么就会遮挡,原理是为什么?

  1. flutter在绘画时,从x 0 y 0开始绘画,也就是 左上角

  2. 意味着两个容器绘画开始的坐标都是相同的,只不过宽高不一样

  3. 那么如果第一个容器宽高为 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的学习文档,不可转载相关推荐

  1. 手把手教最新最全最详细Git使用教程(图文并茂,附Git命令大全学习文档)

    导读 因为教程详细,所以行文有些长,新手边看边操作效果出乎你的预料.GitHub虽然有些许改版,但并无大碍. 最全Git命令学习文档下载(集合整理,非常适合新手) 一.Git是什么? Git是目前世界 ...

  2. 最全编程语言在线 API 文档

    1.常用API文档索引 最全编程语言在线 API 文档:https://tool.oschina.net/apidocs 2.Learn X in Y minutes Learn X in Y min ...

  3. C和C++编程和学习文档

     C和C++编程和学习文档 C和C++编程和学习文档   1 :指针变量名称以p为首字符,这是程序员通常在定义指针时的一个习惯 2 :har * p;    (int *)p 把p强制转换为int型  ...

  4. Bootstrap学习文档(三)

    Bootstrap 注意下面的组件,很多是需要用到 js 的,所以要引入 Bootstrap 的 js 文件和 jquery 文件在示例代码中,我只是没有写,注意加上哦. 字体图标 Bootstrap ...

  5. 安卓学习文档收集汇总

    安卓学习文档收集汇总 https://www.jianshu.com/p/86aed183ce6c?utm_campaign=maleskine&utm_content=note&ut ...

  6. Hadoop大数据平台实践(二):Hadoop生态组件的学习文档

    Hadoop基础组件学习-Yzg-2019-03-06 Hadoop基础组件学习文档.. 1 简介.. 4 HDFS. 5 HDFS读文件.. 6 HDFS写文件.. 7 Mapreduce 8 单词 ...

  7. linux个人学习文档

    Linux系统基础 第1章 Linux简介 1.1开源的力量 1.1.1 我们已经用过的开源软件 1.1.2 开源软件领域的旗帜:Linux 1.1.3 软件开源的好处 1.2 Linux的来历 1. ...

  8. IntelliJ IDEA 14,15 使用教程,实战总结,倾囊相授,内附PDF学习文档

    转载博文,尊重原创,感谢前辈分享,原文地址:"请叫我大师兄__"的CSDN博客主页 文章目录 前言 一.安装 二.使用 1.Debug 2.修改内存 3.一般设置 4.高级设置 5 ...

  9. springboot学习文档

    SpringBoot学习文档 首先真的特别感谢同学的分享以及老师的整理,让我成功入门了springboot的. 一.介绍 小结: - SpringBoot并不是一个新的开发语言- Spring Boo ...

最新文章

  1. mysql在线教程嵌套_MySQL update嵌套
  2. 查看某段代码或语句的被调用路径的方法小结
  3. 类模板(参考《C++ Templates 英文版第二版》)
  4. google js cdn_「效率工具」模拟CDN的浏览器扩展程序,改善在线隐私
  5. Eclipse怎样把文件系统形式的项目作为工程直接导入?
  6. mysql知识点拾遗梳理
  7. wx-jq:一套完全原创的微信小程序插件集合库
  8. Trie图的学习过程
  9. mybatis xml配置
  10. 计算机网络数据通信部分之网络层IP报文格式解析
  11. 7.1 php7.0 微擎_php7.1以上微擎-人人商城小程序授权登录问题
  12. 第六章 用正三和弦为含跳进旋律配和声
  13. 浅谈设计师职业成长轨迹
  14. 从零点亮一个led灯
  15. 计算机辅助医疗未来展望,数字骨科应用与展望:更精确、个性、直观的未来前景...
  16. 网桥是怎么分类的?具体有哪些分类?
  17. numpy中的seed
  18. python实践报告总结_关于开发Python项目的心得总结
  19. level set 介绍4(水平集方法)
  20. 计算机键盘可以分为哪几个区,键盘分为哪几个区?分别是什么?

热门文章

  1. 烦死人了,全彩led显示屏在使用过程中总是跳闸,原因跟解决方案分享
  2. 常见排序算法总结—Java实现
  3. 802.1x准入技术
  4. 《机器学习技法》---核型SVM
  5. 谷歌技术“三宝”之一的Google文件系统和Kosmos 文件系统
  6. 如何将一个向量投影到一个平面上_MIT—线性代数笔记15 子空间投影
  7. 腾讯投资vipkid,全面向在线教育发起进攻
  8. 告别996之最实用VBA办公自动化代码大全详解
  9. Golang 并发控制和锁
  10. aes 256 cbc java,AES256加解密java语言实现