前言
上篇文章介紹了展示列表的構建. 片尾預告了這篇文章的內容, 主要包括列表的刷新,加載, 導航跳轉詳情web頁面. 網絡數據請求. 綁定數據. 效果圖如下:
1.gif
網絡請求
使用開源庫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,封裝一個錯誤頁, 空數據頁面