Flutter:一小時(shí)從零構(gòu)建一個(gè)簡(jiǎn)單的 App,以及你如何做到這一點(diǎn)!
概要
這是一個(gè)偶然的事件,我正在瀏覽我的 Youtube 的訂閱內(nèi)容,看到了很多關(guān)于 Flutter 的相關(guān)視頻,這引起了我的興趣。我很興奮的開(kāi)始閱讀它的文檔。
我喜歡的學(xué)習(xí)方式,是邊動(dòng)手邊學(xué)習(xí)。閱讀完幾頁(yè)文檔并研究如何創(chuàng)建一個(gè)新的項(xiàng)目,我對(duì)自己嘗試新東西,倍感信心。
如你在 Gif 中看到的那樣,這是一個(gè)非常基本的應(yīng)用程序,但它仍然有一些讓我覺(jué)得有趣的地方。
它使用 Google Book Api 從數(shù)據(jù)庫(kù)中讀取書(shū)籍?dāng)?shù)據(jù)。
在數(shù)據(jù)展示前,顯示一個(gè)加載指示器。
將書(shū)名和圖像,都加載顯示出來(lái)。
在傳統(tǒng)的 Android App 中,這將需要幾個(gè)類和不少代碼。
而使用 Flutter,這個(gè) App 有一個(gè)類和 129 行代碼。包括一些引入語(yǔ)句和布局。
這一切,是不是聽(tīng)起來(lái)很棒?讓我們開(kāi)始吧!
開(kāi)始
在我開(kāi)始之前,我并不會(huì)詳細(xì)的介紹這個(gè) App 的所有細(xì)節(jié),請(qǐng)務(wù)必查看示例代碼以及文檔,進(jìn)行參考。
另外,整個(gè) App 的代碼(所有 129 行代碼),都在 Github 上開(kāi)源,我推薦你看看。
https://github.com/Norbert515/BookSearch
- List<Book> _items = new List();
 - final subject = new PublishSubject<String>();
 - bool _isLoading = false;
 
在這個(gè)例子中,我們需要 3 個(gè)變量。
一個(gè)書(shū)本的列表,其中一般被定義成長(zhǎng)這樣:
- class Book {
 - String title,url;
 - Book(this.title,this.url);
 - }
 
一個(gè) Public Subject,它是 RxDart 的一部分,而 RxDart 又是 Rx 的一個(gè)實(shí)現(xiàn),我這里主要用它來(lái)監(jiān)聽(tīng) textChanged 事件。
還有一個(gè)變量,表示我們目前是否在等待服務(wù)端響應(yīng)。
此外,用戶界面包含三個(gè)主要元素。
- 列表
 - 加載器。
 - 文本輸入框。
 
我們將他們放在一起。
- new TextField(
 - decoration: new InputDecoration(
 - hintText: 'Choose a book',
 - ),
 - onChanged: (string) => (subject.add(string)),
 - ),
 
關(guān)于這個(gè)地方的一個(gè)有趣的部分是 onChanged,在這里我們傳遞了一個(gè) Lambda,它講當(dāng)前輸入的文本添加到 subject 上,這使得我們可以在其他地方監(jiān)聽(tīng)回調(diào)。
- _isLoading? new CircularProgressIndicator(): new Container(),
 
如果當(dāng)前出于加載狀態(tài),則顯示進(jìn)度條,否則顯示一個(gè)空的容器。
- new Expanded(
 - child: new ListView.builder(
 - padding: new EdgeInsets.all(8.0),
 - itemCount: _items.length,
 - itemBuilder: (BuildContext context, int index) {
 - return new Card(
 - child: new Padding(
 - padding: new EdgeInsets.all(8.0),
 - child: new Row(
 - children: <Widget>[
 - _items[index].url != null? new Image.network(_items[index].url): new Container(),
 - new Flexible(
 - child: new Text(_items[index].title, maxLines: 10),
 - ),
 - ],
 - )
 - )
 - );
 - },
 - ),
 - ),
 
該列表是一個(gè)基于索引的 ListView。而在其內(nèi)部,我們布局了一個(gè)用于顯示網(wǎng)絡(luò)圖片的 Image 以及一個(gè)用于顯示書(shū)籍描述的 Text。
邏輯代碼
- @override
 - void initState() {
 - super.initState();
 - subject.stream.debounce(new Duration(milliseconds: 600)).listen(_textChanged);
 - }
 
在這個(gè) initState 方法中,我們使用流式編碼來(lái)處理它的事件并消費(fèi)它們。這樣做是為了每次點(diǎn)鍵入文本的時(shí)候,不會(huì)立即向 Api 服務(wù)器發(fā)送請(qǐng)求,而是會(huì)在***一次鍵入文本之后,等待 600ms,再將輸入的字符串發(fā)送到 _textChange() 方法,這也是我們使用 Rx 的唯一原因。
- void _textChanged(String text) {
 - if(text.isEmpty) {
 - setState((){_isLoading = false;});
 - _clearList();
 - return;
 - }
 - setState((){_isLoading = true;});
 - _clearList();
 - http.get("https://www.googleapis.com/books/v1/volumes?q=$text")
 - .then((response) => response.body)
 - .then(JSON.decode)
 - .then((map) => map["items"])
 - .then((list) {list.forEach(_addBook);})
 - .catchError(_onError)
 - .then((e){setState((){_isLoading = false;});});
 - }
 - void _onError(dynamic d) {
 - setState(() {
 - _isLoading = false;
 - });
 - }
 - void _clearList() {
 - setState(() {
 - _items.clear();
 - });
 - }
 
所有的邏輯都在這里了。
首先說(shuō)幾點(diǎn),如果 Text 是空的,我們不再加載并消除列表。
另外,如果我們獲取到 Text 的內(nèi)容,我們將開(kāi)始加載并清除列表。
之后,我們向 Google Api 發(fā)出請(qǐng)求 volumes?q=$text ,其中 text 包含當(dāng)前輸入的字符串。
當(dāng)結(jié)果返回的時(shí)候:
- 獲得返回的內(nèi)容。
 - 解析 JSON 數(shù)據(jù),飯后返回一個(gè) Map。
 - "items" 包含大量的書(shū)籍信息。
 - 循環(huán)迭代之后,使用 _addBook() 方法添加到 "items" 中。
 - 這些 "items" 就是我們需要的標(biāo)題和書(shū)籍封面圖。
 
- void _addBook(dynamic book) {
 - setState(() {
 - _items.add(new Book(book["volumeInfo"]["title"], book["volumeInfo"]["imageLinks"]["smallThumbnail"]));
 - });
 - }
 
小結(jié)
到這里,該 App 是一個(gè)完整的小應(yīng)用。
這是***步,接下來(lái)將使用一個(gè)數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)數(shù)據(jù)。
【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)微信公眾號(hào)聯(lián)系作者獲取授權(quán)】

















 
 
 





 
 
 
 