前言
上篇文章介紹了展示列表的構(gòu)建. 片尾預(yù)告了這篇文章的內(nèi)容, 主要包括列表的刷新,加載, 導(dǎo)航跳轉(zhuǎn)詳情web頁面. 網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求. 綁定數(shù)據(jù). 效果圖如下:
網(wǎng)絡(luò)請(qǐng)求
使用開源庫(kù)dio: ^1.0.6進(jìn)行的網(wǎng)絡(luò)請(qǐng)求.簡(jiǎn)單易用.另附一個(gè)三方庫(kù)查詢地址地址. 方便查找需要用到的資源與輪子
pubspec.yaml上添加dio庫(kù).進(jìn)行一下簡(jiǎn)單的封裝, 新建一個(gè)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;
}
}
然后在已入該文件進(jìn)行網(wǎng)絡(luò)請(qǐng)求
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;
// 參數(shù)
final _param = {'page': pageIndex, 'pageSize': 20, 'sort': 'rankIndex'};
// 返回結(jié)果
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數(shù)據(jù)轉(zhuǎn)化model
NewsModel cellData = new NewsModel.fromJson(responseList[i]);
resultList.add(cellData);
} catch (e) {
// No specified type, handles all
}
}
// 刷新頁面
setState(() {
items.addAll(resultList);
});
// 自定義數(shù)據(jù)
Map<String, dynamic> result = {
"list": resultList,
'total': pageTotal,
'pageIndex': pageIndex,
'pageSize': pageSize,
};
return result;
}
其中NewsModel為數(shù)據(jù)model, 具體內(nèi)容跟數(shù)據(jù)請(qǐng)求格式對(duì)應(yīng) 代碼如下:
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'],
);
}
}
上面代碼可以基本完成了一個(gè)get方法的網(wǎng)絡(luò)請(qǐng)求.下面請(qǐng)求下來數(shù)據(jù), 就要進(jìn)行數(shù)據(jù)綁定
數(shù)據(jù)綁定與列表刷新加載
一個(gè)app中會(huì)存在多個(gè)list, 這樣只要我們封裝好一個(gè)通用的list, 任何頁面就可以使用. 論封裝的重要性. 接下來創(chuàng)建一個(gè)commonLIst.dart文件作為通用list, 需要實(shí)現(xiàn)內(nèi)容:
- 1, 實(shí)現(xiàn)刷新,請(qǐng)求數(shù)據(jù)
- 2, 加載提示,請(qǐng)求數(shù)據(jù)
- 3, 傳入屬性包括(返回的每行組件cell, 頭部視圖, 請(qǐng)求數(shù)據(jù)函數(shù)), 目前這些
參數(shù)設(shè)置
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; // 是否正在請(qǐng)求數(shù)據(jù)中,
bool _hasMore = true; // 是否還有更多數(shù)據(jù)可加載
int _pageIndex = 0; // 頁面的索引
int _pageTotal = 0; // 數(shù)據(jù)總數(shù)
int _pageSize = 20; // 頁面數(shù)量
List items = new List();
ScrollController _scrollController = new ScrollController();
// 監(jiān)聽滑到最底部, 進(jìn)行網(wǎng)絡(luò)請(qǐng)求
@override
void initState() {
super.initState();
_getMoreData();
_scrollController.addListener(() {
// 如果下拉的當(dāng)前位置到scroll的最下面
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
@override
void dispose() {
super.dispose();
_scrollController.dispose();
}
Future _getMoreData() async {
// 如果加載數(shù)據(jù)loading為true,同時(shí)還有更多數(shù)據(jù)需要加載
if (!isLoading && _hasMore) {
setState(() {
isLoading = true;
});
List newEntries = await mokeHttpRequest();
_hasMore = ((_pageIndex + 1) * _pageSize <= _pageTotal);
// 狀態(tài)加載完成執(zhí)行
if (this.mounted) {
setState(() {
items.addAll(newEntries);
isLoading = false;
});
}
} else if (!isLoading && !_hasMore) {
}
}
// 數(shù)據(jù)請(qǐng)求
Future<List> mokeHttpRequest() async {
if (widget.requestApi is Function) {
// 傳入索引值
final listObj = await widget.requestApi({'pageIndex': _pageIndex});
// pageIndex, total, list. 網(wǎng)絡(luò)請(qǐng)求返回值
_pageIndex = listObj['pageIndex'];
_pageTotal = listObj['total'];
_pageSize = listObj['pageSize'];
return listObj['list'];
} else {
// 如沒有請(qǐng)求網(wǎng)絡(luò)方法, 則延遲2s返回空數(shù)組
return Future.delayed(Duration(seconds: 2), () {
return [];
});
}
}
@override
Widget build(BuildContext context) {
// RefreshIndicator為實(shí)現(xiàn)刷新加載使用組件
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) {
// 將數(shù)據(jù)items[index]返回給item組件. 進(jìn)行數(shù)據(jù)綁定
return widget.renderItem(items[index], index);
}
}
},
// 用于監(jiān)控列表滑到底部
controller: _scrollController,
),
// 刷新函數(shù)
onRefresh: _handleRefresh,
color: Colors.green,
);
}
重點(diǎn)介紹一下方法里面沒有實(shí)現(xiàn)的方法
_handleRefresh方法
Future<Null> _handleRefresh() async {
List newEntries = await mokeHttpRequest();
// this.mounted標(biāo)識(shí)完成請(qǐng)求后更新數(shù)據(jù)
if (this.mounted) {
setState(() {
items.clear();
items.addAll(newEntries);
isLoading = false;
_hasMore = true;
});
}
}
_buildProgressIndicator方法顯示刷新與加載的數(shù)據(jù)UI顯示
// 加載中
Widget _buildLoadText() {
return Container(
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Center(
child: Text("沒有數(shù)據(jù)更多了<袅0省姿锭!"),
),
));
}
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(
'數(shù)據(jù)加載中...',
style: TextStyle(fontSize: 14.0),
)
],
)
//child:
),
);
} else {
return _buildLoadText();
}
}
以上一封裝好了. 使用方法, 引入文件路徑之后, 如下代用即可
new CommonList(參數(shù), 參數(shù), 參數(shù))
頭部視圖數(shù)據(jù)目前是寫死的數(shù)據(jù),定義在頁面中
// 附上地址
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
導(dǎo)航跳轉(zhuǎn)
利用new GestureDetector()組件添加onTap方法, 實(shí)現(xiàn)跳轉(zhuǎn), 其中NewsDetails為新建頁面將detailUrl傳遞進(jìn)去, 展示webView頁面
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new NewsDetails(item.detailUrl, '詳情')),
);
},
webView詳情頁面加載
引入webview_flutter: ^0.3.0第三方庫(kù), 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,
),
),
);
}
}
最后
以上實(shí)現(xiàn)的內(nèi)容為前言中圖片顯示內(nèi)容. 總結(jié)一下包括, 網(wǎng)絡(luò)請(qǐng)求與數(shù)據(jù)綁定,頁面跳轉(zhuǎn),webView頁面加載.
預(yù)告
- 1,搜索功能
- 2,封裝一個(gè)錯(cuò)誤頁, 空數(shù)據(jù)頁面