前言
本文部分代碼參考了Flutter 類似物流的 時(shí)間軸 ListView 時(shí)間軸 - 簡(jiǎn)書 鳖悠,前排感謝。
使用的接口是阿里云的:易源數(shù)據(jù)-快遞物流查詢API接口优妙,具體使用和一些細(xì)節(jié)打算專門再寫一個(gè)博客乘综。
最先發(fā)布于俺的CSDN博客,歡迎賞臉:Flutter 使用ListView實(shí)現(xiàn)類似物流的時(shí)間軸(詳細(xì))
簡(jiǎn)書的Markdown居然不識(shí)別[TOC]語法套硼,不能自動(dòng)生成目錄卡辰!
效果圖
先上效果圖,手機(jī)截圖略大邪意,見諒
具體代碼
直接看代碼吧九妈,講解啥的都寫在注釋里了,有點(diǎn)啰嗦雾鬼,想盡量講的詳細(xì)一點(diǎn)僻造,莫怪莫怪
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class DeliverInfoPage extends StatefulWidget{
//從上一個(gè)頁面?zhèn)鬟^來的快遞單號(hào)
String trackingNum;
DeliverInfoPage(this.trackingNum);
@override
State<StatefulWidget> createState() {
return DeliverInfoPageState(trackingNum);
}
}
class DeliverInfoPageState extends State<DeliverInfoPage>{
String trackingNum;
//get請(qǐng)求獲取的數(shù)據(jù)
Map jsonMap;
DeliverInfoPageState(this.trackingNum);
@override
void initState() {
//NetInterface是自己封裝的網(wǎng)絡(luò)接口類逛拱,把項(xiàng)目中用到的接口都放在一起,便于管理
//對(duì)于阿里云接口的具體使用看另一個(gè)帖子吧栏笆。畢竟不是所有人都用的這個(gè)森渐,就不在這里展開了
NetInterface.getDeliverInfo(trackingNum).then((response) {
// print("getDeliverInfo=>"+response.toString());
//jsonMap的具體格式請(qǐng)看阿里云API購(gòu)買頁面做入,本博最后也會(huì)貼出來
jsonMap = json.decode(response.toString());
setState(() { });
}).catchError((response) {
//ToastUtil也是封裝的一個(gè)類,具體代碼是:
/*class ToastUtil{
static void print(String msg){
Fluttertoast.showToast(
msg: msg,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
);
}
}*/
ToastUtil.print("出現(xiàn)錯(cuò)誤同衣,請(qǐng)重試");
print("getDeliverInfo Error=>"+response.toString());
});
}
@override
Widget build(BuildContext context) {
//因?yàn)檫@個(gè)項(xiàng)目是安卓和flutter混合開發(fā)竟块,所以用了WillPopScope攔截退出事件
return WillPopScope(
child: Scaffold(
appBar: AppBar(
title: Text("物流追蹤"),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
}
),
),
//未獲取到數(shù)據(jù)就居中顯示加載圖標(biāo)
body: jsonMap != null ? buildBody(context) : showLoading(),
),
onWillPop: (){
Navigator.pop(context);
},
);
}
Widget buildBody(BuildContext context){
return Column(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(10, 0, 0, 0),
width: double.infinity,
color: Colors.white,
height: 70,
child: Container(
margin: EdgeInsets.all(5),
child: Row(
children: <Widget>[
Container(
height: 60,
width: 60,
margin: EdgeInsets.fromLTRB(5, 5, 10, 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: FadeInImage.assetNetwork(
//用了一個(gè)加載中的GIF作為默認(rèn)占位圖
//注意圖片要在pubspec.yaml聲明一下,我剛寫的時(shí)候忘了耐齐,就無法加載
placeholder: "assets/loading.gif",
image: jsonMap["showapi_res_body"]["logo"],
fit: BoxFit.fitWidth,
),
),
),
Expanded(
child: Column(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.only(left: 10),
alignment: Alignment.centerLeft,
child: Row(
children: <Widget>[
Text("物流狀態(tài):",style: TextStyle(fontSize: 16)),
Text(
"${statusConvert(jsonMap["showapi_res_body"]["status"])}",
style: TextStyle(fontSize: 16, color: Colors.green)
),
],
),
),
),
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.only(left: 10),
alignment: Alignment.centerLeft,
child: Text(
"運(yùn)單編號(hào):$trackingNum",
style: TextStyle(
fontSize: 15,
//顏色稍淡一點(diǎn)
color: Color.fromARGB(95, 0, 0, 0)
)
),
),
),
],
),
)
],
),
),
),
buildListView(context, jsonMap["showapi_res_body"]["data"]),
],
);
}
Widget buildListView(BuildContext context, List list){
return Expanded(
child: Container(
margin: EdgeInsets.fromLTRB(0, 10, 0, 0),
color: Colors.white,
child: ListView.builder(
//想設(shè)置item為固定高度可以設(shè)置這個(gè)浪秘,不過文本過長(zhǎng)就顯示不全了
// itemExtent: 100,
itemCount: list == null ? 0 : list.length,
itemBuilder: (BuildContext context, int position){
return buildListViewItem(context, list, position);
}
),
),
);
}
Widget buildListViewItem(BuildContext context, List list, int position){
if(list.length != 0){
return Container(
color: Colors.white,
padding: EdgeInsets.only(left: 20, right: 10),
child: Row(
children: [
//這個(gè)Container描繪左側(cè)的線和圓點(diǎn)
Container(
margin: EdgeInsets.only(left: 10),
width: 20,
//需要根據(jù)文本內(nèi)容調(diào)整高度蒋情,不然文本太長(zhǎng)會(huì)撐開Container,導(dǎo)致線會(huì)斷開
height: getHeight(list[position]["context"]),
child: Column(
//中心對(duì)齊耸携,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
flex: 1,
child: Container(
//第一個(gè)item圓點(diǎn)上方的線就不顯示了
width: position == 0 ? 0 : 1,
color: Colors.grey,
)
),
//第一個(gè)item顯示稍大一點(diǎn)的綠色圓點(diǎn)
position == 0 ? Stack(
//圓心對(duì)齊(也就是中心對(duì)齊)
alignment: Alignment.center,
children: <Widget>[
//為了實(shí)現(xiàn)類似陰影的圓點(diǎn)
Container(
height: 20,
width: 20,
decoration: BoxDecoration(
color: Colors.green.shade200,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
Container(
height: 14,
width: 14,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.all(Radius.circular(7)),
),
),
],
) : Container(
height: 10,
width: 10,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.all(Radius.circular(5)),
),
),
Expanded(
flex: 2,
child: Container(
width: 1,
color: Colors.grey,
)
),
],
),
),
Expanded(
child: Padding(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Text(
list[position]["context"] + "\n" + list[position]["time"],
style: TextStyle(
fontSize: 15,
//第一個(gè)item字體顏色為綠色+稍微加粗
color: position == 0 ? Colors.green : Colors.black,
fontWeight: position == 0 ? FontWeight.w600 : null,
),
),
),
),
],
),
);
}else{
return Container();
}
}
Widget showLoading(){
return Center(
child: CupertinoActivityIndicator(
radius: 20,
),
);
}
double getHeight(String content){
//具體多長(zhǎng)的文字需要增加高度棵癣,看手機(jī)分辨率和margin、padding的設(shè)置了
if(content.length >= 95){
return 150;
} else if(content.length >= 80 && content.length < 95){
return 130;
} else if(content.length >= 40 && content.length < 80){
return 110;
} else if(content.length >= 20 && content.length < 40){
return 90;
} else {
return 70;
}
}
//把int類型的狀態(tài)轉(zhuǎn)成字符串夺衍,具體對(duì)應(yīng)請(qǐng)看阿里云API購(gòu)買頁面狈谊,本博最后的圖也會(huì)有
String statusConvert(int status){
String returnStatus;
switch(status) {
case -1: { returnStatus = "待查詢"; }
break;
case 0: { returnStatus = "查詢異常"; }
break;
case 1: { returnStatus = "暫無記錄"; }
break;
case 2: { returnStatus = "在途中"; }
break;
case 3: { returnStatus = "派送中"; }
break;
case 4: { returnStatus = "已簽收"; }
break;
case 5: { returnStatus = "用戶拒簽"; }
break;
case 6: { returnStatus = "疑難件"; }
break;
case 7: { returnStatus = "無效單"; }
break;
case 8: { returnStatus = "超時(shí)單"; }
break;
case 9: { returnStatus = "簽收失敗"; }
break;
case 10: { returnStatus = "退回"; }
break;
default: { returnStatus = "未知狀態(tài)"; }
break;
}
return returnStatus;
}
}
返回?cái)?shù)據(jù)的結(jié)構(gòu)
這個(gè)實(shí)際就是易源數(shù)據(jù)-快遞物流查詢API接口的,不是泄露別人隱私哈
注意API接口是這個(gè)沟沙,別看錯(cuò)了
{
"showapi_res_error": "",//showapi平臺(tái)返回的錯(cuò)誤信息
"showapi_res_code": 0,//showapi平臺(tái)返回碼,0為成功,其他為失敗
"showapi_res_id": "5ea941d48d57baae12a0bcd5",
"showapi_res_body": {
"update": 1588141785719,//數(shù)據(jù)最后查詢的時(shí)間
"upgrade_info": "", //提示信息河劝,用于提醒用戶可能出現(xiàn)的情況
"updateStr": "2020-04-29 14:29:45",//數(shù)據(jù)最后更新的時(shí)間
"logo": "http://app2.showapi.com/img/expImg/zto.jpg", //快遞公司logo
"dataSize": 11, //數(shù)據(jù)節(jié)點(diǎn)的長(zhǎng)度
"status": 4, //-1 待查詢 0 查詢異常 1 暫無記錄 2 在途中 3 派送中 4 已簽收 5 用戶拒簽 6 疑難件 7 無效單 8 超時(shí)單 9 簽收失敗 10 退回
"fee_num": 1,
"tel": "95311",//快遞公司電話
"data": [
{
"time": "2019-11-16 21:33:56",
"context": "快件已在 【九江城西港】 簽收, 簽收人: 速遞易, 如有疑問請(qǐng)電聯(lián):(15779254414), 投訴電話:(13687028760), 您的快遞已經(jīng)妥投。風(fēng)里來雨里去, 只為客官您滿意矛紫。上有老下有小, 賞個(gè)好評(píng)好不好赎瞎?【請(qǐng)?jiān)谠u(píng)價(jià)快遞員處幫忙點(diǎn)亮五顆星星哦~】"
},
{
"time": "2019-11-16 07:31:24",
"context": "【九江城西港】 的程繼業(yè)(15779254414) 正在第1次派件, 請(qǐng)保持電話暢通,并耐心等待(95720為中通快遞員外呼專屬號(hào)碼,請(qǐng)放心接聽)"
},
{
"time": "2019-11-16 07:31:23",
"context": "快件已經(jīng)到達(dá) 【九江城西港】"
},
{
"time": "2019-11-15 19:06:30",
"context": "快件離開 【九江】 已發(fā)往 【九江城西港】"
},
{
"time": "2019-11-15 19:06:18",
"context": "快件已經(jīng)到達(dá) 【九江】"
},
{
"time": "2019-11-15 10:45:21",
"context": "快件離開 【南昌中轉(zhuǎn)部】 已發(fā)往 【九江】"
},
{
"time": "2019-11-15 08:02:44",
"context": "快件已經(jīng)到達(dá) 【南昌中轉(zhuǎn)部】"
},
{
"time": "2019-11-13 15:19:48",
"context": "快件離開 【石家莊】 已發(fā)往 【南昌中轉(zhuǎn)部】"
},
{
"time": "2019-11-13 14:22:09",
"context": "快件已經(jīng)到達(dá) 【石家莊】"
},
{
"time": "2019-11-13 14:08:31",
"context": "快件離開 【石家莊市場(chǎng)部】 已發(fā)往 【石家莊】"
},
{
"time": "2019-11-13 10:27:33",
"context": "【石家莊市場(chǎng)部】(0311-68026565颊咬、0311-68026566) 的 付保文四組(031186891089) 已攬收"
}
],
"expSpellName": "zhongtong",//快遞字母簡(jiǎn)稱
"msg": "查詢成功", //返回提示信息
"mailNo": "75312165465979",//快遞單號(hào)
"queryTimes": 1, //無走件記錄時(shí)被查詢次數(shù) 注意:超過8次將會(huì)計(jì)費(fèi),即第9次開始計(jì)費(fèi)
"ret_code": 0,//接口調(diào)用是否成功,0為成功,其他為失敗
"flag": true,//物流信息是否獲取成功
"expTextName": "中通快遞", //快遞簡(jiǎn)稱
"possibleExpList": [] //自動(dòng)識(shí)別結(jié)果
}
}
結(jié)束語
這個(gè)代碼復(fù)制應(yīng)該可以直接運(yùn)行的务甥,除了我封裝的兩個(gè)類,其中一個(gè)已經(jīng)在注釋中給出了贪染,另一個(gè)打算寫在對(duì)阿里云查詢接口具體使用的博客中(如果我不鴿的話缓呛,會(huì)更新這個(gè)帖子的超鏈接的!)