目录
四、网络请求AJAX,在这里我们不使用flutter自带的,我们使用第三方插件进行网络请求(dio插件进行请求)
七、数据序列化(数据很多,不方便根据数据类型一一处理的时候,使用数据序列化,事半功倍)
八、下拉刷新组件(RefreshIndicator)和上拉加载
九、时间格式化,比如:一小时之前,刚刚等等,这里我们借助第三方插件timeago
十二、sliver的使用,页面上滑,将作者和相关信息固定在顶部不消失,实现代码如下,讲解一下
十四、flutter中的输入框,TextField,页面中的Expanded是自适应宽度
一、flutter内的布局
布局分Column(竖向)和Row(横向)
二、flutter内的倒计时 periodic
//开启一个定时器
var _timer;
_timer = Timer.periodic(
Duration(seconds: 1),
(timer) {
print('表示1秒执行一次')
},
);
//关闭一个定时器
_timer.cancel();
三、flutter的生命周期
// flutter的生命周期
// 1、初始化状态,可以获取数据等等操作
@override
void initState() {
super.initState();
}
// 2、构建dom的状态
// @override
// Widget build(){}
// 3、页面销毁的状态(离开页面将要销毁的时候,相当于vue的beforedetroy)
@override
void dispose() {
super.dispose();
}
四、网络请求AJAX,在这里我们不使用flutter自带的,我们使用第三方插件进行网络请求(dio插件进行请求)
//引入dio第三方库
import 'package:dio/dio.dart';
import 'package:newtoutiao/moudle/config.dart';
import 'package:shared_preferences/shared_preferences.dart';
Dio dio = new Dio(); //创建实例
class PubMOdule {
static httpRequestApi(method, url, [data]) async {
// SharedPreferences第三方的存取和获取token的插件,prefs里有get获取token和set存储token
SharedPreferences prefs = await SharedPreferences.getInstance();
try {
// 设置请求头
dio.options.headers['Autoken'] =
prefs.getString('token') ?? ''; //获取token,如果没有token就默认为空
Response response;
switch (method) {
case "get":
response = await dio.get(Config.baseUrl + url);
break;
case "post":
response = await dio.post(Config.baseUrl + url, data: data);
break;
}
return response;
} catch (e) {
print(e);
}
}
static checkToken() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('token',
'qwedd123412erwedwe2'); //存储token,这是正常的请求接口的操作,我们没有请求接口,造假数据进行测试
return prefs.getString('token'); //由于没有请求数据,我们造假数据
}
}
补充一下flutter自带的网络请求
// 获取请求的状态码
// response.statusCode 比如200(成功)404(找不到)403(禁止访问)502(服务器错误)等等
// 官方提供的http请求方式(官方提供的方法太笨重,我们使用第三方的插件进行http请求,不推荐使用)
// 1.引入io
// 2.建立client
// var httpClient = new HttpClient();
// // 3.构造URI
// var uri = new Uri.http('example.com', '/app', {'name': '名字'});
// // 4.发起请求
// var req = await httpClient.getUrl(uri);
// // 5.关闭请求等待
// var res = await req.close();
五、底部TabBar的组件
实现过程:使用Scaffold脚手架构建body和底部导航bottomNavigationBar,通过currentIndex确认点击的序号,通过onTap进行动态切换
import 'package:flutter/material.dart';
import 'package:newtoutiao/news/news.dart';
import 'package:newtoutiao/question/question.dart';
import 'package:newtoutiao/user/user.dart';
import 'package:newtoutiao/video/video.dart';
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
// 创建一个数组
int _index = 0;
List _bodys = [News(), Question(), Video(), User()];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _bodys[_index],
// 底部导航bottomNavigationBar
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.question_answer),
label: '问答',
),
BottomNavigationBarItem(
icon: Icon(Icons.video_label),
label: '视频',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
label: '我的',
),
],
type: BottomNavigationBarType.fixed, //使底部tab自适应
currentIndex: _index, //确认当期点击的是哪个tab
// 底部tab的点击事件
onTap: (value) => {
print(value),
setState(
() => {_index = value},
),
},
),
);
}
}
六、路由的跳转和返回
1、未在main.dart中定义的路由的跳转方式,说白了就是覆盖将需要跳转的路由放到最上面
Navigator.push(context,MaterialPageRoute(builder: (context) => NewsDetails(widget.id),),),
路由的返回,就是将最上面的路由删除拿掉
Navigator.pop(context),
2、在main.dart中定义的路由的跳转
Navigator.pushNamed(context, '/search')},
返回和上面一样
七、数据序列化(数据很多,不方便根据数据类型一一处理的时候,使用数据序列化,事半功倍)
1、定义一个class类,将返回的数据都在这个类里面进行声明,以下数据都是接口返回的字段
class Artical {
String artId;
String title;
int autId;
String autName;
int commCount;
int isTop;
int imgType;
String pubdate;
List images;
Artical.fromJson(json) {
artId = json['art_id'];
title = json['title'];
autId = json['aru_id'];
autName = json['aru_name'];
commCount = json['comm_count'];
isTop = json['is_top'];
imgType = json['img_type'];
pubdate = json['pubdate'];
images = json['images'];
}
}
2、在页面中引用
3、定义变量,并格式化数据,然后在dom里就可以直接使用了
List<Artical> _list = [];
_getData([type]) async {
var data = await PubMOdule.httpRequestApi(
'post', '/getArticals', {'id': widget.id, 'page': page});
List jsonlist = data.data['data']['results']; //接口返回的数据
List<Artical> listData =
jsonlist.map((value) => Artical.fromJson(value)).toList(); //将数据遍历格式化
if (type == 1) {
setState(() {
_list.addAll(listData); //如果向实例里添加,需要在声明的变量前加上实例名称
});
} else {
setState(() {
_list = listData;
});
}
}
@override
Widget build(BuildContext context) {
// RefreshIndicator下拉刷新
return RefreshIndicator(
onRefresh: _refresh,
child: Padding(
padding: EdgeInsets.all(15.0),
child: ListView.builder(
itemCount: _list.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewsDetails(_list[index].artId)));
},
child: NewsItem(_list[index]), //通过组件传值传入无状态组件内
);
},
// controller上拉加载
controller: _controller,
),
),
);
}
}
4、在无状态组件内接收,然后使用
class NewsItem extends StatelessWidget {
final Artical artical; //在这里接收
NewsItem(this.artical);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
artical.imgType == 1
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(
artical.title, //正常使用即可
style: TextStyle(color: Colors.black, fontSize: 18.0),
),
),
八、下拉刷新组件(RefreshIndicator)和上拉加载
@override
Widget build(BuildContext context) {
// RefreshIndicator下拉刷新
return RefreshIndicator(
onRefresh: _refresh, //下拉刷新函数
child: Padding(
padding: EdgeInsets.all(15.0),
child: ListView.builder(
itemCount: _list.length, //页面上tabBar的数量
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewsDetails(_list[index].artId)));
},
child: NewsItem(_list[index]),
);
},
// controller上拉加载
controller: _controller,
),
),
);
}
//上拉加载
ScrollController _controller = ScrollController(); // 上拉加载更多
//在初始化的生命周期内创建监听
@override
void initState() {
super.initState();
_getData();
// 上拉加载更多的监听
_controller.addListener(() {
var maxScroll = _controller.position.maxScrollExtent;//上拉的距离
var pixels = _controller.position.pixels; //触发刷新的距离
if (maxScroll == pixels) {
//s刷新了
_getData(1);
}
});
}
//下拉刷新 定义一个函数,重新获取数据即可
// 下拉刷新
Future _refresh() async {
//走接口
_getData();
// setState(() {
// });
}
九、时间格式化,比如:一小时之前,刚刚等等,这里我们借助第三方插件timeago
1、引入timeago插件
2、直接使用
import 'package:timeago/timeago.dart' as timeago; //处理时间,多久之前
TextSpan(
text: timeago.format(DateTime.parse(artical.pubdate)), //直接使用即可
style: TextStyle(
olor: Colors.grey,
),
)
3、默认是英文的,如果想调为中文,需要手动改插件的文件,按住Ctrl,鼠标点击format,跳转进一个文件,找到EnMessages(),再次Ctrl+鼠标左键,又进入一个文件,如下,然后你将英文改为中文即可
import 'package:timeago/src/messages/lookupmessages.dart';
/// English Messages
class EnMessages implements LookupMessages {
@override
String prefixAgo() => '';
@override
String prefixFromNow() => '';
@override
String suffixAgo() => 'ago';
@override
String suffixFromNow() => 'from now';
@override
String lessThanOneMinute(int seconds) => 'a moment';
@override
String aboutAMinute(int minutes) => 'a minute';
@override
String minutes(int minutes) => '$minutes minutes';
@override
String aboutAnHour(int minutes) => 'about an hour';
@override
String hours(int hours) => '$hours hours';
@override
String aDay(int hours) => 'a day';
@override
String days(int days) => '$days days';
@override
String aboutAMonth(int days) => 'about a month';
@override
String months(int months) => '$months months';
@override
String aboutAYear(int year) => 'about a year';
@override
String years(int years) => '$years years';
@override
String wordSeparator() => ' ';
}
/// English short Messages
class EnShortMessages implements LookupMessages {
@override
String prefixAgo() => '';
@override
String prefixFromNow() => '';
@override
String suffixAgo() => '';
@override
String suffixFromNow() => '';
@override
String lessThanOneMinute(int seconds) => 'now';
@override
String aboutAMinute(int minutes) => '1 min';
@override
String minutes(int minutes) => '$minutes min';
@override
String aboutAnHour(int minutes) => '~1 h';
@override
String hours(int hours) => '$hours h';
@override
String aDay(int hours) => '~1 d';
@override
String days(int days) => '$days d';
@override
String aboutAMonth(int days) => '~1 mo';
@override
String months(int months) => '$months mo';
@override
String aboutAYear(int year) => '~1 yr';
@override
String years(int years) => '$years yr';
@override
String wordSeparator() => ' ';
}
十、边栏组件的使用(Drawer)
1、使用 ListView列表排列
2、DrawerHeader 边栏的标题或者名字之类的
3、ListTile 边栏子类的标题
4、Wrap 横向排列,Wrap同样可以横向排列,一行放不下可以自动换行,这是和row的区别
5、Chip 小徽标
import 'package:flutter/material.dart';
class DrawerList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Drawer(
elevation: 0.0,
child: ListView(
children: [
DrawerHeader(
decoration: BoxDecoration(color: Colors.blue),
child: Center(
child: SizedBox(
width: 60.0,
height: 60.0,
child: CircleAvatar(
child: Text('张三'),
),
),
),
),
ListTile(
title: Text(
'我的频道',
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.normal,
),
),
trailing: Container(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 1.0),
width: 50.0,
height: 25.0,
decoration: BoxDecoration(
border: Border.all(
color: Colors.red,
),
borderRadius: BorderRadius.circular(20.0),
),
child: Text(
'编辑',
style: TextStyle(color: Colors.red),
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 15.0),
// Wrap同样可以横向排列,一行放不下可以自动换行,这是和row的区别
child: Wrap(
spacing: 15.0, //每一个小Chip的间隔
children: [
Chip(
label: Text('html'),
onDeleted: () => {print('删除')},
),
Chip(
label: Text('css'),
onDeleted: null,
),
Chip(
label: Text('app'),
onDeleted: () => {print('删除')},
),
Chip(
label: Text('js'),
onDeleted: null,
),
Chip(
label: Text('vue'),
onDeleted: null,
),
],
),
),
ListTile(
title: Text(
'频道推荐',
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.normal,
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 15.0),
// Wrap同样可以横向排列,一行放不下可以自动换行,这是和row的区别
child: Wrap(
spacing: 15.0, //每一个小Chip的间隔
children: [
FilterChip(
avatar: CircleAvatar(
backgroundColor: Colors.grey,
child: Text(
'+',
style: TextStyle(color: Colors.white, fontSize: 16.0),
),
),
label: Text('java'),
onSelected: (value) => {print('添加')},
),
],
),
),
],
),
);
}
}
十一、子组件调用父组件的方法
1、首先先将父组件的方法传递给子组件
@override
Widget build(BuildContext context) {
// 如果tabBarList是空不加载tab页面,优化设计
return tabBarList.length == 0
? SizedBox()
: DefaultTabController(
length: tabBarList.length,
child: Scaffold(
appBar: AppBar(
title: SearchBtn(),
elevation: 0.0,
// PreferredSize一般用在顶部tabBar或者底部tabBar,进行包裹,preferredSize是距离顶部的距离或者距离底部的距离
bottom: PreferredSize(
preferredSize: Size.fromHeight(50.0),
child: TabBarBtn(tabBarList),
)),
body: TabBarBody(tabBarList),
drawer: DrawerList1(_getChannels), //子组件调用父组件的方法
),
);
}
2、子组件接收VoidCallback
class DrawerList1 extends StatefulWidget {
final VoidCallback refresh; //接收父组件传递过来的方法
DrawerList1(this.refresh);//接收父组件传递过来的方法
@override
_DrawerList1State createState() => _DrawerList1State();
}
3、执行时间,在页面销毁的时候
// 页面关闭的时候的回调
@override
void dispose() {
super.dispose();
widget.refresh();
}
十二、sliver的使用,页面上滑,将作者和相关信息固定在顶部不消失,实现代码如下,讲解一下
1、主要是创建_SliverAppBarDelegate,依赖SliverPersistentHeaderDelegate
2、将页面内传递过来的child接收并且返回
3、minExtent和maxExtent固定的高
import 'package:flutter/material.dart';
import 'package:newtoutiao/news/comment.dart';
import 'package:newtoutiao/news/share_sheet.dart';
class NewsDetails extends StatefulWidget {
final String id;
NewsDetails(this.id);
@override
_NewsDetailsState createState() => _NewsDetailsState();
}
class _NewsDetailsState extends State<NewsDetails> {
@override
Widget build(BuildContext context) {
// CircularProgressIndicator()//正在加载,转圈圈
// 使用slivers组件,实现的功能是当内容向上滚动的时候,可以将题头放入TabBar上
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: false,
elevation: 0.0,
expandedHeight: 80.0,
title: Text('海上生明月,天涯共此时,劝君更尽一杯酒'),
actions: [
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () => {
print('点击了举报'),
// 点击打开一个底部弹窗
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
print(context);
return ShareSheet();
})
},
),
],
),
SliverList(
delegate: SliverChildListDelegate(
[
Padding(
padding: EdgeInsets.all(15.0),
child: Text(
'海上生明月,天涯共此时,劝君更尽一杯酒',
style: TextStyle(
color: Colors.blue,
fontSize: 16.0,
fontWeight: FontWeight.w600),
),
),
],
),
),
//作者
SliverPersistentHeader(
pinned: true, //讲设置的栏固定
delegate: _SliverAppBarDelegate(
child: Container(
color: Colors.blue,
),
),
),
// 内容 dart内可以使用三个引号去除文字的自有格式
SliverList(
delegate: SliverChildListDelegate(
[
Padding(
padding: EdgeInsets.all(15.0),
child: Text(
'''新京报快讯 据滴滴出行官博消息,我们关注到网上关于“长沙22岁女生乘网约车后失联”事件后立即进行核实。乘客于1月22日凌晨1点51分通过滴滴叫车,起点是伏苓冲路某园区东门,凌晨2点乘客上车,司机开始行程,于凌晨2点19分到达目的地猴子石大桥某公交站。''',
style: TextStyle(
color: Colors.black,
fontSize: 14.0,
fontWeight: FontWeight.normal),
),
),
Padding(
padding: EdgeInsets.all(15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'猜你喜欢',
style: TextStyle(
color: Colors.black,
fontSize: 16.0,
fontWeight: FontWeight.w600,
),
),
SizedBox(
height: 10.0,
),
Wrap(
children: [
Container(
// MediaQuery.of(context).size.width / 2 - 20 计算整个屏幕一般的宽度
// color: Colors.red,
margin: EdgeInsets.only(bottom: 10.0),
width: MediaQuery.of(context).size.width / 2 - 20,
child: Text(
'月薪过万的职业,永远不用加班的职业永远不用加班的职业',
maxLines: 1, //最多只有一行
overflow: TextOverflow.ellipsis, //超过使用省略号显示
),
),
Container(
// color: Colors.pink,
margin: EdgeInsets.only(bottom: 10.0),
width: MediaQuery.of(context).size.width / 2 - 20,
child: Text(
'永远不用加班的职业,躺着就能挣钱的职业,躺着就能挣钱的职业',
maxLines: 1, //最多只有一行
overflow: TextOverflow.ellipsis, //超过使用省略号显示
),
),
Container(
margin: EdgeInsets.only(bottom: 10.0),
width: MediaQuery.of(context).size.width / 2 - 20,
child: Text(
'躺着就能挣钱的职业',
maxLines: 1, //最多只有一行
overflow: TextOverflow.ellipsis, //超过使用省略号显示
),
),
Container(
margin: EdgeInsets.only(bottom: 10.0),
width: MediaQuery.of(context).size.width / 2 - 20,
child: Text(
'如何教育下一代',
maxLines: 1, //最多只有一行
overflow: TextOverflow.ellipsis, //超过使用省略号显示
),
),
Container(
margin: EdgeInsets.only(bottom: 10.0),
width: MediaQuery.of(context).size.width / 2 - 20,
child: Text(
'二胎之后我们何去何从',
maxLines: 1, //最多只有一行
overflow: TextOverflow.ellipsis, //超过使用省略号显示
),
),
Container(
margin: EdgeInsets.only(bottom: 10.0),
width: MediaQuery.of(context).size.width / 2 - 20,
child: Text(
'两个孩子刚刚好',
maxLines: 1, //最多只有一行
overflow: TextOverflow.ellipsis, //超过使用省略号显示
),
),
],
)
],
),
),
Comment(),
SizedBox(
height: 35.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
onTap: () => {
print('喜欢'),
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 20.0, vertical: 5.0),
decoration: BoxDecoration(
border: Border.all(
width: 1.0,
color: Colors.red,
),
borderRadius: BorderRadius.circular(25.0),
),
child: Row(
children: [
Icon(
Icons.thumb_up,
color: Colors.red,
),
SizedBox(
width: 5.0,
),
Text(
'喜欢',
style: TextStyle(color: Colors.red),
),
],
),
),
),
GestureDetector(
onTap: () => {
print('不喜欢'),
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 15.0, vertical: 5.0),
decoration: BoxDecoration(
border: Border.all(width: 1.0),
borderRadius: BorderRadius.circular(25.0)),
child: Row(
children: [
Icon(Icons.delete),
Text('不喜欢'),
],
),
),
),
],
),
],
),
),
],
),
);
}
}
//实现功能,当文字向上滚动,把标题和作者映射到tabBar上
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({this.child}); //带{}传值是带有参数的
final Widget child;
@override
double get minExtent => 80.0; //此组件覆盖的的最小高度,当上去的时候是80
@override
double get maxExtent => 80.0; //此组件覆盖的的最大高度 当在自己的位置的时候是80
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return SizedBox(
child: this.child,
);
}
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}
十三、flutter中的弹框
// 点击打开一个底部弹窗
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return ShareSheet();
},
),
//关闭弹窗 是一个路由
Navigator.pop(context),
十四、flutter中的输入框,TextField,页面中的Expanded是自适应宽度
Expanded(
child: Container(
height: 30.0,
decoration: BoxDecoration(
border: Border.all(
color: Colors.black12,
),
borderRadius: BorderRadius.circular(30.0),
),
child: TextField(
decoration: InputDecoration(
hintText: '写评论',
hintStyle: TextStyle(
fontSize: 14.0,
color: Colors.black54,
),
contentPadding:
EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.5),
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
),
onSubmitted: (value) => {
print(value),
},
onChanged: (value) => {
print(value),
},
),
),
),