第三部分 Flutter初体验
1-1 创建项目及项目目录
$ flutter create learn_flutter #终端创建项目
目录结构
dart_tool #记录某些信息
.idea #对当前的项目进行配置, Android Studio是基于IDEA开发的
android #安卓工程
ios #iOS工程
lib #⚠️放flutter源代码
main.dart #启动文件
test #做测试用
.gitignore #提交git忽略
pubspec.yaml #⚠️第三方配置
1-2 Hot Reload 与 Hot Restart
/*
Hot Reload: 热重载 (command + \)
Hot Restart: 热重启 (shift + command + \)
运行一个Flutter项目, 有三种启动方式:
1.冷启动: 所有的代码从零开始启动.(过程非常慢)
2.热重载: 最主要执行build方法
3.热重启: 重新运行整个App
*/
1-3 从零编写项目入口文件
import 'package: flutter/material.dart'; //runApp函数在这个库中
main(){
// 1.调用runApp函数, 需要传入一个Widget
// Widget是一个抽象类, 抽象类不能初始化; 抽象类可以被子类继承, 所以用它的子类进行初始化.
// 多态: 父类引用指向子类对象.
runApp(
Center(
child: Text(
"Hello World",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30,
color: Colors.orange
),
),
)
); //报错: 要指明文字排版方向是 从左往右 还是 从右往左; 如果使用了Material设计风格就不用设置排版了, 因为它做了默认设置从左往右.
}
// Flutter缩进两个空格
1-4 创建一个Material风格App
import 'package: flutter/material.dart';
main(){
runApp(
MaterialApp(
debugShowCheckedModeBanner: false, //去掉"DUBUG"标识
//一个Scaffold可以看成是一个iOS的UIViewController或安卓的activity.
home: Scaffold(//Scaffold脚手架, 帮助我们快速搭建页面
sppBar: AppBar(
title: Text("第一个Flutter程序"),
),
body: Center(
child: Text(
"Hello World",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 30,
color: Colors.orange
),
),
)
)
)
);
}
1-5 重构代码: 抽取Widget组件
import 'package: flutter/material.dart';
//main(){
// runApp(MyApp());
//}
//简写👆:
main() => runApp(MyApp());
/*Widget:
StatefulWidget(有状态的Widget): 在运行过程中有一些状态(data)需要改变
StatelessWidget(无状态的Widget): 内容是确定没有状态(data)的改变
*/
//Flutter 没有命名空间, 只有库的概念. 就是一个文件里名字不能重复
class MyApp extends StatelessWidget {
//子类必须实现父类的抽象方法
@override
//build 方法什么情况下被执行呢?
// 1.当我们的StatelessWidget第一次被插入到Widget树中时(也就是第一次被创建时);
// 2.当我们的父Widget发生改变时, 子Widget会被重新构建;
// 3.如果我们的Widget依赖InheritedWidget的一些数据, InheritedWidget数据发生改变时;
Widget build(BuildContext context){
return MaterialApp(
debugShowCheckedModeBanner: false, //去掉"DUBUG"标识
home: HYHomePage()
);
}
}
class HYHomePage extends StatelessWidget {//自定义组件一般是: 前缀 + 组件名 , 为了区分自定义还是系统的
@override
Widget build(BuildContext context){
return Scaffold(//Scaffold脚手架, 帮助我们快速搭建页面
sppBar: AppBar(
title: Text("第一个Flutter程序"),
),
body: HYContentBody()
);
}
}
/// 最终案例:
//这个代码是有问题的❌
class HYContentBody extends StatelessWidget {
//这里不能放变量, 因为StatelessWidget继承自Widget, Widget有@immutable注解, 被@immutable注解标明的类或者子类都必须是不可变的. 所以StatelessWidget和StatefulWidget都是不可变的
var flag = true;//错误的代码❌
@override
Widget build(BuildContext context){
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
value: flag,
onChanged: (value) => flag = value//错误代码❌
),
Text("同意协议", style: TextStyle(fontSize: 20),)
]
)
);
}
}
// @required: 必须的, 注解告诉某些可选命名参数是必传的. 次注解只出现在Flutter中, dart中没有.
// @immutable: 不可变的, 被注解的类里面所有的成员变量都必须是final的, 不能保存状态.
1-6 修改: 上面状态改变的错误bug
// Android/iOS 是命令式编程, 没有状态的概念, 只说属性、数据;
// vue/react/angular 是声明式编程, 管好状态就行了.
//在Flutter开发中, 所有的Widget都不能定义状态, 因为所有的Widget都有@immutable修饰.
//StatefulWidget不能定义状态 -> 创建一个单独的类(State对象), 这个类负责维护状态
class HYContentBody extends StatefulWidget {
@override
State<StatefulWidget> createState(){//这是个抽象方法, 抽象方法必须实现
return HYContentBodyState();
}
}
class HYContentBodyState extends State<HYContentBody> {
var flag = true;
@override
// 让State来构建(build)这个Widget, 这样就可以在类里创建状态
Widget build(BuildContext context){//这是个抽象方法, 需要自己实现方法体
//它有一个 this.widget;
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
value: flag,
onChanged: (value) {
setState((){// 类似React的虚拟DOM操作
flag = value;
});
},
),
Text("同意协议", style: TextStyle(fontSize: 20),)
]
)
);
}
}
// 调用setState() 会使所有的Widget重建. Widget耗性能比较低, 更像是一个配置文件.
第四部分 StatelessWidget
1-1 商品列表实现
// StatefulWidget分成两个类: 1.继承自StatefulWidget的类(用于创建state 和 可以接受父Widget传过来的数据); 2.State类(用于保存状态)
import 'package: flutter/material.dart';
main() => runApp(MyApp());
// AS代码块: stl 会自动帮我们生成statelesswidget代码
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context){
return MaterialApp(
debugShowCheckedModeBanner: false, //去掉"DUBUG"标识
home: HYHomePage()
);
}
}
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context){
return Scaffold(
sppBar: AppBar(
title: Text("商品列表"),
),
body: HYContentBody()
);
}
}
class HYContentBody extends StatelessWidget {
@override
Widget build(BuildContext context){
//return Column(//不可滚动的
return ListView(//可滚动的
children: <Widget>[
HYHomeProductItem("Apple1","Macbook1","图片地址1"),
SizedBox(height: 8),
HYHomeProductItem("Apple2","Macbook2","图片地址2"),
SizedBox(height: 8),
HYHomeProductItem("Apple3","Macbook3","图片地址3"),
]
);
}
}
class HYHomeProductItem extends StatelessWidget {
//注意: 在Widget里定义的所有变量都必须是final的
final String title;
final String desc;
final String imageURL;
final style1 = TextStyle(fontSize: 25, color: Colors.orange);
final style2 = TextStyle(fontSize: 25, color: Colors.green);
HYHomeProductItem(this.title, this.desc, this.imageURL);//自定义构造函数
@override
Widget build(BuildContext context){
//final style1 = TextStyle(fontSize: 25, color: Colors.orange);//每次调用Build都会重新创建一遍这个代码
return Container(
padding: EdgeInsets.all(8),//设置内边距
decoration: BoxDecoration(
border: Border.all(
width: 5, //设置边框的宽度
color: Colors.black12,//设置边框的颜色: 透明度是12的黑色
)
),//decoration装饰
child: Column(
//crossAxisAlignment: CrossAxisAlignment.start //设置交叉轴对齐方式
children: <Widget>[
Text(title, style: style1),
SizedBox(height: 8),//设置尺寸间距
Text(desc, style: style2),
SizedBox(height: 8),//8 相当于8个像素, 相当于iOS的点像素
Image.network(imageURL)// 图片加载有专门的IO线程
],
)
);
}
}
/*注意⚠️: 显示黄条(relayoutBoundary), 表示你的内容超出了屏幕显示的区域(像素溢出), 也就是超出了布局范围.*/
/*快捷键: 在AS中快捷键 Alt + Enter, 用于给某一个组件包裹另外一个Widget*/
/*快捷键: 在AS中快捷键 command + alt + B, 可以查看它某个类的所有的实现类*/
/* 上面例子中, 文字内容为什么是居中显示? 因为column组件交叉轴默认是居中对齐显示的, 可以设置为crossAxisAlignment: CrossAxisAlignment.start */
1-2 statefulWidget 综合练习
import 'package: flutter/material.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context){
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HYHomePage()
);
}
}
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context){
return Scaffold(
sppBar: AppBar(
title: Text("商品列表"),
),
body: HYContentBody("传递信息")//sp1.
);
}
}
// Widget是不加_: 暴露给别人使用
class HYContentBody extends StatefulWidget {
final String message;
HYHomeContent(this.message);//sp2.
@override
State<StatefulWidget> createState(){
return _HYHomeContentState();
}
}
/*为什么Flutter在设计的时候把StatefulWidget的build方法放在State中, 而不是StatefulWidget 里面?
1.build出来的Widget是需要依赖State中的变量(也叫状态/数据)
2.在Flutter的运行过程中:
Widget是不断的销毁和创建的, 当我们自己的状态发生改变时, 并不希望重新创建一个新的State.
*/
//Widget(描述信息)->Element->Render Object
// State是加_: 状态这个类只是给自己Wideget使用
class _HYHomeContentState extends State<HYHomeContent> {
int _counter = 0;
@override
Widget build(BuildContext context){
return Center(
child: Coluum(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_getButtons(),
Text("当前计数: $_counter", style: TextStyle(fontSize: 25),),
Text("传递的信息:${this.widget.message}")//sp3. this.widget 指向 HYHomeContent对象
],
),
);
}
widget _getButtons(){
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text("+"),
color: Color.pink,
onPressed: (){
setState((){
_counter++;
});
},
),
RaisedButton(
child: Text("-"),
color: Color.purple,
onPressed: (){
setState((){
_counter--;
});
},
),
],
);
}
}
1-3 生命周期
//StatelessWidget生命周期非常简单: 1.构造函数被调用 2.调用build方法;
class HYContentBody extends StatelessWidget {
final String message;
HYHomeContent(this.message){
print("构造函数被调用");
}
@override
Widget build(BuildContext context){
print("调用build方法");
return Text(message);
}
}
/*开发中用的最多的就是StatefulWidget的生命周期: */
class HYHomeContent extends StatefulWidget {
HYHomeContent(){
print("1.调用HYHomeContent的constructor方法");
}
@override
_HYHomeContentState<StatefulWidget> createState(){
print("2.调用HYHomeContent的createState方法");
return _HYHomeContentState();
}
}
class _HYHomeContentState extends State<HYHomeContent> {
_HYHomeContentState(){
print("3.调用_HYHomeContentState的constructor方法");
}
@override
void initState(){//✅
//注意: 这里是必须调用super的, 原因1、父类帮助我们初始化;原因2、不调会报警告, 因为内部实现有个注解@mustCallSuper, 这个注解的意思是必须调用父类方法
super.initState();
print("4.调用_HYHomeContentState的initState方法");
}
@override
void didUpdateWidget(HYHomeContent oldWidget){ //这个方法用的不是很多: 当父Widget重新创建(build)时会调用这个方法
super.didUpdateWidget(oldWidget);
print("调用_HYHomeContentState的didUpdateWidget方法");
}
@override
void didChangeDependencies(){ //⚠️改变了我们的依赖: 如果当前的state依赖一些数据时, 数据发生改变就会调用这个方法.
super.didChangeDependencies();
print("调用_HYHomeContentState的didChangeDependencies方法");
}
@override
Widget build(BuildContext context){
print("5.调用_HYHomeContentState的build方法");
return Column(
children: <Widget>[
RaisedButton(
child: Icon(Icons.add),
onPressed:(){
setState((){//setState本质是给组件做一个标记, 然后调用build, 重新显示
_counter++;
});
},
),
Text("数字:$_counter")
]
);
}
@override
void dispose(){ //✅一般不会去手动销毁
print("6.调用_HYHomeContentState的dispose方法");
super.dispose();
}
}
/* AS快捷键:
StatelessWidget转StatefulWidget快捷键: Alt + 回车 然后选择"Convert to StatefulWidget"
将我们build出来的Widget抽取到一个单独的Widget中快捷键: Alt + enter +w 然后输入组件名即可
返回到上次编辑位置(向前向后跳转) opt + cmd + ← / →
全局搜索类(cmd + O)
*/
第五部分: 基础Widget(叶子组件)
1-1 Flutter的编程范式
/*编程范式:
面向对象编程, 与之对应或者结合开发包括: 面向过程编程、函数式编程、面向协议编程; 另外还有两个编程范式: 命令式编程 和 声明式编程.*/
1-2 文本Widget
// 不可以直接给Text设置宽度
return Text(
"Hello world".toLowerCase(), //转小写
textAlign: TextAlign.center,//文本排版
maxLine: 2,//最大行数
overflow: TextOverflow.ellipsis,//超出部分显示...
textScaleFactor: 1.5,//缩放因子, 默认是1.(放大/缩小 字体)
syle: TextStyle(//文本样式
fontSize: 30,
color: Colors.red,
fontWeight: FontWeight.bold
),
);
//开发中: 富文本
return Text.rich(
TextSpan(
// text: "Hello World",
// style: TextStyle(color: Colors.red)
children: [
TextSpan(text:"Hello World",style: TextStyle(color: Colors.red)),//插入文本
WidgetSpan(child: Icons(Icons.favorite, color: Colors.red)),//插入图标/图片
TextSpan(text:"Hello Flutter",style: TextStyle(color: Colors.green)),
TextSpan(text:"Hello Dart",style: TextStyle(color: Colors.blue)),
]
);
);
//AS快捷键: option + command + B 查看此类的所有实现类, 比如查看抽象类的子类, 来构建对象.
1-3 Button-按钮 Widget
//1.RaisedButton 凸起按钮
return RaisedButton(
child: Text("RaisedButton"),
textColor: Colors.red,
onPressed: ()=> print("RaisedButton Click"),
);
//必传参数, 不传就报错(编译不通过);注解@required 编译可以通过, 但是会报警告.
//2.FlatButton 平铺按钮
return FlatButton(
child: Text("FlatButton"),
textColor: Colors.white,
color: Colors.orange,
onPressed: ()=> print("FlatButton Click"),
);
//3.OutlineButton 边框按钮
return FlatButton(
child: Text("OutlineButton"),
textColor: Colors.white,
color: Colors.orange,
onPressed: ()=> print("OutlineButton Click"),
);
//4.FloatingActionButton 浮动按钮
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context){
return Scaffold(
sppBar: AppBar(
title: Text("基础组件"),
),
body: HYContentBody(),
floatingActionButton: FloatingActionButton(//浮动按钮
child: Icon(Icons.add),
onPressed: ()=> print("FloatingActionButton Click"),
),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, //设置浮动按钮位置
);
}
}
//5.自定义button: 图标-文字-背景-圆角
return FlatButton(
color.Colors.amberAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8) //设置圆角
),
child: Row( //默认情况下, 会占一整行
mainAxisSize: MainAxisSize.min, //默认值MainAxisSize.max: 剩余空间能占多大占多大;
children: <Widget>[
Icon(Icons.favorite, color: Colors.red),
Text("喜欢作者"),
]
),
onPressed: () {},
);
//6.补充
return ButtonTheme(//消除按钮最小宽高
minWidth: 30,//设置最小宽度
height: 10,//设置最小高度
child: FlatButton(
/* 有两个小问题:
1.默认情况下如果Button的宽/高小于48px, Button上下会存在一定间距使其范围达到48px
2.Button变小 (默认情况下Button存在最小尺寸:宽88、高36)---ButtonTheme
3.Button的默认内间距
*/
padding: EdgeInsets.all(0),//消除Button默认内间距
child: Text("FlatButton"),
textColor: Colors.white,
color: Colors.orange,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,//(紧缩包裹)消除上下默认间距
onPressed: ()=> print("FlatButton Click"),
)
);
1-4 图片-image
//抽象类也可以实例化, 就是它有工厂方法的时候, 用工厂方法实例化.
/// 1.网络图片
return Image(
color: Colors.red,//设置图片混入颜色
colorBlendMode: BlendMode.colorDodge,//设置颜色混入模式
image: NetworkImage("图片网络地址"),
width: 200,
height: 200,
fit: BoxFit.contain, //设置图片的填充属性. 其中 BoxFit.fitWidth: 宽度固定,高度自适应;同理BoxFit.fitHeight.
repeat: ImageRepeat.repeatY,//设置图片在某一个方向上不断的重复
// alignment: Alignment.bottomCenter,//设置图片在矩形框的位置
alignment: Alignment(x, y), //范围 -1 到 1
);
//简单写法: return Image.network("图片网络地址");
/// 2.加载本地图片
/*本地图片使用步骤:
1.在Flutter项目中创建一个文件夹, 存储图片.比如在项目目录中创建asssets文件夹:
assets
fonts
images
2.0x
3.0x
xxx.jpeg
2.在pubspec.yaml进行配置:
assets:
- assets/images/ #写个统配符, 这个文件夹下的所有图片都会配置好,可直接使用
#- assets/images/xxx.jpeg #配置用到的图片
#- assets/images/2.0x/xxx.jpeg #配置2倍图
#- assets/images/3.0x/xxx.jpeg #配置3倍图
3.使用图片*/*/
return Image(
image: AssetImage("assets/images/xxx.jpeg"),//本地图片路径
);
//简写: return Image.asset("assets/images/xxx.jpeg");
///3.占位图
return FadeInImage(//设置占位图: 淡入淡出
fadeOutDuration: Duration(milliseconds: 1),
fadeInDuration: Duration(milliseconds: 1),
placeholder: AssetImage("assets/images/xxx.jpeg"),
image: NetworkImage("图片网络地址"),
);
//图片缓存: Flutter默认情况下会对图片做内存缓存的, 最多缓存1000张, 同时最大上限100M空间.超过缓存自动清理
///4.Icon
return Icon(Icons.pats, size: 100, color: Colors.orange); //Icon是字体图标
/* Icon字体图标 和 图片图标 区别:
1.字体图标是矢量图, 放大的时候不会失真;
2.字体图标可以设置颜色;
3.图标很多时, 相对占用空间更小.
*/
return Icon(IconData(0xe478, fontFamily: 'MaterialIcons'), size: 100, color: Colors.orange);
//使用Text来显示字体图标: sp1. 把0xe478数字 转 Unicode编码 sp2.设置对应的字体fontFamily
return const Text('\ue478', style: TextStyle(fontSize: 100, color: Colors.red, fontFamily: 'MaterialIcons'),);
1-5 TextFile
class _MyHomePageState extends State<MyHomePage> {
final usernameTextEditController = TextEditingController();//设置全局变量记录输入框内容
final passwordTextEditController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: usernameTextEditController,
decoration: const InputDecoration(
labelText: "username",//提示文本
icon: Icon(Icons.people),//设置图片
hintText: "请输入用户名",//默认提示
border: OutlineInputBorder(),//设置外边框
filled: true,//开启填充色
fillColor: Colors.orange,//填充颜色
),
onChanged: (value) {//监听值改变
print('onChanged:$value');
},
onSubmitted: (value) {//监听用户点击确定
print('onSubmitted:$value');
},
),
const SizedBox(height: 10,),
TextField(
controller: passwordTextEditController,
decoration: const InputDecoration(
labelText: "password",//提示文本
icon: Icon(Icons.lock),//设置图片
hintText: "请输入密码",//默认提示
border: InputBorder.none,//设置外边框(无边框)
filled: true,//开启填充色
fillColor: Colors.pink,//填充颜色
),
onChanged: (value) {//监听值改变
print('onChanged:$value');
},
onSubmitted: (value) {//监听用户点击确定
print('onSubmitted:$value');
},
),
TextButton(
onPressed: (){
print("用户名:${usernameTextEditController.text}-----密码:${passwordTextEditController.text}");
usernameTextEditController.text = "";//清空输入框内容
passwordTextEditController.text = "";//清空输入框内容
},
child: const Text("登录", style: TextStyle(fontSize: 20, color: Colors.red, backgroundColor: Colors.orange),),
),
],
),
);
}
}
1-6 颜色color
//方式1:
// Color(0xff ff ff ff), 对应: 透明度 R G B
//方式2:
color: Color.fromRGBO(r, g, b, opacity),
第六部分 布局Widget
1-1 Align
return Container(//Container可以设置宽高, 套一个Container为了限制其范围
width: 100,
height: 100,
color: Colors.red,
child: const Align(
alignment: Alignment(1, 1),//改变子组件位置
widthFactor: 10,//设置宽度范围为子组件的多少倍
heightFactor: 10,//设置高度范围为子组件的多少倍
child: Icon(Icons.pets, size: 60,),
),
);
//Center继承自Align
1-2 Padding
return const Padding(
// padding: EdgeInsets.all(80.0),//设置间距方法1
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),//设置间距方法2 ...
child: Text(
'You have pushed the button this many times:',
style: TextStyle(backgroundColor: Colors.red),
),
); //有时可以用Sizebox代替
1-3 Container
return Container(
width: 200,// width: double.infinity,//设置宽度无限大
height: 200,
// color: Colors.red,//设置背景色, 会与设置BoxDecoration冲突
alignment: Alignment(-1, -1),//设置子组件排版位置
padding: EdgeInsets.all(20.0)//设置内边距
margin: EdgeInsets.all(10.0)//设置内边距
child: child: Icon(Icons.pets, size: 60,),
transform: Matrix4.rotationZ(100),//容器设置形变, 比如:物理旋转
decoration: BoxDecoration{
color: Colors.red,//设置背景色
border: Border.all(//设置边框
width: 5,
color: Colors.purple
),
borderRadius: BorderRadius.circular(100),//设置圆角
boxShadow: [
//Offset:偏移方向; spreadRadius: 延伸范围; blurRadius: 模糊范围;
BoxShadow(color: Colors.orange, offset: Offset(10, 10), spreadRadius: 5, blurRadius: 10),
BoxShadow(color: Colors.blue, offset: Offset(-10, 10), spreadRadius: 5, blurRadius: 10),
],
},
);
// 如果没有设置alignment: , 就不会创建Alignment对象, Text作为子组件会被拉满, 从头开始; 设置alignment后, Text就不会拉满,而是变成它内容大小.
第七部分 Flex
1-1 flex组件
// 比较常见的多子布局组件是Row、Column、Stack
return Flex(
direction: Axis.vertical, //Column
// direction: Axis.horizontal, //Row
);
//Column和Row都继承自Flex
// 主轴 Main Axis ; 交叉轴 Cross Axis
// MediaQuery.of(context).size.width //屏幕宽
/*
Row特点:
- 水平方向尽可能占据比较大的空间
* 水平方向也是希望包裹内容, 那么设置mainAxisSize = min
- 垂直方向包裹内容
mainAxisAlignment:
- start: 主轴的开始位置挨个摆放元素(默认值)
- end: 主轴的结束位置挨个摆放元素
- center: 主轴的中心点对齐
- spaceBetween: 左右两边间距为0, 其它元素之间平分间距
- spaceAround: 左右两边的间距是其它元素之间的间距的一半
- spaceEvenly: 所有的间距平分空间
CrossAxisAlignment:
- start: 交叉轴的起始位置对齐
- end: 交叉轴的结束位置对齐
- center: 交叉轴中心点对齐(默认值)
- baseline: 基线对齐(必须有文本的时候才有效果)
- stretch: 将所有的子Widget交叉轴的高度, 拉伸到最大; 想要限制其最大高度,需要外面包裹一层Container,并设置Container的height.
*/
return Container(
height: 300,//通过包一层Container, 来设置最大延伸高度
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
textDirection: TextDirection.rtl,//Row 特有(文字排版方向)
mainAxisSize: MainAxisSize.min, // 默认: MainAxisSize.max
//crossAxisAlignment: crossAxisAlignment.baseline,//基线对齐
//textBaseline: TextBaseline.ideographic, //设置基线(基线对齐必须设置这个)
crossAxisAlignment: crossAxisAlignment.stretch,
children: <Widget>[
Container(width:80, height: 60, color: Colors.red, child:Text("Hellox", style:TextStyle(fontSize:20),),),
...
],
)
);
return Column(
mainAxisAlignment: MainAxisAlignment.end,
verticalDirection: VerticalDirection.down,//Column 特有(文字排版方向)
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: crossAxisAlignment.baseline,
textBaseline: TextBaseline.ideographic,
children: <Widget>[
Container(width:80, height: 60, color: Colors.red, child:Text("Hellox", style:TextStyle(fontSize:20),),),
...
],
);
1-2 、Expanded
return Row(
children: [
/*
* Flexible中的属性: 可延伸、可伸缩的
* - flex
*
* Expanded 相当于 Flexible( fit: FlexFit.tight,)
* Expanded 用的比较多
* */
Flexible(
fit: FlexFit.tight,//按比例分配剩余空间
flex: 1,//使子组件宽度无效, 分配剩余空间的比例
child: Container(width: 80, height: 60, color: Colors.red, ),
),
Flexible(
fit: FlexFit.tight,//按比例分配剩余空间
flex: 2,//使子组件宽度无效, 分配剩余空间的比例
child: Container(width: 12000, height: 100, color: Colors.green, ),
),
Expanded(
flex: 1,//使子组件宽度无效, 分配剩余空间的比例
child: Container(width: 90, height: 80, color: Colors.blue,)
),
Container(width: 50, height: 120, color: Colors.orange, ),
],
),
1-3、Stack组件--重叠效果
/*
* Stack默认大小是包裹内容的
* - alignment: 从什么位置开始排布所有子Widget. 默认topStart
* - fit: 默认loose, 按照子元素原本的大小排布. 很少用
* - overflow: 超出部分如何处理
* - Positioned: 配合Stack组件,来调整某个子元素的位置
* */
return Stack(
alignment: AlignmentDirectional.center,//设置组件排布方式
// fit: StackFit.expand,//将子组件拉伸到尽可能大, 可以外层包一层Container,来设置宽高.
// overflow: Overflow.visible,
children: [
Positioned(//配合Stack组件,来调整某个子元素的位置
left: 20,
bottom: -20,
child: Container(width: 150, height: 300,)
),
Image.asset("assets/images/juren.jepg"),
Text('进击的巨人')
],
)
//使用示例:
Stack(
children: [
Image.asset("assets/images/juren.jepg"),
Positioned(//配合Stack组件,来调整某个子元素的位置
left: 0,
right: 0,
// width: double.infinity,//宽度无限大
bottom: 0,
child: Container(
padding: EdgeInsets.all(8),
color: Color.fromARGB(150, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('进击的巨人', style: TextStyle(fontSize: 20, color: Colors.white),),
// GestureDetector(child: Icon(Icons.favorite, color: Colors.white,),),
IconButton(onPressed: ()=> print('点击了收藏'), icon: Icon(Icons.favorite, color: Colors.white,))
],
),
)
),
],
)
第八部分 滚动Widget-listview、gridview、slivers
1-1、ListView--创建方式一
//立即创建
return ListView(
scrollDirection: Axis.vertical,//滚动方向
reverse: true,//列表内容反转(倒序<->正序)
itemExtent: 100,//item主轴固定高度
children: List.generate(100, (index) {
// return Text("Hello World: $index", style: TextStyle(fontSize: 30),);
return ListTile(
leading: Icon(Icons.people),//前面内容
trailing: Icon(Icons.delete),//后面内容
title: Text("联系人${index + 1}"),
subtitle: Text("联系电话: 1234567"),
);
}),
)
1-2、ListView--创建方式二
ListView.builder(//相当于懒加载创建item
itemCount: 100,//准备展示多少个item, 不会立刻创建
itemBuilder: (BuildContext ctx, int index) {
//itemBuilder 是回调函数,当有item被展示到屏幕时才会被调用
return Text("Hello World: $index");//返回每个Item的UI组件
}
)
1-3、ListView--创建方式三
//同样返回回调函数, 在需要的时候创建
return ListView.separated(
itemBuilder: (BuildContext ctx, int index) {//item样式
return Text("Hello World: $index");
},
separatorBuilder: (BuildContext ctx, int index) {//分割线样式
return Divider(//也可以返回其他Widget
color: Colors.red, //设置分割线的颜色
height: 3, //设置分割线显示区域
thickness: 10,//设置分割线的高度
indent: 30, //设置距离开始侧的距离
endIndent: 30,//设置距离结束侧的距离
);
},
itemCount: 100
)
2-1、GridView-构造方法一
Padding(//给GridView设置水平外边距
padding: const EdgeInsets.symmetric(horizontal: 8),//vertical无效
child: GridView(
//SliverGridDelegateWithFixedCrossAxisCount 每行固定数量
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //固定数量
childAspectRatio: 1.5, //item的宽高比
crossAxisSpacing: 8, //交叉轴间距
mainAxisSpacing: 8,//主轴间距
),
children: List.generate(100, (index) {
return Container(
color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
);
}),
),
)
2-2、GridView-构造方法二
Padding(//给GridView设置水平外边距
padding: const EdgeInsets.symmetric(horizontal: 8),//vertical无效
child: GridView(
//SliverGridDelegateWithMaxCrossAxisExtent 每行设置宽度最大值
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100,//宽度最大是100
childAspectRatio: 1.5, //item的宽高比
crossAxisSpacing: 8, //交叉轴间距
mainAxisSpacing: 8,//主轴间距
),
children: List.generate(100, (index) {
return Container(
color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
);
}),
),
)
2-3、GridView-构造方法三
Padding(//给GridView设置水平外边距
padding: const EdgeInsets.symmetric(horizontal: 8),//vertical无效
/// 当item准备在屏幕上显示时, 才加载
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8, //交叉轴间距
mainAxisSpacing: 8,//主轴间距
),
itemBuilder: (BuildContext ctx, int index){
return Container(//此时: 这里设置高度无效, 只能设置宽高比
color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
);
}
)
)
3-1、Slivers的基本使用
因为我们需要把很多的Sliver放在一个CustomScrollView中, 所以CustomScrollView有一个slivers属性, 里面让我们放对应的一些Sliver:
SliverList: 类似于我们之前使用过的ListView;
SliverFixedExtentList 类似于SliverList只是可以设置滚动的高度;
SliverGrid: 类似于我们之前使用过的GridView;
SliverPadding: 设置Sliver的内边距, 因为可能要单独给Sliver设置内边距;
SliverAppBar: 添加一个AppBar, 通常用来作为CustomScrollView的HeaderView;
SliverSafeArea: 设置内容显示在安全区域(比如不让齐刘海挡住我们的内容)
存放一个sliver的实例代码:
return SafeArea(//设置安全区域
child: CustomScrollView(
slivers: [// 这里面只能放sliver
SliverSafeArea(//也可以在这里设置安全区域
sliver: SliverPadding(
padding: EdgeInsets.all(8),//设置内间距
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate(
(BuildContext ctx, int int){
return Container(
color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
);
},
childCount: 100 //数量100个
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8, //交叉轴间距
mainAxisSpacing: 8,//主轴间距
childAspectRatio: 1.5
)
),
),
)
],
),
),
3-2、Slivers的基本使用-导航
return CustomScrollView(
slivers: [// 这里面只能放sliver
SliverAppBar(// slivers的导航
pinned: true,//默认会随着内容的滚动而滚动的, 设置为true就不滚动了
expandedHeight: 300,//扩展默认高度
flexibleSpace: FlexibleSpaceBar(
title: Text("Hello World"),
background: Image.asset("assets/images/juren.jpeg", fit: BoxFit.cover),
),
),
SliverSafeArea(//也可以在这里设置安全区域
sliver: SliverPadding(
padding: EdgeInsets.all(8),//设置内间距
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate(
(BuildContext ctx, int int){
return Container(
color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
);
},
childCount: 100 //数量100个
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8, //交叉轴间距
mainAxisSpacing: 8,//主轴间距
childAspectRatio: 1.5
)
),
),
)
],
),
3-3、Slivers监听滚动
class HYHomePage extends StatefulWidget {
const HYHomePage({Key? key}) : super(key: key);
@override
_HYHomePageState createState() => _HYHomePageState();
}
class _HYHomePageState extends State<HYHomePage> {
ScrollController _controller = ScrollController(
initialScrollOffset: 300 //设置默认偏移滚动值
);
bool _isShowFloatingBtn = false;
@override
void initState() {
super.initState();
_controller.addListener(() {
print("监听到滚动...滚动值:${_controller.offset}");
setState(() {
_isShowFloatingBtn = _controller.offset >= 1000;
});
});
}
@override
Widget build(BuildContext context) {
/*
* 两种方式可以监听:
* controller:
* 1.可以设置默认值offset
* 2.监听滚动, 也可以监听滚动的位置
* NotificationListener: (这是一个Widget)
* 1.可以监听开始滚动和结束滚动
* */
return Scaffold(
appBar: AppBar(
title: Text("导航标题"),
),
body: NotificationListener(
onNotification: (ScrollNotification notification){
if(notification is ScrollStartNotification) {
print("开始滚动");
} else if(notification is ScrollUpdateNotification){
print("正在滚动... 总滚动范围:${notification.metrics.maxScrollExtent} 当前滚动位置: ${notification.metrics.pixels}");
} else if(notification is ScrollEndNotification){
print("结束滚动");
}
return true;//想冒泡返回false, 不想冒泡返回true
},
child: ListView.builder(
controller: _controller,
itemCount: 100,
itemBuilder: (BuildContext ctx, int index){
return ListTile(
leading: Icon(Icons.people),
title: Text("联系人$index"),
);
}
),
),
/// 实现功能一键置顶
floatingActionButton: _isShowFloatingBtn? FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: (){
_controller.animateTo(
0, //动画结束位置
duration: Duration(seconds: 1), //动画时间
curve: Curves.easeIn //动画效果: 由快到慢、由慢到快等
);
},
): null,
);
}
}