Flutter開(kāi)發(fā):AS編寫(xiě)第一個(gè)Flutter App

原文引用:Flutter中文網(wǎng)

本文將構(gòu)建一個(gè)簡(jiǎn)單的如下功能的APP:為一個(gè)創(chuàng)業(yè)公司生成建議的名稱(chēng)。用戶(hù)可以選擇和取消選擇的名稱(chēng)、保存(收藏)喜歡的名稱(chēng)。該代碼一次生成十個(gè)名稱(chēng),當(dāng)用戶(hù)滾動(dòng)時(shí),會(huì)生成一新批名稱(chēng)。用戶(hù)可以點(diǎn)擊導(dǎo)航欄右邊的列表圖標(biāo),以打開(kāi)到僅列出收藏名稱(chēng)的新頁(yè)面。

注:本篇是基于Flutter中文網(wǎng)的案例,為了更加詳細(xì)易懂,我添加了更多的圖片示例說(shuō)明,所以篇幅較長(zhǎng),但其實(shí)實(shí)際步驟并沒(méi)有太多內(nèi)容,不要怕。

示意圖

包含知識(shí)點(diǎn):

1.Flutter應(yīng)用程序的基本結(jié)構(gòu).

2.查找和使用packages來(lái)擴(kuò)展功能.

3.使用熱重載加快開(kāi)發(fā)周期.

4.如何實(shí)現(xiàn)有狀態(tài)的widget.

5.如何創(chuàng)建一個(gè)無(wú)限的、延遲加載的列表.

6.如何創(chuàng)建并導(dǎo)航到第二個(gè)頁(yè)面.

7.如何使用主題更改應(yīng)用程序的外觀.


第一步:創(chuàng)建Flutter App


根據(jù)上篇文章的方式,新建一個(gè)項(xiàng)目,命名為startip_namer

Flutter開(kāi)發(fā):環(huán)境配置與新建項(xiàng)目

1.替換lib/main.dart

刪除lib / main.dart中的所有代碼,然后替換為下面的代碼,它將在屏幕的中心顯示“Hello World”.

import 'package:flutter/material.dart';?

void main()=>runApp(new MyApp());

class MyApp extends StatelessWidget{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?@override

Widget build (BuildContext context){

return new MaterialApp(

title:'Welcome to Flutter',

home:new Scaffold(

? ? ? ? ? ?appBar:new AppBar(

? ? ? ? ? ? ? ? ? ? ? ? ? title:new Text('Welcome to Flutter'),),

? ? ? ? ? ?body:new Center(

? ? ? ? ? ? ? ? ? ? ? ? ? child:new Text('Hello World'),),

),

);

}}

提示:?將代碼粘貼到應(yīng)用中時(shí),縮進(jìn)可能會(huì)變形。您可以使用Flutter工具自動(dòng)修復(fù)此問(wèn)題:

Android Studio / IntelliJ IDEA: 右鍵單擊Dart代碼,然后選擇?Reformat Code with dartfmt.

VS Code: 右鍵單擊并選擇?Format Document.

Terminal: 運(yùn)行?flutter format <filename>.

示例圖

2.運(yùn)行應(yīng)用程序,你應(yīng)該看到如下界面.

示例圖

分析

? 本示例創(chuàng)建一個(gè)Material APP。Material是一種標(biāo)準(zhǔn)的移動(dòng)端和web端的視覺(jué)設(shè)計(jì)語(yǔ)言。 Flutter提供了一套豐富的Material widgets。

? main函數(shù)使用了(=>)符號(hào), 這是Dart中單行函數(shù)或方法的簡(jiǎn)寫(xiě)。

? 該應(yīng)用程序繼承了 StatelessWidget,這將會(huì)使應(yīng)用本身也成為一個(gè)widget。 在Flutter中,大多數(shù)東西都是widget,包括對(duì)齊(alignment)、填充(padding)和布局(layout)

? Scaffold 是 Material library 中提供的一個(gè)widget, 它提供了默認(rèn)的導(dǎo)航欄、標(biāo)題和包含主屏幕widget樹(shù)的body屬性。widget樹(shù)可以很復(fù)雜。

? widget的主要工作是提供一個(gè)build()方法來(lái)描述如何根據(jù)其他較低級(jí)別的widget來(lái)顯示自己。

? 本示例中的body的widget樹(shù)中包含了一個(gè)Center widget, Center widget又包含一個(gè) Text 子widget。 Center widget可以將其子widget樹(shù)對(duì)其到屏幕中心。


第2步: 使用外部包(package)


在這一步中,您將開(kāi)始使用一個(gè)名為english_words的開(kāi)源軟件包 ,其中包含數(shù)千個(gè)最常用的英文單詞以及一些實(shí)用功能.

可以在pub.dartlang.org上面找到其他許多開(kāi)源軟件包

1.在如下圖的位置(dependencies 中)添加依賴(lài)

pubspec文件管理Flutter應(yīng)用程序的assets(資源,如圖片、package等)。?在pubspec.yaml中,將依賴(lài)包添加到項(xiàng)目

english_words:^3.1.0

示例圖

2.加入后點(diǎn)擊Packages get,將依賴(lài)包安裝到項(xiàng)目中.

Packages get

3.在?lib/main.dart?中, 引入?english_words

import'package:english_words/english_words.dart';

圖中灰色是因?yàn)闆](méi)有使用過(guò),此時(shí)在我們輸入時(shí)便會(huì)有該庫(kù)的相關(guān)提示。

示例圖

4.使用 English words 包生成文本來(lái)替換字符串“Hello World”.

為方便復(fù)制,我將需要的代碼粘貼過(guò)來(lái):

final wordPair = new WordPair.random();

child: new Text(wordPair.asPascalCase),

示例圖

5.使用熱更新

如果你之前的應(yīng)用程序正在運(yùn)行,界面是之前的hello world,即可點(diǎn)擊工具欄中的閃電圖標(biāo)進(jìn)行熱更新,這樣可以更快速地將代碼更新到手機(jī)或虛擬機(jī)

示例圖


第3步: 添加一個(gè)?有狀態(tài)的部件(Stateful widget)


Stateless?widgets 是不可變的, 這意味著它們的屬性不能改變 - 所有的值都是最終的.

Stateful?widgets 持有的狀態(tài)可能在widget生命周期中發(fā)生變化. 實(shí)現(xiàn)一個(gè) stateful widget 至少需要兩個(gè)類(lèi): 一個(gè) StatefulWidget類(lèi)。一個(gè) State類(lèi)。 StatefulWidget類(lèi)本身是不變的,但是 State類(lèi)在widget生命周期中始終存在.

在這一步中,將添加一個(gè)有狀態(tài)的widget-RandomWords,它創(chuàng)建其State類(lèi)RandomWordsState。State類(lèi)將最終為widget維護(hù)生成的隨機(jī)單詞對(duì)和選中的單詞對(duì)。

1.添加有狀態(tài)的 RandomWords widget 到 main.dart

它也可以在MyApp之外的文件的任何位置使用,但是本示例將它放到了文件的底部。RandomWords widget除了創(chuàng)建State類(lèi)之外幾乎沒(méi)有其他任何東西

class RandomWords extends StatefulWidget {

? ? ? ? ? @override

? ? ? ? ? createState() => new RandomWordsState();

}

2.添加 RandomWordsState 類(lèi)

該應(yīng)用程序的大部分代碼都在該類(lèi)中, 該類(lèi)持有RandomWords widget的狀態(tài)。這個(gè)類(lèi)將保存隨著用戶(hù)滾動(dòng)而無(wú)限增長(zhǎng)的生成的單詞對(duì), 以及喜歡的單詞對(duì),用戶(hù)通過(guò)重復(fù)點(diǎn)擊心形 ?? 圖標(biāo)來(lái)將它們從列表中添加或刪除。

class RandomWordsState extends State<RandomWords> {

@override

Widget build(BuildContext context) {

? ? ? ? ? ? ? final wordPair = new WordPair.random();

? ? ? ? ? ? ? return new Text(wordPair.asPascalCase);

? ? ? ? ? ? ? ?}

}

3.生成單詞對(duì)代的碼從MyApp移動(dòng)到RandomWordsState中

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

final wordPair = new WordPair.random(); // 刪除此行

return new MaterialApp(

title: 'Welcome to Flutter',

home: new Scaffold(

? ? ? ? ? appBar: new AppBar( title: new Text('Welcome to Flutter'), ),

? ? ? ? ? body: new Center(

? ? ? ? ? ? ? ? ? ?//child: new Text(wordPair.asPascalCase),

? ? ? ? ? ? ? ? ? ?child: new RandomWords(),

), ), );

}

}

最終main.dart文件如下圖所示:

示例圖


第4步: 創(chuàng)建一個(gè)無(wú)限滾動(dòng)ListView


在這一步中,我們將擴(kuò)展(繼承)RandomWordsState類(lèi),以生成并顯示單詞對(duì)列表。 當(dāng)用戶(hù)滾動(dòng)時(shí),ListView中顯示的列表將無(wú)限增長(zhǎng)。 ListView的builder工廠構(gòu)造函數(shù)允許您按需建立一個(gè)懶加載的列表視圖。

1.向RandomWordsState類(lèi)中添加一個(gè)_suggestions列表以保存建議的單詞對(duì)。 該變量以下劃線(_)開(kāi)頭,在Dart語(yǔ)言中使用下劃線前綴標(biāo)識(shí)符,會(huì)強(qiáng)制其變成私有的。另外,添加一個(gè)biggerFont變量來(lái)增大字體大小

class RandomWordsState extends State<RandomWords> {

? ? ? ? final _suggestions = <WordPair>[];

? ? ? ? final _biggerFont = const TextStyle(fontSize: 18.0);

? ? ? ? ...

}

2.向RandomWordsState類(lèi)添加一個(gè)?_buildSuggestions()?函數(shù). 此方法構(gòu)建顯示建議單詞對(duì)的ListView。

ListView類(lèi)提供了一個(gè)builder屬性,itemBuilder?值是一個(gè)匿名回調(diào)函數(shù), 接受兩個(gè)參數(shù)- BuildContext和行迭代器i。迭代器從0開(kāi)始, 每調(diào)用一次該函數(shù),i就會(huì)自增1,對(duì)于每個(gè)建議的單詞對(duì)都會(huì)執(zhí)行一次。該模型允許建議的單詞對(duì)列表在用戶(hù)滾動(dòng)時(shí)無(wú)限增長(zhǎng)。

class RandomWordsState extends State<RandomWords> {

...

? ? Widget _buildSuggestions() {

? ? ? ? ? ?return new ListView.builder(

? ? ? ? ? ? ? ? ? ? padding: const EdgeInsets.all(16.0),

// 對(duì)于每個(gè)建議的單詞對(duì)都會(huì)調(diào)用一次itemBuilder,然后將單詞對(duì)添加到ListTile行中

// 在偶數(shù)行,該函數(shù)會(huì)為單詞對(duì)添加一個(gè)ListTile row.

// 在奇數(shù)行,該函數(shù)會(huì)添加一個(gè)分割線widget,來(lái)分隔相鄰的詞對(duì)。

// 注意,在小屏幕上,分割線看起來(lái)可能比較吃力。

? ? ? ? ? ? ? ? ? itemBuilder: (context, i) {

// 在每一列之前,添加一個(gè)1像素高的分隔線widget

? ? ? ? ? ? ? ? ? ? ? ? ? if (i.isOdd) return new Divider();

// 語(yǔ)法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i為:1, 2, 3, 4, 5

// 時(shí),結(jié)果為0, 1, 1, 2, 2, 這可以計(jì)算出ListView中減去分隔線后的實(shí)際單詞對(duì)數(shù)量? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? final index = i ~/ 2;

// 如果是建議列表中最后一個(gè)單詞對(duì)

? ? ? ? ? ? ? ? ? ? ? ? ? if (index >= _suggestions.length) {

// ...接著再生成10個(gè)單詞對(duì),然后添加到建議列表? ? ?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? _suggestions.addAll(generateWordPairs().take(10));

}

? ? ? ? ? ? ? ? ? ? ? ? ?return _buildRow(_suggestions[index]);

} ); }}

3.在RandomWordsState中添加一個(gè)_buildRow函數(shù) :

對(duì)于每一個(gè)單詞對(duì),_buildSuggestions函數(shù)都會(huì)調(diào)用一次_buildRow。 這個(gè)函數(shù)在ListTile中顯示每個(gè)新詞對(duì),這使您在下一步中可以生成更漂亮的顯示行

class RandomWordsState extends State<RandomWords> {

...

? ? ? ? ?Widget _buildRow(WordPair pair) {

? ? ? ? ? ? ? ? ? ? ? ?return new ListTile(

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? title: new Text(

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?pair.asPascalCase,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?style: _biggerFont,

), ); }}

4.更新RandomWordsState的build方法以使_buildSuggestions(),而不是直接調(diào)用單詞生成庫(kù)。

class RandomWordsState extends State<RandomWords> {

...

@override

Widget build(BuildContext context) {

//final wordPair = new WordPair.random(); // 刪除這兩行

//return new Text(wordPair.asPascalCase);

return new Scaffold (

appBar: new AppBar(

title: new Text('Startup Name Generator'), ),

body: _buildSuggestions(),

); }

...

}

5.更新MyApp的build方法。從MyApp中刪除Scaffold和AppBar實(shí)例。

用下面部分替換最初的build方法:

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return new MaterialApp(

title: 'Startup Name Generator',

home: new RandomWords(),

); }}

第四步最終代碼:第四步代碼


第5步: 添加交互

在這一步中,您將為每一行添加一個(gè)可點(diǎn)擊的心形 ?? 圖標(biāo)。當(dāng)用戶(hù)點(diǎn)擊列表中的條目,切換其“收藏”狀態(tài)時(shí),將該詞對(duì)添加到或移除出“收藏夾”。

1.添加一個(gè)?_saved?Set(集合) 到RandomWordsState。

這個(gè)集合存儲(chǔ)用戶(hù)喜歡(收藏)的單詞對(duì)。 在這里,Set比List更合適,因?yàn)镾et中不允許重復(fù)的值。

class RandomWordsState extends State<RandomWords> {

final _suggestions = <WordPair>[];

final _saved = new Set<WordPair>();

final _biggerFont = const TextStyle(fontSize: 18.0);

...

}

2.在?_buildRow?方法中添加?alreadySaved來(lái)檢查確保單詞對(duì)還沒(méi)有添加到收藏夾中。

final alreadySaved = _saved.contains(pair);

3.同時(shí)在?_buildRow()中, 添加一個(gè)心形 ?? 圖標(biāo)到 ListTiles以啟用收藏功能。

Widget _buildRow(WordPair pair) {

final alreadySaved = _saved.contains(pair);

return new ListTile(

title: new Text( pair.asPascalCase, style: _biggerFont, ),

trailing: new Icon(

alreadySaved ? Icons.favorite : Icons.favorite_border,

color: alreadySaved ? Colors.red : null,),

); }

4.在?_buildRow中讓心形??圖標(biāo)變得可以點(diǎn)擊。

如果單詞條目已經(jīng)添加到收藏夾中, 再次點(diǎn)擊它將其從收藏夾中刪除。當(dāng)心形??圖標(biāo)被點(diǎn)擊時(shí),函數(shù)調(diào)用setState()通知框架狀態(tài)已經(jīng)改變。

Widget _buildRow(WordPair pair) {

final alreadySaved = _saved.contains(pair);

return new ListTile(

title: new Text( pair.asPascalCase, style: _biggerFont, ),

trailing: new Icon(alreadySaved ? Icons.favorite : Icons.favorite_border,color: alreadySaved ? Colors.red : null,),

onTap: () {? ? ? ?

setState(() {? ? ? ? ?

if (alreadySaved) {

_saved.remove(pair); }

else { _saved.add(pair); } }); },?

); }

此時(shí)可以看到界面:

示例圖

對(duì)比源碼:第五步代碼


第6步: 導(dǎo)航到新頁(yè)面

在這一步中,您將添加一個(gè)顯示收藏夾內(nèi)容的新頁(yè)面(在Flutter中稱(chēng)為路由(route))。您將學(xué)習(xí)如何在主路由和新路由之間導(dǎo)航(切換頁(yè)面)。

在Flutter中,導(dǎo)航器管理應(yīng)用程序的路由棧。將路由推入(push)到導(dǎo)航器的棧中,將會(huì)顯示更新為該路由頁(yè)面。 從導(dǎo)航器的棧中彈出(pop)路由,將顯示返回到前一個(gè)路由。

1.在RandomWordsState的build方法中為AppBar添加一個(gè)列表圖標(biāo)。當(dāng)用戶(hù)點(diǎn)擊列表圖標(biāo)時(shí),包含收藏夾的新路由頁(yè)面入棧顯示。

appBar: new AppBar(

title: new Text('Startup Name Generator'),

actions: <Widget>[

new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), ], ),

2.向RandomWordsState類(lèi)添加一個(gè)?_pushSaved()?方法.

當(dāng)用戶(hù)點(diǎn)擊導(dǎo)航欄中的列表圖標(biāo)時(shí),建立一個(gè)路由并將其推入到導(dǎo)航管理器棧中。此操作會(huì)切換頁(yè)面以顯示新路由。

新頁(yè)面的內(nèi)容在在MaterialPageRoute的builder屬性中構(gòu)建,builder是一個(gè)匿名函數(shù)。

添加Navigator.push調(diào)用,這會(huì)使路由入棧(以后路由入棧均指推入到導(dǎo)航管理器的棧)

添加MaterialPageRoute及其builder。 現(xiàn)在,添加生成ListTile行的代碼。ListTile的divideTiles()方法在每個(gè)ListTile之間添加1像素的分割線。 該?divided?變量持有最終的列表項(xiàng)。

builder返回一個(gè)Scaffold,其中包含名為“Saved Suggestions”的新路由的應(yīng)用欄。 新路由的body由包含ListTiles行的ListView組成; 每行之間通過(guò)一個(gè)分隔線分隔。

class RandomWordsState extends State<RandomWords> {

...

void _pushSaved() {

Navigator.of(context).push(

new MaterialPageRoute( builder: (context) {

final tiles = _saved.map( (pair) {

return new ListTile(

title: new Text( pair.asPascalCase, style: _biggerFont, ), ); }, );

final divided = ListTile .divideTiles( context: context, tiles: tiles, ) .toList();

return new Scaffold( appBar: new AppBar( title: new Text('Saved Suggestions'), ),

body: new ListView(children: divided), ); }, ), );?}

}

此時(shí)界面如圖:


選中效果


選中條目跳轉(zhuǎn)


第7步:使用主題更改UI

在這最后一步中,您將會(huì)使用主題。主題控制您應(yīng)用程序的外觀和風(fēng)格。您可以使用默認(rèn)主題,該主題取決于物理設(shè)備或模擬器,也可以自定義主題以適應(yīng)您的品牌。

您可以通過(guò)配置ThemeData類(lèi)輕松更改應(yīng)用程序的主題。 您的應(yīng)用程序目前使用默認(rèn)主題,下面將更改primary color顏色為白色。

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return new MaterialApp(

title: 'Startup Name Generator',

theme: new ThemeData( primaryColor: Colors.white, ),

home: new RandomWords(),

); } }

實(shí)現(xiàn)效果如圖,可見(jiàn)標(biāo)題欄變?yōu)榘咨珮邮?/p>

示例圖

這里放我本人的最終實(shí)現(xiàn)代碼截圖,每張截圖最下方是下一張截圖的上方,也可以根據(jù)代碼行數(shù)對(duì)比。方便查找。同學(xué)們可以對(duì)比實(shí)現(xiàn)

第一部分
第二部分
第三部分
第四部分
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容