深入講解Flutter應用模板原始碼:計數器示例

zane發表於2019-05-26

用Android Studio和VS Code建立的Flutter應用,原始碼模板是一個計數器示例,通過講解分析計數器示例的原始碼,可以讓讀者對Flutter應用程式結構有個最基本的瞭解,在後續的文章中,筆者將會基於此示例,一步一步新增新的功能來介紹Flutter應用的其他概念與技術。

示例原始碼

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.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}
複製程式碼

執行效果

效果圖

原始碼分析

1、導包

import 'package:flutter/material.dart';
複製程式碼
  • 此行程式碼作用是匯入了Material UI元件庫。Material是一種標準的移動端和web端的視覺設計語言,Flutter預設提供了一套豐富的Material風格的UI元件。

2、主函式(應用入口)

void main() => runApp(MyApp());
複製程式碼
  • 與C/C++、Java類似,Flutter應用中main函式為應用程式的入口,main函式中呼叫了runApp方法,它的功能是啟動Flutter應用,它接受一個Widget引數,在本示例中它是MyApp類的一個例項,該引數代表Flutter應用。
  • main函式使用了(=>)符號,這是Dart中單行函式或方法的簡寫。

3、應用結構

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'),
    );
  }
}
複製程式碼
  • MyApp類代表Flutter應用,它繼承了StatelessWidget類,這意味著應用本身也是一個Widget。在Flutter中,一切皆為元件(Widget),包括對齊(alignment)、填充(padding)和佈局(layout)。
  • Flutter在構建頁面時,會呼叫元件的build方法,Widget的主要工作是提供一個build方法來描述如何構建UI介面(通常都是通過組合、拼裝其他基礎Widget)。
  • MaterialApp是Material庫中提供的Flutter APP框架,通過它可以設定應用的名稱、主題、語言和首頁及路由列表等。MaterialApp也是一個Widget。
  • home為Flutter應用的首頁,它也是一個widget。

4、首頁

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是應用的首頁,它繼承StatefulWidget類,表示它是一個有狀態的Widget(Stateful widget),那麼Stateful widget和Stateless widget有什麼區別呢?

(1)、Stateful widget可以擁有狀態,這些狀態在Widget生命週期中是可以變的,而Stateless widget是不可變的。

(2)、Stateful widget至少由兩個類組成:

①、一個StatefulWidget類;

②、一個State類,StatefulWidget類本身是不變的,但是State類中持有的狀態在Widget生命週期中可能會發生變化。

  • _MyHomePageState類是MyHomePage類對應的狀態類。看到這裡,細心的讀者可能已經發現,和MyApp類不同的是,MyHomePage類中並沒有build方法,取而代之的是,build方法被挪到了_MyHomePageState方法中,至於為什麼這麼做,先留個疑問,在分析完完整程式碼後再來解答,接下來,我們看看_MyHomePageState中都包含哪些東西:
    • 狀態

      int _counter = 0;
      複製程式碼

      _count為儲存螢幕右下角帶“+”號按鈕點選次數的狀態。

    • 設定狀態的自增函式

      void _incrementCounter() {
          setState(() {
              _counter++;
          });
      }
      複製程式碼

      當按鈕點選時,會呼叫此函式,該函式的作用是先自增_counter,然後呼叫setState方法。setState方法的作用是通知Flutter框架,有狀態發生了改變,Flutter框架收到通知後,會執行build方法來根據新的狀態重新構建介面,Flutter對此方法做了優化,使重新執行變的很快,所以讀者可以重新構建任何需要更新的東西,而無需分別去修改各個Widget。

    • 構建UI介面

      構建UI介面的邏輯在build方法中,當MyHomePage第一次建立時,_MyHomePageState類會被建立,當初始化完成後,Flutter框架會呼叫Widget的build方法來構建Widget樹,最終將Widget樹渲染到裝置螢幕上。所以,我們看看_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.display1,
                  ),
                ],
              ),
            ),
            floatingActionButton: new FloatingActionButton(
              onPressed: _incrementCounter,
              tooltip: 'Increment',
              child: new Icon(Icons.add),
            ),
          );
        }
      複製程式碼
    • Scaffold是Material庫中提供的一個Widget,它提供了預設的導航欄、標題和包含主螢幕widget樹的body屬性。路由預設都是通過Scaffold建立。

    • body的Widget樹中包含了一個CenterWidget,Center可以將其子Widget樹對齊到螢幕中心,Center子Widget是一個ColumnWidget,Column的作用是將其所有子Widget沿螢幕垂直方向依次排列,此例中Column包含兩個Text子Widget,第一個TextWidget顯示固定文字“You have pushed the button this many times:”,第二個TextWidget顯示_counter狀態的數值。

    • floatingActionButton是頁面右下角的帶“+”的懸浮按鈕,它的onPressed屬性接受一個回撥函式,代表它被點選後的處理器,本例中直接將_incrementCounter作為其處理函式。

5、總結

現在,我們將整個流程串起來:當右下角的floatingActionButton按鈕被點選之後,會呼叫_incrementCounter函式,在_incrementCounter函式中,首先會自增_counter狀態(計數器),然後setState會通知Flutter框架狀態發生變化,接著,Flutter會呼叫build方法以新的狀態重新構建UI,最終顯示在裝置螢幕上。

疑問解答

為什麼要將build方法放在State中,而不是放在StatefulWidget中?

現在,我們回答之前提出的問題,為什麼build方法在State中,而不是StatefulWidget中?這主要是為了開發的靈活性。如果將build方法在StatefulWidget中則會有兩個問題:

  • 狀態訪問不便

    試想一下,如果我們的Stateful widget有很多狀態,而每次狀態改變都要呼叫build方法。由於狀態是儲存在State中的,如果將build方法放在StatefulWidget中,那麼構建時讀取狀態將會很不方便,因為構建使用者介面過程需要依賴State,所以build方法必須加一個State引數,如下所示:

    Widget build(BuildContext context, State state) {
        //state.counter
        ...
    }
    複製程式碼

    這樣的話,只能將State的所有狀態宣告為公開的狀態,這樣才能在State類外部訪問狀態,但將狀態設定為公開後,狀態將不再具有私密性,這樣的依賴對狀態的修改將會變的不可控。而將build方法放在State中的話,構建過程則可以直接訪問狀態,故而方便。

  • 繼承StatefulWidget不便

    例如,Flutter中有一個動畫Widget的基類AnimatedWidget,它繼承自StatefulWidget類。AnimatedWidget中引入了一個抽象方法build(BuildContext context),繼承自AnimatedWidget的動畫Widget都要實現這個build方法。試想一下,如果StatefulWidget類中已經有了一個build方法,正如上述所述,此時build方法需要接收一個State物件,這就意味著AnimatedWidget必須將自己的State物件(記為_animatedWidgetState)提供給其子類,因為子類需要在其build方法中呼叫父類的build方法,程式碼如下:

    class MyAnimationWidget extends AnimatedWidget{
        @override
        Widget build(BuildContext context, State state){
            //由於子類要用到AnimatedWidget的狀態物件_animatedWidgetState,
            //所以AnimatedWidget必須通過某種方式將其狀態物件_animatedWidgetState暴露給其子類
            super.build(context, _animatedWidgetState)
        }
    }
    複製程式碼

    這樣很不合理:

    • AnimatedWidget的狀態物件是AnimatedWidget內部實現細節,不應該暴露給外部。
    • 如果要將父類狀態暴露給子類,那麼必須得有一種傳遞機制,而做這一套傳遞機制是無意義的,因為父子類之間狀態的傳遞和子類本身邏輯是無關的。

    綜上所述,對於StatefulWidget,將build方法放在State中,可以給開發帶來很大的靈活性。

相關文章