做自(zì)由與創造的先行者

Flutter 編寫第一個Flutter應用(yòng)

Flutter開(kāi)發手冊

第1步: 創建 Flutter app

創建一個簡單的、基于模闆的Flutter應用(yòng)程序,按照創建您的第一個Flutter應用(yòng)中的指南的步驟, 然後将項目命名爲startup_namer(而不是myapp),接下(xià)來(lái)你(nǐ)将會(huì)修改這(zhè)個應用(yòng)來(lái)完成最終的APP。

在這(zhè)個示例中,你(nǐ)将主要編輯Dart代碼所在的 lib/main.dart 文(wén)件,

提示: 将代碼粘貼到(dào)應用(yòng)中時(shí),縮進可能(néng)會(huì)變形。您可以使用(yòng)Flutter工(gōng)具自(zì)動修複此問題:

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

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

Terminal: 運行 flutter format .

替換 lib/main.dart.删除lib / main.dart中的所有代碼,然後替換爲下(xià)面的代碼,它将在屏幕的中心顯示“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)程序,你(nǐ)應該看(kàn)到(dào)如下(xià)界面.

screenshot of hello world app

分析

本示例創建一個Material APP。Material是一種标準的移動端和(hé)web端的視(shì)覺設計(jì)語言。 Flutter提供了(le)一套豐富的Material widgets。

main函數使用(yòng)了(le)(=>)符号, 這(zhè)是Dart中單行函數或方法的簡寫。

該應用(yòng)程序繼承了(le) StatelessWidget,這(zhè)将會(huì)使應用(yòng)本身也(yě)成爲一個widget。 在Flutter中,大(dà)多數東西都是widget,包括對(duì)齊(alignment)、填充(padding)和(hé)布局(layout)

Scaffold 是 Material library 中提供的一個widget, 它提供了(le)默認的導航欄、标題和(hé)包含主屏幕widget樹的body屬性。widget樹可以很(hěn)複雜(zá)。

widget的主要工(gōng)作(zuò)是提供一個build()方法來(lái)描述如何根據其他(tā)較低(dī)級别的widget來(lái)顯示自(zì)己。

本示例中的body的widget樹中包含了(le)一個Center widget, Center widget又包含一個 Text 子widget。 Center widget可以将其子widget樹對(duì)其到(dào)屏幕中心。

第2步: 使用(yòng)外(wài)部包(package)

在這(zhè)一步中,您将開(kāi)始使用(yòng)一個名爲english_words的開(kāi)源軟件包 ,其中包含數千個最常用(yòng)的英文(wén)單詞以及一些(xiē)實用(yòng)功能(néng).

您可以 在pub.dartlang.org上(shàng)找到(dào)english_words軟件包以及其他(tā)許多開(kāi)源軟件包

pubspec文(wén)件管理(lǐ)Flutter應用(yòng)程序的assets(資源,如圖片、package等)。 在pubspec.yaml中,将english_words(3.1.0或更高(gāo)版本)添加到(dào)依賴項列表,如下(xià)面高(gāo)亮(liàng)顯示的行:

dependencies:

flutter:

sdk: flutter

cupertino_icons: ^0.1.0

english_words: ^3.1.0

在Android Studio的編輯器視(shì)圖中查看(kàn)pubspec時(shí),單擊右上(shàng)角的 Packages get,這(zhè)會(huì)将依賴包安裝到(dào)您的項目。您可以在控制台中看(kàn)到(dào)以下(xià)内容:

flutter packages get

Running "flutter packages get" in startup_namer...

Process finished with exit code 0

在 lib/main.dart 中, 引入 english_words, 如高(gāo)亮(liàng)顯示的行所示:

import 'package:flutter/material.dart';

import 'package:english_words/english_words.dart';

在您輸入時(shí),Android Studio會(huì)爲您提供有關庫導入的建議(yì)。然後它将呈現(xiàn)灰色的導入字符串,讓您知(zhī)道(dào)導入的庫尚未使用(yòng)(到(dào)目前爲止)

使用(yòng) English words 包生成文(wén)本來(lái)替換字符串“Hello World”.

Tip: “駝峰命名法” (稱爲 “upper camel case” 或 “Pascal case” ), 表示字符串中的每個單詞(包括第一個單詞)都以大(dà)寫字母開(kāi)頭。所以,“uppercamelcase” 變成 “UpperCamelCase”進行以下(xià)更改, 如:

import 'package:flutter/material.dart';

import 'package:english_words/english_words.dart';

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

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('Hello World'),

child: new Text(wordPair.asPascalCase),

),

),

);

}

}

如果應用(yòng)程序正在運行,請(qǐng)使用(yòng)熱重載按鈕 (lightning bolt icon) 更新正在運行的應用(yòng)程序。每次單擊熱重載或保存項目時(shí),都會(huì)在正在運行的應用(yòng)程序中随機選擇不同的單詞對(duì)。 這(zhè)是因爲單詞對(duì)是在 build 方法内部生成的。每次MaterialApp需要渲染時(shí)或者在Flutter Inspector中切換平台時(shí) build 都會(huì)運行.

screenshot at completion of second step

第3步: 添加一個 有狀态的部件(Stateful widget)

Stateless widgets 是不可變的, 這(zhè)意味着它們的屬性不能(néng)改變 - 所有的值都是最終的.

Stateful widgets 持有的狀态可能(néng)在widget生命周期中發生變化. 實現(xiàn)一個 stateful widget 至少需要兩個類:

一個 StatefulWidget類。

一個 State類。 StatefulWidget類本身是不變的,但(dàn)是 State類在widget生命周期中始終存在.

在這(zhè)一步中,您将添加一個有狀态的widget-RandomWords,它創建其State類RandomWordsState。State類将最終爲widget維護建議(yì)的和(hé)喜歡的單詞對(duì)。

添加有狀态的 RandomWords widget 到(dào) main.dart。 它也(yě)可以在MyApp之外(wài)的文(wén)件的任何位置使用(yòng),但(dàn)是本示例将它放(fàng)到(dào)了(le)文(wén)件的底部。RandomWords widget除了(le)創建State類之外(wài)幾乎沒有其他(tā)任何東西

class RandomWords extends StatefulWidget {

@override

createState() => new RandomWordsState();

}

添加 RandomWordsState 類.該應用(yòng)程序的大(dà)部分代碼都在該類中, 該類持有RandomWords widget的狀态。這(zhè)個類将保存随着用(yòng)戶滾動而無限增長的生成的單詞對(duì), 以及喜歡的單詞對(duì),用(yòng)戶通過重複點擊心形 ❤️ 圖标來(lái)将它們從(cóng)列表中添加或删除。你(nǐ)會(huì)一步一步地建立這(zhè)個類。首先,通過添加高(gāo)亮(liàng)顯示的代碼創建一個最小(xiǎo)類

class RandomWordsState extends State {

}

在添加狀态類後,IDE會(huì)提示該類缺少build方法。接下(xià)來(lái),您将添加一個基本的build方法,該方法通過将生成單詞對(duì)的代碼從(cóng)MyApp移動到(dào)RandomWordsState來(lái)生成單詞對(duì)。将build方法添加到(dào)RandomWordState中,如下(xià)面高(gāo)亮(liàng)代碼所示

class RandomWordsState extends State {

@override

Widget build(BuildContext context) {

final wordPair = new WordPair.random();

return new Text(wordPair.asPascalCase);

}

}

通過下(xià)面高(gāo)亮(liàng)顯示的代碼,将生成單詞對(duì)代的碼從(cóng)MyApp移動到(dào)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(),

),

),

);

}

}

重新啓動應用(yòng)程序。如果您嘗試熱重載,則可能(néng)會(huì)看(kàn)到(dào)一條警告:

Reloading...

Not all changed program elements ran during view reassembly; consider

restarting.

這(zhè)可能(néng)是誤報(bào),但(dàn)考慮到(dào)重新啓動可以确保您的更改在應用(yòng)界面中生效。

應用(yòng)程序應該像之前一樣運行,每次熱重載或保存應用(yòng)程序時(shí)都會(huì)顯示一個單詞對(duì)。

screenshot at completion of third step

遇到(dào)問題?

如果您的應用(yòng)程序運行不正常,可以使用(yòng)下(xià)面鏈接中的代碼來(lái)對(duì)比更正。

lib/main.dart

第4步: 創建一個無限滾動ListView

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

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

class RandomWordsState extends State {

final _suggestions = [];

final _biggerFont = const TextStyle(fontSize: 18.0);

...

}

向RandomWordsState類添加一個 _buildSuggestions() 函數. 此方法構建顯示建議(yì)單詞對(duì)的ListView。ListView類提供了(le)一個builder屬性,itemBuilder 值是一個匿名回調函數, 接受兩個參數- BuildContext和(hé)行叠代器i。叠代器從(cóng)0開(kāi)始, 每調用(yòng)一次該函數,i就會(huì)自(zì)增1,對(duì)于每個建議(yì)的單詞對(duì)都會(huì)執行一次。該模型允許建議(yì)的單詞對(duì)列表在用(yòng)戶滾動時(shí)無限增長。添加如下(xià)高(gāo)亮(liàng)的行:

class RandomWordsState extends State {

...

Widget _buildSuggestions() {

return new ListView.builder(

padding: const EdgeInsets.all(16.0),

// 對(duì)于每個建議(yì)的單詞對(duì)都會(huì)調用(yòng)一次itemBuilder,然後将單詞對(duì)添加到(dào)ListTile行中

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

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

// 注意,在小(xiǎo)屏幕上(shàng),分割線看(kàn)起來(lái)可能(néng)比較吃力。

itemBuilder: (context, i) {

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

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

// 語法 "i ~/ 2" 表示i除以2,但(dàn)返回值是整形(向下(xià)取整),比如i爲:1, 2, 3, 4, 5

// 時(shí),結果爲0, 1, 1, 2, 2, 這(zhè)可以計(jì)算(suàn)出ListView中減去分隔線後的實際單詞對(duì)數量

final index = i ~/ 2;

// 如果是建議(yì)列表中最後一個單詞對(duì)

if (index >= _suggestions.length) {

// ...接着再生成10個單詞對(duì),然後添加到(dào)建議(yì)列表

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

}

return _buildRow(_suggestions[index]);

}

);

}

}

對(duì)于每一個單詞對(duì),_buildSuggestions函數都會(huì)調用(yòng)一次_buildRow。 這(zhè)個函數在ListTile中顯示每個新詞對(duì),這(zhè)使您在下(xià)一步中可以生成更漂亮(liàng)的顯示行在RandomWordsState中添加一個_buildRow函數 :

class RandomWordsState extends State {

...

Widget _buildRow(WordPair pair) {

return new ListTile(

title: new Text(

pair.asPascalCase,

style: _biggerFont,

),

);

}

}

更新RandomWordsState的build方法以使用(yòng)_buildSuggestions(),而不是直接調用(yòng)單詞生成庫。 更改後如下(xià)面高(gāo)亮(liàng)部分:

class RandomWordsState extends State {

...

@override

Widget build(BuildContext context) {

final wordPair = new WordPair.random(); // 删除這(zhè)兩行

return new Text(wordPair.asPascalCase);

return new Scaffold (

appBar: new AppBar(

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

),

body: _buildSuggestions(),

);

}

...

}

更新MyApp的build方法。從(cóng)MyApp中删除Scaffold和(hé)AppBar實例。 這(zhè)些(xiē)将由RandomWordsState管理(lǐ),這(zhè)使得用(yòng)戶在下(xià)一步中從(cóng)一個屏幕導航到(dào)另一個屏幕時(shí), 可以更輕松地更改導航欄中的的路由名稱。用(yòng)下(xià)面高(gāo)亮(liàng)部分替換最初的build方法:

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return new MaterialApp(

title: 'Startup Name Generator',

home: new RandomWords(),

);

}

}

重新啓動應用(yòng)程序。你(nǐ)應該看(kàn)到(dào)一個單詞對(duì)列表。盡可能(néng)地向下(xià)滾動,您将繼續看(kàn)到(dào)新的單詞對(duì)。

screenshot at completion of fourth step

遇到(dào)問題?

如果你(nǐ)的應用(yòng)沒有正常運行,你(nǐ)可以使用(yòng)一下(xià)鏈接中的代碼對(duì)比更正。

lib/main.dart

第5步: 添加交互

在這(zhè)一步中,您将爲每一行添加一個可點擊的心形 ❤️ 圖标。當用(yòng)戶點擊列表中的條目,切換其“收藏”狀态時(shí),将該詞對(duì)添加到(dào)或移除出“收藏夾”。

添加一個 _saved Set(集合) 到(dào)RandomWordsState。這(zhè)個集合存儲用(yòng)戶喜歡(收藏)的單詞對(duì)。 在這(zhè)裏,Set比List更合适,因爲Set中不允許重複的值。

class RandomWordsState extends State {

final _suggestions = [];

final _saved = new Set();

final _biggerFont = const TextStyle(fontSize: 18.0);

...

}

在 _buildRow 方法中添加 alreadySaved來(lái)檢查确保單詞對(duì)還沒有添加到(dào)收藏夾中。

Widget _buildRow(WordPair pair) {

final alreadySaved = _saved.contains(pair);

...

}

同時(shí)在 _buildRow()中, 添加一個心形 ❤️ 圖标到(dào) ListTiles以啓用(yòng)收藏功能(néng)。接下(xià)來(lái),你(nǐ)就可以給心形 ❤️ 圖标添加交互能(néng)力了(le)。添加下(xià)面高(gāo)亮(lià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,

),

);

}

重新啓動應用(yòng)。你(nǐ)現(xiàn)在可以在每一行看(kàn)到(dào)心形❤️圖标️,但(dàn)它們還沒有交互。

在 _buildRow中讓心形❤️圖标變得可以點擊。如果單詞條目已經添加到(dào)收藏夾中, 再次點擊它将其從(cóng)收藏夾中删除。當心形❤️圖标被點擊時(shí),函數調用(yòng)setState()通知(zhī)框架狀态已經改變。添加如下(xià)高(gāo)亮(lià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);

}

});

},

);

}

提示: 在Flutter的響應式風(fēng)格的框架中,調用(yòng)setState() 會(huì)爲State對(duì)象觸發build()方法,從(cóng)而導緻對(duì)UI的更新

熱重載你(nǐ)的應用(yòng)。你(nǐ)就可以點擊任何一行收藏或移除。請(qǐng)注意,點擊一行時(shí)會(huì)生成從(cóng)心形 ❤️ 圖标發出的水(shuǐ)波動畫(huà)

screenshot at completion of 5th step

遇到(dào)了(le)問題?

如果您的應用(yòng)沒有正常運行,請(qǐng)查看(kàn)下(xià)面鏈接處的代碼,對(duì)比更正。

lib/main.dart

第6步: 導航到(dào)新頁面

在這(zhè)一步中,您将添加一個顯示收藏夾内容的新頁面(在Flutter中稱爲路由(route))。您将學習如何在主路由和(hé)新路由之間導航(切換頁面)。

在Flutter中,導航器管理(lǐ)應用(yòng)程序的路由棧。将路由推入(push)到(dào)導航器的棧中,将會(huì)顯示更新爲該路由頁面。 從(cóng)導航器的棧中彈出(pop)路由,将顯示返回到(dào)前一個路由。

在RandomWordsState的build方法中爲AppBar添加一個列表圖标。當用(yòng)戶點擊列表圖标時(shí),包含收藏夾的新路由頁面入棧顯示。提示: 某些(xiē)widget屬性需要單個widget(child),而其它一些(xiē)屬性,如action,需要一組widgets(children),用(yòng)方括号[]表示。将該圖标及其相應的操作(zuò)添加到(dào)build方法中:

class RandomWordsState extends State {

...

@override

Widget build(BuildContext context) {

return new Scaffold(

appBar: new AppBar(

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

actions: [

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

],

),

body: _buildSuggestions(),

);

}

...

}

向RandomWordsState類添加一個 _pushSaved() 方法.

class RandomWordsState extends State {

...

void _pushSaved() {

}

}

熱重載應用(yòng),列表圖标将會(huì)出現(xiàn)在導航欄中。現(xiàn)在點擊它不會(huì)有任何反應,因爲 _pushSaved 函數還是空(kōng)的。

當用(yòng)戶點擊導航欄中的列表圖标時(shí),建立一個路由并将其推入到(dào)導航管理(lǐ)器棧中。此操作(zuò)會(huì)切換頁面以顯示新路由。新頁面的内容在在MaterialPageRoute的builder屬性中構建,builder是一個匿名函數。添加Navigator.push調用(yòng),這(zhè)會(huì)使路由入棧(以後路由入棧均指推入到(dào)導航管理(lǐ)器的棧)

void _pushSaved() {

Navigator.of(context).push(

);

}

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

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();

},

),

);

}

builder返回一個Scaffold,其中包含名爲“Saved Suggestions”的新路由的應用(yòng)欄。 新路由的body由包含ListTiles行的ListView組成; 每行之間通過一個分隔線分隔。添加如下(xià)高(gāo)亮(liàng)的代碼:

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),

);

},

),

);

}

熱重載應用(yòng)程序。收藏一些(xiē)選項,并點擊應用(yòng)欄中的列表圖标,在新路由頁面中顯示收藏的内容。 請(qǐng)注意,導航器會(huì)在應用(yòng)欄中添加一個“返回”按鈕。你(nǐ)不必顯式實現(xiàn)Navigator.pop。點擊後退按鈕返回到(dào)主頁路由。

screenshot at completion of 6th stepsecond route

遇到(dào)了(le)問題?

如果您的應用(yòng)不能(néng)正常工(gōng)作(zuò),請(qǐng)參考下(xià)面鏈接處的代碼,對(duì)比并更正。

lib/main.dart

第7步:使用(yòng)主題更改UI

在這(zhè)最後一步中,您将會(huì)使用(yòng)主題。主題控制您應用(yòng)程序的外(wài)觀和(hé)風(fēng)格。您可以使用(yòng)默認主題,該主題取決于物理(lǐ)設備或模拟器,也(yě)可以自(zì)定義主題以适應您的品牌。

您可以通過配置ThemeData類輕松更改應用(yòng)程序的主題。 您的應用(yòng)程序目前使用(yòng)默認主題,下(xià)面将更改primary color顔色爲白(bái)色。通過如下(xià)高(gāo)亮(liàng)部分代碼,将應用(yòng)程序的主題更改爲白(bái)色:

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(),

);

}

}

熱重載應用(yòng)。 請(qǐng)注意,整個背景将會(huì)變爲白(bái)色,包括應用(yòng)欄。

作(zuò)爲讀者的一個練習,使用(yòng) ThemeData 來(lái)改變UI的其他(tā)方面。 Material library中的 Colors類提供了(le)許多可以使用(yòng)的顔色常量, 你(nǐ)可以使用(yòng)熱重載來(lái)快(kuài)速簡單地嘗試、實驗。

網站(zhàn)建設開(kāi)發|APP設計(jì)開(kāi)發|小(xiǎo)程序建設開(kāi)發
下(xià)一篇:Flutter Widget框架概述
上(shàng)一篇:Flutter 配置編輯器