/*
List里面常用的属性和方法:
常用属性:
length 长度
reversed 翻转
isEmpty 是否为空
isNotEmpty 是否不为空
常用方法:
add 增加
addAll 拼接数组
indexOf 查找 传入具体值
remove 删除 传入具体值
removeAt 删除 传入索引值
fillRange 修改
insert(index,value); 指定位置插入
insertAll(index,list) 指定位置插入List
toList() 其他类型转换成List
join() List转换成字符串
split() 字符串转化成List
forEach
map
where
any
every
*/
void main(){
// List myList=['香蕉','苹果','西瓜'];
// print(myList[1]);
// var list=new List();
// list.add('111');
// list.add('222');
// print(list);
//List里面的属性:
// List myList=['香蕉','苹果','西瓜'];
// print(myList.length);
// print(myList.isEmpty);
// print(myList.isNotEmpty);
// print(myList.reversed); //对列表倒序排序
// var newMyList=myList.reversed.toList();
// print(newMyList);
//List里面的方法:
// List myList=['香蕉','苹果','西瓜'];
//myList.add('桃子'); //增加数据 增加一个
// myList.addAll(['桃子','葡萄']); //拼接数组
// print(myList);
//print(myList.indexOf('苹x果')); //indexOf查找数据 查找不到返回-1 查找到返回索引值
// myList.remove('西瓜');
// myList.removeAt(1);
// print(myList);
// List myList=['香蕉','苹果','西瓜'];
// myList.fillRange(1, 2,'aaa'); //修改
// myList.fillRange(1, 3,'aaa');
// myList.insert(1,'aaa'); //插入 一个
// myList.insertAll(1, ['aaa','bbb']); //插入 多个
// print(myList);
// List myList=['香蕉','苹果','西瓜'];
// var str=myList.join('-'); //list转换成字符串
// print(str);
// print(str is String); //true
var str='香蕉-苹果-西瓜';
var list=str.split('-');
print(list);
print(list is List);
}
//Set
//用它最主要的功能就是去除数组重复内容
//Set是没有顺序且不能重复的集合,所以不能通过索引去获取值
void main(){
// var s=new Set();
// s.add('香蕉');
// s.add('苹果');
// s.add('苹果');
// print(s); //{香蕉, 苹果}
// print(s.toList());
List myList=['香蕉','苹果','西瓜','香蕉','苹果','香蕉','苹果'];
var s=new Set();
s.addAll(myList);
print(s);
print(s.toList());
}
/*
映射(Maps)是无序的键值对:
常用属性:
keys 获取所有的key值
values 获取所有的value值
isEmpty 是否为空
isNotEmpty 是否不为空
常用方法:
remove(key) 删除指定key的数据
addAll({...}) 合并映射 给映射内增加属性
containsValue 查看映射内的值 返回true/false
forEach
map
where
any
every
*/
void main(){
// Map person={
// "name":"张三",
// "age":20
// };
// var m=new Map();
// m["name"]="李四";
// print(person);
// print(m);
//常用属性:
// Map person={
// "name":"张三",
// "age":20,
// "sex":"男"
// };
// print(person.keys.toList());
// print(person.values.toList());
// print(person.isEmpty);
// print(person.isNotEmpty);
//常用方法:
Map person={
"name":"张三",
"age":20,
"sex":"男"
};
// person.addAll({
// "work":['敲代码','送外卖'],
// "height":160
// });
// print(person);
// person.remove("sex");
// print(person);
print(person.containsValue('张三'));
}
/*
forEach
map
where
any
every
*/
void main(){
// List myList=['香蕉','苹果','西瓜'];
// for(var i=0;i5;
// });
// print(newList.toList());
// List myList=[1,3,4,5,7,8,9];
// var f=myList.any((value){ //只要集合里面有满足条件的就返回true
// return value>5;
// });
// print(f);
// List myList=[1,3,4,5,7,8,9];
// var f=myList.every((value){ //每一个都满足条件返回true 否则返回false
// return value>5;
// });
// print(f);
// set
// var s=new Set();
// s.addAll([1,222,333]);
// s.forEach((value)=>print(value));
//map
Map person={
"name":"张三",
"age":20
};
person.forEach((key,value){
print("$key---$value");
});
}
/*
Dart中抽象类: Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。
1、抽象类通过abstract 关键字来定义
2、Dart中的抽象方法不能用abstract声明,Dart中没有方法体的方法我们称为抽象方法。
3、如果子类继承抽象类必须得实现里面的抽象方法
4、如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法。
5、抽象类不能被实例化,只有继承它的子类可以
extends抽象类 和 implements的区别:
1、如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用extends继承抽象类
2、如果只是把抽象类当做标准的话我们就用implements实现抽象类
*/
安装最新的 Xcode
下载androidstudio
https://developer.android.google.cn/studio
下载 Flutter SDK
https://flutter.dev/docs/development/tools/sdk/releases?tab=macos
把下载好的 Flutter SDK 随便减压到你想安装 Sdk 的目录如
/Users/cc/flutter
把 Flutter 安装目录的 bin 目录配置到环境变量,然后把 Flutter 国内镜像也配置到环境 变量里面
vim ~/.zshrc
export PATH=/Users/cc/flutter/bin:$PATH
export ANDROID_HOME="/Users/cc/Library/Android/sdk"
export PATH=${
PATH}:${ANDROID_HOME}/tools
export PATH=${
PATH}:${ANDROID_HOME}/platform-tools
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
source ~/.zshrc
flutter -h 如果能出来一些命令说明 flutter sdk 配置成功。
注意如果配置完成后输入 flutter -h 告诉你 flutter 不是内置命令之类的错误的话,可能 sdk 没有配置成功,也可能 sdk 下载的时候没有下载全
运行 flutter doctor 命令检测环境
每一个 flutter 项目的 lib 目录里面都有一个 main.dart 这个文件就是 flutter 的入口文件
main.dart 里面的
void main() {
runApp(MyApp());
}
//也可以简写
void main() => runApp(MyApp());
其中的 main 方法是 dart 的入口方法。runApp 方法是 flutter 的入口方法。 MyApp 是自定义的一个组件
import 'package:flutter/material.dart';
void main() {
runApp(Center(
child: Text(
"我是一个文本内容",
textDirection: TextDirection.ltr,
),
));
}
在 Flutter 中自定义组件其实就是一个类,这个类需要继承 StatelessWidget/StatefulWidget
前期我们都继承 StatelessWidget。后期给大家讲 StatefulWidget 的使用。
StatelessWidget 是无状态组件,状态不可变的 widget
StatefulWidget 是有状态组件,持有的状态可能在 widget 生命周期改变
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text(
"我是一个文本内容",
textDirection: TextDirection.ltr,
),
);
}
}
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text(
"我是一个文本内容",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 40.0,
fontWeight: FontWeight.bold,
// color: Colors.yellow
color: Color.fromRGBO(255, 222, 222, 0.5)
),
),
);
}
}
1、MaterialApp
MaterialApp 是一个方便的 Widget,它封装了应用程序实现 Material Design 所需要的 一些 Widget。一般作为顶层 widget 使用。
常用的属性:
home(主页)
title(标题)
color(颜色)
theme(主题)
routes(路由)
…
2、Scaffold
Scaffold 是 Material Design 布局结构的基本实现。此类提供了用于显示 drawer、snackbar 和底部 sheet 的 API。
Scaffold 有下面几个主要属性:
appBar - 显示在界面顶部的一个 AppBar。
body - 当前界面所显示的主要内容 Widget。
drawer - 抽屉菜单控件。
…
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "我是一个标题",
home: Scaffold(
appBar: AppBar(
title: Text("Hello Flutter"),
elevation: 30.0, 设置标题阴影 不需要的话值设置成 0.0
),
body: HomeContent(),
),
theme: ThemeData(
//设置主题颜色
primarySwatch: Colors.yellow),
);
}
}
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text(
"我是一个文本内容",
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 40.0,
fontWeight: FontWeight.bold,
// color: Colors.yellow
color: Color.fromRGBO(255, 222, 222, 0.5)
),
),
);
}
}
名称 | 功能 |
---|---|
textAlign | 文本对齐方式(center 居中,left 左 对齐,right 右对齐,justfy 两端对齐) |
textDirection | 文本方向(ltr 从左至右,rtl 从右至 左) |
overflow | 文字超出屏幕之后的处理方式(clip 裁剪,fade 渐隐,ellipsis 省略号) |
textScaleFactor | 字体显示倍率 |
maxLines | 文字显示最大行数 |
style | 字体的样式设置 |
下面是 TextStyle 的参数 :
名称 | 功能 |
---|---|
decoration | 文字装饰线(none 没有线,lineThrough 删 除线,overline 上划线,underline 下划线) |
decorationColor | 文字装饰线颜色 |
decorationStyle | 文字装饰线风格([dashed,dotted]虚线, double 两根线,solid 一根实线,wavy 波浪 线) |
wordSpacing | 单词间隙(如果是负值,会让单词变得更紧 凑 |
letterSpacing | 字母间隙(如果是负值,会让字母变得更紧 凑) |
fontStyle | 文字样式(italic 斜体,normal 正常体) |
fontSize | 文字大小 |
color | 文字颜色 |
fontWeight | 字体粗细(bold 粗体,normal 正常体) |
更多参数:https://docs.flutter.io/flutter/painting/TextStyle-class.html
名称 | 功能 |
---|---|
alignment | topCenter:顶部居中对齐 topLeft:顶部左对齐 topRight:顶部右对齐 center:水平垂直居中对齐 centerLeft:垂直居中水平居左对齐 centerRight:垂直居中水平居右对齐 bottomCenter 底部居中对齐 bottomLeft:底部居左对齐 bottomRight:底部居右对齐 |
decoration | decoration: BoxDecoration( color: Colors.blue, border: Border.all( color: Colors.red, width: 2.0, ), borderRadius: BorderRadius.all( Radius.circular(8.0) ) ) |
margin | margin 属性是表示 Container 与外部其他 组件的距离。 EdgeInsets.all(20.0), |
padding | padding 就是 Container 的内边距,指 Container 边缘与 Child 之间的距离 padding: EdgeInsets.all(10.0) |
transform | 让 Container 容易进行一些旋转之类的 transform: Matrix4.rotationZ(0.2) |
height | 容器高度 |
width | 容器宽度 |
child | 容器子元素 |
更多参数:https://api.flutter.dev/flutter/widgets/Container-class.html
图片组件是显示图像的组件,Image 组件有很多构造函数,这里我们只给大家讲两个
Image.asset 本地图片
Image.network 远程图片
Image 组件的常用属性:
名称 | 类型 | 说明 |
---|---|---|
alignment | alignment | 图片的对齐方式 |
color 和 colorBlendMode | 设置图片的背景颜色,通常和 colorBlendMode 配合一起使用,这样可以是图片颜色和背景色混合。上面的图片就是进行了颜色的混合,绿色背景和图片红色的混合 | |
fit | BoxFit | fit 属性用来控制图片的拉伸和挤压,这都是根据父容器来的。 BoxFit.fill:全图显示,图片会被拉伸,并充满父容器。 BoxFit.contain:全图显示,显示原比例,可能会有空隙。 BoxFit.cover:显示可能拉伸,可能裁切,充满(图片要充满整个容器,还不变形)。 BoxFit.fitWidth:宽度充满(横向充满),显示可能拉伸,可能裁切。 BoxFit.fitHeight :高度充满(竖向充满),显示可能拉伸,可能裁切。 BoxFit.scaleDown:效果和 contain 差不多,但是此属性不允许显示超过源图片大小,可小不可大。 |
repeat | 平铺 | ImageRepeat.repeat : 横向和纵向都进行重复,直到铺满整个画布。 ImageRepeat.repeatX: 横向重复,纵向不重复。 ImageRepeat.repeatY:纵向重复,横向不重复。 |
width | 宽度 一般结合 ClipOval 才能看到效果 | |
height | 高度 一般结合 ClipOval 才能看到效果 |
更多属性参考:https://api.flutter.dev/flutter/widgets/Image-class.html
return Center(
child: Container(
child: Image.network(
"http://pic.baike.soso.com/p/20130828/20130828161137-1346445960.jpg",
alignment: Alignment.topLeft,
color: Colors.red,
colorBlendMode: BlendMode.colorDodge,
// repeat: ImageRepeat.repeatX,
fit: BoxFit.cover,
),
width: 300.0,
height: 400.0,
decoration: BoxDecoration(
color: Colors.yellow
),
),
);
emmm…不记了
widget 作用
ClipRect 将 child 剪裁为给定的矩形大小
ClipRRect 将 child 剪裁为圆角矩形
ClipOval 如果 child 为正方形时剪裁之后是圆形,如果 child 为矩形时,剪裁之后为椭圆形
ClipPath 将 child 按照给定的路径进行裁剪
CustomClipper 并不是一个widget,但是使用CustomClipper可以绘制出任何我们想要的形状
实现圆角图片
return Center(
child: Container(
width: 300.0,
height: 300.0,
decoration: BoxDecoration(
color: Colors.yellow,
borderRadius: BorderRadius.circular(150),
image: DecorationImage(
image: NetworkImage(
"http://pic.baike.soso.com/p/20130828/20130828161137-1346445960.jpg",
),
fit: BoxFit.cover
)
),
),
);
实现圆形图片
return Center(
child: Container(
child: ClipOval(
child: Image.network(
"https://www.itying.com/images/201905/thumb_img/1101_thumb_G_1557845381862.jpg",
width: 150.0,
height: 150.0,
),
),
),
);
new ClipOval(
child: new Image.asset(Utils.getImgPath('ali_connors')),
)
new CircleAvatar(
radius: 36.0,
backgroundImage: AssetImage(
Utils.getImgPath('ali_connors'),
),
)
new Container(
width: 72.0,
height: 72.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: AssetImage(
Utils.getImgPath('ali_connors'),
),
),
),
)
new ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: new Image.asset(Utils.getImgPath('ali_connors')),
)
new Container(
width: 88.0,
height: 88.0,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6.0),
image: DecorationImage(
image: AssetImage(
Utils.getImgPath('ali_connors'),
),
),
),
列表布局是我们项目开发中最常用的一种布局方式。Flutter 中我们可以通过 ListView 来定义 列表项,支持垂直和水平方向展示。通过一个属性就可以控制列表的显示方向。列表有一下 分类:
**1、垂直列表(宽度自动扩展,设置宽度无效)**可以在外层包Container控制
2、垂直图文列表
**3、水平列表(高度自动扩展,设置高度无效)**可以在外层包Container控制
4、动态列表
5、矩阵式列表(网格布局)
名称 | 类型 | 说明 |
---|---|---|
scrollDirection | Axis | Axis.horizontal 水平列表 Axis.vertical 垂直列表 |
padding | EdgeInsetsGeometry | 内边距 |
resolve | bool | 组件反向排序 |
children | List | 列表元素 |
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: ListView(
children: [
ListTile(
leading: Icon(Icons.phone),
title: Text("this is list", style: TextStyle(fontSize: 28.0)),
subtitle: Text('this is list this is list'),
),
ListTile(
title: Text("this is list"),
subtitle: Text('this is list this is list'),
trailing: Icon(Icons.phone),
),
ListTile(
title: Text("this is list"),
subtitle: Text('this is list this is list'),
),
ListTile(
title: Text("this is list"),
subtitle: Text('this is list this is list'),
),
ListTile(
title: Text("this is list"),
subtitle: Text('this is list this is list'),
)
],
),
);
}
}
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 200.0,
margin: EdgeInsets.all(5),
child: ListView(
scrollDirection: Axis.horizontal,
children: [
Container(
width: 180.0,
color: Colors.lightBlue,
),
Container(
width: 180,
color: Colors.amber,
child: ListView(
children: [
Image.network(
"https://resources.ninghao.org/images/childhood-in-a-picture.jpg"),
SizedBox(height: 16.0),
Text("这是一个文本信息",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16.0)),
],
),
)
],
),
);
}
}
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Hello Flutter")),
body: HomeContent(),
),
);
}
}
class HomeContent extends StatelessWidget {
List list = List();
HomeContent() {
for (int i = 0; i < 20; i++) {
list.add("这是第$i条数据");
}
print(list);
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: this.list.length,
itemBuilder: (context, index) {
// print(context);
return ListTile(
leading: Icon(Icons.phone),
title: Text("${list[index]}"),
);
});
}
}
当数据量很大的时候用矩阵方式排列比较清晰。此时我们可以用网格列表组件 GridView 实现布局。
GridView 创建网格列表有多种方式,下面我们主要介绍两种。
1、可以通过 GridView.count 实现网格布局
2、通过 GridView.builder 实现网格布局
常用属性:
名称 | 类型 | 说明 |
---|---|---|
scrollDirection | Axis | 滚动方法 |
padding | EdgeInsetsGeometry | 内边距 |
resolve | bool | 组件反向排序 |
crossAxisSpacing | double | 水平子 Widget 之间间距 |
mainAxisSpacing | double | 垂直子 Widget 之间间距 |
crossAxisCount | int | 一行的 Widget 数量 |
childAspectRatio | double | 子 Widget 宽高比例 |
children | [ ] | |
gridDelegate | SliverGridDelegateWithFix edCrossAxisCount(常用) SliverGridDelegateWithMax CrossAxisExtent |
控制布局主要用在GridView.builder 里面 |
import 'package:cc/res/listData.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Hello Flutter")),
body: LayoutContent(),
),
);
}
}
class LayoutContent extends StatelessWidget {
List _getListData() {
var tempList = listData.map((value) {
return Container(
child: Column(
children: [
Image.network(value["imageUrl"]),
SizedBox(height: 12),
Text(value["title"],
textAlign: TextAlign.center, style: TextStyle(fontSize: 20))
],
),
decoration: BoxDecoration(
border: Border.all(
color: Color.fromRGBO(230, 230, 230, 0.9), width: 1.0)),
);
});
// ('124124','124214')
return tempList.toList();
}
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
// childAspectRatio:0.7,
children: this._getListData(),
);
}
}
import 'package:cc/res/listData.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Hello Flutter")),
body: LayoutContent(),
),
);
}
}
class LayoutContent extends StatelessWidget {
Widget _getListData(context, index) {
return Container(
child: Column(
children: [
Image.network(listData[index]["imageUrl"]),
SizedBox(height: 12),
Text(listData[index]["title"],
textAlign: TextAlign.center, style: TextStyle(fontSize: 20))
],
),
decoration: BoxDecoration(
border: Border.all(
color: Color.fromRGBO(230, 230, 230, 0.9), width: 1.0)),
);
}
@override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: listData.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//横轴元素个数
crossAxisCount: 2,
//纵轴间距
mainAxisSpacing: 20.0,
//横轴间距
crossAxisSpacing: 10.0,
//子组件宽高长度比例
childAspectRatio: 1.0),
itemBuilder: this._getListData,
);
}
}
在 html 中常见的布局标签都有 padding 属性,但是 Flutter 中很多 Widget 是没有 padding 属性。这个时候我们可以用 Padding 组件处理容器与子元素直接的间距。
属性 | 说明 |
---|---|
padding | padding 值, EdgeInsetss 设置填充的值 |
child | 子组件 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.fromLTRB(0, 0, 10, 0),
child: GridView.count(
crossAxisCount: 2,
childAspectRatio: 1.5,
children: [
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network("https://www.itying.com/images/flutter/1.png",
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network("https://www.itying.com/images/flutter/2.png",
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network("https://www.itying.com/images/flutter/3.png",
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network("https://www.itying.com/images/flutter/4.png",
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network("https://www.itying.com/images/flutter/5.png",
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network("https://www.itying.com/images/flutter/6.png",
fit: BoxFit.cover),
),
],
),
);
}
}
属性 | 说明 |
---|---|
mainAxisAlignment | 主轴的排序方式 |
crossAxisAlignment | 次轴的排序方式 |
children | 组件子元素 |
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Hello Flutter")),
body: LayoutDemo(),
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 700,
width: 500,
color: Colors.black26,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconContainer(Icons.home, color: Colors.red),
IconContainer(Icons.home, color: Colors.blue),
IconContainer(Icons.home, color: Colors.orange),
],
),
);
}
}
class IconContainer extends StatelessWidget {
double size;
IconData icon;
Color color;
IconContainer(this.icon, {this.size, this.color = Colors.blue}) {
this.size = 32.0;
}
@override
Widget build(BuildContext context) {
return Container(
width: this.size + 60,
height: this.size + 60,
color: this.color,
child: Center(
child: Icon(
this.icon,
color: Colors.white,
size: this.size,
)),
);
}
}
属性 | 说明 |
---|---|
mainAxisAlignment | 主轴的排序方式 |
crossAxisAlignment | 次轴的排序方式 |
children | 组件子元素 |
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Hello Flutter")),
body: LayoutDemo(),
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 700,
width: 500,
color: Colors.black26,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconContainer(Icons.home, color: Colors.red),
IconContainer(Icons.home, color: Colors.blue),
IconContainer(Icons.home, color: Colors.orange),
],
),
);
}
}
class IconContainer extends StatelessWidget {
double size;
IconData icon;
Color color;
IconContainer(this.icon, {this.size, this.color = Colors.blue}) {
this.size = 32.0;
}
@override
Widget build(BuildContext context) {
return Container(
width: this.size + 60,
height: this.size + 60,
color: this.color,
child: Center(
child: Icon(
this.icon,
color: Colors.white,
size: this.size,
)),
);
}
}
Expanded 可以用在 Row 和 Column 布局中
属性 | 说明 |
---|---|
flex | 元素站整个父 Row /Column 的比例 |
child | 子元素 |
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Hello Flutter")),
body: LayoutDemo(),
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10),
child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(flex: 2, child: IconContainer(Icons.home)),
SizedBox(width: 10),
Expanded(flex: 3, child: IconContainer(Icons.search)),
// SizedBox(width: 10),
// Expanded(child: IconContainer(Icons.send))
],
),
);
}
}
class IconContainer extends StatelessWidget {
double size;
IconData icon;
IconContainer(this.icon, {this.size}) {
this.size = 32.0;
}
@override
Widget build(BuildContext context) {
return Container(
width: 100.0,
height: 100.0,
color: Colors.blue,
child:
Center(child: Icon(this.icon, color: Colors.white, size: this.size)),
);
}
}
Stack 表示堆的意思,我们可以用 Stack 或者 Stack 结合 Align 或者 Stack 结合 Positiond 来实现页面的定位布局
属性 | 说明 |
---|---|
alignment | 配置所有子元素的显示位置 |
children | 子组件 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Stack(
alignment: Alignment.topLeft,
children: [
Container(
height: 400,
width: 300,
color: Colors.red,
),
Text('我是一个文本',style: TextStyle(
fontSize: 40,
color: Colors.white
))
],
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Stack(
alignment: Alignment(1,0.3),
children: [
Container(
height: 400,
width: 300,
color: Colors.red,
),
Text('我是一个文本',style: TextStyle(
fontSize: 20,
color: Colors.white
))
],
),
);
}
}
Stack 组件中结合 Align 组件可以控制每个子元素的显示位置
属性 | 说明 |
---|---|
alignment | 配置所有子元素的显示位置 |
child | 子组件 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Container(
height: 400,
width: 300,
color: Colors.red,
child: Stack(
// alignment: Alignment.center,
children: [
Align(
alignment: Alignment(1,-0.2),
child: Icon(Icons.home,size: 40,color: Colors.white),
),
Align(
alignment: Alignment.center,
child: Icon(Icons.search,size: 30,color: Colors.white),
),
Align(
alignment: Alignment.bottomRight,
child: Icon(Icons.settings_applications,size: 30,color: Colors.white),
)
],
),
),
);
}
}
Stack 组件中结合 Positioned 组件也可以控制每个子元素的显示位置
属性 | 说明 |
---|---|
top | 子元素距离顶部的距离 |
bottom | 子元素距离底部的距离 |
left | 子元素距离左侧距离 |
right | 子元素距离右侧距离 |
child | 子组件 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Container(
height: 400,
width: 300,
color: Colors.red,
child: Stack(
// alignment: Alignment.center,
children: [
Positioned(
// left: 10,
child: Icon(Icons.home,size: 40,color: Colors.white),
),
Positioned(
bottom: 0,
left: 100,
child: Icon(Icons.search,size: 30,color: Colors.white),
),
Positioned(
right: 0,
child: Icon(Icons.settings_applications,size: 30,color: Colors.white),
)
],
),
),
);
}
}
AspectRatio 的作用是根据设置调整子元素 child 的宽高比。
AspectRatio 首先会在布局限制条件允许的范围内尽可能的扩展,widget 的高度是由宽度和比率决定的,类似于 BoxFit 中的 contain,按照固定比率去尽量占满区域。
如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio 最终将会去优先适应布局限制条件,而忽略所设置的比率。
属性 | 说明 |
---|---|
aspectRatio | 宽高比,最终可能不会根据这个值去布局,具体则要看综合因素,外层是否允许按照这种比率进行布局,这只是一个参考值 |
child | 子组件 |
return Center(
child: Container(
width: 200,
child: AspectRatio(
aspectRatio: 2.0 / 1.0,
child: Container(
color: Colors.red,
),
),
),
);
Card 是卡片组件块,内容可以由大多数类型的 Widget 构成,Card 具有圆角和阴影,这让它看起来有立体感。
属性 | 说明 |
---|---|
margin | 外边距 |
child | 子组件 |
Shape | Card 的阴影效果,默认的阴影效果为圆角的长方形边。 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: [
Card(
margin: EdgeInsets.all(10),
child: Column(
children: [
ListTile(
title: Text(
"张三",
style: TextStyle(fontSize: 28),
),
subtitle: Text("高级软件工程师"),
),
Divider(),
ListTile(
title: Text("电话:123123123"),
),
ListTile(
title: Text("地址:北京市海淀区"),
)
],
),
),
Card(
margin: EdgeInsets.all(10),
child: Column(
children: [
ListTile(
title: Text(
"李四",
style: TextStyle(fontSize: 28),
),
subtitle: Text("高级软件工程师"),
),
Divider(),
ListTile(
title: Text("电话:123123123"),
),
ListTile(
title: Text("地址:北京市海淀区"),
)
],
),
),
],
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: listData.map((value) {
return Card(
margin: EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(value["imageUrl"], fit: BoxFit.cover),
),
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(value["imageUrl"]),
),
title: Text(value["description"]),
subtitle: Text(
value["description"],
overflow: TextOverflow.ellipsis,
),
)
],
),
);
}).toList());
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: listData.length,
itemBuilder: (context,index){
return Card(
margin: EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(listData[index]["imageUrl"], fit: BoxFit.cover),
),
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(listData[index]["imageUrl"]),
),
title: Text(listData[index]["description"]),
subtitle: Text(
listData[index]["description"],
overflow: TextOverflow.ellipsis,
),
)
],
),
);
},
);
}
}
Flutter 中通过 RaisedButton 定义一个按钮。RaisedButton 里面有很多的参数,这一讲我们只是简单的进行使用。
return RaisedButton(
child: Text("Flutter"),
textColor: Theme.of(context).accentColor,
onPressed: (){
});
Wrap 可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Row 表 现几乎一致。但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,mainAxis 上空 间不足时,则向 crossAxis 上去扩展显示。
属性 | 说明 |
---|---|
direction | 主轴的方向,默认水平 |
alignment | 主轴的对其方式 |
spacing | 主轴方向上的间距 |
textDirection | 文本方向 |
verticalDirection | 定义了 children 摆放顺序,默认是 down,见Flex 相关属性介绍。 |
runAlignment | run 的对齐方式。run 可以理解为新的行或者列,如果是水平方向布局的话,run 可以理解为新的一行 |
runSpacing | run 的间距 |
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Hello Flutter")),
body: LayoutDemo(),
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 10,
runSpacing: 10,
alignment: WrapAlignment.spaceEvenly,
children: [
MyButton("第 1 集"),
MyButton("第 2 集"),
MyButton("第 3 集"),
MyButton("第 4 集"),
MyButton("第 5 集"),
MyButton("第 6 集第 6 集"),
MyButton("第 7 集"),
MyButton("第 8 集第 6 集"),
MyButton("第 9 集"),
MyButton("第 10 集"),
MyButton("第 11 集"),
],
);
}
}
class MyButton extends StatelessWidget {
final String text;
const MyButton(this.text, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(this.text),
textColor: Theme.of(context).accentColor,
onPressed: () {});
}
}
在 Flutter 中自定义组件其实就是一个类,这个类需要继承 StatelessWidget/StatefulWidget。
StatelessWidget 是无状态组件,状态不可变的 widget
StatefulWidget 是有状态组件,持有的状态可能在 widget 生命周期改变。通俗的讲:如果我们想改变页面中的数据的话这个时候就需要用到 StatefulWidget
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Hello Flutter")),
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
int count = 0;
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Chip(label: Text("${this.count}")),
RaisedButton(
child: Text("增加"),
onPressed: () {
// print(this.count);
setState(() {
this.count++;
});
})
],
),
);
}
}
BottomNavigationBar 是底部导航条,可以让我们定义底部 Tab 切换,bottomNavigationBar是 Scaffold 组件的参数。
BottomNavigationBar 常见的属性
属性名 | 说明 |
---|---|
items | List 底 部 导 航条按钮集合 |
iconSize | icon |
currentIndex | 默认选中第几个 |
fixedColor | 选中的颜色 |
type | BottomNavigationBarType.fixed BottomNavigationBarType.shifting |
(上面解决4个底部导航显示出错) |
class Tabs extends StatefulWidget {
Tabs({Key key}) : super(key: key);
_TabsState createState() => _TabsState();
}
class _TabsState extends State {
int _currentIndex=0;
List _pageList=[
HomePage(),
CategoryPage(),
SettingPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Demo"),
),
body: this._pageList[this._currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: this._currentIndex, //配置对应的索引值选中
onTap: (int index){
setState(() { //改变状态
this._currentIndex=index;
});
},
iconSize:36.0, //icon的大小
fixedColor:Colors.red, //选中的颜色
type:BottomNavigationBarType.fixed, //配置底部tabs可以有多个按钮
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("首页")
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
title: Text("分类")
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text("设置")
)
],
),
);
}
}
Flutter 中的路由通俗的讲就是页面跳转。在 Flutter 中通过 Navigator 组件管理路由导航。
并提供了管理堆栈的方法。如:Navigator.push 和 Navigator.pop
Flutter 中给我们提供了两种配置路由跳转的方式:1、基本路由 2、命名路由
比如我们现在想从 HomePage 组件跳转到 SearchPage 组件。
1、需要在 HomPage 中引入 SearchPage.dart
import '../SearchPage.dart';
2、在 HomePage 中通过下面方法跳转
RaisedButton(
child: Text("跳转到搜索页面"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SerachPage()
)
);
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary,
)
比如我们现在想从 HomePage 组件跳转到 SearchPage 组件传值。
1、需要在 HomPage 中引入 SearchPage.dart
import '../SearchPage.dart';
2、在 HomePage 中通过下面方法跳转
RaisedButton(
child: Text("跳转到搜索页面"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SerachPage(title:"表单") //传值 SerachPage加构造函数并传参数
)
);
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary,
)
1、配置路由
return MaterialApp(
// home:Tabs(),
initialRoute: '/', //初始化的时候加载的路由
routes: {
'/':(contxt)=>Tabs(),
'/search':(contxt) =>SearchPage(),
'/form': (context) => FormPage(),
},
);
2、路由跳转
RaisedButton(
child: Text("跳转到搜索页面"),
onPressed: () {
Navigator.pushNamed(context, '/search');
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary
)d
花里胡哨
抽个鸡儿
Navigator.of(context).pop();
比如我们从用户中心页面跳转到了 registerFirst 页面,然后从 registerFirst 页面通过pushReplacementNamed 跳转到了 registerSecond 页面。这个时候当我们点击 registerSecond的返回按钮的时候它会直接返回到用户中心。
Navigator.of(context).pushReplacementNamed('/registerSecond'); //命名路由替换
// 普通路由替换
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context)=>Second())
);
比如我们从用户中心跳转到 registerFirst 页面,然后从 registerFirst 页面跳转到 registerSecond页面,然后从 registerSecond 跳转到了 registerThird 页面。这个时候我们想的是 registerThird注册成功后返回到用户中心。 这个时候就用到了返回到根路由的方法。
Navigator.of(context).pushAndRemoveUntil(
new MaterialPageRoute(builder: (context) => new Tabs(index:1)),
(route) => route == null
);
属性 | 描述 |
---|---|
leading | 在标题前面显示的一个控件,在首页通常显示应用的 logo;在其他界面通常显示为返回按钮 |
title | 标题,通常显示为当前界面的标题文字,可以放组件 |
actions | 在标题后面显示的一个控件,通常使用 IconButton 来表示,可以放按钮组 |
bottom | 通常放 tabBar,标题下面显示一个 Tab 导航栏 |
backgroundColor | 导航背景颜色 |
iconTheme | 图标样式 |
textTheme | 文字样式 |
centerTitle | 标题是否居中显示 |
class AppBardemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
leading: IconButton(
icon: Icon(Icons.menu),
tooltip: "Search",
onPressed: () {
print('Menu Pressed');
}),
title: Text('FlutterDemo'),
actions: [
IconButton(
icon: Icon(Icons.search),
tooltip: "Search",
onPressed: () {
print('Search Pressed');
}),
IconButton(
icon: Icon(Icons.more_horiz),
tooltip: "more_horiz",
onPressed: () {
print('more_horiz Pressed');
})
],
),
body: Text('这是Appbar'),
);
}
}
TabBar 常见属性:
属性 | 描述 |
---|---|
tabs | 显示的标签内容,一般使用 Tab 对象,也可以是其他的 Widget |
controller | TabController 对象 |
isScrollable | 是否可滚动(是指有很多个appbar时滚动appbar,左右滚动appbar。不是滚动内容) |
indicatorColor | 指示器颜色 |
indicatorWeight | 指示器高度 |
indicatorPadding | 底部指示器的 Padding |
indicator | 指示器 decoration,例如边框等 |
indicatorSize | 指示器大小计算方式,TabBarIndicatorSize.label 跟文字等宽,TabBarIndicatorSize.tab 跟每个 tab 等宽 |
labelColor | 选中 label 颜色 |
labelStyle | 选中 label 的 Style |
labelPadding | 每个 label 的 padding 值 |
unselectedLabelColor | 未选中 label 颜色 |
unselectedLabelStyle | 未选中 label 的 Style |
import 'package:flutter/material.dart';
class AppBardemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController( //DefaultTabController在MaterialApp之后,Scaffold之前
length: 2, //如果页面通过路由挂载,直接return DefaultTabController
child: Scaffold(
appBar: AppBar(
title: TabBar(
tabs: [
Tab(text: '热门'),
Tab(text: "123"),
],
),
),
body: TabBarView(
children: [
ListView(
children: [
ListTile(title: Text("这是第一个 tab")),
ListTile(title: Text("这是第一个 tab")),
ListTile(title: Text("这是第一个 tab"))
],
),
ListView(
children: [
ListTile(title: Text("这是第一个 tab")),
ListTile(title: Text("这是第一个 tab")),
ListTile(title: Text("这是第一个 tab"))
],
),
],
),
),
),
);
}
}
把TabBar放在titile里面
import 'package:flutter/material.dart';
class AppBardemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
isScrollable: true, //如果多个按钮的话可以滑动
// backgroundColor: Colors.red,
leading: IconButton(
icon: Icon(Icons.arrow_back),
tooltip: "Search",
onPressed: () {
Navigator.of(context).pop();
}),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
flex: 1,
child: TabBar(
tabs: [Tab(text: "热门"), Tab(text: "推荐")],
))
],
),
),
body: TabBarView(
children: [
ListView(
children: [
ListTile(title: Text("这是第一个 tab")),
ListTile(title: Text("这是第一个 tab")),
ListTile(title: Text("这是第一个 tab"))
],
),
ListView(
children: [
ListTile(title: Text("这是第一个 tab")),
ListTile(title: Text("这是第一个 tab")),
ListTile(title: Text("这是第一个 tab"))
],
),
],
),
),
),
);
}
}
TabController需要继承有状态组件
import 'package:flutter/material.dart';
class AppBardemoPage extends StatefulWidget {
@override
_AppBardemoPageState createState() => _AppBardemoPageState();
}
class _AppBardemoPageState extends State
with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
// TODO: implement initState
super.initState();
_tabController = new TabController(
vsync: this,
length: 2
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AppBardemoPage"),
bottom: TabBar(
controller: this._tabController, //注意
tabs: [
Tab(text: "热销"), Tab(text: "推荐"),
],
),
),
body: TabBarView(
controller: this._tabController, //注意
children: [
Center(child: Text("热销")),
Center(child: Text("推荐"))
],
),
);
}
}
在 Scaffold 组件里面传入 drawer 参数可以定义左侧边栏,传入 endDrawer 可以定义右侧边栏。侧边栏默认是隐藏的,我们可以通过手指滑动显示侧边栏,也可以通过点击按钮显示侧边栏。
return Scaffold(
appBar: AppBar(
title: Text("Hello Flutter"),
),
drawer: Drawer(
child: Text("左侧边栏"),
),
endDrawer: Drawer(
child: Text("右侧边栏"),
),
);
常见属性:
属性 | 描述 |
---|---|
decoration | 设置顶部背景颜色 |
child | 配置子元素 |
padding | 内边距 |
margin | 外边距 |
drawer: Drawer(
child: Column(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Colors.yellow,
image: DecorationImage(
image: NetworkImage(
"https://www.itying.com/images/flutter/2.png"),
fit: BoxFit.cover)),
child: ListView(
children: [Text("我是一个头部")],
),
),
ListTile(
title: Text("个人中心"),
leading: CircleAvatar(
child: Icon(Icons.people),
),
),
Divider(),
ListTile(
title: Text("系统设置"),
leading: CircleAvatar(
child: Icon(Icons.settings),
),
),
Divider(),
],
),
),
属性 | 描述 |
---|---|
decoration | 设置顶部背景颜色 |
accountName | 账户名称 |
accountEmail | 账户邮箱 |
currentAccountPicture | 用户头像 |
otherAccountsPictures | 用来设置当前账户其他账户头像 |
margin |
drawer: Drawer(
child: Column(
children: [
UserAccountsDrawerHeader(
accountName: Text("喵喵喵?"),
accountEmail: Text("[email protected]"),
currentAccountPicture: CircleAvatar(// 自动处理成圆形,不需要再设置图片fit
backgroundImage:
NetworkImage("https://www.itying.com/images/flutter/3.png"),
),
decoration: BoxDecoration(
color: Colors.yellow,
image: DecorationImage(
image: NetworkImage(
"https://www.itying.com/images/flutter/2.png"),
fit: BoxFit.cover)),
otherAccountsPictures: [
Image.network("https://www.itying.com/images/flutter/4.png"),
Image.network("https://www.itying.com/images/flutter/5.png"),
Image.network("https://www.itying.com/images/flutter/6.png"),
],
),
ListTile(
title: Text("个人中心"),
leading: CircleAvatar(
child: Icon(Icons.people),
),
),
Divider(),
ListTile(
title: Text("系统设置"),
leading: CircleAvatar(
child: Icon(Icons.settings),
),
),
Divider(),
],
),
),
onTap: () {
Navigator.of(context).pop();
Navigator.pushNamed(context, "/tabBarController");
},
Flutter 里有很多的 Button 组件很多,常见的按钮组件有:RaisedButton、FlatButton、IconButton、OutlineButton、ButtonBar、FloatingActionButton 等。
RaisedButton :凸起的按钮,其实就是 Material Design 风格的 Button
FlatButton :扁平化的按钮
OutlineButton:线框按钮
IconButton :图标按钮
ButtonBar:按钮组
FloatingActionButton:浮动按钮
属性名称 | 值类型 | 属性值 |
---|---|---|
onPressed | VoidCallback,一般接收一个方法 | 必填参数,按下按钮时触发的回调,接收一个方法,传 null 表示按钮禁用,会显示禁用相关样式 |
child | Widget | 文本控件 |
textColor | Color | 文本颜色 |
color | Color | 按钮的颜色 |
disabledColor | Color | 按钮禁用时的颜色 |
disabledTextColor | Color | 按钮禁用时的文本颜色 |
splashColor | Color | 点击按钮时水波纹的颜色 |
highlightColor | Color | 点击(长按)按钮后按钮的颜色 |
elevation | double | 阴影的范围,值越大阴影范围越大 |
padding | 内边距 | |
shape | 设置按钮的形状 shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ) //圆角按钮 shape: CircleBorder( side: BorderSide( color: Colors.white, ) ) //圆形按钮 |
class ButtonDemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("按钮演示页面"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text('普通按钮'),
onPressed: () {
print('点击了');
},
),
SizedBox(width: 20),
RaisedButton(
child: Text('有颜色的按钮'),
textColor: Colors.white,
color: Colors.blue,
onPressed: () {
print('点击了');
},
),
SizedBox(width: 20),
RaisedButton(
child: Text('阴影按钮'),
textColor: Colors.white,
color: Colors.blue,
elevation: 10,
onPressed: () {
print('点击了');
},
)
],
),
SizedBox(height: 40),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 60,
width: 200,
child: RaisedButton(
child: Text('有宽高的按钮'),
textColor: Colors.white,
color: Colors.blue,
elevation: 10,
onPressed: () {
print('点击了');
},
))
],
),
SizedBox(height: 40),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Container(
height: 60,
margin: EdgeInsets.all(20),
child: RaisedButton(
child: Text('全屏按钮'),
textColor: Colors.white,
color: Colors.blue,
elevation: 10,
onPressed: () {
print('点击了');
},
),
))
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Container(
height: 60,
margin: EdgeInsets.all(20),
child: RaisedButton(
child: Text('带圆角的按钮'),
textColor: Colors.white,
color: Colors.blue,
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
onPressed: () {
print('点击了');
},
),
))
],
)
],
),
),
);
}
}
FloatingActionButton简称FAB ,可以实现浮动按钮,也可以实现类似闲鱼app的地步凸起导航
属性名称 | 属性值 |
---|---|
child | 子视图,一般为 Icon,不推荐使用文字 |
tooltip | FAB 被长按时显示,也是无障碍功能 |
backgroundColor | 背景颜色 |
elevation | 未点击的时候的阴影 |
hignlightElevation | 点击时阴影值,默认 12.0 |
onPressed | 点击事件回调 |
shape | 可以定义 FAB 的形状等 |
mini | 是否是 mini 类型默认 false |
Flutter 中常见的表单有 TextField 单行文本框,TextField 多行文本框、CheckBox、Radio、SwitchCheckboxListTile、RadioListTile、SwitchListTile、Slide.
TextField 表单常见属性:
属性 | 描述 |
---|---|
maxLines | 设置此参数可以把文本框改为多行文本框 |
onChanged | 文本框改变的时候触发的事件 |
decoration | hintText 类似 html 中的 placeholder border 配置文本框边框 OutlineInputBorder 配合使用 labelText lable 的名称 labelStyle 配置 lable 的样式 |
obscureText | obscureText |
controller | controller 结合 TextEditingController()可以配置表单默认显示的内容 |
TextField(
maxLines: 10,
// obscureText: true,
decoration:
InputDecoration(
hintText: "密码框",
border: OutlineInputBorder()
),
)d
var _username = TextEditingController();
@override
void initState() {
// TODO: implement initState
super.initState();
_username.text = '这是文本框初始值';
}
TextField(
controller: _username,
onChanged: (value) {
// print(value);
setState(() {
this._username.text = value;
});
},
decoration: InputDecoration(
hintText: "请输入你的内容",
),
)
Checkbox 常见属性:
属性 | 描述 |
---|---|
value | true 或者 false |
onChanged | 改变的时候触发的事件 |
activeColor | 选中的颜色、背景颜色 |
checkColor | 选中的颜色、Checkbox 里面对号的颜色 |
CheckboxListTile 常见属性:
属性 | 描述 |
---|---|
value | true 或者 false |
onChanged | 改变的时候触发的事件 |
activeColor | 选中的颜色、背景颜色 |
title | 标题 |
subtitle | 二级标题 |
secondary | 配置图标或者图片 |
selected | 选中的时候文字颜色是否跟着改变 |
Checkbox(
value: _isSelected,
onChanged: (v) {
print(v);
setState(() {
this._isSelected = v;
});
},
activeColor: Colors.red,
checkColor: Colors.blue,
)d
CheckboxListTile(
value: _isSelected,
title: Text("这是一个标题"),
subtitle: Text("这是二级标题"),
onChanged: (v) {
setState(() {
this._isSelected = v;
});
},
activeColor: Colors.red,
secondary:
Image.network("https://www.itying.com/images/flutter/1.png"),
selected: _isSelected,
)
Radio 常用属性:
属性 | 描述 |
---|---|
value | 单选的值 |
onChanged | 改变时触发 |
activeColor | 选中的颜色、背景颜色 |
groupValue | 选择组的值 |
RadioListTile 常用属性:
属性 | 描述 |
---|---|
value | true 或者 false |
onChanged | 改变的时候触发的事件 |
activeColor | 选中的颜色、背景颜色 |
title | 标题 |
subtitle | 二级标题 |
secondary | 配置图标或者图片 |
groupValue | 选择组的值 |
int _groupValue=1;
Radio(
value: 0,
onChanged: (v) {
setState(() {
this._groupValue = v;
});
},
activeColor: Colors.red,
groupValue: _groupValue,
),
Radio(
value: 1,
onChanged: (v) {
setState(() {
this._groupValue = v;
});
},
activeColor: Colors.red,
groupValue: _groupValue,
)
int _groupValue = 1;
_handelChange(v) {
setState(() {
_groupValue = v;
});
}
RadioListTile(
value: 1,
title: Text("nodejs 视频教程"),
subtitle: Text("egg.js 视频教程"),
secondary:
Image.network("https://www.itying.com/images/flutter/1.png"),
groupValue: _groupValue,
onChanged: _handelChange,
),
Divider(),
RadioListTile(
value: 0,
title: Container(
height: 60,
child: Text("这是文本"),
color: Colors.red,
),
subtitle: Text("egg.js 视频教程"),
secondary:
Image.network("https://www.itying.com/images/flutter/1.png"),
groupValue: _groupValue,
onChanged: _handelChange,
)
属性 | 描述 |
---|---|
value | 单选的值 |
onChanged | 改变时触发 |
activeColor | 选中的颜色、背景颜色 |
日期转化成时间戳:
var now = new DateTime.now();
print(now.millisecondsSinceEpoch);//单位毫秒,13 位时间戳
时间戳转化成日期:
var now = new DateTime.now();
var a=now.millisecondsSinceEpoch; //时间戳
print(DateTime.fromMillisecondsSinceEpoch(a));
文档:https://pub.dev/packages/date_format
日期组件:
var _datetime = DateTime.now();
_showDatePicker() async {
var date = await showDatePicker(
context: context,
initialDate: _datetime,
firstDate: DateTime(1900),
lastDate: DateTime(2050));
if (date == null) return;
print(date);
setState(() {
_datetime = date;
});
}
时间组件:
var _time = TimeOfDay(hour: 9, minute: 20);
_showTimePicker() async {
var time = await showTimePicker(context: context, initialTime: _time);
if (time == null) return;
print(time);
setState(() {
this._time = time;
});
}
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
child: Row(
children: [
Text("${formatDate(_datetime, [yyyy, '-', mm, '-', dd])}"),
Icon(Icons.arrow_drop_down)
],
),
onTap: _showDatePicker,
),
InkWell(
child: Row(
children: [
Text("${this._time.format(context)}"),
Icon(Icons.arrow_drop_down)
],
),
onTap: _showTimePicker,
)
],
)
],
)
http://bbs.itying.com/topic/5cfb2a12f322340b2c90e764
https://pub.dev/packages/flutter_cupertino_date_picker
懒得记了。。。
地址:https://pub.dev/packages/flutter_swiper
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
class SwiperPage extends StatefulWidget {
SwiperPage({Key key}) : super(key: key);
_SwiperPageState createState() => _SwiperPageState();
}
class _SwiperPageState extends State {
List
var alertRel = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("提示!"),
content: Text("确定要删除吗"),
actions: [
FlatButton(
child: Text("取消"),
onPressed: () {
Navigator.pop(context, 'Cancle');
},
),
FlatButton(
child: Text("确定"),
onPressed: () {
Navigator.pop(context, 'Ok');
},
)
],
);
});
var simpleRel = await showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: Text("select 单选按钮框"),
children: [
SimpleDialogOption(
child: Text("Option A"),
onPressed: () {
Navigator.pop(context, 'Option A');
},
),
Divider(),
SimpleDialogOption(
child: Text("Option B"),
onPressed: () {
Navigator.pop(context, 'Option B');
},
),
Divider(),
SimpleDialogOption(
child: Text("Option C"),
onPressed: () {
Navigator.pop(context, 'Option C');
},
)
],
);
});
var actionSheet = await showModalBottomSheet(
context: context,
builder: (builder) {
return Container(
height: 200, //高度不设置显示一半
child: Column(
children: [
ListTile(
title: Text("分享 A"),
onTap: () {
Navigator.pop(context, 'A');
},
),
ListTile(
title: Text("分享 B"),
onTap: () {
Navigator.pop(context, 'B');
},
),
ListTile(
title: Text("分享 C"),
onTap: () {
Navigator.pop(context, 'C');
},
)
],
),
);
});
https://pub.dev/packages/fluttertoast
Fluttertoast.showToast(
msg: "This is Short Toast",
toastLength: Toast.LENGTH_SHORT,
timeInSecForIos: 1);
自定义 Dialog 对象,需要继承 Dialog 类,尽管 Dialog 提供了 child 参数可以用来写视图界面,但是往往会达不到我们想要的效果,因为默认的 Dialog 背景框是满屏的。如果我们想完全定义界面,就需要重写 build 函数。
import 'package:flutter/material.dart';
class LoadingDialog extends Dialog {
final String text;
LoadingDialog(this.text);
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Material(
//创建透明层
type: MaterialType.transparency, //透明类型 child: new Center(
child: Container(
width: 300,
height: 200,
color: Colors.white,
child: Column(
children: [
Padding(
padding: EdgeInsets.all(10),
child: Stack(
children: [
Align(
alignment: Alignment.center,
child: Text("关于我们"),
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
child: Icon(Icons.close),
onTap: () {
Navigator.pop(context);
},
),
)
],
),
),
Divider(),
Column(
children: [
Container(
height: 40,
child: Text(this.text),
)
],
)
],
),
),
);
}
}
import 'dart:async';
_showTimer(context) {
var timer;
timer = Timer.periodic(Duration(milliseconds: 1500), (t) {
print('执行');
Navigator.pop(context);
t.cancel();
});
}
import 'dart:async';
import 'package:flutter/material.dart';
class LoadingDialog extends Dialog {
final String text;
LoadingDialog(this.text);
_showTimer(context) {
var timer;
timer = Timer.periodic(Duration(milliseconds: 1500), (t) {
print('执行');
Navigator.pop(context);
t.cancel();
});
}
@override
Widget build(BuildContext context) {
_showTimer(context);
return Material(
//创建透明层
type: MaterialType.transparency, //透明类型
child: Center(
child: Container(
width: 300,
height: 200,
color: Colors.white,
child: Column(
children: [
Padding(
padding: EdgeInsets.all(10),
child: Stack(
children: [
Align(
alignment: Alignment.center,
child: Text("关于我们"),
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
child: Icon(Icons.close),
onTap: () {
Navigator.pop(context);
},
),
)
],
),
),
Divider(),
Column(
children: [
Container(
height: 40,
child: Text(this.text),
)
],
)
],
),
),
),
);
}
}
(小项目直接转,大项目用模型类,笔记在下面)
import 'dart:convert';
var mapData = {"name": "张三", "age": "20"};
var strData = '{"name":"张三","age":"20"}';
print(json.encode(mapData)); //Map转换成Json字符串
print(json.decode(strData)); //Json 字符串转化成 Map 类型
请参考官方文档:https://pub.dev/packages/http
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
String _news = '';
//请求数据
_getData() async {
var apiUrl = "http://127.0.0.1:8080/";
var result = await http.get(apiUrl);
if (result.statusCode == 200) {
// print(json.decode(result.body));
setState(() {
this._news = json.decode(result.body)["msg"];
});
} else {
print(result.statusCode);
}
}
//提交数据
_postData() async {
var apiUrl = "http://127.0.0.1:8080/x";
var result = await http.post(apiUrl, body: {'username': '张三', 'age': '20'});
if (result.statusCode == 200) {
print(json.decode(result.body));
} else {
print(result.statusCode);
}
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(this._news),
RaisedButton(
child: Text('Get请求数据'),
onPressed: _getData,
),
SizedBox(height: 20),
RaisedButton(
child: Text('Post提交数据'),
onPressed: _postData,
),
SizedBox(height: 20),
RaisedButton(
child: Text('Get请求数据、渲染数据演示demo'),
onPressed: () {
Navigator.pushNamed(context, '/http');
},
),
SizedBox(height: 20),
],
),
);
}
}
map遍历,外层套ListView组件
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class HttpDemo extends StatefulWidget {
HttpDemo({Key key}) : super(key: key);
_HttpDemoState createState() => _HttpDemoState();
}
class _HttpDemoState extends State {
List _list = [];
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
}
_getData() async {
var apiUrl = "http://a.itying.com/api/productlist";
var result = await http.get(apiUrl);
if (result.statusCode == 200) {
print(result.body);
setState(() {
this._list = json.decode(result.body)["result"];
/*
{
"result": [{
"_id": "5ac0896ca880f20358495508",
"title": "精选热菜",
"pid": "0",
}, {
"_id": "5ac089e4a880f20358495509",
"title": "特色菜",
"pid": "0",
}
]
}
*/
});
} else {
print("失败${result.statusCode}");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("请求数据Demo"),
),
body: this._list.length > 0
? ListView(
children: this._list.map((value) { //map遍历,外层套ListView组件
return ListTile(
title: Text(value["title"]),
);
}).toList(),
)
: Text("加载中..."));
}
}
ListView.builder
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class HttpDemo extends StatefulWidget {
HttpDemo({Key key}) : super(key: key);
_HttpDemoState createState() => _HttpDemoState();
}
class _HttpDemoState extends State {
List _list = [];
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
}
_getData() async {
var apiUrl = "http://a.itying.com/api/productlist";
var result = await http.get(apiUrl);
if (result.statusCode == 200) {
print(result.body);
setState(() {
this._list = json.decode(result.body)["result"];
/*
{
"result": [{
"_id": "5ac0896ca880f20358495508",
"title": "精选热菜",
"pid": "0",
}, {
"_id": "5ac089e4a880f20358495509",
"title": "特色菜",
"pid": "0",
}
]
}
*/
});
} else {
print("失败${result.statusCode}");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("请求数据Demo"),
),
body: this._list.length > 0
? ListView.builder(
itemCount: this._lidst.length,
itemBuilder: (context, index) {
return ListTile(
title: Text("${this._list[index]["title"]}"),
);
},
)
: Text("加载中..."));
}
}
dio 是一个强大的 Dart Http 请求库,支持 Restful API、FormData、拦截器、请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器等…
https://pub.dev/packages/dio
https://github.com/flutterchina/dio/blob/master/README-ZH.md
不做栗子,依官方最新文档栗子为准
在 Flutter 官方 sdk 中给我们提供了下拉刷新的组件 RefreshIndicator。但是没有提供上拉分页加载更多的组件。但是在 Flutter ListView 中有一个ScrollController 属性,它就是专门来控制 ListView 滑动事件,在这里我们可以根据 ListView 的位置来判断是否滑动到了底部来做加载更多的处理。
Api 接口:http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("请求数据 Dio Demo"),
),
body: this._list.length > 0
? RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
itemCount: this._list.length,
itemBuilder: (context, index) {
return ListTile(title: Text(this._list[index]["title"]));
}))
: Text("加载中..."));
}
Future _onRefresh() async {
print('执行刷新');
}
_scrollController.position.pixels 滚动的距离
_scrollController.position.maxScrollExtent 总距离
ScrollController _scrollController = ScrollController(); //listview 的控制器
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
//监听滚动条事件
_scrollController.addListener(() {
if (_scrollController.position.pixels >
_scrollController.position.maxScrollExtent - 20) {
print("滚动到了最底部");
_getData();
}
});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose(); //不用了砍掉,提高性能
}
ListView.builder(
itemCount: this._list.length,
controller: _scrollController, //注意
itemBuilder: (context, index) {
}
)
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
class NewsPage extends StatefulWidget {
@override
_NewsPageState createState() => _NewsPageState();
}
class _NewsPageState extends State<NewsPage> {
ScrollController _scrollController = ScrollController(); //listview 的控制器
List _list = [];
int _page = 1;
bool isLoading = true; //是否正在加载数据
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
//监听滚动条事件
_scrollController.addListener(() {
if (_scrollController.position.pixels >
_scrollController.position.maxScrollExtent - 20) {
print("滑动到了最底部");
_getData();
}
});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose(); //不用了砍掉,提高性能
}
_getData() async {
String apiUrl =
"http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=${this._page}";
Response result = await Dio().get(apiUrl);
var res = json.decode(result.data)["result"];
// print(json.decode(result.data)["result"]);
setState(() {
this._list.addAll(res);
this._page++;
});
//判断是否是最后一页
if (res.length < 20) {
setState(() {
this.isLoading = false;
});
}
}
Widget _getMoreWidget() {
if (isLoading) {
return Center(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'加载中...',
style: TextStyle(fontSize: 16.0),
),
CircularProgressIndicator(
strokeWidth: 1.0,
)
],
),
),
);
} else {
return Center(
child: Text("--我是有底线的--"),
);
}
}
//下拉刷新
Future<void> _onRefresh() async {
print("执行刷新");
this._getData();
await Future.delayed(Duration(seconds: 3), () {
print("refresh");
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("请求数据 Dio Demo"),
),
body: this._list.length > 0
? RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: _scrollController,
itemCount: this._list.length,
itemBuilder: (context, index) {
if (index == this._list.length - 1) {
return Column(
children: <Widget>[
ListTile(
title: Text(this._list[index]["title"], maxLines: 1),
onTap: () {
Navigator.pushNamed(context, '/newsContent');
},
),
Divider(),
_getMoreWidget()
],
);
} else {
return Column(
children: <Widget>[
ListTile(
title: Text(this._list[index]["title"], maxLines: 1),
onTap: () {
Navigator.pushNamed(context, '/newsContent');
},
),
Divider()
],
);
}
},
),
)
: _getMoreWidget(),
);
}
}
//解决重复请求的问题
bool flag=true;
@override
void initState() {
super.initState();
_getData();
//监听滚动条滚动事件
_scrollController.addListener((){
//_scrollController.position.pixels //获取滚动条滚动的高度
//_scrollController.position.maxScrollExtent //获取页面高度
if(_scrollController.position.pixels>_scrollController.position.maxScrollExtent-20){
if(this.flag && this._hasMore){ //如果已经请求就不再获取数据
_getData();
}
}
});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose(); //不用了砍掉,提高性能
}
//获取数据
_getData() async {
// 请求数据之前置为false
setState(() {
this.flag=false;
});
var api =‘url‘;
var result = await Dio().get(api);
。。。
// 数据请求完成之后置为true
setState(() {
。。。
this.flag=true;
});
}
//回到顶部
_scrollController.jumpTo(0);
//导航改变的时候触发
_subHeaderChange(id) {
if (id == 4) {
_scaffoldKey.currentState.openEndDrawer();
setState(() {
this._selectHeaderId = id;
});
} else {
setState(() {
this._selectHeaderId = id;
this._sort ="${this._subHeaderList[id - 1]["fileds"]}_${this._subHeaderList[id - 1]["sort"]}";
//重置分页
this._page = 1;
//重置数据
this._productList = [];
//改变sort排序
this._subHeaderList[id - 1]['sort'] =
this._subHeaderList[id - 1]['sort'] * -1;
//回到顶部
_scrollController.jumpTo(0);
//重置_hasMore
this._hasMore = true;
//重新请求
this._getProductListData();
});
}
}
涉及的 api 接口:
新闻列表: http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1
新闻详情:http://www.phonegap100.com/appapi.php?a=getPortalArticle&aid=20
列表页
import 'package:cc/pages/NewsContent.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
class NewsPage extends StatefulWidget {
@override
_NewsPageState createState() => _NewsPageState();
}
class _NewsPageState extends State {
ScrollController _scrollController = ScrollController(); //listview 的控制器
List _list = [];
int _page = 1;
bool isLoading = true; //是否正在加载数据
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
//监听滚动条事件
_scrollController.addListener(() {
if (_scrollController.position.pixels >
_scrollController.position.maxScrollExtent - 20) {
print("滑动到了最底部");
_getData();
}
});
}
_getData() async {
String apiUrl =
"http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=${this._page}";
Response result = await Dio().get(apiUrl);
var res = json.decode(result.data)["result"];
// print(json.decode(result.data)["result"]);
setState(() {
this._list.addAll(res);
this._page++;
});
//判断是否是最后一页
if (res.length < 20) {
setState(() {
this.isLoading = false;
});
}
}
Widget _getMoreWidget() {
if (isLoading) {
return Center(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'加载中...',
style: TextStyle(fontSize: 16.0),
),
CircularProgressIndicator(
strokeWidth: 1.0,
)
],
),
),
);
} else {
return Center(
child: Text("--我是有底线的--"),
);
}
}
//下拉刷新
Future _onRefresh() async {
print("执行刷新");
this._getData();
await Future.delayed(Duration(seconds: 3), () {
print("refresh");
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("请求数据 Dio Demo"),
),
body: this._list.length > 0
? RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: _scrollController,
itemCount: this._list.length,
itemBuilder: (context, index) {
if (index == this._list.length - 1) {
return Column(
children: [
ListTile(
title: Text(this._list[index]["title"], maxLines: 1),
onTap: () {
Navigator.pushNamed(context, '/newsContent');
},
),
Divider(),
_getMoreWidget()
],
);
} else {
return Column(
children: [
ListTile(
title: Text(this._list[index]["title"], maxLines: 1),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
NewsContent(this._list[index]["aid"])));
},
),
Divider()
],
);
}
},
),
)
: _getMoreWidget(),
);
}
}
详情页
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:dio/dio.dart';
class NewsContent extends StatefulWidget {
var aid;
NewsContent(this.aid);
createState() => _NewsContentState();
}
class _NewsContentState extends State<NewsContent> {
List list = [];
_getData() async {
String apiUrl =
"http://www.phonegap100.com/appapi.php?a=getPortalArticle&aid=${this.widget.aid}";
Response response = await Dio().get(apiUrl);
setState(() {
this.list = json.decode(response.data)["result"];
});
}
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
}
@override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(
title: Text(this.list.length > 0 ? this.list[0]["title"] : ""),
),
body: ListView(
children: <Widget>[
Text(this.list.length > 0 ? this.list[0]["content"] : "")
],
),
),
);
}
}
https://pub.dev/packages/flutter_html
鸡肋。。。只能解析部分HTML标签
建议使用WebView_flutter官方库,inappbrowser翘辫子了
https://pub.dev/packages/flutter_inappbrowser
ios模拟器测试失败。。。
https://pub.dev/packages/device_info
1、申请成为开发者
2、创建应用配置获取 Key (参考教程演示)
https://lbs.amap.com/api/android-sdk/guide/create-project/get-key
https://pub.dev/packages/amap_location
https://pub.dev/packages/image_picker
/*拍照*/
_takePhoto() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_imgPath = image;
});
}
/*相册*/
_openGallery() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_imgPath = image;
});
}
https://pub.dev/packages/dio
Dio2.x和3.x代码不同,依Dio官方文档为准
//上传图片
_uploadImage(File _imageDir) async {
//注意:dio3.x版本为了兼容web做了一些修改,上传图片的时候需要把File类型转换成String类型,具体代码如下
var fileDir = _imageDir.path;
FormData formData = FormData.fromMap({
"name": "zhangsna 6666666666",
"age": 20,
"sex": "男",
"file": await MultipartFile.fromFile(fileDir, filename: "xxx.jpg")
});
var response =
await Dio().post("http://jd.itying.com/imgupload", data: formData);
print(response);
}
video_player 官方库,支持flutter web
在 Flutter 里官方提供了一个 video_player 插件可以播放视频。但是 video_player 有一些局限性。没法控制底部播放进度等。 所以我们主要给大家讲解一个第三方的视频播放库chewie。chewie 是一个非官方的第三方视频播放组件,看起来好像是基于 HTML5 播放的组件。chewie 相对 video_player 来说,有控制栏和全屏的功能。Chewie 使用 video_player 引擎并将其包裹在友好的 Material 或 Cupertino UI 中!
https://pub.dev/packages/video_player
https://pub.dev/packages/chewie
iOS警告
chewie使用的视频播放器插件在iOS模拟器上不起作用。开发/测试期间必须使用iOS设备。
coffee 21:44:59
安利一下 flutter_ijkplayer 视频播放器,这两天试了很多个,还是这个解决了目前的问题
coffee 22:31:46
flutter_tencentplayer 腾讯云的ios无法播放rtmp协议的直播流
coffee 22:33:13
官方提供的video_player 没有ui,需要自己实现
coffee 22:34:47
chewie 包装了一层video_player提供了ui,我视频回放用的这个,费了好大力气才搞定使用原视频尺寸播放,今儿发现 flutter_ijkplayer 很好用,不过回放功能暂时不打算改了,好不容易调完
import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:video_player/video_player.dart';
class ChewieVideoDemo extends StatefulWidget {
ChewieVideoDemo({Key key}) : super(key: key);
_ChewieVideoDemoState createState() => _ChewieVideoDemoState();
}
class _ChewieVideoDemoState extends State {
VideoPlayerController videoPlayerController;
ChewieController chewieController;
@override
void initState() {
// TODO: implement initState super.initState();
videoPlayerController = VideoPlayerController.network(
'http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4');
chewieController = ChewieController(
videoPlayerController: videoPlayerController,
aspectRatio: 3 / 2,
autoPlay: true,
looping: true,
);
}
@override
void dispose() {
// TODO: implement dispose super.dispose();
videoPlayerController.dispose();
chewieController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('标题'),
),
body: Center(
child: Chewie(
controller: chewieController,
),
),
);
}
}
https://pub.dev/packages/connectivity
import 'package:flutter/material.dart';
import 'package:connectivity/connectivity.dart';
class NetworkPage extends StatefulWidget {
NetworkPage({Key key}) : super(key: key);
_NetworkPageState createState() => _NetworkPageState();
}
class _NetworkPageState extends State {
String _state;
var _subscription;
@override
initState() {
super.initState();
_subscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
// Got a new connectivity status!
if (result == ConnectivityResult.mobile) {
setState(() {
_state = "手机网络";
});
// I am connected to a mobile network. } else if (result == ConnectivityResult.wifi) {
setState(() {
_state = "Wifi 网络";
});
// I am connected to a wifi network. }else{
setState(() {
_state = "没有网络";
});
}
});
}
@override
dispose() {
super.dispose();
_subscription.cancel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("检测网络变化"),
),
body: Text("${_state}"),
);
}
}
https://pub.dev/packages/shared_preferences
注意:
如果SharedPreferences prefs = await SharedPreferences.getInstance();写在runapp()的外层,
要加上
1、设置值
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
prefs.setBool(key, value)
prefs.setDouble(key, value)
prefs.setInt(key, value)
prefs.setStringList(key, value)
2、获取值
SharedPreferences prefs = await SharedPreferences.getInstance();
var data=prefs.getString("name");
3、删除值
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove(key); //删除指定键
prefs.clear();//清空键值对
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class StoragePage extends StatefulWidget {
StoragePage({Key key}) : super(key: key);
_StoragePageState createState() => _StoragePageState();
}
class _StoragePageState extends State {
_saveData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("name", "张三");
// prefs.setBool(key, value)
// prefs.setDouble(key, value)
// prefs.setInt(key, value)
// prefs.setStringList(key, value)
}
_getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var data = prefs.getString("name");
print(data);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("本地存储"),
),
body: Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
RaisedButton(
child: Text('保存数据'),
onPressed: _saveData,
),
SizedBox(height: 10),
RaisedButton(
child: Text('获取数据'),
onPressed: _getData,
)
]),
),
);
}
}
https://pub.dev/packages/barcode_scan
错误多多,修改大大,不做记录。。。用到再说
1、获取本地版本号
2、请求服务器获取服务器版本号
3、本地版本和服务器版本不一致提示升级,弹窗提示用户是否更新
4、用户确定升级,调用文件传输方法下载 apk 文件
5、监听下载进度
6、下载完成打开 Apk 进行安装
注意:在 Ios 中没法直接下载安装,如果版本不一致直接跳转到 Ios 应用对应的应用市场就可以了。
配置版本号: (Flutter应用获取的不是这里的版本号,在pubspec.yaml文件)
<manifest android:hardwareAccelerated="true" android:versionCode="1" android:versionName="0.0.1" package="io.jdshop.demo" xmlns:android="http://schemas.android.com/apk/res/android">
配置 AndroidMenifest.xml 文件
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
插件名称 | 描述 | 插件地址 |
---|---|---|
package_info | 检测版本号 | https://pub.dev/packages/package_info |
path_provider | 获取文件存储路径 | https://pub.dev/packages/path_provider |
flutter_downloader | flutter_downloader | https://pub.dev/packages/flutter_downloader |
open_file | 打开文件插件 | https://pub.dev/packages/open_file |
https://pub.dev/packages/package_info
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
String packageName = packageInfo.packageName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
print("appName:${appName}");
print("packageName:${packageName}");
print("version:${version}");
print("buildNumber:${buildNumber}");
https://pub.dev/packages/path_provider
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
var directory = await getExternalStorageDirectory();
String storageDirectory=directory.path;
print("tempPath:${tempPath}");
print("appDocDir:${appDocPath}");
print("StorageDirectory:${storageDirectory}");
https://pub.dev/packages/flutter_downloader
final directory = await getExternalStorageDirectory();
String _localPath = directory.path;
final taskId = await FlutterDownloader.enqueue(
url: "http://www.ionic.wang/jdshop.apk",
savedDir: _localPath,
showNotification:
true, // show download progress in status bar (for Android)
openFileFromNotification:
true, // click on notification to open downloaded file (for Android)
https://pub.dev/packages/open_file
OpenFile.open("${_localPath}/jdshop.apk");
1、服务器的 App 版本必须大于本地 App 版本
2、本地 App 和服务器 App 的包名称 签名必须一致,这样的话服务器的包才可以替换本地的包。
import 'package:flutter/material.dart';
import 'package:package_info/package_info.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:open_file/open_file.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
class AppVersionPage extends StatefulWidget {
AppVersionPage({Key key}) : super(key: key);
_AppVersionPageState createState() => _AppVersionPageState();
}
class _AppVersionPageState extends State {
@override
void initState() {
// TODO: implement initState
super.initState();
this._getPackageInfo();
this._getAppPath();
}
//弹出Dialog
_showDialog() async {
var alertRel = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("更新APP提示!"),
content: Text("发现新的版本,新版本修复了如下bug 是否更新!"),
actions: [
FlatButton(
child: Text("否"),
onPressed: () {
Navigator.pop(context, 'Cancle');
},
),
FlatButton(
child: Text("是"),
onPressed: () {
Navigator.pop(context, 'Ok');
},
)
],
);
});
}
//获取版本号
_getPackageInfo() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
String packageName = packageInfo.packageName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
print("appName:${appName}");
print("packageName:${packageName}");
print("version:${version}");
print("buildNumber:${buildNumber}");
}
//获取路径
_getAppPath() async {
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
var directory = await getExternalStorageDirectory();
String storageDirectory = directory.path;
print("tempPath:${tempPath}");
print("appDocDir:${appDocPath}");
print("StorageDirectory:${storageDirectory}");
}
//下载打开文件
_downLoad() async {
final directory = await getExternalStorageDirectory();
String _localPath = directory.path;
final taskId = await FlutterDownloader.enqueue(
url: "http://www.ionic.wang/jdshop.apk",
savedDir: _localPath,
showNotification:
true, // show download progress in status bar (for Android)
openFileFromNotification:
true, // click on notification to open downloaded file (for Android)
);
FlutterDownloader.registerCallback((id, status, progress) {
print(status);
// code to update your UI
print('1111111');
print(progress);
});
//打开文件
OpenFile.open("${_localPath}/jdshop.apk");
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.arrow_downward),
onPressed: _downLoad,
),
appBar: AppBar(
title: Text("app升级演示"),
),
body: Text("app升级演示"),
);
}
}
1、Flutter url_launcher 模块
Flutter url_launcher 模块可以让我们实现打开外部浏览器、打开外部应用、发送短信、拨打电话等功能。
https://pub.dev/packages/url_launcher
2、Flutter url_launcher 模块的使用
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class _UrlLauncherState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('UrlLauncher'),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: ListView(
children: [
RaisedButton(
child: Text('打开外部浏览器'),
onPressed: () async {
const url = 'https://cflutter.com';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
},
),
SizedBox(
height: 10,
),
RaisedButton(
child: Text('拨打电话'),
onPressed: () async {
const tel = 'tel:10086';
if (await canLaunch(tel)) {
await launch(tel);
} else {
throw 'Could not launch $tel';
}
},
),
SizedBox(height: 10),
RaisedButton(
child: Text('发送短信'),
onPressed: () async {
const tel = 'sms:10086';
if (await canLaunch(tel)) {
await launch(tel);
} else {
throw 'Could not launch $tel';
}
},
),
SizedBox(height: 10),
RaisedButton(
child: Text('打开外部应用'),
onPressed: () async {
// weixin://
const app = 'alipays://';
if (await canLaunch(app)) {
await launch(app);
} else {
throw 'Could not launch $app';
}
},
)
],
),
),
),
);
}
}
3、关于打开其他 app 请参考这个帖子
https://www.cflutter.com/topic/5d0853733b57e317a4d0af01
Android 是在 android ▸ app ▸ src ▸ main▸ AndroidManifest.xml 中修改 android:label=“XXX”;
Android 在 android ▸ app ▸ src ▸ res ▸ mipmap 下面对应的文件夹中替换相应图片
Android 添加启动界面
打开文件 android/app/src/main/res/drawable/launch_background.xml
修改内容,打开注释了的代码 launch_image 那段。
里面的ic_launch.png是图标,启动画面添加launch_image.png,格式要求png
注意@mipmap/launch_image 就是你要设置的启动界面的图片资源名字,你要放置到对应的文件夹里面
密度 | 代表分辨率 |
---|---|
ldpi | 240 x 320 |
mdpi | 320 x 480 |
hdpi | 480 x 800 |
xhdpi | 720 x 1280 |
xxhdpi | 1080 x 1920 |
xxxhdpi | 3840×2160 |
1、竖向 ListView 嵌套横向 ListView 注意事项:
在竖向 ListView 中嵌套横向 ListView 的时候要注意给横向 ListView 外层加一个容器,然后外层这个容器要设置高度,外层这个容器可以是 SizedBox ,也可以是 Container。
2、ListView 嵌套 GridView 注意事项:
由于 GridView 和 ListView 都是可以滚动的组件,所以嵌套的时候要注意把里面的组件改为不可滚动组件。
重要属性:
shrinkWrap: true, //解决无限高度问题
physics:NeverScrollableScrollPhysics(), //禁用滑动事件
我写的代码还用适配???
1、使用 dart:convert 手动序列化 JSON
2、模型类中序列化 JSON
小项目中使用 dart:convert 手动序列化 JSON 非常好,也非常快速。但是随着项目的增大,dart:convert 手动序列化 JSON 的话失去了大部分静态类型语言特性:类型安全、自动补全和最重要的编译时异常。这样一来,我们的代码可能会变得非常容易出错。
当我们访问 name 或 email 字段时,我们输入的很快,导致字段名打错了。但由于这个 JSON 在 map 结构中,所以编译器不知道这个错误的字段名。
为了解决上面的问题在大型项目中使用的更多的是在模型类中序列化 JSON。
import 'dart:convert';
var mapData = {"name": "张三", "age": "20"};
var strData = '{"name":"张三","age":"20"}';
print(json.encode(mapData)); //Map转换成Json字符串
print(json.decode(strData)); //Json 字符串转化成 Map 类型
class FocusModel {
String sId;
String title;
String status;
String pic;
String url;
FocusModel({this.sId, this.title, this.status, this.pic, this.url});
FocusModel.fromJson(Map json) {
sId = json['_id'];
title = json['title'];
status = json['status'];
pic = json['pic'];
url = json['url'];
}
Map toJson() {
final Map data = new Map();
data['_id'] = this.sId;
data['title'] = this.title;
data['status'] = this.status;
data['pic'] = this.pic;
data['url'] = this.url;
return data;
}
}
var strData='{"_id":"59f6ef443ce1fb0fb02c7a43","title":"笔记本电脑","status":"1","pic":"public\\upload\\UObZahqPYzFvx_C9CQjU8KiX.png","url":"12"}';
var data=FocusModel.fromJson(strData);
可参考:https://flutterchina.club/json/
https://javiercbk.github.io/json_to_dart/
IndexedStack 和 Stack 一样,都是层布局控件, 可以在一个控件上面放置另一个控件,但唯一不同的是 IndexedStack 在同一时刻只能显示子控件中的一个控件,通过 Index 属性来设置显示的控件。
IndexedStack 来保持页面状态的优点就是配置简单。IndexedStack 保持页面状态的缺点就是不方便单独控制每个页面的状态。
body: IndexedStack(
index: this._currentIndex,
children: [],
),
花里胡哨。。。
final GlobalKey _scaffoldKey = new GlobalKey();
return Scaffold( key:_scaffoldKey,
appBar: AppBar(
title: Text("商品列表"),
) )
Expanded(
flex: 1,
child: InkWell( onTap: () {
_scaffoldKey.currentState.openEndDrawer(); },
child: Text("筛选", textAlign: TextAlign.center), ),
)
return MaterialApp(
debugShowCheckedModeBanner: false, // home: Tabs(),
initialRoute: '/', onGenerateRoute:onGenerateRoute, theme: ThemeData(
primaryColor: Colors.white ),
);
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () {
showMenu(
context: context,
position: RelativeRect.fromLTRB(500, 76, 10, 0),
items: [
PopupMenuItem(
child: Row(
children: [
Icon(Icons.home),
Container(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Text("首页"),
)
],
),
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.search),
Container(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Text("搜索"),
)
],
),
)
]);
})
实际就是点击事件后,弹出showModalBottomSheet,参考diolog笔记
参考:https://www.cflutter.com/topic/5d202202403aa10564178c65
通俗的讲:当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用 Flutter 中的状态管理来管理统一的状态(数据),实现不同组件直接的传值和数据共享。
现在 Flutter 的状态管理方案很多,redux、bloc、state、provide、provider。
目前我们推荐使用 provider,这个是官方提供的状态管理解决方案。相比其他状态管理库使用起来比较方便。
provider 是 Flutter 团队推出的状态管理模式。
官方地址为:https://pub.dev/packages/provider
注意:provider 和 provide 是两个库哦。Flutter 官方推荐使用的是 provider 哦,provider 是flutter 官方出的。provide 不是 Flutter 官方写的哦。
(官方文档为准,builder关键字变creat)
1、配置依赖 provider: ^4.3.3
2、新建一个文件夹叫 provider,在 provider 文件夹里面放我们对于的状态管理类
3、在 provider 里面新建 Counter.dart
4、Counter.dart 里面新建一个类继承 minxins 的 ChangeNotifier 代码如下
import 'package:flutter/material.dart';
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
5、找到 main.dart 修改代码如下
import 'package:flutter/material.dart';
import 'routers/router.dart';
import 'package:provider/provider.dart';
import 'provider/Counter.dart';
void main() => runApp(MyApp());
// void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Provider.value(value: foo),
ChangeNotifierProvider(create: (_) => Counter()), // provider4.x的写法全局监听
//ChangeNotifierProvider(builder: (_) => Counter()),
],
child: MaterialApp(
// home: Tabs(),
debugShowCheckedModeBanner: false,
initialRoute: '/productContent',
onGenerateRoute: onGenerateRoute,
theme: ThemeData(
// primaryColor: Colors.yellow
primaryColor: Colors.white),
));
}
}
6、获取值、以及设置值
import 'package:provider/provider.dart';
import '../../provider/Counter.dart';
Widget build(BuildContext context) {
final counter = Provider.of(context); // counter.init();//在build里面
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
counter.increment();
},
),
body: Text("counter 的值:${counter.count}"));
}
扩展栗子
import 'package:flutter/material.dart';
class Cart with ChangeNotifier {
List _cartList = []; //状态
// int _cartNum=0; //数量直接从数组中获取,不用在定义一个获取数量的方法
int get cartNum => this._cartList.length; //数量直接从数组中获取,不用在定义一个获取数量的方法
List get cartList => this._cartList;
addData(value) {
this._cartList.add(value);
notifyListeners();
}
deleteData(value) {
this._cartList.remove(value);
notifyListeners();
}
}
花里胡哨。。。
通过MediaQuery.removePadding
可以移除元素的pandding,需要注意要指定移除哪个方向的padding,例如移除上面的padding
MediaQuery.removePadding(
removeTop: true,
context: context,
child: ,
)
https://pub.dev/packages/flutter_staggered_grid_view
new StaggeredGridView.countBuilder(
crossAxisCount: 4,
itemCount: 8,
itemBuilder: (BuildContext context, int index) => new Container(
color: Colors.green,
child: new Center(
child: new CircleAvatar(
backgroundColor: Colors.white,
child: new Text('$index'),
),
)),
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(2, index.isEven ? 2 : 1), //固定个数修改count()为fit(2)
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
)
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
// title: Text("SliverAppBar"),
// pinned: true,
floating: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
title: Text("Hello Flutter".toUpperCase()),
background: Image.network(
"https://images.unsplash.com/photo-1579964190836-13f5022f5c40?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60",
fit: BoxFit.cover,
),
),
),
SliverGrid(
delegate: SliverChildBuilderDelegate((context, index) {
return Container(
child: Center(
child: Text("$index"),
),
);
}, childCount: 1000),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0))
],
),
);
简单做法:
直接设置模式
theme: ThemeData.dark(),
自动切换模式
darkTheme: ThemeData.dark()
正片:
夜间模式(Dark Mode),也被称为暗黑模式或深色模式,是一种高对比度,或者反色模式的显示模式,开启之后在夜间可以缓解疲劳,更易于阅读,同时也能在一定程度上达到省电的效果。
使用MaterialApp
的darkTheme
选项,可以很方便地适配跟随系统的DarkMode:
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.blue,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
);
也可直接写做
darkTheme: ThemeData.dark()
上述的跟随系统自动切换暗黑模式的体验可能并不是很好,比如用户不喜欢夜间模式或者App的夜间模式配色适配并不是很好,这就会导致用户无法手动控制app的夜间模式或者只能关闭系统的设置。因此我们可以增加手动控制以及跟随系统的选项,让用户选择是否开启以及开启的方式。
在flutter中可以使用shared_preferences来保存用户的配置数据,具体使用方法详见:shared_preferences使用
主题的手动切换是影响全局的,如果通过常规的数据流向很难做到。常见的几种状态管理:
Provider是Google I/O 2019大会宣布的现在官方推荐的状态管理方式,我们需要在设置页里面通过用户设置,把变更状态共享给其他Widget,这里采用Provider这种方式来实现状态共享。
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
class DarkModeModel with ChangeNotifier {
/// 夜间模式 0: 关闭 1: 开启 2: 随系统
int _darkMode;
static const Map darkModeMap = {
0: "关闭",
1: "开启",
2: "跟随系统"
};
static const String STORE_KEY = 'darkMode';
SharedPreferences _prefs;
int get darkMode => _darkMode;
DarkModeModel() {
_init();
}
void _init() async {
this._prefs = await SharedPreferences.getInstance();
int localMode = this._prefs.getInt(STORE_KEY);
changeMode(localMode ?? 0);
}
void changeMode(int darkMode) async {
_darkMode = darkMode;
notifyListeners();
SharedPreferences prefs = this._prefs ?? SharedPreferences.getInstance();
await prefs.setInt(STORE_KEY, darkMode);
}
}
如果手动控制是否开启夜间模式,可以设置MaterialApp
的theme
选项为ThemeData.dark()
theme: ThemeData.dark()
因为需要同时保留随系统自动切换与手动切换,而darkTheme
选项和theme
又有冲突,所以这里需要根据darkModeModel.darkMode
的取值来渲染不同的MaterialApp
,如果是手动模式再根据darkModeModel.darkMode
的取值来渲染不同的theme
。
MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => DarkModeModel())
],
child: Consumer(
builder: (context, darkModeModel, _) {
return darkModeModel.darkMode == 2
? MaterialApp(
title: '特效君',
theme: ThemeData(
primarySwatch: Colors.blue,
),
darkTheme: ThemeData.dark(),
home: MainPage(title: '特效君'),
)
: MaterialApp(
title: '特效君',
theme: darkModeModel.darkMode == 1
? ThemeData.dark()
: ThemeData(
primarySwatch: Colors.blue,
),
home: MainPage(title: '特效君'),
);
},
),
)
这样我们就可以给用户提供自动跟随系统切换以及手动控制的选项了
login.dart
class Login extends StatefulWidget {
@override
_LoginState createState() => _LoginState();
}
class _LoginState extends State {
String _nickname = "";
String _password = "";
final _formKey = GlobalKey();
bool _autoValidate = false;
_toLogin() async {
Response response = await Dio().post("http://127.0.0.1:8080/auth",
data: {"nickname": _nickname, "password": _password});
LoginModel signUp = LoginModel.fromJson(response.data);
if (signUp.code == 2000) {
Scaffold.of(context).showSnackBar(SnackBar(content: Text(signUp.msg)));
} else {
Scaffold.of(context).showSnackBar(SnackBar(content: Text(signUp.msg)));
}
}
void _submitForm() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
_toLogin();
} else {
setState(() {
_autoValidate = true;
});
}
}
String _validateNickname(String value) {
if (value.isEmpty) {
return "<昵称>不能为空!";
} else if (value.length > 20) {
return "<昵称>不能大于20个字符!";
}
return null;
}
String _validatePassword(String value) {
if (value.isEmpty) {
return "<密码>不能为空!";
} else if (value.length < 6) {
return "<密码>不能小于6位!";
}
return null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
// 外层包一个Container方便设置内外边距,背景图片等
child: Container(
padding: EdgeInsets.all(40.0),
child: ListView(
children: [
// 登录LOGO
Container(
height: 100,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
"https://c-ssl.duitang.com/uploads/item/201607/23/20160723143004_vyjTa.thumb.1000_0.jpeg",
),
),
),
),
// 表单
Form(
key: _formKey,
child: Column(
children: [
// 昵称
TextFormField(
decoration: InputDecoration(
labelText: "昵称",
hintText: "请输入登录昵称",
helperText: '',
),
onSaved: (String value) {
_nickname = value;
},
autovalidate: _autoValidate,
validator: _validateNickname,
),
// 密码
TextFormField(
decoration: InputDecoration(
labelText: "密码",
hintText: "请输入登录密码",
helperText: '',
),
obscureText: true,
onSaved: (String value) {
_password = value;
},
autovalidate: _autoValidate,
validator: _validatePassword,
),
// 登录按钮
Container(
child: RaisedButton(
child: Text("登录"),
onPressed: _submitForm,
color: Theme.of(context).accentColor,
elevation: 0.0,
),
),
],
),
),
// 注册新账号
Container(
child: FlatButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => SignUp()));
},
child: Text("注册新账号"),
),
),
],
),
),
),
);
}
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: _sliverBuilder,
body: Center(
child: Text('hahaha'),
)),
);
}
}
List _sliverBuilder(BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(
centerTitle: true, //标题居中
expandedHeight: 200.0, //展开高度200
backgroundColor: Colors.tealAccent,
floating: false, //不随着滑动隐藏标题
pinned: false, //不固定在顶部
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
background: Image.asset(
"assets/pic.jpg",
fit: BoxFit.cover,
),
),
)
];
}
https://pub.dev/packages/pk_skeleton
class _HomeState extends State {
@override
Widget build(BuildContext context) {
SelfAdapt _adapt = SelfAdapt.init(context);
return Container(
width: _adapt.width,
height: _adapt.height,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://img.zcool.cn/community/0372d195ac1cd55a8012062e3b16810.jpg'),
fit: BoxFit.cover,
),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
// title: Text('首页'),
),
drawer: MyDrawer(),
body: Container(
width: _adapt.width,
padding: _adapt.setfromLTRB(100, 0, 100, 0),
child: Text('hello'),
),
),
);
}
}
注册账户-----创建应用-----appkey—应用报名要一致
集成
1.下载依赖
Android:
在 /android/app/build.gradle 中添加下列代码:
android: {
....
defaultConfig {
applicationId "替换成自己应用 ID"
...
ndk {
//选择要添加的对应 cpu 类型的 .so 库。
abiFilters 'armeabi', 'armeabi-v7a', 'x86', 'x86_64', 'mips', 'mips64', 'arm64-v8a',
}
manifestPlaceholders = [
JPUSH_PKGNAME : applicationId,
JPUSH_APPKEY : "appkey", // NOTE: JPush 上注册的包名对应的 Appkey.
JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可.
]
}
}
void initState() {
super.initState();
this.initJpush();
// ///创建 JPush
// JPush jpush = new JPush();
// ///配置应用 Key
// jpush.setup(
// appKey: "3cc9670e26b5b3e83cabe979",
// channel: "theChannel",
// production: false,
// /// 设置是否打印 debug 日志
// debug: true,
// );
}
或者init:
void initState() {
// TODO: implement initState
super.initState();
this.initJpush();
}
//监听极光推送 (自定义的方法)
//https://github.com/jpush/jpush-flutter-plugin/blob/master/documents/APIs.md
initJpush() async {
JPush jpush = new JPush();
//获取注册的id
jpush.getRegistrationID().then((rid) {
print("获取注册的id:$rid");
});
//初始化
jpush.setup(
appKey: "17d78ecf32c322db169a1d98",
channel: "theChannel",
production: false,
debug: true, // 设置是否打印 debug 日志
);
//设置别名 实现指定用户推送
jpush.setAlias("jg123").then((map) {
print("设置别名成功");
});
try {
//监听消息通知
jpush.addEventHandler(
// 接收通知回调方法。
onReceiveNotification: (Map message) async {
print("flutter onReceiveNotification: $message");
},
// 点击通知回调方法。
onOpenNotification: (Map message) async {
print("flutter onOpenNotification: $message");
},
// 接收自定义消息回调方法。
onReceiveMessage: (Map message) async {
print("flutter onReceiveMessage: $message");
},
);
} catch (e) {
print('极光sdk配置异常');
}
}
指定设备推送
sockio
var http=require('http');
var fs=require('fs'); /*fs内置的模块*/
var app=http.createServer(function(req,res){
//加载静态页面
fs.readFile('app.html',function(err,data){
res.writeHead(200,{"Content-Type":"text/html;charset='utf-8'"});
res.end(data);
})
})
//引入socket.io
var io = require('socket.io')(app);
io.on('connection', function (socket) {
console.log('服务器建立连接了');
//服务器获取客户端广播的数据
socket.on('addcart',function(data){
console.log(data);
//服务器给客户端发送数据
//socket.emit(); /*谁给我发信息我把信息广播给谁*/
//io.emit() ; /*群发 给所有连接服务器的客户都广播数据*/
//socket.emit('to-client','我是服务器的数据'+data.client);
io.emit('to-client','我是服务器的数据'+data.client)
})
});
app.listen(3000);
/*使用socket.io
1.安装
npm install socket.io
2、引入建立连接
var io = require('socket.io')(app);
io.on('connection', function (socket) {
console.log('服务器建立连接了');
});
3、在客户端 html里面引入js
http://localhost:3000/socket.io/socket.io.js
* */
android:theme="@style/Theme.AppCompat"
package com.xinxing.luckly_flutter
//import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity: FlutterFragmentActivity() {
}
FlutterIos中使用生物识别认证的一些配置
Info.plist中加入下面配置
NSFaceIDUsageDescriptionfaceid进行身份验证?