Dart语法
每段语气都需要逗号隔开
定义变量
Dart用两种定义变量的方式,第一种明确变量类型定义,第二种通过推导类型定义 通过数据类型定义变量,dart的数据类型有: 数字(int/double),字符串(String),布尔(Boolean),列表,集合(Set),映射(Map),符文。
一:使用变量类型定义
入口函数main
void main(List<String> args) {
int firstFlutterCode = 12;
print(firstFlutterCode);
}
二:类型推导定义
声明变量关键词:var、final、const
final:用于定义常量,可以通过计算或者函数进行赋值
const:用于什么常量,必须赋值,且值必须在编写时就确定,不可通过函数结果或者计算结果赋值
void main(List<String> args) {
int firstFlutterCode = 12;
var a = 12;
const b = 'wosh';
const ab = 12 / a; //这种方式不对,不可以这样
final c = 12 / 3;
final ac = 12 / a; //可以通过计算实现赋值
print(firstFlutterCode);
print(a);
print(b);
print(c);
print(ab);
}
三:字符串拼接
通过${变量拼接}
void main(List<String> args) {
final a = 12;
final b = 'xiaozi';
print("我的名字叫$b,今年有${a}岁");
}
如果是变量,可以省略{},如果是对象调动变量的形式,就不可以省略大括号。
四:集合相关
列表:
final list = ["12", '12'];
set集合:
final set = {'12', 12};
map映射:
final map = {'name': '小智', 'age': 13};
五:定义函数
void main(List<String> args) {
print(sum(1, 2));
}
int sum(int a, int b) {
return a + b;
}
函数定义有返回值必须明确放回类型,不能使用var,final,const定义,但是可以不写类型,可以推断出返回值类型,但是不推荐使用。
可选参数:
注意:可选参数必须给初始化值,如果不给,需要使用?
[参数1,参数2…]命名可选参数
void p(String name, [int? a, int sex = 0]) {}
p('小智', 12);
{参数1,参数2,…}
命名可选参数在调用的时候必须带参数名,可选参数必须带初始化值,如果不带,将会被报错。
void f(String name, {int? a, int b = 12}) {}
f('小智', a: 12);
注意:只有可选参数才能有形参默认值
函数作为函数的参数
void main(List<String> args) {
p(f);
}
void p(Function foo) {
foo();
}
void f() {
print("aa");
}
参数作为匿名函数的时候:
p(() {
print("bbb");
});
void p(Function foo) {
foo();
}
箭头函数作为参数:
箭头函数:当函数体只有一行代码的时候可以使用
p(() => print('ccc'));
void p(Function foo) {foo();}
函数作为参数带参数:
void main(List<String> args) {
p(fn);
}
void p(Function f) {
f('12');
}
void fn(String a) {
print(a);
}
这种写法我们使用的不多,我们一般直接将函数的返回值作为函数进行传参
void main(List<String> args) {
p(f);
}
void p(void fn(int a, int b)) {
fn(12, 23);
}
void f(int d, int c) {
print("${d}+++${c}");
}
此时的阅读比较差,可以使用typedef定义函数参数
void main(List<String> args) {
p(f);
}
typedef C = void Function(int a, int b);
void p(C c) {
c(12, 23);
}
void f(int d, int c) {
print("${d}+++${c}");
}
函数作为返回值
void main(List<String> args) {
var a = demo();
var b = a(12, 23);
print(b);
}
demo() {
return (num1, num2) {
return num1 + num2;
};
}
运算符
这部分值写和JavaScript不一样的运算符
整除:~/
3~\2:结果为1
赋值运算符:
??=:表示如果变量为null,赋值,不为null将不赋值。
void main(List<String> args) {
var name = 'xiaozhi';
name ??= '小红';
print(name);
}
??:表示前面的变量不为null,就是用改变量,如果为null,将使用后面的值
void main(List<String> args) {
var name = 'xiaozhi';
var myName = name ?? '小丽';
print(myName);
}
级联运算符
…类似JavaScript的链式调用
void main(List<String> args) {
//一般情况下我是时这么调用的
var p = Person();
p.name='xiao';
p.show();
p.eat();
//级联方式
var p1 = Person()
..name = 'xiaozhi'
..eat()
..show();
}
class Person {
var name;
void show() {
print("yifu");
}
void eat() {
print("吃法");
}
}
循环控制
if的条件必须是rue或者false,如果一个字符串为空或者为null的时候,不会将其转成boolean类型
for循环有两种,普通for循环和for…in循环
其他循环体和JavaScript一样的。
类和对象
使用class关键词定义
void main(List<String> args) {
var p1 = Person('12');
var p = Person.nameAndAge('12', '13');
p.show();
}
class Person {
var name;
var age;
//dart是没有构造方法重载的
Person(this.name);
//命名构造函数
Person.nameAndAge(this.age, this.name);
show() {
print(name);
print(age);
}
}
我们可以使用构造函数和命名构造函数的方式实现方法重载
Object类型和dynamic类型
Object类型:通过多态实现的变量,编译期调用方法会报错
void main(List<String> args) {
Object obj = '123';
obj.substring(1);//报错
}
dynamic:表示的是动态类型,编译期调用方法不会报错,但是在运行阶段可能会报错,导致运行错误。
dynamic obj = '123';
obj.substring(1)
重写tostring方法
class Person {
var name;
var age;
Person(this.name, this.age);
String toString() {
return "$name,$age";
}
}
类的初始化列表
使用finale修饰的,不能再方法内进行赋值,可以通过以下方式赋值
class Person {
final String name;
final int age;
Person(this.name, {int age}):this.age=age??10{
//不支持该方式,使用以上方式
//this.age=age
};
}
多个初始化列表使用逗号隔开。
下面这种方式叫做构造函数重定向:
class Person {
String name;
int age;
Person(String name) : this._personConstructer(name, 0);
Person._personConstructer(this.name, this.age);
}
常量构造函数:
构造函数使用const修饰,成员变量使用final修饰,保证每次创建对象都保证只创建一个对象
class Person {
final String name;
final String age;
const Person(this.name, this.age);
}
工厂构造函数
class Person {
String name;
String age;
Person(this.name, this.age);
static final Map<String, Person> _nameCache = {};
static final Map<String, Person> _ageCache = {};
factory Person.withName(String name) {
if (_nameCache.containsKey(name)) {
return _nameCache[name];
} else {
final p = Person(name, '0');
_nameCache[name] = p;
return p;
}
}
factory Person.withAge(String age) {
if (_nameCache.containsKey(age)) {
return _ageCache[age];
} else {
final p = Person('defualt', age);
_nameCache[age] = p;
return p;
}
}
}
代码都是对的,不知道为什么报错,不知道是不是语法变了。艹!!!!!!
get/set
class Person {
String name;
set setName(String name) {
this.name = name;
}
String get getName {
return name;
}
}
代码还是报错,我真的服气了,写的代码和学习的一模一样都能报错!!必须先初始化值!!!!
类的继承
在子类的初始化列表内调用父类构造函数
class Animal {
String name;
Animal(this.name);
}
class Person extends Animal {
String name;
String age;
Person(this.name, this.age) : super(name);
}
抽象类
// 注意二: 抽象类不能实例化
abstract class Shape {
String getInfo() {
return "形状";
}
//factory Shape() {
// return Rectangle();
//}
}
// 注意一:继承自抽象类后, 必须实现抽象类的抽象方法
class Rectangle extends Shape {
@override
int getArea() {
return 100;
}
}
隐式接口
Dart中没有哪一个关键字是来定义接口的,没有这些关键字interface/protocol,默认情况下所有的类都是隐式接口, Dart支持单继承,当将一个类当做接口使用时, 那么实现这个接口的类, 必须实现这个接口中所有方法
class Runner {
void running() {
}
}
class Flyer {
void flying() {
}
class Animal {
void eating() {
print("动物次东西");
}
void running() {
print("running");
}
}
class SuperMan extends Animal implements Runner, Flyer {
@override
void eating() {
super.eating();
}
@override
void flying() {
}
}
mixin混入
使用mixin定义混入类,使用with引入混入
main(List<String> args) {
final sm = SuperMan();
sm.running();
sm.flying();
}
mixin Runner {
void running() {
print("runner running");
}
}
mixin Flyer {
void flying() {
print("flying");
}
}
class Animal {
void eating() {
print("动物次东西");
}
void running() {
print("animal running");
}
}
class SuperMan extends Animal with Runner, Flyer {
@override
void eating() {
super.eating();
}
void running() {
print("SuperMan running");
}
}
类属性和类方法
class Person {
// 成员变量
String name;
// 静态属性(类属性)
static String courseTime;
// 对象方法
void eating() {
print("eating");
}
// 静态方法(类方法)
static void gotoCourse() {
print("去上课");
}
}
枚举类型
main(List<String> args) {
final color = Colors.red;
switch (color) {
case Colors.red:
print("红色");
break;
case Colors.blue:
print("蓝色");
break;
case Colors.green:
print("绿色");
break;
}
print(Colors.values);
print(Colors.red.index);
}
enum Colors {
red,
blue,
green
}
使用类库
使用系统库
系统的库: import ‘dart:库的名字’;
// import 'dart:io';
// import 'dart:isolate';
// import 'dart:async';
// import 'dart:math';
import 'dart:math';
main(List<String> args) {
final num1 = 20;
final num2 = 30;
print(min(num1, num2));
}
使用自定义库
就是引入自己创建的dart文件,使用里面的方法和变量等
// import 'utils/math_utils.dart' as mUtils;
// import "utils/math_utils.dart" show sum, mul;
// import "utils/math_utils.dart" hide mul;
// import 'utils/date_utils.dart';
import "utils/utils.dart";
main(List<String> args) {
// print(sum(20, 30));
print(sum(20, 30));
// print(mul(20, 30));
print(dateFormat());
min(20, 30);
}
date_utils.dart文件:
String dateFormat() {
return "2020-12-12";
}
math_utils.dart文件:
int sum(int num1, int num2) {
return num1 + num2;
}
int mul(int num1, int num2) {
return num1 * num2;
}
int min(int num1, int num2) {
return num1 > num2 ? num2: num1;
}
提取公共代码到一个文件内:utils.dart(看以上代码)
export 'math_utils.dart';
export 'date_utils.dart';
使用第三方库
导入方式一样,只是地址为第三方地址
import 'package:http/http.dart' as http;
main(List<String> args) async {
var url = 'http://123.207.32.32:8000/home/multidata';
var response = await http.get(url);
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');
}
flutter环境配置
环境配置地址:https://flutter.cn/docs/get-started/install/windows
按照这个环境配置即可,如需使用其他模拟器,请自行百度。
创建项目
flutter create 项目名 ----创建项目指令
flutter run ----运行项目
文件
.dart_tool:用于存放第三方dart库使用,记录第三方库的相关信息,只要下载会自动记录
android:flutter工程
ios:ios工程包
web:web项目工程
window:windows桌面应用工程
build:打包目录
lib:工程源代码存放处,主入口文件存放处。
test:测试代码存放处。
第一个flutter工程
将main中的代码全部删除,写入:
import 'package:flutter/cupertino.dart';
void main(List<String> args) {
runApp(app)
}
入口需要执行一个全局函数runApp,用于运行项目,runApp函数接收一个类型为Widget的参数。
注意:在flutter中,万物皆是Widget
在flutter中,几乎所有的组件都实现了Widget(按照前端的说法,就是一个组件),属于Widget类型,此时我们的app参数可以写入:
import 'package:flutter/cupertino.dart';
void main(List<String> args) {
var app = Text("hello wolrd ");
runApp(app);
}
这时候会报一个错。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v49dFA6Q-1652173929950)(\img\fristerror.PNG)]
这时候会发现,这个错误是没有设置文字布局的排版方向,因为flutter考虑到在很多国家的文字排版都不一样,所以将排版方向设置为必填项目。
重启项目,结果为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kNJDXFC6-1652173929959)(\img\helloworld.PNG)]
此时第一个项目运行成功!
模板脚手架
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main(List<String> args) {
var app = MaterialApp(
home: Scaffold(
appBar: AppBar(
title:Text("我的第一个应用"),
) ,
body: Center(
child: Text(
"任性的代码",
style: TextStyle(
color:Colors.amber,
)
),
),
),
);
runApp(app);
}
MaterialApp:App的风格为Material提供的风格,Material中的组件定义了自己的默认样式,
Scaffold:脚手架,主要有appBar和body两个属性,appBar表示应用的上头,body应用的内容。
widget
widget:按照前端的说法widget就是一个个个的组件,也就说flutter可以按照组件化编程,但是和react或者vue不同的是,flutter的相关样式或者其他代码是写在一块的。注意:全部的widget都是无状态的,只能通过定义类的形式来定义、获取、修改相关的状态。
组件有个widget属性,表示父组件的实例
widget有两种:StatelessWidget和StatefulWidget两种
- StatelessWidget: 没有状态改变的Widget,通常这种Widget仅仅是做一些展示工作而已;
- StatefulWidget: 需要保存状态,并且可能出现状态改变的Widget;
StatelessWidget
void main(List<String> args) {
var app = MaterialApp(
home: AppHomePage(),
);
runApp(app);
}
class AppHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
//throw UnimplementedError();
return Scaffold(
appBar: AppBar(
title: Text("我的"),
),
body: AppBodyPage());
}
}
class AppBodyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Text("任性的代码",
style: TextStyle(
color: Colors.amber,
)),
);
}
}
StatefulWidget
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HYHomePage()
);
}
}
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("HYHomePage Build");
return Scaffold(
appBar: AppBar(
title: Text("第一个Flutter程序"),
),
body: HYContentBody()
);
}
}
// StatefullWidget: 继承自StatefulWidget的类(可以接收父Widget传过来的数据)/State类(状态)
// flag: 状态
// Stateful不能定义状态 -> 创建一个单独的类, 这个类负责维护状态
class HYContentBody extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return HYContentBodyState();
}
}
class _HYContentBodyState extends State<HYContentBody> {
var _flag = true;
@override
Widget build(BuildContext context) {
print("HYContentBodyState Build");
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
value: flag,
onChanged: (value) {
this.setState(() {
flag = value;
});
},
),
Text("同意协议", style: TextStyle(fontSize: 20))
],
),
);
}
}
StatefulWidget本身是不能用于定义状态的,而是通过其内部的createState函数创建一个状态,该状态需要继承一个State类,在State类中可以使用setState来实现对状态的改变。
注意:StateWidget用于管理状态的类和其状态最好加‘_’,加’_'之后该类只能被StateWidget类使用。
widget生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ysn5YaCT-1652173929960)(\img\shengming.PNG)]
对于StatelessWidget无状态组件,其生命周期只有:constructor>build>deactivate>dispose这条路径。
对于StatefulWidget有状态组件,生命周期完整过程如上图所示(图缺少最开始的调用的constructor)。
生命周期完整介绍:
https://www.jianshu.com/p/6ed6f7de01ff
didChangeDependencies():当State对象的依赖发生变化时会被调用;didChangeDependencies 方法调用后,组件的状态变为 dirty,立即调用 build 方法。
reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。
didUpdateWidget():在父widget重新构建子widget时,子widget的didUpdateWidget可能会被调用。
eactivate():当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。
dispose():当State对象从树中被永久移除时调用;通常在此回调中释放资源。
TextWidget
普通文本:Text
文本使用的组件叫做Text,在相关的属性可以通过点击该组件查看源码
const Text(
String this.data, {
Key? key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
this.textHeightBehavior,
}) :
- 控制文本布局的参数: 如文本对齐方式 textAlign、文本排版方向 textDirection,文本显示最大行数 maxLines、文本截断规则 overflow 等等,这些都是构造函数中的参数;
- 控制文本样式的参数: 如字体名称 fontFamily、字体大小 fontSize、文本颜色 color、文本阴影 shadows 等等,这些参数被统一封装到了构造函数中的参数 style 中。
Text(text,
textAlign: TextAlign.center,
maxLines: 3,
overflow:TextOverflow.ellipsis,
style: TextStyle(
color:Colors.green,
fontSize:30,
fontStyle: FontStyle.italic,
)
);
注意:在之前我们在runApp中直接使用Text需要设置文字方向,而在MaterialApp中不需要设置主要是MaterialApp已经有默认的文本方向了,而且,我们设置Text的文本方向不是问Text widget本身设置的而是为其RichText设置的,Text会渲染成一个RichText。
富文本Text.rich()
接收一个TextSpan类型的widget,在TextSpan内可以创建多个TextSpan
Text.rich(
TextSpan(
children: [
TextSpan(text:'你好',style: TextStyle(color:Colors.red)),
TextSpan(text: '好不好!', style: TextStyle(color: Colors.green)),
TextSpan(text: '别呀!燕子!', style: TextStyle(color: Colors.black)),
]
)
);
Button Widget
RaisedButton:带有阴影的按钮
RaisedButton(
child: Text("RaisedButton"),
textColor: Colors.white,
color: Colors.purple,
onPressed: () => print("RaisedButton Click"),
),
FlatButton:扁平化按钮
FlatButton(
child: Text("FlatButton"),
color: Colors.orange,
onPressed: () => print("FlatButton Click"),
),
OutlineButton:线框按钮
OutlineButton(
child: Text("OutlineButton"),
onPressed: () => print("OutlineButton"),
),
FloatingActionButton:悬浮按钮
这个按钮可以和appBar和body同级,
自定义按钮:使用基础FlatButton组件自定义
FlatButton(
color: Colors.amberAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.favorite, color: Colors.red,),
Text("喜欢作者")
],
),
onPressed: ()=>{},
)
ButtonTheme:按钮风格,
定义按钮小部件的默认配置,
ButtonTheme(
minWidth: 30,
height: 10,
child: FlatButton(
padding: EdgeInsets.all(0),
color: Colors.red,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
child: Text("Flat Button1"),
textColor: Colors.white,
onPressed: () {},
),
)
图标和字体图标
Icon(Icons.pets, size: 300, color: Colors.orange,);
Icon(IconData(0xe91d, fontFamily: 'MaterialIcons'), size: 300, color: Colors.orange,);
Text("\ue91d", style: TextStyle(fontSize: 100, color: Colors.orange, fontFamily: "MaterialIcons"),);
Image组件
- Image.assets:加载本地资源图片;
- Image.network:加载网络中的图片;
加载网络图片
return Center(
child: Container(
child: Image.network(
"http://img0.dili360.com/ga/M01/48/3C/wKgBy1kj49qAMVd7ADKmuZ9jug8377.tub.jpg",
alignment: Alignment.topCenter,
repeat: ImageRepeat.repeatY,
color: Colors.red,
colorBlendMode: BlendMode.colorDodge,
),
width: 300,
height: 300,
color: Colors.yellow,
),
);
加载本地图片
需要现在pubspec.yaml配置路径,打开assets配置项。
return Center(
child: Container(
width: 300,
height: 300,
color: Colors.yellow,
child: Image.asset("images/test.jpeg"),
),
);
圆角图像
方式一:CircleAvatar
return Center(
child: CircleAvatar(
radius: 100,
backgroundImage: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
child: Container(
alignment: Alignment(0, .5),
width: 200,
height: 200,
child: Text("兵长利威尔")
),
),
);
方式二:ClipOval
return Center(
child: ClipOval(
child: Image.network(
"https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
width: 200,
height: 200,
),
),
);
圆角图片ClipRRect
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(
"https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
width: 200,
height: 200,
),
),
);
文本输入TextField
重要属性:
controller:数据绑定控制器
onChanged:数据改变时回调函数
decoration:TextField相关装饰
final mytest = TextEditingController();//控制变量,用于将变量绑定到输入框
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: mytest,
onChanged: (value) => {
print(value)
},
decoration: InputDecoration(
icon: Icon(Icons.people),
labelText: '用户名',
hintText: "请输入用户名",
hoverColor: Colors.blue,
labelStyle: TextStyle(
color: Colors.red,
fontSize: 20,
)),
),
FlatButton(
child: Text("点击"),
onPressed: () => {
print(mytest.text)
},
)
],
);
}
flutter布局
SizedBox
用于设置两组件间间隙
children: <Widget>[
TextField(.....),
SizedBox(height: 10,),
TextField(.....),
SizedBox(height: 10,),
Container(....)
],
单子布局
单子组件只能容纳一个组件,即属性child。
Align
该组件用于设置元素位置相关布局,其Center组件完全继承自该组件。
Align(
alignment: Alignment(1, 1),
widthFactor: 5,
heightFactor: 5,
child: Icon(Icons.pets, size: 50)
);
Padding
该组件可用于设置内边距
Padding(
padding: EdgeInsets.only(
bottom: 10
),
child: Text("你好啊,李银河",
style: TextStyle(fontSize: 30, backgroundColor: Colors.red),),
);
Container
表示的是一个单子布局容器,该容器类似html的div标签,但容纳的组件只能是一个。
注意:属性color和decoration只能出现一个。
alignment:0.0为原点,距离边距为1,
Container(
width: 200,
height: 200,
alignment: Alignment(0, 0),
padding: EdgeInsets.all(20),
margin: EdgeInsets.all(10),
child: Text("Hello World"),
decoration: BoxDecoration(
color: Colors.red,
border: Border.all(
width: 5,
color: Colors.purple
),
boxShadow: [
BoxShadow(color: Colors.orange, offset: Offset(10, 10), spreadRadius: 5, blurRadius: 10),
BoxShadow(color: Colors.blue, offset: Offset(-10, 10), spreadRadius: 5, blurRadius: 10),
]
),
);
多子布局
Flex布局
事实上,我们即将学习的Row组件和Column组件都继承自Flex组件。
- Flex组件和Row、Column属性主要的区别就是多一个direction。
- 当direction的值为Axis.horizontal的时候,则是Row。
- 当direction的值为Axis.vertical的时候,则是Column。
Row
水平方向尽可能占据比较大的空间,水平方向也是希望包裹内容, 那么设置mainAxisSize = min 垂直方向包裹内容 MainAxisAlignment:主轴
start: 主轴的开始位置挨个摆放元素(默认值)
end: 主轴的结束位置挨个摆放元素
center: 主轴的中心点对齐
spaceBetween: 左右两边的间距为0, 其它元素之间平分间距
spaceAround: 左右两边的间距是其它元素之间的间距的一半
spaceEvenly: 所有的间距平分空间
CrossAxisAlignment:交叉轴
start: 交叉轴的起始位置对齐
end: 交叉轴的结束位置对齐
center: 中心点对齐(默认值)
baseline: 基线对齐(必须有文本的时候才起效果)
stretch: 先Row占据交叉轴尽可能大的空间, 将所有的子Widget交叉轴的高度, 拉伸到最大
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"进击的巨人挺不错的",
style: TextStyle(fontSize: 20, color: Colors.white),
),
IconButton(
icon: Icon(
Icons.favorite,
color: _isFavor? Colors.red : Colors.white,
),
onPressed: () {
setState(() {
_isFavor = !_isFavor;
});
},
)
columns
这个和Row类似,但是是垂直布局
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
textBaseline: TextBaseline.alphabetic,
verticalDirection: VerticalDirection.down,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 80,
height: 60,
color: Colors.red,
child: Text(
"Hellxo",
style: TextStyle(fontSize: 20),
),
),
Container(
width: 120,
height: 100,
color: Colors.green,
child: Text(
"Woxrld",
style: TextStyle(fontSize: 30),
),
),
Container(
width: 90,
height: 80,
color: Colors.blue,
child: Text(
"abxc",
style: TextStyle(fontSize: 12),
),
),
Container(
width: 50,
height: 120,
color: Colors.orange,
child: Text(
"cxba",
style: TextStyle(fontSize: 40),
),
),
],
);
Stack
默认的大小是包裹内容的,就是组件如何重叠
alignment: 从什么位置开始排布所有的子Widget
fit: expand(很少) 将子元素拉伸到尽可能大
overflow: 超出部分如何处理
Stack(
children: <Widget>[
Image.asset("assets/images/juren.jpeg"),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8),
color: Color.fromARGB(150, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"进击的巨人挺不错的",
style: TextStyle(fontSize: 20, color: Colors.white),
),
IconButton(
icon: Icon(
Icons.favorite,
color: _isFavor? Colors.red : Colors.white,
),
onPressed: () {
setState(() {
_isFavor = !_isFavor;
});
},
)
],
),
),
)
],
);
Positioned
定位组件,
Positioned(
left: 20,
bottom: -50,
child: Container(
width: 150,
height: 150,
color: Colors.red,
)),
flutter滚动
ListView
ListView有三种方式可以实现
- ListView():默认构造器
- ListView.build:和默认方式用法没有什么差异,但是会增加首屏的渲染时间,
- ListView.separated:可以生成列表项之间的分割器,它除了比
ListView.builder 多了一个separatorBuilder 参数,该参数是一个分割器生成器。
ListView()
ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("人的一切痛苦,本质。", style: textStyle),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("人活在世界上,不可以有偏差;。", style: textStyle),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("我活在世上,遇见些有趣的事。", style: textStyle),
)
],
);
ListTile的使用
在开发中,我们经常见到一种列表,有一个图标或图片(Icon),有一个标题(Title),有一个子标题(Subtitle),还有尾部一个图标(Icon)
ListView(
children: <Widget>[
ListTile(
leading: Icon(Icons.people, size: 36,),
title: Text("联系人"),
subtitle: Text("联系人信息"),
trailing: Icon(Icons.arrow_forward_ios),
),
ListTile(
leading: Icon(Icons.email, size: 36,),
title: Text("邮箱"),
subtitle: Text("邮箱地址信息"),
trailing: Icon(Icons.arrow_forward_ios),
),
ListTile(
leading: Icon(Icons.message, size: 36,),
title: Text("消息"),
subtitle: Text("消息详情信息"),
trailing: Icon(Icons.arrow_forward_ios),
),
ListTile(
leading: Icon(Icons.map, size: 36,),
title: Text("地址"),
subtitle: Text("地址详情信息"),
trailing: Icon(Icons.arrow_forward_ios),
)
],
);
垂直方向滚动
我们可以通过设置 scrollDirection 参数来控制视图的滚动方向。
我们通过下面的代码实现一个水平滚动的内容:
- 这里需要注意,我们需要给Container设置width,否则它是没有宽度的,就不能正常显示。
- 或者我们也可以给ListView设置一个itemExtent,该属性会设置滚动方向上每个item所占据的宽度。
ListView(
scrollDirection: Axis.horizontal,
itemExtent: 200,
children: <Widget>[
Container(color: Colors.red, width: 200),
Container(color: Colors.green, width: 200),
Container(color: Colors.blue, width: 200),
Container(color: Colors.purple, width: 200),
Container(color: Colors.orange, width: 200),
],
);
ListView.build
ListView.build适用于子Widget比较多的场景
该方法有两个重要参数:
- itemBuilder:列表项创建的方法。当列表滚动到对应位置的时候,ListView会自动调用该方法来创建对应的子Widget。类型是IndexedWidgetBuilder,是一个函数类型。
- itemCount:表示列表项的数量,如果为空,则表示ListView为无限列表。
ListView.builder(
itemCount: 100,
itemExtent: 80,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("标题$index"), subtitle: Text("详情内容$index"));
}
);
ListView.separated
ListView.separated 可以生成列表项之间的分割器,它除了比ListView.builder 多了一个separatorBuilder 参数,该参数是一个分割器生成器。
下面我们看一个例子:奇数行添加一条蓝色下划线,偶数行添加一条红色下划线
ListView.separated(
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: Icon(Icons.people),
title: Text("联系人${index+1}"),
subtitle: Text("联系人电话${index+1}"),
);
},
separatorBuilder: (BuildContext context, int index) {
return index % 2 == 0 ? redColor : blueColor;
},
itemCount: 100
);
生成多个元素
List.generate(100, (index) {
return Container(
color: Colors.purple,
alignment: Alignment(0, 0),
child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
);
});
GridView组件
GridView用于展示多列的展示,在开发中也非常常见,比如直播App中的主播列表、电商中的商品列表等等。
GridView构造函数
重要属性:
- SliverGridDelegateWithFixedCrossAxisCount:一列个数
- SliverGridDelegateWithMaxCrossAxisExtent:设置每个元素的宽度,通过宽度计算出每列的个数。
class MyGridCountDemo extends StatelessWidget {
List<Widget> getGridWidgets() {
returnList.generate(100, (index) {
return Container(
color: Colors.purple,
alignment: Alignment(0, 0),
child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
);
});
}
@override
Widget build(BuildContext context) {
return GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.0
),
children: getGridWidgets(),
);
}
}
-----------------------------------------------------分割线-----------------------------------------
class MyGridExtentDemo extends StatelessWidget {
List<Widget> getGridWidgets() {
returnList.generate(100, (index) {
return Container(
color: Colors.purple,
alignment: Alignment(0, 0),
child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
);
});
}
@override
Widget build(BuildContext context) {
return GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.0
),
children: getGridWidgets(),
);
}
}
两种方式也可以不设置delegate
可以分别使用:GridView.count构造函数 和GridView.extent 构造函数实现相同的效果,这里不再赘述。
GridView.build
和ListView一样,使用构造函数会一次性创建所有的子Widget,会带来性能问题,所以我们可以使用GridView.build 来交给GridView自己管理需要创建的子Widget。
class _GridViewBuildDemoState extends State<GridViewBuildDemo> {
List<Anchor> anchors = [];
@override
void initState() {
getAnchors().then((anchors) {
setState(() {
this.anchors = anchors;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GridView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.2
),
itemCount: anchors.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.network(anchors[index].imageUrl),
SizedBox(height: 5),
Text(anchors[index].nickname, style: TextStyle(fontSize: 16),),
Text(anchors[index].roomName, maxLines: 1, overflow: TextOverflow.ellipsis,)
],
),
);
}
),
);
}
}
Slivers
我们考虑一个这样的布局:一个滑动的视图中包括一个标题视图(HeaderView),一个列表视图(ListView),一个网格视图(GridView)。
我们怎么可以让它们做到统一的滑动效果呢?使用前面的滚动是很难做到的。
Flutter中有一个可以完成这样滚动效果的Widget:CustomScrollView,可以统一管理多个滚动视图。
在CustomScrollView中,每一个独立的,可滚动的Widget被称之为Sliver。
补充:Sliver可以翻译成裂片、薄片,你可以将每一个独立的滚动视图当做一个小裂片。
Slivers的基本使用
因为我们需要把很多的Sliver放在一个CustomScrollView中,所以CustomScrollView有一个slivers属性,里面让我们放对应的一些Sliver:
- SliverList:类似于我们之前使用过的ListView;
- SliverFixedExtentList:类似于SliverList只是可以设置滚动的高度;
- SliverGrid:类似于我们之前使用过的GridView;
- SliverPadding:设置Sliver的内边距,因为可能要单独给Sliver设置内边距;
- SliverAppBar:添加一个AppBar,通常用来作为CustomScrollView的HeaderView;
- SliverSafeArea:设置内容显示在安全区域(比如不让齐刘海挡住我们的内容)
简单演示一下:SliverGrid+SliverPadding+SliverSafeArea的组合
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: <Widget>[
SliverSafeArea(
sliver: SliverPadding(
padding: EdgeInsets.all(8),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment(0, 0),
color: Colors.orange,
child: Text("item$index"),
);
},
childCount: 20
),
),
),
)
],
);
}
}
Slivers的组合使用
使用官方的示例程序,将SliverAppBar+SliverGrid+SliverFixedExtentList做出如下界面:
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return showCustomScrollView();
}
Widget showCustomScrollView() {
returnnew CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Coderwhy Demo'),
background: Image(
image: NetworkImage(
"https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg",
),
fit: BoxFit.cover,
),
),
),
new SliverGrid(
gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
returnnew Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: new Text('grid item $index'),
);
},
childCount: 10,
),
),
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
returnnew Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: new Text('list item $index'),
);
},
childCount: 20
),
),
],
);
}
}
监听滚动事件
对于滚动的视图,我们经常需要监听它的一些滚动事件,在监听到的时候去做对应的一些事情。
比如视图滚动到底部时,我们可能希望做上拉加载更多;
比如滚动到一定位置时显示一个回到顶部的按钮,点击回到顶部的按钮,回到顶部;
比如监听滚动什么时候开始,什么时候结束;
在Flutter中监听滚动相关的内容由两部分组成:ScrollController和ScrollNotification。
ScrollController
在Flutter中,Widget并不是最终渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常这种监听事件以及相关的信息并不能直接从Widget中获取,而是必须通过对应的Widget的Controller来实现。
ListView、GridView的组件控制器是ScrollController,我们可以通过它来获取视图的滚动信息,并且可以调用里面的方法来更新视图的滚动位置。
另外,通常情况下,我们会根据滚动的位置来改变一些Widget的状态信息,所以ScrollController通常会和StatefulWidget一起来使用,并且会在其中控制它的初始化、监听、销毁等事件。
我们来做一个案例,当滚动到1000位置的时候,显示一个回到顶部的按钮:
jumpTo(double offset) 、animateTo(double offset,...) :这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。- ScrollController间接继承自Listenable,我们可以根据ScrollController来监听滚动事件。
class MyHomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
ScrollController _controller;
bool _isShowTop = false;
@override
void initState() {
// 初始化ScrollController
_controller = ScrollController();
// 监听滚动
_controller.addListener(() {
var tempSsShowTop = _controller.offset >= 1000;
if (tempSsShowTop != _isShowTop) {
setState(() {
_isShowTop = tempSsShowTop;
});
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ListView展示"),
),
body: ListView.builder(
itemCount: 100,
itemExtent: 60,
controller: _controller,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("item$index"));
}
),
floatingActionButton: !_isShowTop ? null : FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: () {
_controller.animateTo(0, duration: Duration(milliseconds: 1000), curve: Curves.ease);
},
),
);
}
}
NotificationListener
如果我们希望监听什么时候开始滚动,什么时候结束滚动,这个时候我们可以通过NotificationListener 。
- NotificationListener是一个Widget,模板参数T是想监听的通知类型,如果省略,则所有类型通知都会被监听,如果指定特定类型,则只有该类型的通知会被监听。
- NotificationListener需要一个onNotification回调函数,用于实现监听处理逻辑。
- 该回调可以返回一个布尔值,代表是否阻止该事件继续向上冒泡,如果为
true 时,则冒泡终止,事件停止向上传播,如果不返回或者返回值为false 时,则冒泡继续。
案例: 列表滚动, 并且在中间显示滚动进度
class MyHomeNotificationDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() => MyHomeNotificationDemoState();
}
class MyHomeNotificationDemoState extends State<MyHomeNotificationDemo> {
int _progress = 0;
@override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notification) {
// 1.判断监听事件的类型
if (notification is ScrollStartNotification) {
print("开始滚动.....");
} elseif (notification is ScrollUpdateNotification) {
// 当前滚动的位置和总长度
final currentPixel = notification.metrics.pixels;
final totalPixel = notification.metrics.maxScrollExtent;
double progress = currentPixel / totalPixel;
setState(() {
_progress = (progress * 100).toInt();
});
print("正在滚动:${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
} elseif (notification is ScrollEndNotification) {
print("结束滚动....");
}
returnfalse;
},
child: Stack(
alignment: Alignment(.9, .9),
children: <Widget>[
ListView.builder(
itemCount: 100,
itemExtent: 60,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("item$index"));
}
),
CircleAvatar(
radius: 30,
child: Text("$_progress%"),
backgroundColor: Colors.black54,
)
],
),
);
}
}
flutter异步处理和发网络请求
异步处理可以参考这个地址:
https://www.jianshu.com/p/c0e30769ea7e
flutter实现网络请求
在Flutter中常见的网络请求方式有三种:HttpClient、http库、dio库;
dio库
官方提供的HttpClient和http都可以正常的发送网络请求,但是对于现代的应用程序开发来说,我们通常要求的东西会更多:比如拦截器、取消请求、文件上传/下载、超时设置等等;
这个时候,我们可以使用一个在Flutter中非常流行的三方库:dio;
官网有对dio进行解释:
dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等…
使用dio三方库必然也需要先在pubspec中依赖它:
dio:^3.0.1
代码演练:
import'package:dio/dio.dart';
void dioNetwork() async {
// 1.创建Dio请求对象
final dio = Dio();
// 2.发送网络请求
final response = await dio.get("http://123.207.32.32:8000/api/v1/recommend");
// 3.打印请求结果
if (response.statusCode == HttpStatus.ok) {
print(response.data);
} else {
print("请求失败:${response.statusCode}");
}
}
dio库的封装
http_config.dart
class HTTPConfig {
staticconst baseURL = "https://httpbin.org";
staticconst timeout = 5000;
}
http_request.dart
import'package:dio/dio.dart';
import'package:testflutter001/service/config.dart';
class HttpRequest {
staticfinal BaseOptions options = BaseOptions(
baseUrl: HTTPConfig.baseURL, connectTimeout: HTTPConfig.timeout);
staticfinal Dio dio = Dio(options);
static Future<T> request<T>(String url,
{String method = 'get', Map<String, dynamic> params, Interceptor inter}) async {
// 1.请求的单独配置
final options = Options(method: method);
// 2.添加第一个拦截器
Interceptor dInter = InterceptorsWrapper(
onRequest: (RequestOptions options) {
// 1.在进行任何网络请求的时候, 可以添加一个loading显示
// 2.很多页面的访问必须要求携带Token,那么就可以在这里判断是有Token
// 3.对参数进行一些处理,比如序列化处理等
print("拦截了请求");
return options;
},
onResponse: (Response response) {
print("拦截了响应");
return response;
},
onError: (DioError error) {
print("拦截了错误");
return error;
}
);
List<Interceptor> inters = [dInter];
if (inter != null) {
inters.add(inter);
}
dio.interceptors.addAll(inters);
// 3.发送网络请求
try {
Response response = await dio.request<T>(url, queryParameters: params, options: options);
return response.data;
} on DioError catch(e) {
return Future.error(e);
}
}
}
代码使用:
HttpRequest.request("https://httpbin.org/get", params: {"name": "why", 'age': 18}).then((res) {
print(res);
});
HttpRequest.request("https://httpbin.org/post",
method: "post", params: {"name": "why", 'age': 18}).then((res) {
print(res);
});
路由、导航和事件处理
组件封装
评分五星封装
// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors, use_key_in_widget_constructors, prefer_const_constructors_in_immutables
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class YJBStarRating extends StatefulWidget {
final double? rating; //得分
final double maxRating; //满分
final int count; //星星个数
final double size; //大小
final Color unsetColor;
final Color selectColor;
YJBStarRating(
{@required this.rating,
this.maxRating = 10,
this.count = 5,
this.size = 30,
this.unsetColor = Colors.grey,
this.selectColor = Colors.red});
@override
State<YJBStarRating> createState() => _YJBStarRatingState();
}
class _YJBStarRatingState extends State<YJBStarRating> {
@override
Widget build(BuildContext context) {
return Stack(children: [
Row(
children: buildunsetStar(),
),
Row(children: buildSelectStar())
]);
}
List<Widget> buildunsetStar() {
return List.generate(widget.count, (index) {
return Icon(
Icons.star_border,
color: widget.unsetColor,
size: widget.size,
);
});
}
List<Widget> buildSelectStar() {
List<Widget> starList = [];
final markStar = widget.maxRating / widget.count; //每一个多少分
final fullStar = (widget.rating! / markStar).floor(); //满的个数
final double otherStar =
((widget.rating! / markStar) - fullStar) * widget.size;
//满的个数
for (int i = 0; i < fullStar; i++) {
starList.add(Icon(
Icons.star,
color: widget.selectColor,
size: widget.size,
));
}
//裁剪不满的
starList.add(ClipRect(
clipper: MyClip(otherStar),
child: Icon(
Icons.star,
color: widget.selectColor,
size: widget.size,
)));
return starList;
}
}
class MyClip extends CustomClipper<Rect> {
final double clipSize;
MyClip(this.clipSize);
@override
Rect getClip(Size size) {
return Rect.fromLTWH(0, 0, clipSize, size.height);
}
@override
bool shouldReclip(MyClip oldClipper) {
return false;
}
}
虚线封装
// ignore_for_file: prefer_const_constructors, sized_box_for_whitespace
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class YJBDashed extends StatelessWidget {
final Axis axis; //方向
final int count; //线点
final double dashHeight; //高度
final double dashWidth; //宽度
final Color color; //颜色
// ignore: use_key_in_widget_constructors
YJBDashed(
{@required this.axis = Axis.horizontal,
this.count = 20,
this.dashHeight = 2,
this.color = Colors.grey,
this.dashWidth = 4});
@override
Widget build(BuildContext context) {
// ignore: avoid_unnecessary_containers
return Flex(
direction: axis,
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: List.generate(count, (index) {
return SizedBox(
width: dashWidth,
height: dashHeight,
child: DecoratedBox(
decoration: BoxDecoration(color: color),
),
);
}));
}
}
底部导航栏
底部导航:和body同级的一个属性:bottomNavigationBar
路由切换:body属性使用IndexedStack
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import './home/home.dart';
import './mine/mine.dart';
class YJBMainPage extends StatefulWidget {
const YJBMainPage({Key? key}) : super(key: key);
@override
State<YJBMainPage> createState() => _YJBMainPageState();
}
class _YJBMainPageState extends State<YJBMainPage> {
int tabIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: tabIndex,
// ignore: prefer_const_literals_to_create_immutables
children: [YJBHmoe(), YJBMine()],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: tabIndex,
onTap: (value) {
// ignore: unnecessary_this
setState(() {this.tabIndex = value;});
},
// ignore: prefer_const_literals_to_create_immutables
items: [
BottomBarItem(Icons.home,Colors.red,'HOME'),
BottomBarItem(Icons.person, Colors.red, 'MINE'),
]));
}
// ignore: non_constant_identifier_names
BottomNavigationBarItem BottomBarItem(iconType,color,text) {
return BottomNavigationBarItem(
icon: Icon(iconType),
activeIcon: Icon(iconType,color: color,),
label: text);
}
}
|