Flutter實戰 從頭擼一個「孤島」APP(No.3、書單、搜尋框、Dio初探)

洋小洋同學發表於2019-11-30

前言

閱讀建議

  • 適宜人群:從事前端開發以及移動端開發的大佬們&著手去了解Flutter 新技術的上進人士
  • 本篇詞數: 2328詞(也不知道怎麼計算出來的)
  • 時長:內容基礎、並不費腦,所以一會兒就看完
  • 場景:擁擠的地鐵、沙發等

讀讀評論

Flutter實戰 從頭擼一個「孤島」APP(No.2、閃屏Splash Page、引導頁) 這篇發表之後呢,並沒有收到很多鼓勵,只有大佬,牛皮啊這一條評論,是來自掘友平賀才人,那可能大家並不太清楚,這一系列的Flutter,是要做成什麼樣子 應用到什麼Flutter技術,其中我們會使用到

  • widget 小部件使用、有狀態``無狀態
  • 常用的第三方外掛
  • Model Json 兩者藕斷絲連的關係轉化
  • Provider 全域性狀態管理
  • Fluro 路由管理庫
  • Dio 網路請求
  • ……

總結反省

那本篇我就先畫一下這個APP,會長什麼樣,UI並不是我原創,其中會應用到一套後臺的API

  • 完整的開發文件 後臺API 這是[林間有風團隊]小程式相關的介面,暫時用這個先

  • APP完整的樣子

    Flutter實戰  從頭擼一個「孤島」APP(No.3、書單、搜尋框、Dio初探)

    Flutter實戰  從頭擼一個「孤島」APP(No.3、書單、搜尋框、Dio初探)

  • 回顧專案目錄

Flutter實戰  從頭擼一個「孤島」APP(No.3、書單、搜尋框、Dio初探)

以後我們們儘量以

  • UI檢視部分
  • 後臺互動處理
  • 小BUG修復

這樣的三部分來構建正篇文章的結構,那我們這段旅程就來一起開發【書單】這部分,do it,

1. UI介面

1.1 頂部搜尋欄、搜尋框、搜尋條

  • 功能:實現搜尋功能,能夠對書籍進行搜尋
  • 包含:歷史搜尋、熱門搜尋、搜尋的取消等

我們還是秉承面向部件、面向元件開發的中心思想,二話不說 ,我還是打算在widgwts資料夾下新建搜尋這個部件,並命名widget_search_bar.dart使用第三方的外掛

1.2 search_widget

  • 當前版本 ^1.0.0

  • 使用:軟體包提供了一個搜尋小部件,用於從資料列表中選擇一個選項。提供基於搜尋文字的專案過濾。

     // import 'package:loader_search_bar/loader_search_bar.dart';
     
    複製程式碼

在書寫本篇的時候,由於大意把引包這部分寫錯了,寫成了另一個用於搜尋的包,感謝掘友 輝太郎2019友情提出,深表感謝,以下是正確的引包路徑

----------------------更正-------------


import 'package:search_widget/search_widget.dart';
複製程式碼

在這裡貼上包檔案地址search_widget

main函式中初始化一下相容配置

// 相容異常處理
void enablePlatformOverrideForDesktop() {
  if (!kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux)) {
    debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
  }
}

複製程式碼

那其實是main中主要是進行一些初始化操作

void main() {
  enablePlatformOverrideForDesktop();
  runApp(MyApp());
}
複製程式碼

初始化list 資料

 List<LeaderBoard> list = <LeaderBoard>[
    LeaderBoard("Flutter", 54),
    LeaderBoard("React", 22.5),
    LeaderBoard("Vue", 24.7),
    LeaderBoard("小程式", 22.1),
  ];
複製程式碼

這些資料是顯示在列表這兒的

Flutter實戰  從頭擼一個「孤島」APP(No.3、書單、搜尋框、Dio初探)

核心程式碼便是

     SearchWidget<LeaderBoard>(
            dataList: list, // 資料來源
            hideSearchBoxWhenItemSelected: false,
            listContainerHeight: MediaQuery.of(context).size.height / 4,
            // 基於搜尋查詢過濾資料列表的選項
            queryBuilder: (query, list) {
              return list
                  .where((item) =>
                      item.username.toLowerCase().contains(query.toLowerCase()))
                  .toList();
            },
            // 彈出列表項構建器的選項。這基本上返回一個小部件以在彈出視窗中顯示為列表項
            popupListItemBuilder: (item) {
              return PopupListItemWidget(item);
            },
            // 為選定的列表項構建器提供的選項,當使用者從彈出列表中選擇一個項時啟用
            selectedItemBuilder: (selectedItem, deleteSelectedItem) {
              return SelectedItemWidget(selectedItem, deleteSelectedItem);
            },
            // 定製小部件
            noItemsFoundWidget: NoItemsFound(),
            // 提供自定義TextField的選項。接受TextEditingController和FocusNode作為引數
            textFieldBuilder: (controller, focusNode) {
              return MyTextField(controller, focusNode);
            },
            // 用於獲取所選擇的選項。返回所選專案;如果刪除了該項,則返回null
            onItemSelected: (item) {
              setState(() {
                _selectedItem = item;
              });
            },
          ),
複製程式碼

1.3 MediaQuery

listContainerHeight: MediaQuery.of(context).size.height / 4,
複製程式碼

在這部分,有用到MediaQuery,它便是適配螢幕的一種方式,在之前第一段旅程的時候,也有提到螢幕適配

在搜尋的時候,我並不想出它出現搜尋的顯示

Flutter實戰  從頭擼一個「孤島」APP(No.3、書單、搜尋框、Dio初探)

 selectedItemBuilder: (selectedItem, deleteSelectedItem) {
              return SelectedItemWidget(selectedItem, deleteSelectedItem);
            },

複製程式碼

看一下原始碼,它還是要求我們們必須傳入的,那我們們就把這部分返回一個空的Container

Flutter實戰  從頭擼一個「孤島」APP(No.3、書單、搜尋框、Dio初探)

class SelectedItemWidget extends StatelessWidget {
  const SelectedItemWidget(this.selectedItem, this.deleteSelectedItem);

  final LeaderBoard selectedItem;
  final VoidCallback deleteSelectedItem;

  @override
  Widget build(BuildContext context) {
    return Container();
	// 這Container()便是返回的選中後的值
    // Container(
    //   padding: const EdgeInsets.symmetric(
    //     vertical: 2,
    //     horizontal: 4,
    //   ),
    //   child: Row(
    //     children: <Widget>[
    //       Expanded(
    //         child: Padding(
    //           padding: const EdgeInsets.only(
    //             left: 16,
    //             right: 16,
    //             top: 8,
    //             bottom: 8,
    //           ),
    //           child: Text(
    //             selectedItem.username,
    //             style: const TextStyle(fontSize: 14),
    //           ),
    //         ),
    //       ),
    //       IconButton(
    //         icon: Icon(Icons.delete_outline, size: 22),
    //         color: Colors.grey[700],
    //         onPressed: deleteSelectedItem,
    //       ),
    //     ],
    //   ),
    // );
  }
}

複製程式碼

1.4 資料模型

在初始化資料的時候LeaderBoard,我們填充了LeaderBoard("Flutter", 54), 這每一個資料其實是一個資料模型,這裡我們們先不說JSON 與Model之間的轉換

2. 後臺通訊

2.1 Dio 初探

在之後的旅程與後臺通訊的過程中,我們就選取dio這個庫來輔助請求,這就類似於React、Vue 專案中的axios,

dio是一個強大的Dart Http請求庫,支援Restful API、FormData、攔截器、請求取消、Cookie管理、檔案上傳/下載、超時、自定義介面卡等...意思是總得有個與後臺通訊的工具吧,不是嗎?當前的版本是3.0.7

  • 新增依賴
dependencies:
  dio: ^3.0.7


複製程式碼
  • 在需要的檔案引入
import 'package:dio/dio.dart';

複製程式碼

讓我們先來傳送個請求試一下,那麼後臺的介面選哪個呢,我記得之前有寫個線上模擬的介面,是用的淘寶開源的

RAP2

  • 介面地址 線上介面模擬

  • 響應資料

    {
      "code": 200,
      "data": [
        {
          "name": "洋小洋同學",
          "age": 18,
          "flag": "up"
        }
      ]
    }
    
    複製程式碼
  • 請求方式 GET 也就是說可以直接在瀏覽器位址列 輸入便會返回結果

那麼我們怎麼利用前文所提到的dio 去傳送請求呢?既然是一個網路請求,那還是老樣子,少不了是要封裝的,那本章節的我們們先試試水,初探一下。

這個請求的方法暫且寫在utils這個公共方法的資料夾裡,並命名my_http.dart,寫上一段簡單的程式碼

import 'package:dio/dio.dart';

void getHttp() async {
  try {
    Response response = await Dio().get(
        "http://rap2api.taobao.org/app/mock/236998/isolated/island/api/v1/test");
    print('後臺返回的結果是 ${response}');
  } catch (e) {
    print(e);
  }
}



複製程式碼

由於我們接下來將要請求書單的資料,那我們打算在book_list_page.dart初始化呼叫,在有狀態的元件是有這個初始化的方法的,在其中可以做一些事情,有點類似前端的生命週期鉤子,顯然在除錯的控制檯返回的正是我們預期的結果

Flutter實戰  從頭擼一個「孤島」APP(No.3、書單、搜尋框、Dio初探)

2.2 後臺API 分析

先來看一下真實後臺返回的介面資料,其返回一個列表,包含所有熱門書籍的概要資訊

  • 獲取熱門書籍
  • 請求方式 GET
  • URL :/book/hot_list
  • Response 200
  • 返回報文
[{
	"author": "[\u7f8e]\u4fdd\u7f57\u00b7\u683c\u96f7\u5384\u59c6",
	"fav_nums": 259,
	"id": 7,
	"image": "https://img3.doubanio.com/lpic/s4669554.jpg",
	"like_status": 0,
	"title": "\u9ed1\u5ba2\u4e0e\u753b\u5bb6"
}, {
	"author": "MarkPilgrim",
	"fav_nums": 145,
	"id": 65,
	"image": "https://img3.doubanio.com/lpic/s4059293.jpg",
	"like_status": 0,
	"title": "Dive Into Python 3"
}, {
	"author": "MagnusLieHetland",
	"fav_nums": 88,
	"id": 183,
	"image": "https://img3.doubanio.com/lpic/s4387251.jpg",
	"like_status": 0,
	"title": "Python\u57fa\u7840\u6559\u7a0b"
}, {
	"author": "[\u54e5\u4f26\u6bd4\u4e9a]\u52a0\u897f\u4e9a\u00b7\u9a6c\u5c14\u514b\u65af",
	"fav_nums": 99,
	"id": 1002,
	"image": "https://img3.doubanio.com/lpic/s6384944.jpg",
	"like_status": 0,
	"title": "\u767e\u5e74\u5b64\u72ec"
}, {
	"author": "[\u65e5]\u5ca9\u4e95\u4fca\u4e8c",
	"fav_nums": 78,
	"id": 1049,
	"image": "https://img1.doubanio.com/view/subject/l/public/s29775868.jpg",
	"like_status": 0,
	"title": "\u60c5\u4e66"
}, {
	"author": "[\u7f8e]\u4e54\u6cbb\u00b7R\u00b7R\u00b7\u9a6c\u4e01",
	"fav_nums": 52,
	"id": 1061,
	"image": "https://img3.doubanio.com/lpic/s1358984.jpg",
	"like_status": 0,
	"title": "\u51b0\u4e0e\u706b\u4e4b\u6b4c\uff08\u5377\u4e00\uff09"
}, {
	"author": "[\u65e5]\u4e1c\u91ce\u572d\u543e",
	"fav_nums": 81,
	"id": 1120,
	"image": "https://img3.doubanio.com/lpic/s4610502.jpg",
	"like_status": 0,
	"title": "\u767d\u591c\u884c"
}, {
	"author": "\u91d1\u5eb8",
	"fav_nums": 50,
	"id": 1166,
	"image": "https://img1.doubanio.com/lpic/s23632058.jpg",
	"like_status": 0,
	"title": "\u5929\u9f99\u516b\u90e8"
}, {
	"author": "[\u65e5]\u4e1c\u91ce\u572d\u543e",
	"fav_nums": 13,
	"id": 1308,
	"image": "https://img3.doubanio.com/lpic/s3814606.jpg",
	"like_status": 0,
	"title": "\u6076\u610f"
}, {
	"author": "[\u82f1]J\u00b7K\u00b7\u7f57\u7433",
	"fav_nums": 33,
	"id": 1339,
	"image": "https://img3.doubanio.com/lpic/s1074376.jpg",
	"like_status": 0,
	"title": "\u54c8\u5229\u00b7\u6ce2\u7279\u4e0e\u963f\u5179\u5361\u73ed\u7684\u56da\u5f92"
}, {
	"author": "\u97e9\u5bd2",
	"fav_nums": 21,
	"id": 1383,
	"image": "https://img1.doubanio.com/lpic/s3557848.jpg",
	"like_status": 0,
	"title": "\u4ed6\u7684\u56fd"
}, {
	"author": "[\u82f1]J\u00b7K\u00b7\u7f57\u7433",
	"fav_nums": 32,
	"id": 1398,
	"image": "https://img1.doubanio.com/lpic/s2752367.jpg",
	"like_status": 0,
	"title": "\u54c8\u5229\u00b7\u6ce2\u7279\u4e0e\u6b7b\u4ea1\u5723\u5668"
}, {
	"author": "\u738b\u5c0f\u6ce2",
	"fav_nums": 17,
	"id": 1560,
	"image": "https://img1.doubanio.com/lpic/s3463069.jpg",
	"like_status": 0,
	"title": "\u4e09\u5341\u800c\u7acb"
}, {
	"author": "[\u4f0a\u6717]\u739b\u8d5e\u00b7\u838e\u5854\u78a7",
	"fav_nums": 16,
	"id": 7821,
	"image": "https://img3.doubanio.com/lpic/s6144591.jpg",
	"like_status": 0,
	"title": "\u6211\u5728\u4f0a\u6717\u957f\u5927"
}, {
	"author": "[\u65e5]\u6751\u4e0a\u6625\u6811",
	"fav_nums": 10,
	"id": 8854,
	"image": "https://img1.doubanio.com/lpic/s29494718.jpg",
	"like_status": 0,
	"title": "\u8fdc\u65b9\u7684\u9f13\u58f0"
}, {
	"author": "\u4e09\u6bdb",
	"fav_nums": 18,
	"id": 8866,
	"image": "https://img3.doubanio.com/lpic/s2393243.jpg",
	"like_status": 0,
	"title": "\u68a6\u91cc\u82b1\u843d\u77e5\u591a\u5c11"
}, {
	"author": "\u97e9\u5bd2",
	"fav_nums": 21,
	"id": 15198,
	"image": "https://img1.doubanio.com/lpic/s1080179.jpg",
	"like_status": 0,
	"title": "\u50cf\u5c11\u5e74\u5566\u98de\u9a70"
}, {
	"author": "\u9c81\u8fc5",
	"fav_nums": 30,
	"id": 15984,
	"image": "https://img3.doubanio.com/lpic/s27970504.jpg",
	"like_status": 0,
	"title": "\u671d\u82b1\u5915\u62fe"
}, {
	"author": "[\u65e5]\u4e95\u4e0a\u96c4\u5f66",
	"fav_nums": 33,
	"id": 21050,
	"image": "https://img3.doubanio.com/lpic/s2853431.jpg",
	"like_status": 0,
	"title": "\u704c\u7bee\u9ad8\u624b31"
}, {
	"author": "[\u65e5]\u65b0\u4e95\u4e00\u4e8c\u4e09",
	"fav_nums": 24,
	"id": 51664,
	"image": "https://img3.doubanio.com/lpic/s29034294.jpg",
	"like_status": 0,
	"title": "\u4e1c\u4eac\u65f6\u5473\u8bb0"
}]

複製程式碼
  • response_description

    引數欄位 含義
    author 作者
    fav_nums 點贊數
    id 書籍ID
    image 書籍圖片
    like_status 是否點贊
    title 書籍題目

    未完待續。。。

本篇小結

那其實本週做的東西是有點少的,只是和大家熟悉了一下介面的樣子,而主要開發的書單這一塊呢,現在介面返回的資料已經知曉了,也明確了應用dio這個庫,不過癮的話那就

謝謝你看到這裡,如果感到有點用的話,希望點點關注、給個贊、寫點個人看法在評論..自己也會穿插著寫一些深刻的關於某個技能點的文章,先從Flutter 中的狀態管理這個主題開始吧,那加油,我是洋小洋同學,所有的程式碼都會同步到github

特別感謝

掘友

  • 輝太郎2019 在評論區指出文章引包的錯誤,大讚··

--END--THANKS--

相關文章