深刻講解Flutter應用模板源碼:計數器示例

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

示例源碼

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),
      ),
    );
  }
}
複製代碼

運行效果

效果圖

源碼分析

一、導包

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

二、主函數(應用入口)

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

三、應用結構

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。

四、首頁

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是不可變的。bash

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

①、一個StatefulWidget類;框架

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

  • _MyHomePageState類是MyHomePage類對應的狀態類。看到這裏,細心的讀者可能已經發現,和MyApp類不一樣的是,MyHomePage類中並無build方法,取而代之的是,build方法被挪到了_MyHomePageState方法中,至於爲何這麼作,先留個疑問,在分析完完整代碼後再來解答,接下來,咱們看看_MyHomePageState中都包含哪些東西:
    • 狀態ide

      int _counter = 0;
      複製代碼

      _counter爲保存屏幕右下角帶「+」號按鈕點擊次數的狀態。函數

    • 設置狀態的自增函數源碼分析

      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做爲其處理函數。

五、總結

如今,咱們將整個流程串起來:當右下角的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中,能夠給開發帶來很大的靈活性。

相關文章
相關標籤/搜索