Flutter實(shí)戰(zhàn) 計(jì)數(shù)器應(yīng)用示例

2021-03-06 16:04 更新

用 Android Studio 和 VS Code創(chuàng)建的 Flutter 應(yīng)用模板默認(rèn)是一個(gè)簡單的計(jì)數(shù)器示例。本節(jié)先仔細(xì)講解一下這個(gè)計(jì)數(shù)器 Demo 的源碼,讓讀者對 Flutter 應(yīng)用程序結(jié)構(gòu)有個(gè)基本了解,然后在隨后的小節(jié)中將會基于此示例,一步一步添加一些新的功能來介紹 Flutter 應(yīng)用的其它概念與技術(shù)。

對于接下來的示例,希望讀者可以跟著筆者一起親自動(dòng)手來寫一下,這樣不僅可以加深印象,而且也會對介紹的概念與技術(shù)有一個(gè)真切的體會。如果你還不是很熟悉 Dart 語言或者沒有移動(dòng)開發(fā)經(jīng)驗(yàn),不用擔(dān)心,只要你熟悉面向?qū)ο蠛突揪幊谈拍睿ㄈ缱兞?、循環(huán)和條件控制),則可以完成本示例。

#2.1.1 創(chuàng)建Flutter應(yīng)用模板

通過 Android Studio 或 VS Code 創(chuàng)建一個(gè)新的 Flutter 工程,命名為"first_flutter_app"。創(chuàng)建好后,就會得到一個(gè)計(jì)數(shù)器應(yīng)用的 Demo。

注意,默認(rèn) Demo 示例可能隨著編輯器 Flutter 插件的版本變化而變化,本例中會介紹計(jì)數(shù)器示例的全部代碼,所以不會對本示例產(chǎn)生影響。

我們先運(yùn)行創(chuàng)建的工程,效果如圖2-1所示:

圖2-1

該計(jì)數(shù)器示例中,每點(diǎn)擊一次右下角帶“+”號的懸浮按鈕,屏幕中央的數(shù)字就會加1。

在這個(gè)示例中,主要 Dart 代碼是在 lib/main.dart 文件中,下面是它的源碼:

import 'package:flutter/material.dart';


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


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


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


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


class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;


  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

#分析

  1. 導(dǎo)入包。

   import 'package:flutter/material.dart';

此行代碼作用是導(dǎo)入了 Material UI 組件庫。Material (opens new window)是一種標(biāo)準(zhǔn)的移動(dòng)端和 web 端的視覺設(shè)計(jì)語言, Flutter默認(rèn)提供了一套豐富的 Material 風(fēng)格的 UI 組件。

  1. 應(yīng)用入口。

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

  • 與 C/C++、Java 類似,F(xiàn)lutter 應(yīng)用中main函數(shù)為應(yīng)用程序的入口。main函數(shù)中調(diào)用了runApp 方法,它的功能是啟動(dòng) Flutter 應(yīng)用。runApp它接受一個(gè)Widget參數(shù),在本示例中它是一個(gè)MyApp對象,MyApp()是 Flutter 應(yīng)用的根組件。
  • main函數(shù)使用了(=>)符號,這是 Dart 中單行函數(shù)或方法的簡寫。

  1. 應(yīng)用結(jié)構(gòu)。

   class MyApp extends StatelessWidget {
     @override
     Widget build(BuildContext context) {
       return new MaterialApp(
         //應(yīng)用名稱  
         title: 'Flutter Demo', 
         theme: new ThemeData(
           //藍(lán)色主題  
           primarySwatch: Colors.blue,
         ),
         //應(yīng)用首頁路由  
         home: new MyHomePage(title: 'Flutter Demo Home Page'),
       );
     }
   }

  • MyApp類代表 Flutter 應(yīng)用,它繼承了 StatelessWidget類,這也就意味著應(yīng)用本身也是一個(gè) widget。
  • 在 Flutter 中,大多數(shù)東西都是 widget(后同“組件”或“部件”),包括對齊(alignment)、填充(padding)和布局(layout)等,它們都是以 widget 的形式提供。
  • Flutter 在構(gòu)建頁面時(shí),會調(diào)用組件的build方法,widget 的主要工作是提供一個(gè) build()方法來描述如何構(gòu)建 UI 界面(通常是通過組合、拼裝其它基礎(chǔ) widget)。
  • MaterialApp 是 Material 庫中提供的 Flutter APP 框架,通過它可以設(shè)置應(yīng)用的名稱、主題、語言、首頁及路由列表等。MaterialApp也是一個(gè) widget。
  • home 為 Flutter 應(yīng)用的首頁,它也是一個(gè) widget。

#2.1.2 首頁

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}


class _MyHomePageState extends State<MyHomePage> {
 ...
}

MyHomePage 是 Flutter 應(yīng)用的首頁,它繼承自StatefulWidget類,表示它是一個(gè)有狀態(tài)的組件(Stateful widget)。關(guān)于 Stateful widget 我們將在第三章“Widget簡介”一節(jié)仔細(xì)介紹,現(xiàn)在我們只需簡單認(rèn)為有狀態(tài)的組件(Stateful widget) 和無狀態(tài)的組件(Stateless widget)有兩點(diǎn)不同:

  1. Stateful widget 可以擁有狀態(tài),這些狀態(tài)在 widget 生命周期中是可以變的,而 Stateless widget 是不可變的。

  1. Stateful widget 至少由兩個(gè)類組成:

  • 一個(gè)StatefulWidget類。
  • 一個(gè) State類; StatefulWidget類本身是不變的,但是State類中持有的狀態(tài)在widget 生命周期中可能會發(fā)生變化。

_MyHomePageState類是MyHomePage類對應(yīng)的狀態(tài)類??吹竭@里,讀者可能已經(jīng)發(fā)現(xiàn):和MyApp 類不同, MyHomePage類中并沒有build方法,取而代之的是,build方法被挪到了_MyHomePageState方法中,至于為什么這么做,先留個(gè)疑問,在分析完完整代碼后再來解答。

#State類

接下來,我們看看_MyHomePageState中都包含哪些東西:

  1. 該組件的狀態(tài)。由于我們只需要維護(hù)一個(gè)點(diǎn)擊次數(shù)計(jì)數(shù)器,所以定義一個(gè)_counter狀態(tài):

   int _counter = 0; //用于記錄按鈕點(diǎn)擊的總次數(shù)

_counter 為保存屏幕右下角帶“+”號按鈕點(diǎn)擊次數(shù)的狀態(tài)。

  1. 設(shè)置狀態(tài)的自增函數(shù)。

   void _incrementCounter() {
     setState(() {
        _counter++;
     });
   }

當(dāng)按鈕點(diǎn)擊時(shí),會調(diào)用此函數(shù),該函數(shù)的作用是先自增_counter,然后調(diào)用setState 方法。setState方法的作用是通知 Flutter 框架,有狀態(tài)發(fā)生了改變,F(xiàn)lutter 框架收到通知后,會執(zhí)行build方法來根據(jù)新的狀態(tài)重新構(gòu)建界面, Flutter 對此方法做了優(yōu)化,使重新執(zhí)行變的很快,所以你可以重新構(gòu)建任何需要更新的東西,而無需分別去修改各個(gè) widget。

  1. 構(gòu)建 UI 界面

構(gòu)建 UI 界面的邏輯在build方法中,當(dāng)MyHomePage第一次創(chuàng)建時(shí),_MyHomePageState類會被創(chuàng)建,當(dāng)初始化完成后,F(xiàn)lutter 框架會調(diào)用 Widget 的build方法來構(gòu)建 widget 樹,最終將 widget 樹渲染到設(shè)備屏幕上。所以,我們看看_MyHomePageStatebuild方法中都干了什么事:

     Widget build(BuildContext context) {
       return new Scaffold(
         appBar: new AppBar(
           title: new Text(widget.title),
         ),
         body: new Center(
           child: new Column(
             mainAxisAlignment: MainAxisAlignment.center,
             children: <Widget>[
               new Text(
                 'You have pushed the button this many times:',
               ),
               new Text(
                 '$_counter',
                 style: Theme.of(context).textTheme.headline4,
               ),
             ],
           ),
         ),
         floatingActionButton: new FloatingActionButton(
           onPressed: _incrementCounter,
           tooltip: 'Increment',
           child: new Icon(Icons.add),
         ),
       );
     }

  • Scaffold 是 Material 庫中提供的頁面腳手架,它提供了默認(rèn)的導(dǎo)航欄、標(biāo)題和包含主屏幕widget樹(后同“組件樹”或“部件樹”)的body屬性,組件樹可以很復(fù)雜。本書后面示例中,路由默認(rèn)都是通過Scaffold創(chuàng)建。
  • body的組件樹中包含了一個(gè)Center 組件,Center 可以將其子組件樹對齊到屏幕中心。此例中, Center 子組件是一個(gè)Column 組件,Column的作用是將其所有子組件沿屏幕垂直方向依次排列; 此例中Column子組件是兩個(gè) Text,第一個(gè)Text 顯示固定文本 “You have pushed the button this many times:”,第二個(gè)Text 顯示_counter狀態(tài)的數(shù)值。
  • floatingActionButton是頁面右下角的帶“+”的懸浮按鈕,它的onPressed屬性接受一個(gè)回調(diào)函數(shù),代表它被點(diǎn)擊后的處理器,本例中直接將_incrementCounter方法作為其處理函數(shù)。

現(xiàn)在,我們將整個(gè)計(jì)數(shù)器執(zhí)行流程串起來:當(dāng)右下角的floatingActionButton按鈕被點(diǎn)擊之后,會調(diào)用_incrementCounter方法。在_incrementCounter方法中,首先會自增_counter計(jì)數(shù)器(狀態(tài)),然后setState會通知Flutter框架狀態(tài)發(fā)生變化,接著,F(xiàn)lutter框架會調(diào)用build方法以新的狀態(tài)重新構(gòu)建UI,最終顯示在設(shè)備屏幕上。

[#](#為什么要將 build 方法放在 state 中-而不是放在statefulwidget中)為什么要將 build 方法放在 State 中,而不是放在 StatefulWidget中?

現(xiàn)在,我們回答之前提出的問題,為什么build()方法放在 State(而不是StatefulWidget)中 ?這主要是為了提高開發(fā)的靈活性。如果將build()方法放在StatefulWidget中則會有兩個(gè)問題:

  • 狀態(tài)訪問不便

試想一下,如果我們的StatefulWidget有很多狀態(tài),而每次狀態(tài)改變都要調(diào)用build方法,由于狀態(tài)是保存在 State 中的,如果build方法在StatefulWidget中,那么build方法和狀態(tài)分別在兩個(gè)類中,那么構(gòu)建時(shí)讀取狀態(tài)將會很不方便!試想一下,如果真的將build方法放在 StatefulWidget 中的話,由于構(gòu)建用戶界面過程需要依賴 State,所以build方法將必須加一個(gè)State參數(shù),大概是下面這樣:

    Widget build(BuildContext context, State state){
        //state.counter
        ...
    }

這樣的話就只能將 State 的所有狀態(tài)聲明為公開的狀態(tài),這樣才能在 State 類外部訪問狀態(tài)!但是,將狀態(tài)設(shè)置為公開后,狀態(tài)將不再具有私密性,這就會導(dǎo)致對狀態(tài)的修改將會變的不可控。但如果將build()方法放在 State 中的話,構(gòu)建過程不僅可以直接訪問狀態(tài),而且也無需公開私有狀態(tài),這會非常方便。

  • 繼承StatefulWidget不便

例如,F(xiàn)lutter 中有一個(gè)動(dòng)畫widget的基類AnimatedWidget,它繼承自StatefulWidget類。AnimatedWidget中引入了一個(gè)抽象方法build(BuildContext context),繼承自AnimatedWidget的動(dòng)畫widget都要實(shí)現(xiàn)這個(gè)build方法?,F(xiàn)在設(shè)想一下,如果StatefulWidget 類中已經(jīng)有了一個(gè)build方法,正如上面所述,此時(shí)build方法需要接收一個(gè)state對象,這就意味著AnimatedWidget必須將自己的 State 對象(記為_animatedWidgetState)提供給其子類,因?yàn)樽宇愋枰谄?code>build方法中調(diào)用父類的build方法,代碼可能如下:

  class MyAnimationWidget extends AnimatedWidget{
      @override
      Widget build(BuildContext context, State state){
        //由于子類要用到AnimatedWidget的狀態(tài)對象_animatedWidgetState,
        //所以AnimatedWidget必須通過某種方式將其狀態(tài)對象_animatedWidgetState
        //暴露給其子類   
        super.build(context, _animatedWidgetState)
      }
  }

這樣很顯然是不合理的,因?yàn)?/p>

  1. AnimatedWidget的狀態(tài)對象是AnimatedWidget內(nèi)部實(shí)現(xiàn)細(xì)節(jié),不應(yīng)該暴露給外部。
  2. 如果要將父類狀態(tài)暴露給子類,那么必須得有一種傳遞機(jī)制,而做這一套傳遞機(jī)制是無意義的,因?yàn)楦缸宇愔g狀態(tài)的傳遞和子類本身邏輯是無關(guān)的。

綜上所述,可以發(fā)現(xiàn),對于StatefulWidget,將build方法放在State中,可以給開發(fā)帶來很大的靈活性。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號