- List里面常用的属性和方法:
- Set
- Map
- forEach,map, where,any,every
- extends抽象类 和 implements
- Flutter环境搭建
- 入口文件、入口方法
- 第一个 Demo Center 组件的 使用
- 把内容单独抽离成一个组件
- 给 Text 组件增加一些装饰
- 用MaterialApp 和 Scaffold两个组件装饰 App
- Text 组件
- Container 组件
- 图片组件
- 引入本地图片
- 裁剪布局之 ClipRect、ClipRRect、ClipOval、ClipPath、CustomClipper
- 实现圆角以及实现圆形图片
- 圆形头像
- ClipOval
- ② CircleAvatar
- ③ BoxDecoration BoxShape.circle
- 圆角头像
- ① ClipRRect
- ② BoxDecoration BoxShape.rectangle
- 列表组件概述
- 列表参数
- 基本列表
- 水平列表
- 动态列表(动态循环数据)
- GridView 组件的常用参数
- GridView.count 实现网格布局
- GridView.builder 实现网格布局
- Paddiing 组件
- Row 水平布局组件
- Column 垂直布局组件
- Expanded 类似 Web 中的 Flex 布局
- Stack 组件
- Stack Align
- Stack Positioned
- AspectRatio 组件
- Card 组件
- Card 组件实现一个图文列表布局
- RaisedButton 定义一个按钮
- Wrap 组件
- 自定义有状态组件
- BottomNavigationBar 组件
- 路由
- 基本路由使用
- 基本路由跳转传值
- 命名路由
- 命名路由跳转传值
- 命名路由单独抽离到一个文件
- 返回到上一级页面
- 替换路由
- 返回到根路由
- AppBar 自定义顶部按钮图标、颜色
- AppBar 中自定义 TabBar 实现顶部 Tab 切换
- 把 TabBar 放在导航最顶部
- AppBar 中自定义 TabBar 实现 Tabs 的另一种方法。
- Flutter Drawer 侧边栏
- DrawerHeader
- UserAccountsDrawerHeader
- 侧边栏路由跳转
- 按钮组件介绍
- 按钮组件中的一些属性
- FloatingActionButton 介绍
- 常用表单介绍
- TextField 文本框组件
- Checkbox、CheckboxListTile 多选框组件
- Radio、RadioListTile 单选按钮组件
- 开关 Switch
- 日期和时间戳
- 第三方库 date_format 的使用
- 调用自带日期组件和时间组件
- 调用自带日期组件和时间组件改为中文
- 调用第三方时间组件
- 轮播图组件
- 一、AlertDialog
- 二、SimpleDialog
- 三、showModalBottomSheet
- 四、showToast
- 自定义Dialog
- 定时器
- 定时器结合 Dialog
- JSON 字符串和 Map 类型的转换(小项目)
- 使用 http 库进行网络请求
- Dio 库
- 下拉刷新和上拉分页
- 下拉刷新
- 上拉分页加载更多
- 核心代码
- 完整代码
- 解决请求重复问题
- 滚动条回到顶部
- 参考代码
- 实现一个简单的新闻 APP
- 解析 html
- WebView 组件 inappbrowser的使用
- 获取设备信息
- 使用高德定位准备工作获取 key
- 实现用高德定位
- image_picker 实现相机拍照和相册选择
- 上传图片到服务器
- 视频播放
- chewie 视频播放完整 demo
- 检测网络
- 检测网络完整 demo
- 本地存储
- 本地存储里面常用的一些方法
- 本地存储完整 demo
- 扫描二维码条形码插件
- 检测应用版本号、服务器下载文件以及实现 App 自动升级、安装
- 1、Android App 升级执行流程
- 2、升级 app 之前的准备工作配置权限
- 3、Android 升级 app 涉及的 API 库
- 4、获取版本信息
- 5、获取文件存储路径
- 6、下载文件
- 7、打开文件
- 8、注意事项
- 完整代码
- 调用 url_launcher 模块打开外部浏览器 打开外部应用 拨打电话 发送短信
- Android 修改应用名称、应用图标、应用启动画面
- **1**、Android 修改应用名称
- **2**、Android 修改应用图标
- **3**、Android 修改应用启动画面
- 竖向 ListView 嵌套横向 ListView ,以及ListView 嵌套 GridView
- 不同终端屏幕适配问题
- JSON 序列化反序列化(模型类)
- JSON字符串和Map类型的转换 dart:convert手动序列化 JSON
- 在模型类中序列化 JSON
- json_to_dart 自动生成模型类
- IndexedStack 保持页面状态
- AutomaticKeepAliveClientMixin 保持页面状态
- 通过事件打开侧边栏
- 修改主题样式
- 下拉菜单 showMenu
- 弹出底部菜单
- StatefulBuilder更新Flutter showDialog 、showModalBottomSheet 中的状态
- 状态管理
- provider库和flutter provide库
- provider 的使用
- event_bus 事件广播 事件监听
- MediaQuery.removePadding移除元素的pandding
- 瀑布流布局
- Sliver牛逼!!!
- 适配夜间模式
- 夜间模式跟随系统
- 手动开启夜间模式
- 保存用户配置
- 状态管理
- 通用夜间模式Provider Model类
- MaterialApp修改
- 登录注册案例
- Flutter SliverAppBar 隐藏/显示导航栏
- 骨架屏
- flutter 全屏背景图(包括appbar和状态栏)
- 极光推送:
- 指纹
/*
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
//用它最主要的功能就是去除数组重复内容
//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());
}
Map
/*
映射(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
/*
forEach
map
where
any
every
*/
void main(){
// List myList=['香蕉','苹果','西瓜'];
// for(var i=0;i5;
// });
// 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");
});
}
extends抽象类 和 implements
/*
Dart中抽象类: Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口。
1、抽象类通过abstract 关键字来定义
2、Dart中的抽象方法不能用abstract声明,Dart中没有方法体的方法我们称为抽象方法。
3、如果子类继承抽象类必须得实现里面的抽象方法
4、如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法。
5、抽象类不能被实例化,只有继承它的子类可以
extends抽象类 和 implements的区别:
1、如果要复用抽象类里面的方法,并且要用抽象方法约束自类的话我们就用extends继承抽象类
2、如果只是把抽象类当做标准的话我们就用implements实现抽象类
*/
Flutter环境搭建
安装最新的 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 是自定义的一个组件
第一个 Demo Center 组件的 使用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,
),
);
}
}
给 Text 组件增加一些装饰
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)
),
),
);
}
}
用MaterialApp 和 Scaffold两个组件装饰 App
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)
),
),
);
}
}
Text 组件
名称功能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
Container 组件 名称功能alignmenttopCenter:顶部居中对齐topLeft:顶部左对齐topRight:顶部右对齐center:水平垂直居中对齐centerLeft:垂直居中水平居左对齐centerRight:垂直居中水平居右对齐bottomCenter 底部居中对齐bottomLeft:底部居左对齐bottomRight:底部居右对齐decorationdecoration: BoxDecoration(color: Colors.blue,border: Border.all(color: Colors.red,width: 2.0,),borderRadius:BorderRadius.all(Radius.circular(8.0)))marginmargin 属性是表示 Container 与外部其他 组件的距离。 EdgeInsets.all(20.0),paddingpadding 就是 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 组件的常用属性:
名称类型说明alignmentalignment图片的对齐方式color 和 colorBlendMode设置图片的背景颜色,通常和 colorBlendMode 配合一起使用,这样可以是图片颜色和背景色混合。上面的图片就是进行了颜色的混合,绿色背景和图片红色的混合fitBoxFitfit 属性用来控制图片的拉伸和挤压,这都是根据父容器来的。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…不记了
裁剪布局之 ClipRect、ClipRRect、ClipOval、ClipPath、CustomClipperwidget 作用 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,
),
),
),
);
圆形头像
ClipOval
new ClipOval(
child: new Image.asset(Utils.getImgPath('ali_connors')),
)
② CircleAvatar
new CircleAvatar(
radius: 36.0,
backgroundImage: AssetImage(
Utils.getImgPath('ali_connors'),
),
)
③ BoxDecoration BoxShape.circle
new Container(
width: 72.0,
height: 72.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: AssetImage(
Utils.getImgPath('ali_connors'),
),
),
),
)
圆角头像
① ClipRRect
new ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: new Image.asset(Utils.getImgPath('ali_connors')),
)
② BoxDecoration BoxShape.rectangle
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、矩阵式列表(网格布局)
列表参数 名称类型说明scrollDirectionAxisAxis.horizontal 水平列表Axis.vertical 垂直列表paddingEdgeInsetsGeometry内边距resolvebool组件反向排序childrenList列表元素 基本列表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 实现布局。
GridView 创建网格列表有多种方式,下面我们主要介绍两种。
1、可以通过 GridView.count 实现网格布局
2、通过 GridView.builder 实现网格布局
常用属性:
名称类型说明scrollDirectionAxis滚动方法paddingEdgeInsetsGeometry内边距resolvebool组件反向排序crossAxisSpacingdouble水平子 Widget 之间间距mainAxisSpacingdouble垂直子 Widget 之间间距crossAxisCountint一行的 Widget 数量childAspectRatiodouble子 Widget 宽高比例children[ ]gridDelegateSliverGridDelegateWithFixedCrossAxisCount(常用)SliverGridDelegateWithMaxCrossAxisExtent控制布局主要用在GridView.builder 里面 GridView.count 实现网格布局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(),
);
}
}
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 {
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,
);
}
}
Paddiing 组件
在 html 中常见的布局标签都有 padding 属性,但是 Flutter 中很多 Widget 是没有 padding 属性。这个时候我们可以用 Padding 组件处理容器与子元素直接的间距。
属性说明paddingpadding 值, 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),
),
],
),
);
}
}
Row 水平布局组件
属性说明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,
)),
);
}
}
Column 垂直布局组件
属性说明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 类似 Web 中的 Flex 布局
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 或者 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
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
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 组件
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 组件
Card 是卡片组件块,内容可以由大多数类型的 Widget 构成,Card 具有圆角和阴影,这让它看起来有立体感。
属性说明margin外边距child子组件ShapeCard 的阴影效果,默认的阴影效果为圆角的长方形边。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("地址:北京市海淀区"),
)
],
),
),
],
);
}
}
Card 组件实现一个图文列表布局
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,
),
)
],
),
);
},
);
}
}
RaisedButton 定义一个按钮
Flutter 中通过 RaisedButton 定义一个按钮。RaisedButton 里面有很多的参数,这一讲我们只是简单的进行使用。
return RaisedButton(
child: Text("Flutter"),
textColor: Theme.of(context).accentColor,
onPressed: (){
});
Wrap 组件
Wrap 可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Row 表 现几乎一致。但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,mainAxis 上空 间不足时,则向 crossAxis 上去扩展显示。
属性说明direction主轴的方向,默认水平alignment主轴的对其方式spacing主轴方向上的间距textDirection文本方向verticalDirection定义了 children 摆放顺序,默认是 down,见Flex 相关属性介绍。runAlignmentrun 的对齐方式。run 可以理解为新的行或者列,如果是水平方向布局的话,run 可以理解为新的一行runSpacingrun 的间距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 组件
BottomNavigationBar 是底部导航条,可以让我们定义底部 Tab 切换,bottomNavigationBar是 Scaffold 组件的参数。
BottomNavigationBar 常见的属性
属性名说明itemsList 底 部 导 航条按钮集合iconSizeiconcurrentIndex默认选中第几个fixedColor选中的颜色typeBottomNavigationBarType.fixedBottomNavigationBarType.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
);
AppBar 自定义顶部按钮图标、颜色
属性描述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'),
);
}
}
AppBar 中自定义 TabBar 实现顶部 Tab 切换
TabBar 常见属性:
属性描述tabs显示的标签内容,一般使用 Tab 对象,也可以是其他的 WidgetcontrollerTabController 对象isScrollable是否可滚动(是指有很多个appbar时滚动appbar,左右滚动appbar。不是滚动内容)indicatorColor指示器颜色indicatorWeight指示器高度indicatorPadding底部指示器的 Paddingindicator指示器 decoration,例如边框等indicatorSize指示器大小计算方式,TabBarIndicatorSize.label 跟文字等宽,TabBarIndicatorSize.tab 跟每个 tab 等宽labelColor选中 label 颜色labelStyle选中 label 的 StylelabelPadding每个 label 的 padding 值unselectedLabelColor未选中 label 颜色unselectedLabelStyle未选中 label 的 Styleimport '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 放在导航最顶部
把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"))
],
),
],
),
),
),
);
}
}
AppBar 中自定义 TabBar 实现 Tabs 的另一种方法。
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("推荐"))
],
),
);
}
}
Flutter Drawer 侧边栏
在 Scaffold 组件里面传入 drawer 参数可以定义左侧边栏,传入 endDrawer 可以定义右侧边栏。侧边栏默认是隐藏的,我们可以通过手指滑动显示侧边栏,也可以通过点击按钮显示侧边栏。
return Scaffold(
appBar: AppBar(
title: Text("Hello Flutter"),
),
drawer: Drawer(
child: Text("左侧边栏"),
),
endDrawer: Drawer(
child: Text("右侧边栏"),
),
);
DrawerHeader
常见属性:
属性描述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(),
],
),
),
UserAccountsDrawerHeader
属性描述decoration设置顶部背景颜色accountName账户名称accountEmail账户邮箱currentAccountPicture用户头像otherAccountsPictures用来设置当前账户其他账户头像margin
drawer: Drawer(
child: Column(
children: [
UserAccountsDrawerHeader(
accountName: Text("喵喵喵?"),
accountEmail: Text("xxx@xxx.com"),
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:浮动按钮
按钮组件中的一些属性 属性名称值类型属性值onPressedVoidCallback,一般接收一个方法必填参数,按下按钮时触发的回调,接收一个方法,传 null 表示按钮禁用,会显示禁用相关样式childWidget文本控件textColorColor文本颜色colorColor按钮的颜色disabledColorColor按钮禁用时的颜色disabledTextColorColor按钮禁用时的文本颜色splashColorColor点击按钮时水波纹的颜色highlightColorColor点击(长按)按钮后按钮的颜色elevationdouble阴影的范围,值越大阴影范围越大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 介绍
FloatingActionButton简称FAB ,可以实现浮动按钮,也可以实现类似闲鱼app的地步凸起导航
属性名称属性值child子视图,一般为 Icon,不推荐使用文字tooltipFAB 被长按时显示,也是无障碍功能backgroundColor背景颜色elevation未点击的时候的阴影hignlightElevation点击时阴影值,默认 12.0onPressed点击事件回调shape可以定义 FAB 的形状等mini是否是 mini 类型默认 false 常用表单介绍Flutter 中常见的表单有 TextField 单行文本框,TextField 多行文本框、CheckBox、Radio、SwitchCheckboxListTile、RadioListTile、SwitchListTile、Slide.
TextField 文本框组件TextField 表单常见属性:
属性描述maxLines设置此参数可以把文本框改为多行文本框onChanged文本框改变的时候触发的事件decorationhintText 类似 html 中的 placeholderborder 配置文本框边框 OutlineInputBorder 配合使用labelText lable 的名称labelStyle 配置 lable 的样式obscureTextobscureTextcontrollercontroller 结合 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、CheckboxListTile 多选框组件
Checkbox 常见属性:
属性描述valuetrue 或者 falseonChanged改变的时候触发的事件activeColor选中的颜色、背景颜色checkColor选中的颜色、Checkbox 里面对号的颜色CheckboxListTile 常见属性:
属性描述valuetrue 或者 falseonChanged改变的时候触发的事件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、RadioListTile 单选按钮组件
Radio 常用属性:
属性描述value单选的值onChanged改变时触发activeColor选中的颜色、背景颜色groupValue选择组的值RadioListTile 常用属性:
属性描述valuetrue 或者 falseonChanged改变的时候触发的事件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,
)
开关 Switch
属性描述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));
第三方库 date_format 的使用
文档: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 list = [
{"url": "https://www.itying.com/images/flutter/1.png"},
{"url": "https://www.itying.com/images/flutter/2.png"},
{"url": "https://www.itying.com/images/flutter/3.png"}
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('轮播图组件演示'),
),
body: Column(
children: [
Container(
width: double.infinity,
child: AspectRatio(
aspectRatio: 16 / 9,
child: new Swiper(
itemBuilder: (BuildContext context, int index) {
return new Image.network(
this.list[index]["url"],
fit: BoxFit.fill,
);
},
itemCount: list.length,
pagination: new SwiperPagination(),
autoplay: true,
// control: new SwiperControl(),
),
),
)
],
),
);
}
}
一、AlertDialog
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');
},
)
],
);
});
二、SimpleDialog
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');
},
)
],
);
});
三、showModalBottomSheet
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');
},
)
],
),
);
});
四、showToast
https://pub.dev/packages/fluttertoast
Fluttertoast.showToast(
msg: "This is Short Toast",
toastLength: Toast.LENGTH_SHORT,
timeInSecForIos: 1);
自定义Dialog
自定义 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();
});
}
定时器结合 Dialog
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),
)
],
)
],
),
),
),
);
}
}
JSON 字符串和 Map 类型的转换(小项目)
(小项目直接转,大项目用模型类,笔记在下面)
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 类型
使用 http 库进行网络请求
请参考官方文档: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 库
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 {
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 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.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();
});
}
}
实现一个简单的新闻 APP
涉及的 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 {
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: [
Text(this.list.length > 0 ? this.list[0]["content"] : "")
],
),
),
);
}
}
解析 html
https://pub.dev/packages/flutter_html
鸡肋。。。只能解析部分HTML标签
WebView 组件 inappbrowser的使用建议使用WebView_flutter官方库,inappbrowser翘辫子了
https://pub.dev/packages/flutter_inappbrowser
ios模拟器测试失败。。。
获取设备信息https://pub.dev/packages/device_info
使用高德定位准备工作获取 key1、申请成为开发者
2、创建应用配置获取 Key (参考教程演示)
https://lbs.amap.com/api/android-sdk/guide/create-project/get-key
实现用高德定位https://pub.dev/packages/amap_location
image_picker 实现相机拍照和相册选择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 很好用,不过回放功能暂时不打算改了,好不容易调完
chewie 视频播放完整 demoimport '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
检测网络完整 demoimport '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();//清空键值对
本地存储完整 demo
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
错误多多,修改大大,不做记录。。。用到再说
检测应用版本号、服务器下载文件以及实现 App 自动升级、安装 1、Android App 升级执行流程1、获取本地版本号
2、请求服务器获取服务器版本号
3、本地版本和服务器版本不一致提示升级,弹窗提示用户是否更新
4、用户确定升级,调用文件传输方法下载 apk 文件
5、监听下载进度
6、下载完成打开 Apk 进行安装
注意:在 Ios 中没法直接下载安装,如果版本不一致直接跳转到 Ios 应用对应的应用市场就可以了。
配置版本号: (Flutter应用获取的不是这里的版本号,在pubspec.yaml文件)
2、升级 app 之前的准备工作配置权限
配置 AndroidMenifest.xml 文件
3、Android 升级 app 涉及的 API 库
插件名称描述插件地址package_info检测版本号https://pub.dev/packages/package_infopath_provider获取文件存储路径https://pub.dev/packages/path_providerflutter_downloaderflutter_downloaderhttps://pub.dev/packages/flutter_downloaderopen_file打开文件插件https://pub.dev/packages/open_file
4、获取版本信息
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}");
5、获取文件存储路径
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}");
6、下载文件
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)
7、打开文件
https://pub.dev/packages/open_file
OpenFile.open("${_localPath}/jdshop.apk");
8、注意事项
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升级演示"),
);
}
}
调用 url_launcher 模块打开外部浏览器 打开外部应用 拨打电话 发送短信
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 修改应用名称、应用图标、应用启动画面 1、Android 修改应用名称Android 是在 android ▸ app ▸ src ▸ main▸ AndroidManifest.xml 中修改 android:label=“XXX”;
2、Android 修改应用图标Android 在 android ▸ app ▸ src ▸ res ▸ mipmap 下面对应的文件夹中替换相应图片
3、Android 修改应用启动画面Android 添加启动界面
打开文件 android/app/src/main/res/drawable/launch_background.xml
修改内容,打开注释了的代码 launch_image 那段。
里面的ic_launch.png是图标,启动画面添加launch_image.png,格式要求png
注意@mipmap/launch_image 就是你要设置的启动界面的图片资源名字,你要放置到对应的文件夹里面
密度代表分辨率ldpi240 x 320mdpi320 x 480hdpi480 x 800xhdpi720 x 1280xxhdpi1080 x 1920xxxhdpi3840×2160 竖向 ListView 嵌套横向 ListView ,以及ListView 嵌套 GridView1、竖向 ListView 嵌套横向 ListView 注意事项:
在竖向 ListView 中嵌套横向 ListView 的时候要注意给横向 ListView 外层加一个容器,然后外层这个容器要设置高度,外层这个容器可以是 SizedBox ,也可以是 Container。
2、ListView 嵌套 GridView 注意事项:
由于 GridView 和 ListView 都是可以滚动的组件,所以嵌套的时候要注意把里面的组件改为不可滚动组件。
重要属性:
shrinkWrap: true, //解决无限高度问题
physics:NeverScrollableScrollPhysics(), //禁用滑动事件
不同终端屏幕适配问题我写的代码还用适配???
JSON 序列化反序列化(模型类)1、使用 dart:convert 手动序列化 JSON
2、模型类中序列化 JSON
小项目中使用 dart:convert 手动序列化 JSON 非常好,也非常快速。但是随着项目的增大,dart:convert 手动序列化 JSON 的话失去了大部分静态类型语言特性:类型安全、自动补全和最重要的编译时异常。这样一来,我们的代码可能会变得非常容易出错。
当我们访问 name 或 email 字段时,我们输入的很快,导致字段名打错了。但由于这个 JSON 在 map 结构中,所以编译器不知道这个错误的字段名。
为了解决上面的问题在大型项目中使用的更多的是在模型类中序列化 JSON。
JSON字符串和Map类型的转换 dart:convert手动序列化 JSONimport 'dart:convert';
var mapData = {"name": "张三", "age": "20"};
var strData = '{"name":"张三","age":"20"}';
print(json.encode(mapData)); //Map转换成Json字符串
print(json.decode(strData)); //Json 字符串转化成 Map 类型
在模型类中序列化 JSON
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/
json_to_dart 自动生成模型类https://javiercbk.github.io/json_to_dart/
IndexedStack 保持页面状态IndexedStack 和 Stack 一样,都是层布局控件, 可以在一个控件上面放置另一个控件,但唯一不同的是 IndexedStack 在同一时刻只能显示子控件中的一个控件,通过 Index 属性来设置显示的控件。
IndexedStack 来保持页面状态的优点就是配置简单。IndexedStack 保持页面状态的缺点就是不方便单独控制每个页面的状态。
body: IndexedStack(
index: this._currentIndex,
children: [],
),
AutomaticKeepAliveClientMixin 保持页面状态
花里胡哨。。。
通过事件打开侧边栏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 ),
);
下拉菜单 showMenu
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笔记
StatefulBuilder更新Flutter showDialog 、showModalBottomSheet 中的状态参考:https://www.cflutter.com/topic/5d202202403aa10564178c65
状态管理通俗的讲:当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用 Flutter 中的状态管理来管理统一的状态(数据),实现不同组件直接的传值和数据共享。
现在 Flutter 的状态管理方案很多,redux、bloc、state、provide、provider。
目前我们推荐使用 provider,这个是官方提供的状态管理解决方案。相比其他状态管理库使用起来比较方便。
provider库和flutter provide库provider 是 Flutter 团队推出的状态管理模式。
官方地址为:https://pub.dev/packages/provider
注意:provider 和 provide 是两个库哦。Flutter 官方推荐使用的是 provider 哦,provider 是flutter 官方出的。provide 不是 Flutter 官方写的哦。
provider 的使用(官方文档为准,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();
}
}
event_bus 事件广播 事件监听
花里胡哨。。。
MediaQuery.removePadding移除元素的pandding通过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,
)
Sliver牛逼!!!
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()
- 这种方式是自动跟随iOS/Android的系统设置来切换的,无需用户再单独设置
上述的跟随系统自动切换暗黑模式的体验可能并不是很好,比如用户不喜欢夜间模式或者App的夜间模式配色适配并不是很好,这就会导致用户无法手动控制app的夜间模式或者只能关闭系统的设置。因此我们可以增加手动控制以及跟随系统的选项,让用户选择是否开启以及开启的方式。
保存用户配置在flutter中可以使用shared_preferences来保存用户的配置数据,具体使用方法详见:shared_preferences使用
状态管理主题的手动切换是影响全局的,如果通过常规的数据流向很难做到。常见的几种状态管理:
- InheritedWidget
- Scoped model
- BLoC
- Redux
- Provider
Provider是Google I/O 2019大会宣布的现在官方推荐的状态管理方式,我们需要在设置页里面通过用户设置,把变更状态共享给其他Widget,这里采用Provider这种方式来实现状态共享。
通用夜间模式Provider Model类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修改
如果手动控制是否开启夜间模式,可以设置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("注册新账号"),
),
),
],
),
),
),
);
}
}
Flutter SliverAppBar 隐藏/显示导航栏
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
flutter 全屏背景图(包括appbar和状态栏)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进行身份验证?