Drat语法
1、基本语法
返回修饰词 main (){}
void main(){
//程序的主入口
}
2、基本数据类型
Drat是强类型语言
var
代表不确定类型 当变量第一次被赋值时,那么该变量就是当前类型,重新赋值不能改变类型
int
整数型
double
浮点型
bool
布尔型
String
字符串类型
num
数字类型不区分浮点与整数型
void main(){
int a = 10;
a = 10.1;//会报错
String b = "this is Drat";
bool flag = true;
double c = 10.2;
var d = 123;
d = "123";//报错
}
3、final 与const
final
要求变量只能初始化一次,并不要求赋的值一定是编译时常量,可以是常量也可以不是。而const
要求在声明时初始化,并且赋值必需为编译时常量。
void main(){
}
List集合
List类似前端的数组,注意的是
add()
增加元素
List a = new List();
a.add(456)
类
class Person{
String name;
int age;
Person(String name,int age){
this.name = name;
this.age = age;
}//构造函数一种写法
Person(this.name,this.age){}//构造函数一种写法
void printInfo(){
print("${this.name} --- ${this.age}");
}
}
Flutter
1、区分StatefullWidget
和StatelessWidget
的区别
StatefullWidget
:为动态组件,状态会发生改变的,例如进度条等。
StatelessWidget
:静态组件,例如文本框。
2、Hello Word
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
title : "欢迎学习,Flutter",
home : Scaffold(
appBar : AppBar(title:Text("Hello Wrod")),
body : Center(child:Text("Hello Wrod",style:TextStyle(color:Colors.blue,fontSize:50)))
)
);
}
}
image.png
3、MaterialApp组件
MaterialApp
组件一般是我们的根组件他可以定义我们的APP的主题颜色,已经背景等
查看MaterialApp源文件
MaterialApp({
Key key,
this.title = '', // 设备用于为用户识别应用程序的单行描述
this.home, // 应用程序默认路由的小部件,用来定义当前应用打开的时候,所显示的界面
this.color, // 在操作系统界面中应用程序使用的主色。
this.theme, // 应用程序小部件使用的颜色。
this.routes = const <String, WidgetBuilder>{}, // 应用程序的顶级路由表
this.navigatorKey, // 在构建导航器时使用的键。
this.initialRoute, // 如果构建了导航器,则显示的第一个路由的名称
this.onGenerateRoute, // 应用程序导航到指定路由时使用的路由生成器回调
this.onUnknownRoute, // 当 onGenerateRoute 无法生成路由(initialRoute除外)时调用
this.navigatorObservers = const <NavigatorObserver>[], // 为该应用程序创建的导航器的观察者列表
this.builder, // 用于在导航器上面插入小部件,但在由WidgetsApp小部件创建的其他小部件下面插入小部件,或用于完全替换导航器
this.onGenerateTitle, // 如果非空,则调用此回调函数来生成应用程序的标题字符串,否则使用标题。
this.locale, // 此应用程序本地化小部件的初始区域设置基于此值。
this.localizationsDelegates, // 这个应用程序本地化小部件的委托。
this.localeListResolutionCallback, // 这个回调负责在应用程序启动时以及用户更改设备的区域设置时选择应用程序的区域设置。
this.localeResolutionCallback, //
this.supportedLocales = const <Locale>[Locale('en', 'US')], // 此应用程序已本地化的地区列表
this.debugShowMaterialGrid = false, // 打开绘制基线网格材质应用程序的网格纸覆盖
this.showPerformanceOverlay = false, // 打开性能叠加
this.checkerboardRasterCacheImages = false, // 打开栅格缓存图像的棋盘格
this.checkerboardOffscreenLayers = false, // 打开渲染到屏幕外位图的图层的棋盘格
this.showSemanticsDebugger = false, // 打开显示框架报告的可访问性信息的覆盖
this.debugShowCheckedModeBanner = true, // 在选中模式下打开一个小的“DEBUG”横幅,表示应用程序处于选中模式
})
利用MaterialApp修改我们App的主题
return MaterialApp(title: "MaterialApp",
home:Scaffold(
appBar : AppBar(title:Text("Flutter MaterialApp"))
),
theme:ThemeData(
primaryColor: Colors.yellow,//appbar为黄色
scaffoldBackgroundColor:Colors.pink//设置底层背景为粉红色
)
);
image.png
4、Container组件的属性
Container
类似前端的div元素
属性 | 描述 | 用法 |
---|---|---|
child |
Container容器中包含的子组件 | child: Text("这是Container的子组件") |
alignment |
将子组件对齐方式 | alignment:Alignment.bottomRight |
width |
Container容器的宽度 | width:300.0 |
height |
Container容器的高度 | height:300.0 |
padding |
Container容器的内边距 |
padding: EdgeInsets.all(20.0) 或者 padding: EddgeInsets.fromLTRB(double left, double top, double right, double bottom)
|
margin |
Container容器的外边距 |
margin: EdgeInsets.all(20.0) 或者 margin: EddgeInsets.fromLTRB(double left, double top, double right, double bottom)
|
color |
Container容器的背景色 |
Colors.yellow 或者 color:Color.fromRGBO(r, g, b, opacity)
|
decoration |
装饰器,可以设置边框、圆角、背景图片,背景色:注意此属性不能与color属性同时使用
|
具体可看代码 |
transform |
变换旋转 | transform: Matrix4.rotationZ(0.3) |
@override
Widget build(BuildContext context) {
return Container(
child:Text("这是包裹在Container组件内的Text组件。"),
alignment : Alignment.bottomRight,
width: 300.0,
height: 300.0,
padding: EdgeInsets.fromLTRB(20.0, 40.0, 60.0, 40.0),
margin: EdgeInsets.all(40.0),
//color: Colors.yellow,//背景色不能与decoration同时使用,
decoration:BoxDecoration(
color: Colors.yellow,//背景色
border: Border.all(//线条样式
color: Colors.black,//边框颜色
width: 3.0,//线框
style: BorderStyle.solid,//实现
),
image: DecorationImage(//背景图片 注意使用https地址图片
image: NetworkImage('https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2606284972,2670724825&fm=26&gp=0.jpg'),
alignment: Alignment.topCenter,//对齐方式
),
)
);
}
image.png
4-1使用Container制造圆形且带阴影
Container(
width:50.0,
height:50.0,
decoration:BoxDecoration(
color:Colors.yellow,//背景图片
shape:BoxShape.circle,//圆形
boxShadow:[BoxShadow(color: Colors.black, offset: Offset(0, 0),blurRadius: 2.0, spreadRadius: 0.0)]//阴影
)
)
5、TextWidget常用属性
属性 | 描述 | 用法 |
---|---|---|
textAlign |
文本对齐方式 | textAlign:TextAlign.right |
overflow |
文本超出样式 | overflow:TextOverflow.ellipsis, |
maxLines |
最大显示的行数 | maxLines:2 |
style |
设置文本样式 | style:TextStyle(color:Color.fromRGBO(25, 201, 124, 1),) |
style_color |
属于style属性下,设置文本颜色 | style:TextStyle(color:Color.fromRGBO(25, 201, 124, 1),) |
style_fontSize |
属于style属性下,设置文本字体大小 | style:TextStyle(fontSize: 20.0,) |
style_fontWeight |
属于style属性下,设置文本字体加粗 | style:TextStyle(fontWeight: FontWeight.w800,) |
style_fontStyle |
属于style属性下,设置文本字体斜体 | style:TextStyle(fontStyle: FontStyle.italic,) |
style_decoration |
属于style属性下,设置文本中间的横线 | style: TextStyle(decoration: TextDecoration.lineThrough) |
Text("这是包裹在Container组件中的Text子组件",
style:TextStyle(
color:Color.fromRGBO(30, 201, 124, 1),
fontSize: 20.0,
fontWeight: FontWeight.w800,
fontStyle: FontStyle.italic
),
textAlign:TextAlign.right,
overflow:TextOverflow.ellipsis,
maxLines:2
)
6、Image组件
Image
一种是网络图片、一种是本地图片
Image.network()
、Image.asset()
属性 | 描述 | 用法 |
---|---|---|
默认值 | 图片应用的地址 | - |
color |
图片的颜色,直接使用会覆盖掉图片 | color:Colors.pink |
colorBlendMode |
混合模式,类似在图片上弄个遮罩需要与color一起使用 | colorBlendMode: BlendMode.screen |
fit |
图片裁剪 | fit:BoxFit.cover |
repeat |
平铺 | repeat: ImageRepeat.repeat, |
BoxFit.cover
:可能拉伸,裁切,充满,但是不会是图片变形
BoxFit.fill
:图片充满整个容器,会被拉伸
BoxFit.contain
:全图显示(不是充满容器),保留原宽高比例,会有空隙
BoxFit.cover
BoxFit.fill
image.png
BoxFit.contain
image.png
Container(
width:300.0,
height:300.0,
child: Image.network(//设置远程图片
"https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2606284972,2670724825&fm=26&gp=0.jpg",
fit: BoxFit.cover,
repeat: ImageRepeat.repeat,
color:Colors.green,
colorBlendMode: BlendMode.screen,
),
decoration: BoxDecoration(
//color:Colors.lime
border: Border.all(
color:pink,
width: 4,
style: BorderStyle.solid
),
),
)
image.png
6-1、 实现圆角、以及实现圆形图片
第一种:使用container背景图片实现圆形图片
Container(
width: 300.0,
height: 300.0,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2606284972,2670724825&fm=26&gp=0.jpg"),
fit:BoxFit.cover//填充背景图片
),
border:Border.all(
width:3,
color:Colors.black,
style: BorderStyle.solid
),
borderRadius:BorderRadius.circular(150.0)//圆角
),
)
第二种利用
ClipOval
组件实现圆形图片 推荐使用这个
return Center(
child:Container(
child:ClipOval(
child:Image.network("图片地址",width:100.0,height:100.0,fit:BoxFit.cover)
)
)
);
第三种利用
CircleAvatar
CircleAvatar(
radius: 25.0,
backgroundImage: NetworkImage("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2611652800,2596506430&fm=26&gp=0.jpg")
);
6-3、image组件之引入本地图片
1、在项目根目录创建静态资源文件夹
创建images文件夹其次在这个文件夹下创建
2.0x和3.0x文件夹
,然后把静态资源考进去,images文件夹下也得拷一份
- images
-- 2.0x
-- 3.0x
image.png
2、在配置文件夹中写入刚刚的路径
pubspec.yaml
,注意:格式需要对齐,不要保留空格
assets:
- images/1.jpg
- images/3.0x/1.jpg
- images/2.0x/1.jpg
3、使用图片
Container(
child: ClipOval(
child: Image.asset("images/1.jpg",width: 200.0,height: 200.0,fit: BoxFit.fill,),
),
)
image.png
7、 Icon组件
图标组件
属性 | 描述 | 用法 |
---|---|---|
size |
图标大小 | size: 30.0, |
color |
图标颜色 | color: Colors.blue, |
基础值 |
具体的图标 | Icons.sentiment_neutral |
Icon(Icons.sentiment_neutral,
size: 30.0,
color: Colors.blue
)
image.png
8、 ListView组件
ListView
:基础列表组件、水平列表组件、图标组件,相当于父容器,里面很多子元素
属性 | 描述 | 使用 |
---|---|---|
scrollDirection |
定义水平列表还是垂直列表 |
scrollDirection:Axis.horizontal 水平列表scrollDirection:Axis.vertical 垂直列表 |
padding |
外边距 | padding: EdgeInsets.all(20.0) 或者 padding: EddgeInsets.fromLTRB(double left, double top, double right, double bottom) |
children |
子元素,并且它的子元素不是一个,数组类型 | children:[Container(width: 100.0,height: 100.0,color: Colors.red,),Container(width: 100.0,height: 100.0,color: Colors.blue,),Container(width: 100.0,height: 100.0,color: Colors.pink,),Container(width: 100.0,height: 100.0,color: Colors.yellow,)] |
ListView(
scrollDirection:Axis.vertical,
children: <Widget>[
Image.asset("images/1.jpg",width: 200.0,height: 300.0,fit: BoxFit.fill,),
Text("这是列表组件"),
Container(width: 200.0,height: 200.0,color: Colors.red,)
],
),
8-1、我们可以配合ListTitle一起使用
ListTitle
组件,类似于新闻页主标题,副标题
属性 | 描述 | 使用 |
---|---|---|
leading |
前面的图标 |
leading:ICon(Icons.sentiment_neutral) 也可以图片 |
trailing |
后面的图标 | trailing:ICon(Icons.sentiment_neutral) |
title |
一级标题 | title:Text("阿斯达四大") |
title |
一级标题 | title:Text("阿斯达四大") |
subtitle |
二级标题/文本/描述 | subtitle:Text("阿斯达四大") |
这是listTtile的效果
ListView(
scrollDirection:Axis.vertical,
padding: EdgeInsets.all(10.0),
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB(0, 0, 0, 10.0),
child: ListTile(
leading:Image.asset("images/1.jpg",width: 100.0,height: 200.0,fit: BoxFit.fill,),
trailing:Icon(Icons.sentiment_neutral,
size: 30.0,
color: Colors.blue
),
title:Container(
child:Text(
"黄山旅游董事长章德辉:景区停摆每天损失450万 关注“两只票”",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w900
),
),
),
subtitle: Text("1月13日-23日,每日经济新闻推出《专访董事长·第一季》,华谊兄弟董事长王中军、工业富联董事长李军旗、通威集团董事局主席刘汉元等多位重磅嘉宾接受采访,畅谈行业现状、直陈公司利弊、展望未来前景,在业界引发强烈反响。时值全国“两会”即将召开之际,又是抗击新冠肺炎疫情之后,行业、企业复苏的关键时刻,作为知名上市公司的领头人,他们怎样看待疫情对行业、企业的影响?又如何带领企业走出困境?")
),
),
Container(
margin: EdgeInsets.fromLTRB(0, 0, 0, 10.0),
child: ListTile(
leading:Image.asset("images/1.jpg",width: 100.0,height: 200.0,fit: BoxFit.fill,),
trailing:Icon(Icons.sentiment_neutral,
size: 30.0,
color: Colors.blue
),
title:Container(
child:Text(
"黄山旅游董事长章德辉:景区停摆每天损失450万 关注“两只票”",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w900
),
),
),
subtitle: Text("1月13日-23日,每日经济新闻推出《专访董事长·第一季》,华谊兄弟董事长王中军、工业富联董事长李军旗、通威集团董事局主席刘汉元等多位重磅嘉宾接受采访,畅谈行业现状、直陈公司利弊、展望未来前景,在业界引发强烈反响。时值全国“两会”即将召开之际,又是抗击新冠肺炎疫情之后,行业、企业复苏的关键时刻,作为知名上市公司的领头人,他们怎样看待疫情对行业、企业的影响?又如何带领企业走出困境?")
),
)
],
),
image.png
9、动态组件
动态组件:顾名思义会改变组件状态的,例如进度条,或者我们拿到数据循环出组件(
类似v-for
);
我们看个简单的例子
我们要知道 任何的组件他都是一个类,只不过继承了
StatelessWidget、StatefulWidget
这两个接口,只要是类,他就有变量成员,方法
class MyBody extends StatelessWidget{
List<Widget> _getData(){ //重点
List<Widget> MyList = new List();
for(int i = 0 ;i<20;i++){
MyList.add(
Container(
padding: EdgeInsets.all(10.0),
child: ListTile(
leading: Icon(Icons.smartphone,size: 38,color: Colors.pink,),
title: Text("美药企跳过疫苗研发关键实验环节 美股还会暴跌吗",style: TextStyle(fontSize: 22,fontWeight: FontWeight.w900),),
subtitle: Text("据报道,美药企跳过疫苗研发关键实验环节,直接进行人体临床试验,而该做法备受一些专家质疑。美国总统特朗普此前称,争取2020年年底前实现疫苗的量产和分发。值得一提的是,对于加速疫苗的研发,一些美国网友也不认同上述做法。",maxLines:3,overflow:TextOverflow.ellipsis),
),
)
);
}
return MyList;
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return ListView(
children: this._getData()
);
}
}
image.png
mock
拿取服务端数据渲染
//服务端数据
var ListData = [
{
"title":"北京一单位33人集中发热 初判与使用中央空调有关",
"pubtime":2,
"reply":255,
"imageUrl" : "https://cms-bucket.ws.126.net/2020/0518/f55c820fp00qaitd8006qc000s600e3c.png"
},
{
"title":'蓬佩奥"警告"中国不要干涉美记者在港工作 中方回应',
"pubtime":4,
"reply":0,
"imageUrl" : "https://cms-bucket.ws.126.net/2020/0518/b76b3832j00qaiq2u004tc000s600e3c.jpg"
},
{
"title":"失联6天的翼装女飞行员遇难:遗体最先被村民发现",
"pubtime":5,
"reply":"4.2万",
"imageUrl" : "https://cms-bucket.ws.126.net/2020/0518/4689114ep00qail4e00e9c000s600e3c.png"
}]
List<Widget> _getData(){
var result = ListData.map((item){
return ListTile(
leading: Image.network(item["imageUrl"],width: 90.0,height:60.0,fit: BoxFit.fill,),
title:Text(item["title"],style: TextStyle(fontSize:18,fontWeight: FontWeight.w900),),
subtitle: Row(
children: <Widget>[
Container(child: Text(item['pubtime'].toString() + "小时前"),margin: EdgeInsets.fromLTRB(0, 10, 10.0, 0),),
Container(child: Text(item['reply'].toString() + "万人查看"),margin: EdgeInsets.fromLTRB(0, 10, 10.0, 0),)
],
)
);
});
return result.toList();
}
image.png
9-1、ListView.builder方法
itemCount
必填参数:循环的次数
itemBuilder
:循环主体
@override
Widget build(BuildContext context) {
return ListView.builder(//两个必填参数itemCount循环次数
itemCount: ListData.length,
itemBuilder: (ctx,index){//itemBuilder循环体
return ListTile(
leading: Image.network(ListData[index]["imageUrl"],width: 90.0,height: 70.0,fit: BoxFit.fill,),
title: Text(ListData[index]["title"],style: TextStyle(fontSize: 20,fontWeight: FontWeight.w900),),
);
}
);
10、GridView组件
image.png栅格布局:
常用有两种方法:
1、GridView.builder()
2、GridView.count()
请注意当GridView被放在Cloumn或者Row里面使用那么一定要指定GridView的高度,即在GridView外面包裹一层Container并设置高度
属性 | 描述 | 用法 |
---|---|---|
scrollDirection |
横向还是纵向 | scrollDirection:Axis.vertical |
padding |
内边距 | 同上面几个例子 |
resolve |
组件反向排序 | resolve:true |
crossAxisSpacing |
一行子元素的间距 | crossAxisSpacing:20.0 |
mainAxisSpacing |
垂直方向的子元素的间距 | mainAxisSpacing:20.0 |
crossAxisCount |
一行子元素的个数 | crossAxisCount:3 |
childAspectRatio |
子元素的宽高比例 | childAspectRatio:1.0 |
chidren |
子元素集合 | chidren:<Widget>[] |
gridDelegate |
该属性是GridView.builder中使用 | gridDelegate:SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:3,mainAxisSpacing : 20.0,crossAxisSpacing :20.0,childAspectRatio:0.6) |
physics |
禁止滚动 | physics:NeverScrollableScrollPhysics() |
10-1、简单的例子
GridView.count
List<Widget> _getData(){
var list = ListData.map((i){
return Container(
decoration: BoxDecoration(
border: Border.all(width:0.3,style: BorderStyle.solid,color: Colors.pink)
),
child: Column(
children: <Widget>[
Image.network(i["imageUrl"],height: 50.0,fit: BoxFit.cover,),
Container(height: 10.0,),
Text(i["title"],style: TextStyle(fontSize: 13.0),maxLines: 2,overflow: TextOverflow.ellipsis)
],
),
);
});
return list.toList();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return GridView.count(
crossAxisCount: 3,//一行三个
crossAxisSpacing:20.0,//一行之间的间距
mainAxisSpacing:20.0,//垂直之间的间距
padding: EdgeInsets.all(10.0),//内边距
children:this._getData(),//子元素
childAspectRatio:0.6,//设置宽高比例不能直接设置宽高的
);
}
10-2、GridView.builder
Widget _getData(ctx,i){
return Container(
decoration: BoxDecoration(
border: Border.all(width:0.3,style: BorderStyle.solid,color: Colors.pink)
),
padding: EdgeInsets.fromLTRB(15, 5, 15, 10),
child: Column(
children: <Widget>[
Image.network(ListData[i]["imageUrl"],height: 120.0,fit: BoxFit.fitWidth,),
SizedBox(height: 6.0,),//撑开间距
Text(ListData[i]["title"],style: TextStyle(fontSize: 16.0,fontWeight: FontWeight.w900),maxLines: 2,overflow: TextOverflow.ellipsis)
],
),
);
}
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount:3,
mainAxisSpacing : 20.0,
crossAxisSpacing :20.0,
childAspectRatio:0.6
),
padding: EdgeInsets.all(10.0),
itemCount: ListData.length,
itemBuilder: this._getData//注意这个地方不是调用哦
);
}
11、Padding、Row、Colum、Expanded组件
11-1、Padding组件
在Flutter中有许多组件是没有padding属性,我们之前都是包裹在Container中使用
属性 | 描述 | 使用 |
---|---|---|
padding |
内边距 | padding:EdgeInsets.all(10.0) |
child |
子元素 | child:Text() |
ListView(
children:[
Padding(padding:padding:EdgeInsets.all(10.0),child:Text("121313")),
Padding(padding:padding:EdgeInsets.all(10.0),child:Text("121313")),
]
)
11-2、Row组件
水平列表组件
mainAxisAlignment、crossAxisAlignment
是相对于父元素的位置起值的,如果父元素没有那么默认就是内容的高度
属性 | 描述 | 使用 |
---|---|---|
crossAxisAlignment |
纵轴对齐方式 | crossAxisAlignment:CrossAxisAlignment.spaceEvenly |
mainAxisAlignment |
横轴对齐方式 | mainAxisAlignment:MainAxisAlignment.center |
children |
子元素集合 | children:[] |
测试下mainAxisAlignment、crossAxisAlignment
return Container(
width: 800.0,
height: 600.0,
color: Colors.lightBlue,
child: Row(
children: <Widget>[
Container(width: 100.0,height: 100.0,color: Colors.red,),
Container(width: 100.0,height: 100.0,color: Colors.orange,),
Container(width: 100.0,height: 100.0,color: Colors.green,)
],
)
);
默认情况
MainAxisAlignment.spaceEvenly均匀分配横轴方向
image.png
纵轴相对于父元素,如果父元素没有,那么无效
CrossAxisAlignment.start
image.png
11-2、Colum组件
用法属性和Row一致
mainAxisAlignment:MainAxisAlignment.spaceEvenly,
crossAxisAlignment:CrossAxisAlignment.start,
image.png
11-3、Expanded组件
Expanded
组件类似于前端的display:flex;
注意一点:在Row中使用Expanded的时候,无法指定Expanded中的子组件的宽度width,但可以指定其高度height。同理,在Column中使用Expanded的时候,无法指定Expanded中的子组件的高度height,可以指定宽度width。
属性 | 描述 | 使用 |
---|---|---|
flex |
占用多少格 |
flex:1 整数型 |
child |
子元素 | child:Text() |
和Row组件一起使用
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(child: Container(height:200.0,color: Colors.blue,),flex: 1),
Expanded(child: Container(height:200.0,color: Colors.pink),flex: 2),
],
);
}
image.png
和Cloumn一起使用
return Column(
children: <Widget>[
Expanded(child: Container(height:200.0,color: Colors.blue,),flex: 1),
Expanded(child: Container(height:200.0,color: Colors.pink),flex: 2),
],
);
image.png
12、布局小结
实现下面的布局
image.png
@override
Widget build(BuildContext context) {
return Padding(padding: EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
Container(height: 180.0,color: Colors.black,),
Padding(
padding: EdgeInsets.fromLTRB(0, 10.0, 0, 0),
child: Row(
children: <Widget>[
Expanded(
child: Padding(padding: EdgeInsets.fromLTRB(0, 0, 10.0, 0),child: Image.network("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1809168321,2277139267&fm=26&gp=0.jpg"),),
flex: 2,
),
Expanded(
child:Column(
children: <Widget>[
Image.network("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2484090652,3266104108&fm=26&gp=0.jpg",fit: BoxFit.fill,),
SizedBox(height: 10.0,),
Image.network("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2382576479,3688530667&fm=26&gp=0.jpg",fit: BoxFit.fill)
],
),
flex: 1,
)
],
),
)
],
),
);
}
13、Stack、Align、Positioned组件
Stack、Align、Positioned
是用来定位布局使用的。
Stack
只能单一的组件进行定位
Stack
与其他两个组件同时使用就可以对多个组件进行定位
13-1、Stack堆叠组件
属性 | 描述 | 用法 |
---|---|---|
alignment |
配置所有子元素显示的位置 | alignment:AlignmentDirectional.topStart |
children |
子元素集合 | children:[] |
Stack(
children: <Widget>[
Container(width: 300.0,height: 400.0,color: Colors.red,),
Container(width: 100.0,height: 100.0,color: Colors.black,),
Text("测试测试测试测试测试测试",style: TextStyle(color: Colors.white),)
],
);
image.png
结果发现子组件都堆叠在一起了
alignment: Alignment.center
image.png
Alignment.center
会把所有的组件居中堆叠在一起``
Alignment
自带很多方位,当自带的方位不满足我们的需求,我们可以自定义方位
Alignment()
他有两个参数X、Y 分别是0、1、-1之间的值
Alignment(-1,-1)
最左上角
Alignment(-1,0)
贴近左侧垂直居中
Alignment(-1,1)
贴近左侧垂直靠底部(左下角)
Alignment(0,-1)
横轴居中贴近顶部
Alignment(0,0)
居中
Alignment(0,1)
横轴居中贴近底部
Alignment(1,-1)
右上角
当然也可以小数位
单一使用Stack的缺点我们也知道了,没办法对子组件一一定位
13-2、Stack与Align组件一起使用
这样就可以实现我们类似前端的定位了
Center(
child: Container(//给了个最大的容器
width: 400.0,
height: 400.0,
color: Colors.yellow,
child: Stack(//然后使用Stack
children: <Widget>[
Align(//使用Align对子组件定位
alignment:Alignment.topRight,
child: Icon(Icons.home,size:40.0,color: Colors.red,)
),
Align(
alignment:Alignment.bottomRight,
child: Icon(Icons.settings,size:40.0,color: Colors.blue,),
),
Align(
alignment:Alignment(0,0),//我们使用实例化alignment:Alignment类
child: Icon(Icons.slow_motion_video,size:40.0,color: Colors.black,),
),
],
),
),
);
image.png
13-3、Stack与Position一起使用(推荐使用这个)
Position
组件就比Align好用多了,他有left、top、bottom、right
四个值
Center(
child: Container(
width: 400.0,
height: 400.0,
color: Colors.yellow,
child: Stack(
children: <Widget>[
Positioned(
left:50,
child: Icon(Icons.home,size:40.0,color: Colors.red,)
),
Positioned(
left:150,
top: 100,
bottom: 20,
child: Icon(Icons.settings,size:40.0,color: Colors.blue,),
),
Positioned(
left:250,
child: Icon(Icons.slow_motion_video,size:40.0,color: Colors.black,),
),
],
),
),
);
image.png
14、AspectRatio组件
AspectRatio
定义子元素相对于父元素的宽高比例
属性 | 描述 | 用法 |
---|---|---|
aspectRatio |
定义子元素相对父元素的宽高比 | aspectRatio:2.0/1.0 |
child |
子元素 | child:Widget |
Container(
width: 300.0,
child: AspectRatio(
aspectRatio: 2.0/1.0,
child: Container(
color:Colors.yellow
),
),
);
黄色区域的宽高比例是2/1
image.png
15、Card
类似于前端中ElementUI中的Card差不多,我们可以给想要加阴影的容器套一层Card就有阴影效果了
属性 | 描述 | 用法 |
---|---|---|
margin |
外边距 | margin: EdgeInsets.all(10.0), |
shadowColor |
阴影颜色 | shadowColor:Colors.red, |
elevation |
阴影扩散的大小 | elevation : 10.0 |
return Contanier(width:200.0,height:200.0,child:Card());
16、练习布局
实现以下布局
image.pngclass MyBody extends StatelessWidget{
List list = new List();
MyBody(){
this.list = [{
'avtorImg' : "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1452751795,1897979528&fm=26&gp=0.jpg",
'avtorName' : "十年之后_1",
'avtorDesc' : "1描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
'backGround' : "https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3778173668,1422088699&fm=26&gp=0.jpg"
},{
'avtorImg' : "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2405043393,1737103092&fm=26&gp=0.jpg",
'avtorName' : "十年之后_2",
'avtorDesc' : "2描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
'backGround' : "https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2335751370,711568964&fm=26&gp=0.jpg"
},{
'avtorImg' : "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3940043770,1553058007&fm=26&gp=0.jpg",
'avtorName' : "十年之后_3",
'avtorDesc' : "3描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
'backGround' : "https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1532844926,3671374399&fm=26&gp=0.jpg"
},{
'avtorImg' : "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2255910787,3354486640&fm=26&gp=0.jpg",
'avtorName' : "十年之后_4",
'avtorDesc' : "4描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
'backGround' : "https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2155983538,3860699715&fm=26&gp=0.jpg"
},{
'avtorImg' : "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2611652800,2596506430&fm=26&gp=0.jpg",
'avtorName' : "十年之后_5",
'avtorDesc' : "5描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述",
'backGround' : "https://dss1.bdstatic.com/6OF1bjeh1BF3odCf/it/u=1242146115,2880436607&fm=74&app=80&f=JPEG&size=f121,90?sec=1880279984&t=0136ee81b657a616fbe11b994842e68e"
}];
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return ListView.builder(
itemCount: this.list.length,
itemBuilder: (ctx,i){
return Padding(
padding: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 0),
child: Card(
elevation:3.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(aspectRatio: 10.0/5.0,child: Image.network(this.list[i]["backGround"],fit: BoxFit.cover,),),
Padding(
padding: EdgeInsets.fromLTRB(0,10.0,0,10.0),
child: ListTile(
leading: ClipOval(child: Image.network(this.list[i]["avtorImg"],width:50.0,height:50.0,fit: BoxFit.cover,),),
title: Text(this.list[i]["avtorName"],style: TextStyle(fontWeight: FontWeight.w600),),
subtitle: Text(this.list[i]["avtorDesc"],maxLines:1,overflow: TextOverflow.ellipsis,style: TextStyle(color: Color.fromRGBO(102, 102, 102, 1))),
)
)
],
)
)
);
}
);
}
}
17、RaisedButton按钮组件
如果需要给按钮宽高,那么可以通过给
RaisedButton
包裹一层Container
设置宽高
属性 | 描述 | 用法 |
---|---|---|
color |
按钮的背景颜色 | color:Colors.*** |
textColor |
按钮里面的文本颜色 | textColor:Colors.pink |
child |
子元素 | child:Text() |
onPressed |
点击事件 |
onPressed(){} /onPressed:自定义函数
|
disabledTextColor |
按钮禁用状态时文本颜色 | disabledTextColor:Colors.pink |
disabledColor |
按钮禁用状态时背景颜色 | disabledColor:Colors.pink |
elevation |
按钮的阴影 | elevation:20.0 |
shape |
圆角、原型圆形按钮 | 看例子 |
splashColor |
点击按钮之后颜色变化过渡的效果 | splashColor: Colors.red |
focusColor、hoverColor
圆角:shape:RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25.0)) )
RaisedButton(
padding: EdgeInsets.all(10.0),
child: Text("按钮"),
onPressed: (){},
shape:RoundedRectangleBorder(//圆角按钮
borderRadius: BorderRadius.all(Radius.circular(25.0))
),
);
17-1圆形按钮
使用
shape
属性,如果文本溢出,我们可以在包裹一层Container
容器
RaisedButton(onPressed: (){
print("圆形按钮");
},child: Text("圆形按钮"),elevation: 20,splashColor: Colors.red,shape:CircleBorder(
side:BorderSide(color:Colors.white)
),)
17-2图标按钮
RaisedButton.icon()
RaisedButton.icon(onPressed: (){
print("图标按钮");
}, icon: Icon(Icons.search), label: Text("图标按钮")),
18、Wrap流式布局
它类似与
GridView.count
,但是GridView.count
需要定义一行显示几个,而且子元素宽度自动填满
属性 | 描述 | 用法 |
---|---|---|
spacing |
子元素之间的间距 | spacing:20.0 |
runSpacing |
垂直之间的间距 | runSpacing:20.0 |
direction |
主轴的方向,默认水平 | direction:Axis.horizontal, |
alignment |
主轴的对齐方式 | alignment:WrapAlignment.start |
Wrap(
spacing : 3.0,
runSpacing : 20.0,
direction:Axis.horizontal,
children: <Widget>[
MyButton("啊实打实群"),
MyButton("啊实打"),
MyButton("啊实打2352实群"),
MyButton("实群"),
MyButton("实群"),
MyButton("啊实打2实群"),
MyButton("啊实打实群3463"),
MyButton("群"),
MyButton("啊实43群"),
MyButton("啊4群"),
],
);
image.png
19、StatefulWidget
当页面改变数据的时候我们需要使用
StatefulWidget
,例如当我们点击按钮,让文本的内容发生改变
在StatefulWidget
中如果需要初始化值需要在initState
方法中实现
int _curderIndex = 0;
List _navList = new List();
@override
void initState() {
super.initState();
this._navList = [
];
}
class HomePage extends StatelessWidget {
String text = "你好Flutter1";
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(this.text),
SizedBox(height: 30.0,),
RaisedButton(
child: Text("按钮"),
onPressed: (){
this.text = "asdq";
print(this.text);
},
)
],
);
}
}
通过上述例子我们知道 文本不会发生变化但是值是变化了的。这说明在StatelessWidget不会改变组件的状态了
19-1、定义一个StatefulWidget
组件
class HomePage extends StatefulWidget{
HomePage(Key key) : super({key : key});//可以省略
_HomePageState createState()=>_HomePageState ();
}
class _HomePageState extends State<HomePage>{
@overirde
Widget build(BuildContext context){
return Text("1");
}
}
在
StatefulWidget
组件中有一个setState((){})
的方法,可以改变组件的状态
class _HomePageState extends State<HomePage>{
int num = 0;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Chip(label: Text("点击${num}次")),
SizedBox(height: 30.0,),
RaisedButton(child: Text("按钮"),
onPressed: (){
setState(() {//重点
this.num ++;
});
})
],
);
}
}
通过动态组件我们写一个案例:点击按钮增加一条数据
class _HomePageState extends State<HomePage>{
int num = 0;
List list = new List();
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Column(
children: this.list.map((e){**重点**
return Text(e);
}).toList(),
),
SizedBox(height:30.0),
RaisedButton(
child: Text("按钮"),
onPressed: (){
setState((){**重点**
this.num ++;
this.list.add("${this.num}条数据");
});
}
)
],
);
}
}
image.png
20、BottomNavigationBar自定义底部导航栏组件,以及页面切换
BottomNavigationBar
底部导航栏组件,它是属于Scaffold
组件中的属性
属性 | 值类型 | 说明 |
---|---|---|
items |
一个为BottomNavigationBarItem 的集合 |
items:[ BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页"))] |
onTap |
点击导航栏的回调事件 | onTap:(index){print(index);}, |
currentIndex |
默认选中的下标 | currentIndex:2 |
type |
导航栏的类型:fixed、shifting
|
type:BottomNavigationBarType.shifting |
fixedColor |
底部导航栏type为fixed时导航栏的颜色,如果为空的话默认使用ThemeData.primaryColor | fixedColor:Colors.red |
iconSize |
导航栏图片的大小 | iconSize:23 |
bottomNavigationBar: BottomNavigationBar(
currentIndex : 1,
onTap:(i){},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页")),
BottomNavigationBarItem(icon: Icon(Icons.list),title: Text("分类")),
BottomNavigationBarItem(icon: Icon(Icons.settings),title: Text("设置")),
],
),
20-1、设置点击导航栏切换页面/选中状态
我们知道改变状态需要在
StatefulWidget
中实现,那么我们把整个Scaffold
单独抽出成一个组件
class HomePage extends StatefulWidget{
HomePage({Key key}):super(key:key);
@override
_HomePageState createState()=>_HomePageState();
}
class _HomePageState extends State<HomePage>{
int index = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("页面定位布局"),),
body: MyBody(),
bottomNavigationBar: BottomNavigationBar(
currentIndex : this.index,
onTap:(i){
setState(() {
this.index = i;
});
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页")),
BottomNavigationBarItem(icon: Icon(Icons.list),title: Text("分类")),
BottomNavigationBarItem(icon: Icon(Icons.settings),title: Text("设置")),
],
),
drawer: Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(child: Text("Drawer"),decoration: BoxDecoration(color:Colors.pink),),
ListTile(leading: Icon(Icons.settings),title: Text("设置"),),
],
),
),
);
}
}
20-2、利用bottomNavigationBar切换页面
我们在lib中新建一个
pages
文件夹,然后创建几个简单的Widget
import 'package:flutter/material.dart';
import './tabs/home.dart';
import './tabs/list.dart';
import './tabs/seting.dart';
class Tabs extends StatefulWidget{
Tabs({Key key}) : super (key:key);
@override
_TabsState createState()=>_TabsState();
}
class _TabsState extends State<Tabs>{
int _curInedex = 0;
List _pageList = [//页面数组
HomePage(),
ListPage(),
SetingPage()
];
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(title:Text("导航栏的使用,切换")),
bottomNavigationBar: BottomNavigationBar(
currentIndex: this._curInedex,
onTap: (i){
setState((){
this._curInedex = i;
});
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页")),
BottomNavigationBarItem(icon: Icon(Icons.line_weight),title: Text("分类")),
BottomNavigationBarItem(icon: Icon(Icons.settings),title: Text("设置")),
],
),
drawer: Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(child: Text("抽屉组件"),decoration:BoxDecoration(color:Colors.pink)),
ListTile(leading: Icon(Icons.settings),title: Text("设置"),)
],
),
),
body: this._pageList[this._curInedex],重点
);
}
}
21、Flutter中的路由
Navigator.of()
跳转某一页面,Navigator.pop()回到上一页面
Navigator.of(context).push(
MaterialPageRoute(builder:()=>页面组件())
)
21-1、页面跳转传值
类似于
vue
的router
传值
非常简单,我们知道构造函数是可以传参的而我们的builder :()=>页面组件(这里是可以传参的)
Navigator.of(context).push(
MaterialPageRoute(builder:()=>MyPage(title:"标题"));
)
class MyPage extends StatelessWidget{
String title = "";
MyPage({title:this.title})
...
}
22-2、路由命名跳转
使用
Navigator.pushNamed()
如果要使用pushNamed
方法那么需要在MaterialApp
中定义好路由
MaterialApp(
title: "路由跳转",
theme: ThemeData(primaryColor: Colors.pink),
routes: {//这里的key可以随意起
"/form":(BuildContext context)=>FormPage(),
"/serach":(BuildContext context)=>SerachPage()
},
home: MyScaffold()
);
按钮中实现路由跳转
onPressed:(){
Navigator.pushNamed(context, "/serach");
}
22-3、命名路由传值、路由验证
命名路由传值不能像构造参数传值那样,需要指定
onGenerateRoute
属性Flutter中文网,onGenerateRoute
属于MaterialApp
的属性
路由拦截验证
假设我们要开发一个电商APP,当用户没有登录时可以看店铺、商品等信息,但交易记录、购物车、用户个人信息等页面需要登录后才能看。
首先我们定义路由Map
案例:我们超前使用本地存储功能获取userName
MaterialApp(
routes: {
"/login" : (BuildContext context)=>LoginPage(),
"/home" :(BuildContext context)=>HomePage(),
"/card" :(BuildContext context)=>CardPage(),
...
},
"onGenerateRoute":(RouteSettings setings){
final String routeName = settings.name;//获取进入的路由名字,
final prefs = await SharedPreferences.getInstance(); //本地存储获取
final userName = prefs.getString("userName");
if(routeName !="/login" && !userName.isEmpty){//如果用户名不存在那么我们跳转至登录页面
Navigator.pushNamed(context, "/login");
}
}
);
命名路由传参
这里有个巨坑,如果此时
MaterialApp
还配置routes
属性会报错;
切记,切记,切记
final _routes = {
"/login" : (BuildContext context)=>LoginPage(),
"/home" :(BuildContext context)=>HomePage(),
"/card" :(BuildContext context,{arguments})=>CardPage(infoCard:arguments),
...
},
MaterialApp(
"onGenerateRoute" : (RouteSettings setings){
final String name = settings.name;
final Function pageBuilder = this._routes[name];
if (pageBuilder != null) {
if (settings.arguments != null) {
// 如果透传了参数
return MaterialPageRoute(
builder: (context) =>
pageBuilder(context, arguments: settings.arguments));
} else {
// 没有透传参数
return MaterialPageRoute(builder: (context) => pageBuilder(context));
}
}
}
)
然后我们需要在 CardPage页面中接收`infoCard`参数
class CardPage extends StatelessWidget{
final infoCard;
CardPage({this.infoCard});
...
}
配置根路由
我们开发
Vue
项目知道,一般会配置一个根路由
在flutter
中配置跟路由使用initialRoute
属性,该属性属于MaterialApp
,当使用了该属性时,在使用home
属性可能会导致混乱
MaterialApp(
initialRoute:"/home"
)
22-4、替换根路由
Navigator.pushReplacementNamed();
Navigator.of(context).pushAndRemoveUntil;
使用场景:当我们跳转页面中在跳转页面,例如当我们点击注册按钮进入注册页面,然后输入完信息之后,点击下一步,跳转第二步注册,然后点击注册完成按钮,返回的页面应该不是第一步中的注册页面,而是其实页面
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("请输入手机号,密码,点击下一步继续完成注册操作"),
RaisedButton(onPressed: (){
//进入第二步骤注册页面
Navigator.pushReplacementNamed(context, "/regSend");
},child: Text("下一步"),)
],
)
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("注册完成"),
RaisedButton(onPressed: (){
//此时pop返回的不是第一步注册页面了,而是其实页面了
Navigator.of(context).pop();
},child: Text("完成注册"),)
],
)
如果页面嵌套很多层,而每次都使用Navigator.pushReplacementNamed();
保存根确实有些小麻烦,那么我们可以使用pushAndRemoveUntil
方法,不管你嵌套多少层,直接使用该方法直接可返回跟路由
Navigator.of(context).pushAndRemoveUntil(
//这个案例是返回tab页面
new MaterialPageRoute(builder: (context)=>Tabs(index:1)),
(route)=>route == null);
23、floatingActionButton浮动按钮
floatingActionButton
是属于Scaffold
中的属性
floatingActionButton: FloatingActionButton(onPressed: (){
Navigator.pop(context);//路由返回
},child: Text("返回"),),
23-1 控制floatingActionButton的显示位置
floatingActionButtonLocation
属性该属性是在Scaffold
中的,
与floatingActionButton
是同级关系
Scaffold(
floatingActionButton: FloatingActionButton(onPressed: (){},child: Icon(Icons.add),),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,//底部中间
)
23-2实现底部导航的凸起按钮
image.png思路:我们可以定义5个
bottomNavigationBar
然后使floatingActionButton
盖住中间的那一个然后点击事件我们改变curIndex的值即可
Scaffold(
floatingActionButton: Container(
width: 62.0,
height: 62.0,
padding: EdgeInsets.all(3.0),//padding
decoration: BoxDecoration(
shape:BoxShape.circle,//圆形
color: Colors.white,//白色
boxShadow:[BoxShadow(color: Colors.black26, offset: Offset(0, 0),blurRadius: 2.0, spreadRadius: 0.0)]//阴影
),
child: FloatingActionButton(onPressed:(){
setState(() {
this.curIndex = i;
});
},splashColor: null,elevation:0,child: Icon(Icons.add,size: 40.0,color: Color.fromRGBO(51, 51, 51, 1),),backgroundColor: Colors.yellow,),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.home),title: Text("首页")),
BottomNavigationBarItem(icon: Icon(Icons.list),title: Text("分类")),
BottomNavigationBarItem(icon: Icon(Icons.text_fields),title: Text("卖闲置")),
BottomNavigationBarItem(icon: Icon(Icons.shopping_cart),title: Text("购物车")),
BottomNavigationBarItem(icon: Icon(Icons.people),title: Text("我的")),
],
type:BottomNavigationBarType.fixed,
currentIndex: this.curIndex,
onTap: (i){
setState(() {
this.curIndex = i;
});
},
),
)
image.png
24、SingleChildScrollView
当我们软键盘弹起的时候,会盖住我们的内容,这是就会报越界,使用
SingleChildScrollView
页面会随高度改变而又滚动条
25、自定义AppBar以及Tab切换
在使用
AppBar
我们之前一直都是直接title
属性带过,现在我们仔细看看它有哪些属性
属性 | 描述 | 用法 |
---|---|---|
title |
标题 |
title:Text("标题") 当然也可以不是Text |
leading |
AppBar左侧组件 |
leading:Icon(Icons.back) 当然也可以不是Icon |
actions |
右侧组件集合 | actions:[Icon(Icons.back),Icon(Icons.back)] |
backgroundColor |
Appbar背景颜色 | backgroundColor:Colors.blue |
bottom |
通常放TabBar,标题下面放一栏tab切换 | bottom:TabBar(tabs: [ ]) |
iconTheme |
appbar中的图标统一样式 | iconTheme: IconThemeData(color: Colors.pink,opacity:0.3,size:20), |
textTheme |
文字样式 | textTheme : TextTheme(headline1:TextStyle(color: Colors.black)) |
centerTitle |
标题是否居中 | centerTitle:true |
Scaffold(
appBar : AppBar(
leading: IconButton(icon: Icon(Icons.pin_drop), onPressed: (){
//这里我们使用了IconButton组件图标按钮点击返回根组件
Navigator.pushNamed(context, "/");
}),
backgroundColor: Colors.blue,
title: Text("AppBarDemo"),
actions: [//右侧组件集合
Icon(Icons.home),
Text("电话")
],
centerTitle : true,//标题居中
iconTheme: IconThemeData(color: Colors.pink,opacity:1,size:30),
textTheme : TextTheme(headline1:TextStyle(color: Colors.black),overline:TextStyle(color: Colors.black))
)
)
image.png
25-1Tab切换
进行Tab切换在
Scaffold
组件中需要包裹一层DefaultTabController
组件
TabBar常用属性
属性 | 描述 | 用法 |
---|---|---|
tabs |
tab的内容 | tabs: [Tab(text: "tab1"),Tab(text: "tab2",) ] |
controller |
TabController对象 | controller:TabController() |
isScrollable |
是否可以滚动,当有多个Tab选项时就会被挤在一起,设置为true 可以解决 |
isScrollable:false |
indicatorColor |
指示器颜色 | indicatorColor:Colors.blue |
indicatorWeight |
指示器高度 | indicatorWeight:2 |
indicatorPadding |
指示器的padding | indicatorPadding:EdgeInsets.all(10), |
indicator |
指示器的描述样式 | indicator: Decoration(***) |
labelColor |
文字的颜色 | labelColor:Colors.blue |
labelStyle |
文字的样式 | labelStyle:TextStyle() |
labelPadding |
文字的padding | labelPadding:EdgeInsets.all(10) |
unselectedLabelColor |
未选中的文字颜色 | unselectedLabelColor:Colors.pink |
unselectedLabelStyle |
未选中的文字样式 | lunselectedLabelStyle:TextStyle() |
DefaultTabController(
length:2,//定义有几个tab切换
child:Scaffold(
appBar:AppBar(
title:Text("自定义Tab切换"),
bottom:TaBar(
tabs:[
Tab(text:"音乐",icon:Icon(Icons.headset)),
Tab(text:"推荐"),
],//tab切换
),
),
body:TabBarView(//主内容需要用TabBarView包括
children:[
Center(child:Text("对应音乐")),//顺序对应bottom中的tabs顺序
Center(child:Text("对应推荐")),
]
)
)
)
image.png
这个是单独页面定义自动以Tab切换,如果我们要在底部导航栏页面增加Tab切换怎么做呢。因为我们底部导航栏页面是分别抽出来公共
Scaffold
组件的,如果被DefaultTabController
包裹,那么所有的底部导航栏页面都会增加Tab切换
Scaffold中可以继续嵌套Scaffold
DefaultTabController(length: 2, child: Scaffold(
appBar: AppBar(
leading: Icon(null),
centerTitle: true,
title: Row(//这里title我们改成了TaBar
children: <Widget>[
Expanded(child: TabBar(labelColor:Colors.blue,tabs: [
Tab(text: "tab1"),
Tab(text: "tab2",)
]))
],
),
),
body: TabBarView(children: [
Column(
mainAxisAlignment:MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("页面传值跳转,类似于vue、Router跳转传值"),
RaisedButton(onPressed: (){
Navigator.of(context).push(
MaterialPageRoute(builder: (context)=>FormPage(formInfo:{'id':"传值的12"}))
);
},child: Text("传值跳转"),)
],
),
Center(child:Text("第二个tab2"))
]),
)
)
image.png
25-2 自定义TabBar
为什么我们要自定义呢?为什么不用上述的例子呢?用上述的例子我们无法监听tab切换的事件,而自定义我们可以监听
如果要使用自定义TabBar的话,组件必须是StatefulWidget
动态组件并且需要实现SingleTickerProviderStateMixin
class TabBarController extends StatefulWidget{
_TabBarControllerState createState()=> _TabBarControllerState();
}
//这里要实现SingleTickerProviderStateMixin
class _TabBarControllerState extends State<TabBarController> with SingleTickerProviderStateMixin{
TabController _tabController;
@override
void initState(){
this._tabController = TabController(length:2,vsync:this);
this._tabController.addListener((){//监听切换事件
print(this._tabController.index);//切换的下标
});
}
@override
Widget build(BuildContext context) {
return Scaffold{
appBar:AppBar(
title:Text("TabBarController"),
bottom:TabBar(tabs:[
Tab(child:Text("tab1")),
Tab(child:Text("tab2")),
],
controller:this._tabController//别忘了这一步
)
),
body:TabBarView(
children : [
Center(child:Text("切换至Tab1")),
Center(child:Text("切换至Tab2")),
],
controller:this._tabController//别忘了这一步TabBarView也需要加上
)
}
}
}
26、Draw抽屉侧边栏
drawer
是属于Scaffold
中的属性左侧侧边栏
endDrawer
是属于Scaffold
中的属性右侧侧边栏
Scaffold(
drawer: Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(child: Text("抽屉组件"),decoration:BoxDecoration(color:Colors.pink)),
ListTile(leading: Icon(Icons.settings),title: Text("设置"),)
],
),
),
endDrawer : Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(child: Text("抽屉组件"),decoration:BoxDecoration(color:Colors.pink)),
ListTile(leading: Icon(Icons.settings),title: Text("设置"),)
],
),
)
)
image.png
26-1使用UserAccountsDrawerHeader在侧边栏显示用户信息
属性 | 描述 | 用法 |
---|---|---|
accountName |
用户名 | accountName:Text("十年之后") |
accountEmail |
可以称为用户描述寄语,官方是邮箱的意思 | accountEmail:Text("简书写你所想") |
currentAccountPicture |
用户头像图片 | currentAccountPicture:CircleAvatar(backgroundImage: NetworkImage(***))) |
drawer: Drawer(
child: ListView(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text("十年之后"), accountEmail: Text("简书,写你所想"),
currentAccountPicture:CircleAvatar(backgroundImage: NetworkImage("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2586537036,779451914&fm=26&gp=0.jpg"),radius: 25.0,)
),
ListTile(
leading: Icon(Icons.settings),
title: Text("设置"),
)
],
),
),
27 ButtonBar按钮组
ButtonBar
里面放N个按钮
属性 | 描述 | 用法 |
---|---|---|
children |
按钮组 | children:[] |
alignment |
按钮组的对齐方式 | alignment:MainAxisAlignment.start, |
ButtonBar(
alignment:MainAxisAlignment.start,
children: <Widget>[
FlatButton(onPressed: (){},child: Text("扁平化按钮"),color:Colors.blue),
],
)
28表单
28-1TextFaild
输入表单类似
input
属性 | 描述 | 应用 |
---|---|---|
decoration - hintText |
类似于前端placeholder提示信息 | decoration:InputDecoration(hintText:"输入名字") |
decoration - border |
给输入框增加边框 | decoration:InputDecoration(border:OutlineInputBorder()) |
maxLength |
限制输入框输入的字符长度 |
maxLength:4 限制只能输入4个字符 |
maxLines |
多行文本,类似于textarea
|
maxLines:4 限制4行 |
obscureText |
密码框 | obscureText:true |
controller |
输入框里面输入的值 | controller:TextEditingController(text:'十年之后') |
decoration - labelText |
labelText | decoration:InputDecoration(labelText:"用户名") |
decoration - icon |
表单图标 | decoration:InputDecoration(icon: Icon(Icons.home)) |
onChanged |
监听输入框文本改变的事件 | onChanged:(val){print(val);} |
TextField(
obscureText : true,//密码
maxLength : 14,//只能输入14个字符
maxLines : 1,//只有一行
decoration : InputDecoration(
border:OutlineInputBorder(),//边框
labelText:"密码",
icon:Icon(Icons.format_indent_increase)
)
)
image.png
28-1-1、TextFaild获取输入的值/修改值
利用
TextFaild
中的controller
属性我们绑定值
var user_name = TextEditingController();
@initState(){
user_name.text = "十年之后初始值";注意这里修改的是text的属性的值
}
TextField(
controller:user_name,这里赋值
decoration: InputDecoration(
labelText: "用户名",
contentPadding: EdgeInsets.all(3),
border: OutlineInputBorder()
),
)
点击按钮获取表单输入的值
TextField(
controller:user_name,这里赋值
decoration: InputDecoration(
labelText: "用户名",
contentPadding: EdgeInsets.all(3),
border: OutlineInputBorder()
),
)
RaisedButton(onPressed: (){
print(this.user_name.text);注意这里是获取user_name中的text
},child: Text("获取表单的值"),)
点击修改输入框的值
RaisedButton(onPressed: (){
setState(() {
this.user_name.text = "十年之后Dart与Flutter";
});
},child: Text("修改表单的值"),),
28-2 CheckBox多选框
属性 | 描述 | 应用 |
---|---|---|
value |
绑定的值 |
value:flag 事先定义flag |
onChanged |
监听状态改变 | onChanged:(val){setState((){flag=val;})} |
activeColor |
选中时的颜色 | activeColor:Colors.blue |
var flag = false;
Row(children:[
Checkbox(value: flag, onChanged: (val){
setState(() {
this.flag = val;
});
}),
Text(this.flag?"选中":"未选中")
])
28-2-1 CheckBoxListTitle
属性 | 描述 | 用法 |
---|---|---|
selected |
选中高亮 | selected:判断当前枚举值是否等该枚举值就高亮 |
CheckBoxListTitle
与ListTitle
类似
CheckboxListTile(value: flag,onChanged: (val){
setState(() {
flag = val;
});
},title:Text("同意协议"),subtitle: Text("23456"),),
image.png
28-3 Radio单选框
Radio
往往是成对出现的,怎么保证多个Radio
是一个组呢?
这时候我们要用groupValue
属性给多个Radio
绑定同一个值
属性 | 描述 | 应用 |
---|---|---|
value |
该单选框的枚举值 |
value:1 写上你的枚举值不用是变量 |
groupValue |
绑定的值 |
groupValue:this.sex 事先定义sex |
onChanged |
监听改变事件 | onChanged:(val){setState((){sex=val;})} |
var sex = 1;
Row(
children: <Widget>[
Text("男:"),
Radio(value: 1, groupValue: this.sex, onChanged: (v){
setState(() {
this.sex = v;
});
}),
Text("女:"),
Radio(value: 2, groupValue: this.sex, onChanged: (v){
setState(() {
this.sex = v;
});
})
],
)
28-3-1、RadioListTitle
属性 | 描述 | 用法 |
---|---|---|
selected |
选中高亮 | selected:判断当前枚举值是否等该枚举值就高亮 |
与CheckBoxListTitle
类似
RadioListTile(value: 1, groupValue: this.a, onChanged: (v){
setState(() {
this.a = v;
});
},title: Text("爬山"),subtitle: Text("2020-06-02"),selected:this.a==1),
RadioListTile(value: 2, groupValue: this.a, onChanged: (v){
setState(() {
this.a = v;
});
},title: Text("游泳"),subtitle: Text("2020-06-02"),selected:this.a==2),
image.png
28-4、Switch开关
Switch(value: b, onChanged: (v){
setState(() {
b =v;
});
})
28-4-1 SwitchListTitle
SwitchListTile(value: b, onChanged: (v){
setState(() {
b =v;
});
},title: Text("开关"),subtitle: Text("描述"),)
28-5、练习表单
image.png重点是利用数组循环map渲染兴趣
Scaffold(
appBar: AppBar(title: Text("formDemo练习"),),
body: Container(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
TextField(
controller: this.user_name,
onChanged: (v){
setState(() {
this.user_name.text = v;
});
},
decoration: InputDecoration(
labelText: "用户名",
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(3.0)
),
),
SizedBox(height: 10.0,),
Row(
children: <Widget>[
Text("性别"),
Radio(value: true, groupValue: this.sex, onChanged: this._setSex),
Text("男"),
Radio(value: false, groupValue: this.sex, onChanged: this._setSex),
Text("女"),
],
),
SizedBox(height: 10.0,),
Row(
children: this._listCheck(),
),
SizedBox(height: 10.0,),
TextField(
controller: this.user_info,
maxLines: 4,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText:"用户信息"
),
onChanged: (v){
setState(() {
this.user_info.text = v;
});
},
),
SizedBox(height:10.0),
RaisedButton(onPressed: (){},child: Text("提交"),color: Colors.blue,textColor: Colors.white,)
],
)
)
);
29、DateTime时间、时间组件
29-1、获取当前时间
DateTime.now()
print(DateTime.now()) 2020-06-03 15:06:30.832249
29-2、获取时间戳
millisecondsSinceEpoch
print(DateTime.now().millisecondsSinceEpoch) 1591167990832
29-3、时间戳转化成日期对象
DateTime.fromMillisecondsSinceEpoch(时间戳)
DateTime date = DateTime.now() 获取当前时间
int timeStamp = date.millisecondsSinceEpoch;
print(DateTime.fromMillisecondsSinceEpoch(timeStamp))
29-4、转化成指定的年月日
使用第三方库
date_format
地址
1、在项目的跟目录pubspec.yaml
中追加包
dependencies:
date_format: ^1.0.8
2、在需要用到插件的文件中添加
import 'package:date_format/date_format.dart';
3、使用
DateTime date = DateTime.now();
print(formatDate(date, [yyyy, '年', mm, '月', dd,'日',' ',HH,':',nn,':',ss]));
2020年06月03日 15:23:43
29-5、flutter自带的日期组件
image.png使用日期组件并获取选中后的值
DateTime date = DateTime.now();//获取当前日期
void _showDatePicker() async{//自定义显示日期方法
var result = await showDatePicker(
context : context,//上下文
initialDate:date,//默认选中的日期
firstDate:DateTime(1980),//日期组件的起始日期我们定在1980年
lastDate:DateTime(2050),//日期组件的结束日期
);
print(result );//这个就是我们选中之后的值注意该方法为异步方法
setState((){
this.date = result;
})
}
//然后我们在点击事件中调用这个自定义方法
InkWell(
onTap:(){
this._showDatePicker()//调用日期控件
},
child:Text("显示日期组件")
)
29-6、flutter自带的时间控件
image.pngDateTime time = DateTime.now();//获取当前日期
void _showTimePicker() async{//自定义显示日期方法
var result = await showTimePicker(
context: context,
initialTime: TimeOfDay(hour:time.hour,minute:time.minute)//重点哦
);
print(result );//这个就是我们选中之后的值注意该方法为异步方法
setState((){
this.time = result;
})
}
//然后我们在点击事件中调用这个自定义方法
InkWell(
onTap:(){
this._showTimePicker()//调用日期控件
},
child:Text("显示时间组件")
)
29-7、怎么把日期、时间控件转为中文
要用到flutter中的国际化,具体可以查看Flutter 中的国际化
29-8、第三方日期、时间组件
DatePicker.showDatePicker(context,
showTitleActions: true,
minTime: DateTime(2018, 3, 5),
maxTime: DateTime(2019, 6, 7), onChanged: (date) {
print('change $date');
}, onConfirm: (date) {
print('confirm $date');
},
currentTime: DateTime.now(),
locale: LocaleType.zh
);
30、InkWell组件
效果:给一些没有点击事件的组件增加事件的,
例如如果我们要给Text
增加点击事件,以往我们只能套子button中,但现在不用
InkWell(
child:Text("监听点击事件"),
onTap:(){
print(DateTime.now())
}
)
31、第三方轮播图插件
flutter_swiper
Swiper需要包裹在Container
中,并且需要设置宽高不然报错
Swiper(
itemBuilder: (BuildContext context,int index){
return new Image.network("http://via.placeholder.com/350x150",fit: BoxFit.fill,);
},
itemCount: 3,
pagination: new SwiperPagination(),
control: new SwiperControl(),
),
);
32、各种DiaLog弹出层
AlertDialog
、SimpleDialog
,showModalBottomSheet
这些DiaLog
需要放在showDialog
方法中
关闭遮罩Navigator.pop()
32-1、AlertDialog
void _showAlertDiaLog() async{
var result = await showDialog(context: context,builder: (context){
return AlertDialog(
title: Text("提示信息"),
content: Text("您确定要删除么?"),
actions: <Widget>[
RaisedButton(onPressed: (){
print("确定");
Navigator.pop(context,'传值给result');传值出去
},child: Text("是"),),
RaisedButton(onPressed: (){
print("否");
Navigator.pop(context);
},child: Text("否"),)
],
);
});
print(result);
}
image.png
32-2、SimpleDialog
void _showSimpleDiaLog()async {
var result = await showDialog(context: context,builder: (context){
return SimpleDialog(
title: Text("提示信息"),
children: [
SimpleDialogOption(
onPressed:(){
Navigator.pop(context,'测试');
},
child: Text("测试"),
),
Divider(),
SimpleDialogOption(
onPressed:(){
Navigator.pop(context,'测试1');
},
child: Text("测试1"),
),
Divider(),
SimpleDialogOption(
onPressed:(){
Navigator.pop(context,'测试2');
},
child: Text("测试2"),
),
Divider(),
//当然也可以是其他的组件
InkWell(
onTap: (){
Navigator.pop(context,'InkWell组件');
},
child: Text("InkWell组件"),
)
],
);
});
print(result);
}
image.png
32-3、showModalBottomSheet类似于购物车/时间选择器底部弹出框
image.png他不用使用
showDialog
来弹出
void _showShowModalBottomSheet(){
showModalBottomSheet(
context: context,
builder: (context){
return Container(
height: 250.0,//控制弹出的高度
child: Column(
children: <Widget>[
ListTile(
title: Text("分享A"),
onTap: (){
Navigator.pop(context);
},
),
Divider(),
ListTile(
title: Text("分享B"),
onTap: (){Navigator.pop(context);},
),
Divider(),
ListTile(
title: Text("分享C"),
onTap: (){Navigator.pop(context);},
),
],
),
);
}
);
}
32-4、使用第三方插件toast
image.pngdependencies:
fluttertoast: ^4.0.1
import 'package:fluttertoast/fluttertoast.dart';
void _showShowToast(){
Fluttertoast.showToast(
msg: "提示信息",
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,//设置停留时间 只在IOS端有效
backgroundColor: Colors.black,
textColor: Colors.white,
fontSize: 16.0
);
}
33、flutter中网络请求
33-1、JSON字符串与flutter中的Map类型相互转换
需要调用
import 'dart:convert';
jsonDecode
字符串json转为Map
jsonEncode
Map转为字符串
String list_json = '{"result":"测试","code":0}';
jsonDecode(list_json ); = >转为Map
Map map = {"result":"测试","code":0};
jsonEncode(map );
技巧
container圆形/阴影
Container(
width:50.0,
height:50.0,
decoration:BoxDecoration(
color:Colors.yellow,//背景图片
shape:BoxShape.circle,//圆形
boxShadow:[BoxShadow(color: Colors.black, offset: Offset(0, 0),blurRadius: 2.0, spreadRadius: 0.0)]//阴影
)
)
width/height相对于父容器100%
double.infinity
container(
height:double.infinity,
height:double.infinity,
)
总结目前学的组件有哪些
名称 | 描述 |
---|---|
MaterialApp |
根组件 |
Scaffold |
根组件中的home |
Text |
文本组件 |
Container |
容器组件 |
ListView |
列表组件 |
ListView.builder |
列表组件中的循环动态生成 |
SizedBox |
这个组件目的撑开组件与组件之间的距离 |
GridView.count() /GridView.builder()
|
栅格化布局 |
Padding |
内边距组件EdgeInsets.only(bottom: 10.0) 仅bottom增加边距 |
Row |
横轴排列组件 |
Cloumn |
纵轴排列组件 |
Expanded |
Flex布局组件 |
Image |
图片组件 |
Icon |
图标组件 |
Stack |
对子元素统一定位 |
Align |
与Stack 一起配合使用,对多个子元素进行定位 |
Position |
与Stack 一起配合使用,对多个子元素进行定位 |
AspectRatio |
定义子组件与父元素的宽高比例 |
Card |
卡片,可以给容器加阴影效果 |
ClipOval |
圆形组件 |
CircleAvatar |
圆形组件含有backgroundImage |
Wrap |
流式布局,当子元素横轴铺满时自动换行至第二行 |
RaisedButton |
凸起按钮 |
BottomNavigationBar |
导航栏属于Scaffold 中的参数/属性 |
floatingActionButton |
浮动底部按钮导航栏属于Scaffold 中的参数/属性,可以实现底部凸起导航按钮
|
SingleChildScrollView |
解决键盘弹起覆盖内容而引起的越界错误 |
IconButton |
图标按钮,注意此图标按钮不带字 |
RaisedButton.icon |
带字的图标按钮 |
FlatButton |
扁平化按钮,不带边框,不带阴影 |
OutlineButton |
只带边框的按钮,没有背景色 |
BottonBar |
按钮组 |
floatingActionButtonLocation |
不是button控制floatingActionButton 的位置 |
TextFaild |
输入框 |
Divider() |
直接使用效果是一根线条/分割线 |
CheckBox |
复选框 |
CheckBoxListTitle() |
带标题的复选框,可以包裹Container控制宽高 |
Radio() |
单选框 |
RadioListTitle() |
带标题的单选框,可以包裹Container控制宽高 |
Switch() |
开关 |
SwitchListTitle() |
带标题的开关,可以包裹Container控制宽高 |
InkWell |
给一些没有点击事件的组件增加监听点击事件 |
showDiaLog() |
用来显示弹出框 |
AlertDiaLog() |
提示框 |
SimpleDialog() |
提示框1 |
showModalBottomSheet() |
底部弹出框/类似日期控件 |
网友评论