Flutter:一小時(shí)從零構(gòu)建一個(gè)簡單的 App,以及你如何做到這一點(diǎn)!
概要
這是一個(gè)偶然的事件,我正在瀏覽我的 Youtube 的訂閱內(nèi)容,看到了很多關(guān)于 Flutter 的相關(guān)視頻,這引起了我的興趣。我很興奮的開始閱讀它的文檔。
我喜歡的學(xué)習(xí)方式,是邊動(dòng)手邊學(xué)習(xí)。閱讀完幾頁文檔并研究如何創(chuàng)建一個(gè)新的項(xiàng)目,我對自己嘗試新東西,倍感信心。
如你在 Gif 中看到的那樣,這是一個(gè)非常基本的應(yīng)用程序,但它仍然有一些讓我覺得有趣的地方。
它使用 Google Book Api 從數(shù)據(jù)庫中讀取書籍?dāng)?shù)據(jù)。
在數(shù)據(jù)展示前,顯示一個(gè)加載指示器。
將書名和圖像,都加載顯示出來。
在傳統(tǒng)的 Android App 中,這將需要幾個(gè)類和不少代碼。
而使用 Flutter,這個(gè) App 有一個(gè)類和 129 行代碼。包括一些引入語句和布局。
這一切,是不是聽起來很棒?讓我們開始吧!
開始
在我開始之前,我并不會(huì)詳細(xì)的介紹這個(gè) App 的所有細(xì)節(jié),請務(wù)必查看示例代碼以及文檔,進(jìn)行參考。
另外,整個(gè) App 的代碼(所有 129 行代碼),都在 Github 上開源,我推薦你看看。
https://github.com/Norbert515/BookSearch
- List<Book> _items = new List();
- final subject = new PublishSubject<String>();
- bool _isLoading = false;
在這個(gè)例子中,我們需要 3 個(gè)變量。
一個(gè)書本的列表,其中一般被定義成長這樣:
- class Book {
- String title,url;
- Book(this.title,this.url);
- }
一個(gè) Public Subject,它是 RxDart 的一部分,而 RxDart 又是 Rx 的一個(gè)實(shí)現(xiàn),我這里主要用它來監(jiān)聽 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)聽回調(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è)用于顯示書籍描述的 Text。
邏輯代碼
- @override
- void initState() {
- super.initState();
- subject.stream.debounce(new Duration(milliseconds: 600)).listen(_textChanged);
- }
在這個(gè) initState 方法中,我們使用流式編碼來處理它的事件并消費(fèi)它們。這樣做是為了每次點(diǎn)鍵入文本的時(shí)候,不會(huì)立即向 Api 服務(wù)器發(fā)送請求,而是會(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();
- });
- }
所有的邏輯都在這里了。
首先說幾點(diǎn),如果 Text 是空的,我們不再加載并消除列表。
另外,如果我們獲取到 Text 的內(nèi)容,我們將開始加載并清除列表。
之后,我們向 Google Api 發(fā)出請求 volumes?q=$text ,其中 text 包含當(dāng)前輸入的字符串。
當(dāng)結(jié)果返回的時(shí)候:
- 獲得返回的內(nèi)容。
- 解析 JSON 數(shù)據(jù),飯后返回一個(gè) Map。
- "items" 包含大量的書籍信息。
- 循環(huán)迭代之后,使用 _addBook() 方法添加到 "items" 中。
- 這些 "items" 就是我們需要的標(biāo)題和書籍封面圖。
- void _addBook(dynamic book) {
- setState(() {
- _items.add(new Book(book["volumeInfo"]["title"], book["volumeInfo"]["imageLinks"]["smallThumbnail"]));
- });
- }
小結(jié)
到這里,該 App 是一個(gè)完整的小應(yīng)用。
這是***步,接下來將使用一個(gè)數(shù)據(jù)庫來存儲數(shù)據(jù)。
【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號聯(lián)系作者獲取授權(quán)】