Linux中國

在 Flutter 移動應用程序中創建一個列表

Flutter 是一個流行的開源工具包,它可用於構建跨平台的應用。在文章《用 Flutter 創建移動應用》中,我已經向大家展示了如何在 Linux 中安裝 Flutter 並創建你的第一個應用。而這篇文章,我將向你展示如何在你的應用中添加一個列表,點擊每一個列表項可以打開一個新的界面。這是移動應用的一種常見設計方法,你可能以前見過的,下面有一個截圖,能幫助你對它有一個更直觀的了解:

![測試 Flutter 應用](/data/attachment/album/202105/31/201524m9453rl9mtmdzx99.gif "Testing the Flutter app")

Flutter 使用 Dart 語言。在下面的一些代碼片段中,你會看到以斜杠開頭的語句。兩個斜杠(//)是指代碼注釋,用於解釋某些代碼片段。三個斜杠(///)則表示的是 Dart 的文檔注釋,用於解釋 Dart 類和類的屬性,以及其他的一些有用的信息。

查看Flutter應用的主要部分

Flutter 應用的典型入口點是 main() 函數,我們通常可以在文件 lib/main.dart 中找到它:

void main() {
 runApp(MyApp());
}

應用啟動時,main() 會被調用,然後執行 MyApp()MyApp 是一個無狀態微件(StatelessWidget),它包含了MaterialApp() 微件中所有必要的應用設置(應用的主題、要打開的初始頁面等):

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       primarySwatch: Colors.blue,
       visualDensity: VisualDensity.adaptivePlatformDensity,
     ),
     home: MyHomePage(title: 'Flutter Demo Home Page'),
   );
 }
}

生成的 MyHomePage() 是應用的初始頁面,是一個有狀態的微件,它包含包含可以傳遞給微件構造函數參數的變數(從上面的代碼看,我們傳了一個 title 變數給初始頁面的構造函數):

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

有狀態微件(StatefulWidget)表示這個微件可以擁有自己的狀態:_MyHomePageState。調用 _MyHomePageState 中的 setState() 方法,可以重新構建用戶界面:

class _MyHomePageState extends State<MyHomePage> {
 int _counter = 0; // Number of taps on + button.

 void _incrementCounter() { // Increase number of taps and update UI by calling setState().
   setState(() {
     _counter++;
   });
 }
 ...
}

不管是有狀態的,還是無狀態的微件,它們都有一個 build() 方法,該方法負責微件的 UI 外觀。

@override
Widget build(BuildContext context) {
 return Scaffold( // Page widget.
   appBar: AppBar( // Page app bar with title and back button if user can return to previous screen.
     title: Text(widget.title), // Text to display page title.
   ),
   body: Center( // Widget to center child widget.
     child: Column( // Display children widgets in column.
       mainAxisAlignment: MainAxisAlignment.center,
       children: <Widget>[
         Text( // Static text.
           &apos;You have pushed the button this many times:&apos;,
         ),
         Text( // Text with our taps number.
           &apos;$_counter&apos;, // $ sign allows us to use variables inside a string.
           style: Theme.of(context).textTheme.headline4,// Style of the text, 「Theme.of(context)」 takes our context and allows us to access our global app theme.
         ),
       ],
     ),
   ),
        // Floating action button to increment _counter number.
   floatingActionButton: FloatingActionButton(
     onPressed: _incrementCounter,
     tooltip: &apos;Increment&apos;,
     child: Icon(Icons.add),
   ),
 );
}

修改你的應用

一個好的做法是,把 main() 方法和其他頁面的代碼分開放到不同的文件中。要想將它們分開,你需要右擊 lib 目錄,然後選擇 「New > Dart File」 來創建一個 .dart 文件:

![創建一個新的 Dart 文件](/data/attachment/album/202105/31/201524vap110sxfx80p21z.png "Create a new Dart file")

將新建的文件命名為 items_list_page

切換回到 main.dart 文件,將 MyHomePage_MyHomePageState 中的代碼,剪切並粘貼到我們新建的文件。然後將游標放到 StatefulWidget 上(下面紅色的下劃線處), 按 Alt+Enter 後出現下拉列表,然後選擇 package:flutter/material.dart

![導入 Flutter 包](/data/attachment/album/202105/31/201524zlsefqh50efa8g5a.png "Importing Flutter package")

經過上面的操作我們將 flutter/material.dart 包添加到了 main.dart 文件中,這樣我們就可以使用 Flutter 提供的默認的 material 主題微件。

然後, 在類名 MyHomePage 右擊,「Refactor > Rename...」將其重命名為 ItemsListPage

![重命名 StatefulWidget 類](/data/attachment/album/202105/31/201525tzuhx5xtxk4tp99s.png "Renaming StatefulWidget class")

Flutter 識別到你重命名了 StatefulWidget 類,它會自動將它的 State 類也跟著重命名:

![State 類被自動重命名](/data/attachment/album/202105/31/201525d6w2q2kiiijzni0w.png "State class renamed automatically")

回到 main.dart 文件,將文件名 MyHomePage 改為 ItemsListPage。 一旦你開始輸入, 你的 Flutter 集成開發環境(可能是 IntelliJ IDEA 社區版、Android Studio 和 VS Code 或 VSCodium),會給出自動代碼補完的建議。

![IDE 建議自動補完的代碼](/data/attachment/album/202105/31/201525xbzpb6bwwf595clw.png "IDE suggests autocompleting code")

按回車鍵即可完成輸入,缺失的導入語句會被自動添加到文件的頂部。

![添加缺失的導入語句](/data/attachment/album/202105/31/201526erzso7rg6g0sdsqk.png "Adding missing import ")

到此,你已經完成了初始設置。現在你需要在 lib 目錄創建一個新的 .dart 文件,命名為 item_model。(注意,類命是大寫駝峰命名,一般的文件名是下劃線分割的命名。)然後粘貼下面的代碼到新的文件中:

/// Class that stores list item info:
/// [id] - unique identifier, number.
/// [icon] - icon to display in UI.
/// [title] - text title of the item.
/// [description] - text description of the item.
class ItemModel {
 // class constructor
 ItemModel(this.id, this.icon, this.title, this.description);

 // class fields
 final int id;
 final IconData icon;
 final String title;
 final String description;
}

回到 items_list_page.dart 文件,將已有的 _ItemsListPageState 代碼替換為下面的代碼:

class _ItemsListPageState extends State<ItemsListPage> {

// Hard-coded list of [ItemModel] to be displayed on our page.
 final List<ItemModel> _items = [
   ItemModel(0, Icons.account_balance, &apos;Balance&apos;, &apos;Some info&apos;),
   ItemModel(1, Icons.account_balance_wallet, &apos;Balance wallet&apos;, &apos;Some info&apos;),
   ItemModel(2, Icons.alarm, &apos;Alarm&apos;, &apos;Some info&apos;),
   ItemModel(3, Icons.my_location, &apos;My location&apos;, &apos;Some info&apos;),
   ItemModel(4, Icons.laptop, &apos;Laptop&apos;, &apos;Some info&apos;),
   ItemModel(5, Icons.backup, &apos;Backup&apos;, &apos;Some info&apos;),
   ItemModel(6, Icons.settings, &apos;Settings&apos;, &apos;Some info&apos;),
   ItemModel(7, Icons.call, &apos;Call&apos;, &apos;Some info&apos;),
   ItemModel(8, Icons.restore, &apos;Restore&apos;, &apos;Some info&apos;),
   ItemModel(9, Icons.camera_alt, &apos;Camera&apos;, &apos;Some info&apos;),
 ];

 @override
 Widget build(BuildContext context) {
   return Scaffold(
       appBar: AppBar(
         title: Text(widget.title),
       ),
       body: ListView.builder( // Widget which creates [ItemWidget] in scrollable list.
         itemCount: _items.length, // Number of widget to be created.
         itemBuilder: (context, itemIndex) => // Builder function for every item with index.
             ItemWidget(_items[itemIndex], () {
           _onItemTap(context, itemIndex);
         }),
       ));
 }

 // Method which uses BuildContext to push (open) new MaterialPageRoute (representation of the screen in Flutter navigation model) with ItemDetailsPage (StateFullWidget with UI for page) in builder.
 _onItemTap(BuildContext context, int itemIndex) {
   Navigator.of(context).push(MaterialPageRoute(
       builder: (context) => ItemDetailsPage(_items[itemIndex])));
 }
}

// StatelessWidget with UI for our ItemModel-s in ListView.
class ItemWidget extends StatelessWidget {
 const ItemWidget(this.model, this.onItemTap, {Key key}) : super(key: key);

 final ItemModel model;
 final Function onItemTap;

 @override
 Widget build(BuildContext context) {
   return InkWell( // Enables taps for child and add ripple effect when child widget is long pressed.
     onTap: onItemTap,
     child: ListTile( // Useful standard widget for displaying something in ListView.
       leading: Icon(model.icon),
       title: Text(model.title),
     ),
   );
 }
}

為了提高代碼的可讀性,可以考慮將 ItemWidget 作為一個單獨的文件放到 lib 目錄中。

現在唯一缺少的是 ItemDetailsPage 類。在 lib 目錄中我們創建一個新文件並命名為 item_details_page。然後將下面的代碼拷貝進去:

import &apos;package:flutter/material.dart&apos;;

import &apos;item_model.dart&apos;;

/// Widget for displaying detailed info of [ItemModel]
class ItemDetailsPage extends StatefulWidget {
 final ItemModel model;

 const ItemDetailsPage(this.model, {Key key}) : super(key: key);

 @override
 _ItemDetailsPageState createState() => _ItemDetailsPageState();
}

class _ItemDetailsPageState extends State<ItemDetailsPage> {
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text(widget.model.title),
     ),
     body: Center(
       child: Column(
         children: [
           const SizedBox(height: 16),
           Icon(
             widget.model.icon,
             size: 100,
           ),
           const SizedBox(height: 16),
           Text(
             &apos;Item description: ${widget.model.description}&apos;,
             style: TextStyle(fontSize: 18),
           )
         ],
       ),
     ),
   );
 }
}

上面的代碼幾乎沒什麼新東西,不過要注意的是 _ItemDetailsPageState 里使用了 widget.item.title 這樣的語句,它讓我們可以從有狀態類中引用到其對應的微件(StatefulWidget)。

添加一些動畫

現在讓我們來添加一些基礎的動畫:

  1. 找到 ItemWidget 代碼塊(或者文件)
  2. 將游標放到 build() 方法中的 Icon() 微件上
  3. Alt+Enter,然後選擇「Wrap with widget...」

![查看微件選項](/data/attachment/album/202105/31/201526rrcimnffoz2bfz05.png "Wrap with widget option")

輸入 Hero,然後從建議的下拉列表中選擇 Hero((Key key, @required this, tag, this.create))

![查找 Hero 微件](/data/attachment/album/202105/31/201526c33izlcl6gklkyet.png "Finding the Hero widget")

下一步, 給 Hero 微件添加 tag 屬性 tag: model.id

![在 Hero 微件上添加 tag 屬性為 model.id](/data/attachment/album/202105/31/201527u3ifl1y4uxu1sevv.png "Adding the tag property model.id to the Hero widget")

最後我們在 item_details_page.dart 文件中做相同的修改:

![修改item_details_page.dart文件](/data/attachment/album/202105/31/201527t90kkp90mm6op9wx.png "Changing item_details_page.dart file")

前面的步驟,其實我們是用 Hero() 微件對 Icon() 微件進行了封裝。還記得嗎?前面我們定義 ItemModel 類時,定義了一個 id field,但沒有在任何地方使用到。因為 Hero 微件會為其每個子微件添加一個唯一的標籤。當 Hero 檢測到不同頁面(MaterialPageRoute)中存在相同標籤的 Hero 時,它會自動在這些不同的頁面中應用過渡動畫。

可以在安卓模擬器或物理設備上運行我們的應用來測試這個動畫。當你打開或者關閉列表項的詳情頁時,你會看到一個漂亮的圖標動畫:

![測試 Flutter 應用](/data/attachment/album/202105/31/201524m9453rl9mtmdzx99.gif "Testing the Flutter app")

收尾

這篇教程,讓你學到了:

  • 一些符合標準的,且能用於自動創建應用的組件。
  • 如何添加多個頁面以及在頁面間傳遞數據。
  • 如何給多個頁面添加簡單的動畫。

如果你想了解更多,查看 Flutter 的 文檔(有一些視頻和樣例項目的鏈接,還有一些創建 Flutter 應用的「秘方」)與 源碼,源碼的開源許可證是 BSD 3。

via: https://opensource.com/article/20/11/flutter-lists-mobile-app

作者:Vitaly Kuprenko 選題:lujun9972 譯者:ywxgod 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國