Flutter 使用ListView實(shí)現(xiàn)類似物流的時(shí)間軸(詳細(xì))

前言

本文部分代碼參考了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è)帖子的超鏈接的!)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杭隙,一起剝皮案震驚了整個(gè)濱河市哟绊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌痰憎,老刑警劉巖票髓,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異铣耘,居然都是意外死亡洽沟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蜗细,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裆操,“玉大人,你說我怎么就攤上這事炉媒∽偾” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵吊骤,是天一觀的道長(zhǎng)缎岗。 經(jīng)常有香客問我,道長(zhǎng)白粉,這世上最難降的妖魔是什么传泊? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任鼠渺,我火速辦了婚禮,結(jié)果婚禮上眷细,老公的妹妹穿的比我還像新娘拦盹。我一直安慰自己,他們只是感情好薪鹦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布掌敬。 她就那樣靜靜地躺著,像睡著了一般池磁。 火紅的嫁衣襯著肌膚如雪奔害。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天地熄,我揣著相機(jī)與錄音华临,去河邊找鬼。 笑死端考,一個(gè)胖子當(dāng)著我的面吹牛雅潭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播却特,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扶供,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了裂明?” 一聲冷哼從身側(cè)響起椿浓,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闽晦,沒想到半個(gè)月后扳碍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仙蛉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年笋敞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荠瘪。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夯巷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哀墓,到底是詐尸還是另有隱情鞭莽,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布麸祷,位于F島的核電站,受9級(jí)特大地震影響褒搔,放射性物質(zhì)發(fā)生泄漏阶牍。R本人自食惡果不足惜喷面,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望走孽。 院中可真熱鬧惧辈,春花似錦、人聲如沸磕瓷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽困食。三九已至边翁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硕盹,已是汗流浹背符匾。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘩例,地道東北人啊胶。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像垛贤,于是被迫代替她去往敵國(guó)和親焰坪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354