前言
上篇文章介绍了展示列表的构建. 片尾预告了这篇文章的内容, 主要包括列表的刷新,加载, 导航跳转详情web页面. 网络数据请求. 绑定数据. 效果图如下:
网络请求
使用开源库dio: ^1.0.6进行的网络请求.简单易用.另附一个三方库查询地址地址. 方便查找需要用到的资源与轮子
pubspec.yaml上添加dio库.进行一下简单的封装, 新建一个apiUtils.dart文件, 如下
import 'package:dio/dio.dart';
import 'dart:async';
var dio = new Dio();
class ApiUtils {
static Future get(String url,{Map<String,dynamic> params}) async{
var response = await dio.get(url, data: params);
return response.data;
}
static Future post(String url,Map<String,dynamic> params) async{
var response = await dio.post(url, data: params);
return response.data;
}
}
然后在已入该文件进行网络请求
Future<Map> _getListData([Map<String, dynamic> params]) async {
// URL地址
const juejin_flutter =
'https://timeline-merger-ms.juejin.im/v1/get_tag_entry?src=web&tagId=5a96291f6fb9a0535b535438';
var pageIndex = (params is Map) ? params['pageIndex'] : 0;
// 参数
final _param = {'page': pageIndex, 'pageSize': 20, 'sort': 'rankIndex'};
// 返回结果
var response = await ApiUtils.get(juejin_flutter, params: _param);
var responseList = response['d']['entrylist'];
var pageTotal = response['d']['total'];
var pageSize = 20;
if (!(pageTotal is int) || pageTotal <= 0) {
pageTotal = 0;
}
pageIndex += 1;
List resultList = new List();
for (int i = 0; i < responseList.length; i++) {
try {
// json数据转化model
NewsModel cellData = new NewsModel.fromJson(responseList[i]);
resultList.add(cellData);
} catch (e) {
// No specified type, handles all
}
}
// 刷新页面
setState(() {
items.addAll(resultList);
});
// 自定义数据
Map<String, dynamic> result = {
"list": resultList,
'total': pageTotal,
'pageIndex': pageIndex,
'pageSize': pageSize,
};
return result;
}
其中NewsModel为数据model, 具体内容跟数据请求格式对应 代码如下:
class NewsModel {
bool hot;
String isCollection;
String tag;
String username;
int collectionCount;
int commentCount;
String title;
String detailUrl;
NewsModel(
{this.hot,
this.tag,
this.username,
this.collectionCount,
this.commentCount,
this.title,
this.detailUrl,
this.isCollection});
factory NewsModel.fromJson(Map<String, dynamic> json) {
String _tag = '';
if (json['tags'].length > 0) {
_tag = '${json['tags'][0]['title']}/';
}
return NewsModel(
hot: json['hot'],
collectionCount: json['collectionCount'],
commentCount: json['commentsCount'],
tag: '$_tag${json['category']['name']}',
username: json['user']['username'],
title: json['title'],
detailUrl: json['originalUrl'],
isCollection: json['type'],
);
}
}
上面代码可以基本完成了一个get方法的网络请求.下面请求下来数据, 就要进行数据绑定
数据绑定与列表刷新加载
一个app中会存在多个list, 这样只要我们封装好一个通用的list, 任何页面就可以使用. 论封装的重要性. 接下来创建一个commonLIst.dart文件作为通用list, 需要实现内容:
- 1, 实现刷新,请求数据
- 2, 加载提示,请求数据
- 3, 传入属性包括(返回的每行组件cell, 头部视图, 请求数据函数), 目前这些
参数设置
class CommonList extends StatefulWidget {
final renderItem;
final requestApi;
final headerView;
const CommonList([this.requestApi, this.renderItem, this.headerView])
: super();
@override
State<StatefulWidget> createState() {
return new CommonListState();
}
}
返回的主要代码, 一看就懂, 我就不介绍了.
bool isLoading = false; // 是否正在请求数据中,
bool _hasMore = true; // 是否还有更多数据可加载
int _pageIndex = 0; // 页面的索引
int _pageTotal = 0; // 数据总数
int _pageSize = 20; // 页面数量
List items = new List();
ScrollController _scrollController = new ScrollController();
// 监听滑到最底部, 进行网络请求
@override
void initState() {
super.initState();
_getMoreData();
_scrollController.addListener(() {
// 如果下拉的当前位置到scroll的最下面
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
@override
void dispose() {
super.dispose();
_scrollController.dispose();
}
Future _getMoreData() async {
// 如果加载数据loading为true,同时还有更多数据需要加载
if (!isLoading && _hasMore) {
setState(() {
isLoading = true;
});
List newEntries = await mokeHttpRequest();
_hasMore = ((_pageIndex + 1) * _pageSize <= _pageTotal);
// 状态加载完成执行
if (this.mounted) {
setState(() {
items.addAll(newEntries);
isLoading = false;
});
}
} else if (!isLoading && !_hasMore) {
}
}
// 数据请求
Future<List> mokeHttpRequest() async {
if (widget.requestApi is Function) {
// 传入索引值
final listObj = await widget.requestApi({'pageIndex': _pageIndex});
// pageIndex, total, list. 网络请求返回值
_pageIndex = listObj['pageIndex'];
_pageTotal = listObj['total'];
_pageSize = listObj['pageSize'];
return listObj['list'];
} else {
// 如没有请求网络方法, 则延迟2s返回空数组
return Future.delayed(Duration(seconds: 2), () {
return [];
});
}
}
@override
Widget build(BuildContext context) {
// RefreshIndicator为实现刷新加载使用组件
return new RefreshIndicator(
child: ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == 0 && index != items.length && widget.headerView is Function) {
return widget.headerView();
}
if (index == items.length) {
return _buildProgressIndicator();
} else {
if (widget.renderItem is Function) {
// 将数据items[index]返回给item组件. 进行数据绑定
return widget.renderItem(items[index], index);
}
}
},
// 用于监控列表滑到底部
controller: _scrollController,
),
// 刷新函数
onRefresh: _handleRefresh,
color: Colors.green,
);
}
重点介绍一下方法里面没有实现的方法
_handleRefresh方法
Future<Null> _handleRefresh() async {
List newEntries = await mokeHttpRequest();
// this.mounted标识完成请求后更新数据
if (this.mounted) {
setState(() {
items.clear();
items.addAll(newEntries);
isLoading = false;
_hasMore = true;
});
}
}
_buildProgressIndicator方法显示刷新与加载的数据UI显示
// 加载中
Widget _buildLoadText() {
return Container(
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Center(
child: Text("没有数据更多了!!!"),
),
));
}
Widget _buildProgressIndicator() {
if (_hasMore) {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: Column(
children: <Widget>[
new Opacity(
opacity: isLoading ? 1.0 : 0.0,
child: new CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.green)),
),
SizedBox(height: 20.0),
Text(
'数据加载中...',
style: TextStyle(fontSize: 14.0),
)
],
)
//child:
),
);
} else {
return _buildLoadText();
}
}
以上一封装好了. 使用方法, 引入文件路径之后, 如下代用即可
new CommonList(参数, 参数, 参数)
头部视图数据目前是写死的数据,定义在页面中
// 附上地址
https://img.alicdn.com/tfs/TB1W4hMAwHqK1RjSZJnXXbNLpXa-519-260.jpg,
https://img.alicdn.com/tfs/TB1XmFIApzqK1RjSZSgXXcpAVXa-720-338.jpg',
https://img.alicdn.com/tfs/TB1mClCABLoK1RjSZFuXXXn0XXa-600-362.jpg,
https://img.alicdn.com/tfs/TB1fXxIAAvoK1RjSZFNXXcxMVXa-600-362.jpg
导航跳转
利用new GestureDetector()组件添加onTap方法, 实现跳转, 其中NewsDetails为新建页面将detailUrl传递进去, 展示webView页面
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new NewsDetails(item.detailUrl, '详情')),
);
},
webView详情页面加载
引入webview_flutter: ^0.3.0第三方库, detailUrl为传入的属性之后代码如下:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class NewsDetails extends StatefulWidget{
final detailUrl;
final title;
const NewsDetails([this.detailUrl, this.title]) : super();
@override
State<StatefulWidget> createState() {
return new NewsDetailsState();
}
}
class NewsDetailsState extends State<NewsDetails>{
@override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
backgroundColor: Colors.green
),
body: new Container(
child: new WebView(
initialUrl: widget.detailUrl,
),
),
);
}
}
最后
以上实现的内容为前言中图片显示内容. 总结一下包括, 网络请求与数据绑定,页面跳转,webView页面加载.
预告
- 1,搜索功能
- 2,封装一个错误页, 空数据页面