1. 程式人生 > >Flutter初體驗(二)—— 建立第一個Flutter APP

Flutter初體驗(二)—— 建立第一個Flutter APP

Flutter初體驗(二)— 建立第一個Flutter APP

在第一篇文章 Flutter初體驗(一)—Mac 安裝配置,學習了配置 Flutter 開發環境,並運行了Demo專案,本篇根據官方教程,學習建立自己的第一個Flutter APP。

  參考文件 https://flutter.io/get-started/codelab/

專案需求

您將實施一個簡單的移動應用程式,為一家創業公司生成建議名稱。使用者可以選擇和取消選擇名稱,儲存最好的名稱。該程式碼一次生成十個名稱。當用戶滾動時,會生成新批次的名稱。使用者可以點選應用欄右上方的列表圖示以移動到僅列出收藏名稱的新路線

  • 下面的GIF動畫顯示我們將要完成應用程式

學習成果:
  • Flutter應用程式的基本結構
  • 學習查詢和使用包來擴充套件功能
  • 使用熱過載加快開發週期
  • 如何實現狀態化元件
  • 如何建立一個無限的,延遲載入的列表
  • 如何建立並導航到第二個螢幕
  • 如何使用主題更改應用程式的外觀

第1步 :建立一個執行的Flutter 應用程式

在第一篇文章中已經講述瞭如何配置Flutter開發環境,並且建立一個示例Flutter專案,現在將會修改這個示例程式來完成我們的專案。

本次操作,主要編輯Dart程式碼中的lib/main.dart

提示: 將程式碼貼上到您的應用中時,縮排可能會變形。您可以使用Flutter工具自動修復此問題

  • Android Studio / IntelliJ IDEA:右鍵單擊Dart程式碼,然後選擇使用dartfmt重新格式化程式碼。
  • VS程式碼:右鍵單擊並選擇格式化文件
  • Terminal: 執行 flutter format

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'), ), ), ); } }

2.執行工程,將會看到下面的螢幕截圖

總結

  • 本示例建立一個Material應用程式。 Material是一種在移動裝置和web端是統一標準的視覺設計語言。 Flutter提供了一套豐富的Material小部件
  • 主要方法指定箭頭(=>)表示法,它是用於單行函式或方法的簡寫
  • 該應用程式擴充套件了使應用程式本身成為小部件的StatelessWidget。在Flutter中,幾乎所有東西都是一個小部件,包括對齊,填充和佈局
  • 小部件的主要工作是提供一個build()方法,該方法描述如何根據其他較低級別的小部件顯示小部件
  • 此示例的小部件樹由包含Text小部件的中心小部件組成。中心小部件將其小部件子樹對齊到螢幕中心

第2步:使用外部包

在這一步中,您將開始使用名為english_words的開源軟體包,其中包含數千個最常用的英文單詞以及一些實用功能
1.pubspec檔案管理Flutter應用程式的資源。在pubspec.yaml中,將english_words(3.1.0或更高版本)新增到依賴項列表。新行強調如下:

  dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0

2.在Android Studio的編輯器檢視,單擊右上角Packages get。將該包引入您的專案。將會在控制檯中看到以下內容

flutter packages get
Running "flutter packages get" in flutter_app...
Process finished with exit code 0

3.在lib / main.dart中,將english_words匯入,如下所示:

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

在輸入過程中,Android Studio會為您提供有關庫匯入的建議。然後它將呈現灰色的匯入字串,讓您知道匯入的庫未使用(到目前為止)

4.使用English words 包來生成文字取代字串“Hello World”
進行以下更改:

  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'), // Replace the   highlighted text...
           child: new Text(wordPair.asPascalCase),  // With this  highlighted text.
          ),
        ),
      );
   }
  }

5.如果應用程式正在執行,使用熱過載按鈕(⚡️)更新正在執行的應用程式,每次點選熱過載或者儲存專案時,都會在正在執行的應用程式中隨機顯示一個不同的單詞,這是單詞配對是在構建方法內部生成的,每次MaterialApp需要渲染時都會執行它,或在檢查器中切換平臺時。

第3步:新增一個有狀態的元件

無狀態元件是不可變的,意味著他們的屬性不能被改變–所有的值都是最終值

有狀態的小部件保持在小部件的生命週期中可能改變的狀態,實現一個有狀態的小部件至少需要兩個類:1)一個StatefulWidget類,它建立一個取代 2)一個State類的例項。 StatefulWidget類本身是不可變的,但State類在整個構件的生命週期中保持不變

在這一步中,您將新增一個有狀態的小部件RandomWords,它建立其狀態類RandomWordsState
1.將有狀態的RandomWords小部件新增到main.dart,它可以在MyApp之外的檔案中的任何位置使用,但解決方案將它放在檔案的底部。 RandomWords小部件除了建立其狀態類之外無其他作用

    class RandomWords extends StatefulWidget {
      @override
      createState() => new RandomWordsState();
    }

2.新增RandomWordsState類。該應用程式的大部分程式碼都駐留在該類中,該類保持RandomWords小部件的狀態,這個類將儲存隨著使用者滾動而無限增長的生成的單詞對,以及最喜歡的單詞對,因為使用者通過切換紅心圖示來將它們從列表中新增或刪除。

你會一點一點地建立這個類。首先,通過新增突出顯示的文字建立一個最小類:

   class RandomWordsState extends State<RandomWords> {
}

3.在新增狀態類後,IDE會抱怨該類缺少構建方法,接下來,您將新增一個基本構建方法,該方法通過將單詞生成程式碼從MyApp移動到RandomWordsState來生成單詞對

將構建方法新增到RandomWordState中,如突出顯示的文字所示:

    class RandomWordsState extends State<RandomWords> {
      @override
      Widget build(BuildContext context) {
        final wordPair = new WordPair.random();
        return new Text(wordPair.asPascalCase);
        }
      }

4.通過下面突出顯示的更改從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(wordPair.asPascalCase), // Change the highlighted text to...
          child: new RandomWords(), // ... this highlighted text
        ),
      ),
    );
  }
}

重新啟動應用程式。如果您嘗試重新載入熱點,則可能會看到警告:

  Reloading...
Not all changed program elements ran during view reassembly; consider
restarting.

這可能是誤報,但考慮重新啟動以確保您的更改反映在應用的使用者介面中。

應用程式應該像以前一樣執行,每次熱重新載入或儲存應用程式時都會顯示一個字對。

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

在這一步中,您將展開RandomWordsState以生成並顯示單詞配對列表。當用戶滾動時,ListView小部件中顯示的列表將無限增長。 ListView的構建器工廠建構函式允許您根據需要懶惰地構建列表檢視。

1.將一個_suggestions列表新增到RandomWordsState類以儲存建議的詞對。該變數以下劃線(_)開頭 - 在下劃線前加上一個帶有下劃線的識別符號可以強化Dart語言的隱私。

另外,新增一個largerFont變數來使字型變大。

  class RandomWordsState extends State<RandomWords> {
  ...
  final _suggestions = <WordPair>[];

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

2.將一個_buildSuggestions()函式新增到RandomWordsState類。此方法構建顯示建議詞對的ListView。

ListView類提供了一個構建器屬性itemBuilder,一個指定為匿名函式的工廠構建器和回撥函式,兩個引數傳遞給函式 - BuildContext和行迭代器.迭代器從0開始,每次呼叫該函式時遞增,每次建議的單詞配對一次.該模型允許建議的列表在使用者滾動時無限增長。


class RandomWordsState extends State<RandomWords> {
  ...
  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      // The itemBuilder callback is called once per suggested word pairing,
      // and places each suggestion into a ListTile row.
      // For even rows, the function adds a ListTile row for the word pairing.
      // For odd rows, the function adds a Divider widget to visually
      // separate the entries. Note that the divider may be difficult
      // to see on smaller devices.
      itemBuilder: (context, i) {
        // Add a one-pixel-high divider widget before each row in theListView.
        if (i.isOdd) return new Divider();

        // The syntax "i ~/ 2" divides i by 2 and returns an integer result.
        // For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2.
        // This calculates the actual number of word pairings in the ListView,
        // minus the divider widgets.
        final index = i ~/ 2;
        // If you've reached the end of the available word pairings...
        if (index >= _suggestions.length) {
          // ...then generate 10 more and add them to the suggestions list.
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      }
    );
  }
}

3._buildSuggestions函式每個字對呼叫_buildRow一次。這個函式在ListTile中顯示每個新對,這允許您在下一步中使行更具吸引力

RandomWordsState新增_buildRow函式:

class RandomWordsState extends State<RandomWords> {
  ...

  Widget _buildRow(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
}

4.更新RandomWordsState的構建方法以使用_buildSuggestions(),而不是直接呼叫單詞生成庫。進行突出顯示的更改:

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {

    //final wordPair = new WordPair.random(); // Delete these two lines.
    //return new Text(wordPair.asPascalCase);

    return new Scaffold (
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),  
    );
  }
  ...
}

5.更新MyApp的構建方法。從MyApp中刪除Scaffold和AppBar例項。這些將由RandomWordsState管理,當用戶在下一步中從一個螢幕導航到另一個螢幕時,可以更輕鬆地更改應用欄中的路由名稱。

用下面突出顯示的構建方法替換原始方法:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}

重新啟動應用程式。你應該看到一個單詞配對清單。儘可能向下滾動,您將繼續看到新的單詞配對。

第5步:新增互動性

在這一步中,您將為每一行新增可點選的心形圖示。當用戶點選列表中的條目,切換其“收藏”狀態時,該單詞配對被新增到一組儲存的收藏夾中或從中刪除

1.將一個_saved集合新增到RandomWordsState。這個集合儲存使用者最喜歡的單詞配對。 Set比List更受歡迎,因為正確實施的Set不允許重複輸入。

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _saved = new Set<WordPair>();  //新增 saved 集合

  final _biggerFont = const TextStyle(fontSize: 18.0);

}

2.在_buildRow函式中,新增alreadySaved檢查以確保單詞配對尚未新增到收藏夾.

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  ...
}

3.同樣在_buildRow()中,將心形圖示新增到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.重新啟動應用程式。你現在應該在每一行看到開放的心,但它們還沒有互動。

5.在_buildRow函式中讓心形圖示可點選。如果單詞條目已經新增到收藏夾中,再次點選它將其從收藏夾中刪除。當心形圖示被輕敲時,函式呼叫setState()來通知框架狀態已經改變。

新增下列程式碼:

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

熱過載應用程式。你應該能夠點選任何一行以獲得最喜歡的,或不適合的條目。注意,點選一行會生成從心形圖示散發的漣漪動畫。

第6步:導航到新的螢幕

在這一步中,您將新增一個顯示收藏夾的新螢幕(在Flutter中稱為路線)。您將學習如何在主路線和新路線之間導航。

在Flutter中,導航器管理包含應用程式路線的堆疊。將路線推入導航器的堆疊,將顯示更新為該推入的路線。從導航器的堆疊中彈出路徑,將顯示返回到上一個路線。

1.向RandomWordsState的構建方法中的AppBar新增列表圖示。當用戶點選列表圖示時,包含收藏夾專案的新路線被推送到導航器,顯示該圖示。

提示:某些小部件屬性採用單個小部件(子級),而其他屬性(如操作)則採用小部件(子級)陣列,如方括號([])所示。

將該圖示及其相應的操作新增到構建方法中:

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        //新增actions
        actions: <Widget>[
          new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

2.將一個_pushSaved()函式新增到RandomWordsState類。

class RandomWordsState extends State<RandomWords> {
  ...
  void _pushSaved() {
  }
}

熱重新載入應用程式。列表圖標出現在應用程式欄中。點選它什麼也沒做,因為_pushSaved函式是空的。

3.當用戶點選應用欄中的列表圖示時,建立一條路線並將其推送到導航器的堆疊。此操作會更改螢幕以顯示新路線。

新頁面的內容是使用匿名函式在MaterialPageRoute的構建器屬性中構建的。

將呼叫新增到Navigator.push,如突出顯示的程式碼所示,將路線推送到導航器的堆疊。

void _pushSaved() {
  Navigator.of(context).push(
  );
}

4.新增MaterialPageRoute及其構建器。現在,新增生成ListTile行的程式碼。ListTile的divideTiles()方法在每個ListTile之間新增水平間距。divided的變數儲存最後的行,通過便利函式toList()轉換為列表。

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

5.構建器屬性返回一個Scaffold,其中包含名為“Saved Suggestions”的新路線的應用程式欄。新路由的主體由包含ListTiles行的ListView組成;每行由一個分隔符分隔。

新增下面突出顯示的程式碼:

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

        //新增 Scaffold 名為 Saved Suggestions

        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Saved Suggestions'),
          ),
          body: new ListView(children: divided),
        );
      },
    ),
  );
}

6.熱過載應用程式。最喜歡的一些選擇,並點選應用欄中的列表圖示。新路線顯示包含收藏夾。請注意,導航器會在應用欄中新增一個“返回”按鈕。你不必顯式實現Navigator.pop。點選後退按鈕返回到主頁路線。

第7步:使用主題更改UI

在最後一步中,您將使用該應用的主題,主題控制你的應用的外觀和感覺。您可以使用預設主題,該主題取決於物理裝置或模擬器,也可以自定義主題以反映您的品牌。

1.您可以通過配置ThemeData類輕鬆更改應用程式的主題。您的應用程式目前使用預設主題,但您將更改主要顏色為白色。

將突出顯示的程式碼新增到MyApp,將應用程式的主題更改為藍色:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      //修改主題顏色
      theme: new ThemeData(
        primaryColor: Colors.blue,
      ),
      home: new RandomWords(),
    );
  }
}

2.熱重新載入應用程式。請注意,整個背景是白色的,甚至是應用欄。

3.作為讀者的練習,使用ThemeData來改變使用者介面的其他方面。材質庫中的Colors類提供了許多可以使用的顏色常量,而熱過載使得使用者介面的試驗變得快速而簡單。

搞定!

我們編寫了一個在iOS和Android上執行的互動式Flutter應用程式。在這個codelab中,你有:

  • 從頭開始建立一個Flutter應用程式。
  • 書寫Dart程式碼。
  • 利用外部的第三方庫。
  • 使用熱過載加快開發週期。
  • 實現一個有狀態的小部件,為你的應用增加互動性。
  • 用ListView和ListTiles建立一個延遲載入的無限滾動列表。
  • 建立了一條路線並添加了在主路線和新路線之間移動的邏輯。
  • 瞭解如何使用主題更改應用UI的外觀。