赞
踩
目录
四、网络请求AJAX,在这里我们不使用flutter自带的,我们使用第三方插件进行网络请求(dio插件进行请求)
七、数据序列化(数据很多,不方便根据数据类型一一处理的时候,使用数据序列化,事半功倍)
八、下拉刷新组件(RefreshIndicator)和上拉加载
九、时间格式化,比如:一小时之前,刚刚等等,这里我们借助第三方插件timeago
十二、sliver的使用,页面上滑,将作者和相关信息固定在顶部不消失,实现代码如下,讲解一下
十四、flutter中的输入框,TextField,页面中的Expanded是自适应宽度
布局分Column(竖向)和Row(横向)
- //开启一个定时器
- var _timer;
- _timer = Timer.periodic(
- Duration(seconds: 1),
- (timer) {
- print('表示1秒执行一次')
- },
- );
-
- //关闭一个定时器
- _timer.cancel();
- // flutter的生命周期
- // 1、初始化状态,可以获取数据等等操作
- @override
- void initState() {
- super.initState();
- }
-
- // 2、构建dom的状态
- // @override
- // Widget build(){}
-
-
- // 3、页面销毁的状态(离开页面将要销毁的时候,相当于vue的beforedetroy)
- @override
- void dispose() {
- super.dispose();
- }
- //引入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();
实现过程:使用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),
- ),
- ),
- @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(() {
-
- // });
- }
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() => ' ';
- }
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();
- }
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;
- }
- }
- // 点击打开一个底部弹窗
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return ShareSheet();
- },
- ),
-
- //关闭弹窗 是一个路由
- Navigator.pop(context),
- 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),
- },
- ),
- ),
- ),
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。