Flutter入門篇(一)

距離Google發佈Flutter已經有很長一段時間了,又是一門新的技術,那麼咱們究竟是學呢仍是學呢仍是學呢?不要問我,我不知道,鬼特麼知道我這輩子還要學習多少東西。其實新技術的出現也意味着,老技術會面臨淘汰危機,而你將面臨着失業危機。用一句話來講:你永遠不知道意外和驚喜哪一個先來~~java

環境搭建

Flutter的安裝就不在這裏演示了,能夠從下面幾個網站上學習安裝。網絡

這些網站也經過豐富的Flutter學習資料app

Flutter的第一個應用

在建立一個Flutter應用後,咱們能夠看到以下的demo代碼。(其中註釋是我的翻譯,若有不正確請諒解)less

import 'package:flutter/material.dart';

//應用啓動
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // 這個App的根Widget
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', //應用名
      theme: ThemeData(
        // 這個應用的主題
        //
        // 你用 "flutter run"運行這個應用,你將看到一個藍色的ToolBar。
        // 你也能夠改變下面primarySwatch 的值,從Colors.blue變成 Colors.green。
        // 而後執行 "hot reload" ,能夠看到計數器並無恢復初始狀態0,這個應用也並無重啓。
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  // 咱們能夠知道這個MyHomePage 的 widget 是這個應用的首頁,並且它是有狀態的,
  //這就意味着下面定義的State對象中的字段可以影響應用的顯示。

 //這個類是這個狀態的配置類,它所持有的這個title值是其父類提供的,
 //被建立狀態的方法使用,在Widget的子類中老是被標記爲「final」

  final String title;

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

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

  void _incrementCounter() {
    setState(() {
      // 回調setState去告訴Flutter framework 已經有一些狀態發生了改變,
      // 讓下面這個返回Widget的build方法去展現更新的內容。固然,若是咱們沒有回調
      // 這個setState,那麼build方法也不會被調用,也就不會有什麼更新展現。
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // 這個方法在每次setState的時候被調用,例如上面的_incrementCounter方法。
    //
    // Flutter framework 是被優化過的,因此它的從新運行build方法是很是快速的,只須要
    // 運行你須要更新的內容,不須要去分別全部的widgets的實例。
    return Scaffold(
      appBar: AppBar(
        // 咱們可以使用在App.build方法中建立的值,而且賦值
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center是一個佈局Widget,它提供一個child 而且規定了只能居於父類的正中心
        child: Column(
          // Column 也是一個佈局Widget,它有一系列的子佈局而且這些子佈局都是垂直方向的。
          // 默認狀況下,Column會調整它本身的大小去適應子級的橫向大小。
          //
          // 調用 "debug painting"能夠看每個widget的線框
          //
          // Column 有大量的屬性去控制本身的大小和它子級的位置,這裏使用了mainAxisAlignment
          // 讓其子佈局內容是垂直方向排列。
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

複製代碼

運行Flutter

我使用的是Android Studio 建立的Flutter應用,能夠看到以下所示的編譯界面ide

圖片來自網絡

  • 點擊Run (就是那個綠色的三角)以後咱們能夠看到以下運行結果:

  • 點擊藍色的「+」號咱們能夠看到,中間的數字一直在增長,因此demo給個人是一個簡單計數器的實現

Demo分析

咱們從官網知道Flutter是用Dart語言進行編碼的,咱們是否是須要單獨去學習掌握這門語言呢?在我看來是不須要的,由於單獨去學習一門新的語言的過程是很枯燥的,咱們能夠從Demo中去學習,這樣更高效一些。因此咱們來分析一下上述例子給了咱們一個怎樣的知識點。佈局

import 'package:flutter/material.dart';

void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
複製代碼

經過上述代碼咱們知道:性能

  • 首先導入了一個叫作materialdart文件。
  • 經過一個main()方法調用了MyApp的一個類。
  • 這個MyApp就是這個應用的入口。(根據runApp可知)

對於一個Flutter小白就會有疑問了:學習

  • 爲何要導入material的文件呢? 遇到這樣不明白的地方,咱們就能夠去官網查資料了,官網給的回答以下:

Flutter提供了許多widgets,可幫助您構建遵循Material Design的應用程序。Material應用程序以MaterialApp widget開始, 該widget在應用程序的根部建立了一些有用的widget,其中包括一個Navigator, 它管理由字符串標識的Widget棧(即頁面路由棧)。Navigator可讓您的應用程序在頁面之間的平滑的過渡。 是否使用MaterialApp徹底是可選的,可是使用它是一個很好的作法。優化


也就是說主要是爲了向開發者提供已經實現好了的material設計風格,咱們能夠進入(Windows下Ctrl +鼠標左鍵,Mac下Command+鼠標左鍵material.dart源碼,能夠發現以下:動畫

library material;

export 'src/material/about.dart';
export 'src/material/animated_icons.dart';
...
// 不少,這裏就不佔用大量篇幅
export 'widgets.dart';
複製代碼

從官網咱們知道已經有大量的widgets供給咱們使用,那麼這些在哪裏呢? 固然就是上面的widgets.dart文件了,咱們進入這個文件中能夠看到內容大體以下:

export 'src/widgets/animated_cross_fade.dart';
...
export 'src/widgets/framework.dart';
...
export 'src/widgets/will_pop_scope.dart';
複製代碼

也是不一樣的dart文件,咱們進入第一個animated_cross_fade

class AnimatedCrossFade extends StatefulWidget {
/// Creates a cross-fade animation widget.
	...
}
複製代碼

從給的註釋能夠知道,這就是一個帶淡入淡出動畫的Widget,這個Widget繼承自StatefulWidget,能夠看到StatefulWidget也就是繼承自Widget

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key);

  /// Creates a [StatelessElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}


abstract class StatefulWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatefulWidget({ Key key }) : super(key: key);

  /// Creates a [StatefulElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatefulElement createElement() => StatefulElement(this);
  @protected
  State createState();
}
複製代碼

到此咱們驚奇的發現,Demo代碼中的MyApp繼承的StatelessWidget原來也在這裏,可是MyHomePage卻繼承自StatefulWidget,這是爲何呢?這就會引出第二個問題:

  • StatelessWidgetStatefulWidget 的區別是什麼呢?

StatefulWidget能夠擁有狀態,這些狀態在widget生命週期中是能夠變的,而StatelessWidget是不可變的。 StatefulWidget至少由兩個類組成:

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

StatelessWidget用於不須要維護狀態的場景,它一般在build方法中經過嵌套其它Widget來構建UI,在構建過程當中會遞歸的構建其嵌套的Widget


這也就是爲何MyApp是繼承自StatelessWidget 而 MyHomePage 繼承自StatefulWidget:

  • MyApp中不須要更改狀態,僅僅嵌套一個MyHomePage 的Widget
  • 而MyHomePage 要繼承StatefulWidget的緣由是:經過點擊+去增長數字大小,改變了顯示的狀態,因此須要繼承StatefulWidget

分析執行方式

咱們回到 MyApp這個類的build方法中,能夠看到它返回了一個MaterialApp的一個Widget,在前面說過,Material Design的應用是以MaterialApp widget開始的,因此返回了一個MaterialApp

return MaterialApp(
      title: 'Flutter Demo', //應用名
      theme: ThemeData(
        primarySwatch: Colors.blue, // 主題色
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'), // 首頁
    );
複製代碼

從上能夠知道因爲計數是一個可變的更新狀態,那麼就須要兩個類去實現:

  • 一個繼承自StatefulWidget, 就是咱們的MyHomePage
  • 一個繼承自State用於維護這個狀態,也就是咱們的_MyHomePageState
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // 咱們能夠知道這個MyHomePage 的 widget 是這個應用的首頁,並且它是有狀態的,
  //這就意味着下面定義的State對象中的字段可以影響應用的顯示。

 //這個類是這個狀態的配置類,它所持有的這個title值是其父類提供的,
 //被建立狀態的方法使用,在Widget的子類中老是被標記爲「final」

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}
複製代碼

MyHomePage這個類裏面沒有太多內容:

  • 經過構造方法將title值傳入
  • 經過createState 返回了一個_MyHomePageState的有狀態的State

到此處咱們知道了實際上對數據的操做確定就在_MyHomePageState中:

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

  void _incrementCounter() {
    setState(() {
      // 回調setState去告訴Flutter framework 已經有一些狀態發生了改變,
      // 讓下面這個返回Widget的build方法去展現更新的內容。固然,若是咱們沒有回調
      // 這個setState,那麼build方法也不會被調用,也就不會有什麼更新展現。
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // 這個方法在每次setState的時候被調用,例如上面的_incrementCounter方法。
    //
    // Flutter framework 是被優化過的,因此它的從新運行build方法是很是快速的,只須要
    // 運行你須要更新的內容,不須要去分別全部的widgets的實例。
    return Scaffold(
      appBar: AppBar(
        // 咱們可以使用在App.build方法中建立的值,而且賦值
        title: Text(widget.title),
      ),
      body: Center(
        // Center是一個佈局Widget,它提供一個child 而且規定了只能居於父類的正中心
        child: Column(
          // Column 也是一個佈局Widget,它有一系列的子佈局而且這些子佈局都是垂直方向的。
          // 默認狀況下,Column會調整它本身的大小去適應子級的橫向大小。
          //
          // 調用 "debug painting"能夠看每個widget的線框
          //
          // Column 有大量的屬性去控制本身的大小和它子級的位置,這裏使用了mainAxisAlignment
          // 讓其子佈局內容是垂直方向排列。
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
複製代碼

能夠看出這裏提供了兩個方法:build_incrementCounter,咱們已經知道通常Widget裏面的build方法返回的是一個頁面佈局。

_incrementCounter的實現內容很簡單: 就是使用setState方法去自增這個_counter,但此處一點要注意,更改狀態必定要使用 setState,若是不調用 setState將不會有任何的改變,即便你自增了這個_counter。(能夠本身嘗試一下)

咱們從註釋中可知,這個build方法在每次更新狀態(setState)的時候進行調用,咱們在build的方法中增長一行打印的代碼進行驗證:

@override
  Widget build(BuildContext context) {
    print("build again");
    return Scaffold(
		...
	);
複製代碼

發現果真是每一次點擊+就會調用一次build方法,那這就會引出一個問題:這樣每一次都進行更新,會影響新能嗎?


Flutter framework 被優化於快速重啓運行,只須要運行你須要更新的內容,不須要去分別從新構建全部的widgets的實例。


因此徹底沒必要擔憂這個每次都執行build方法會影響性能。

從總體的佈局咱們知道,build返回了一個Scaffold的widget:

class Scaffold extends StatefulWidget {
  /// Creates a visual scaffold for material design widgets.
  const Scaffold({ Key key, this.appBar, this.body, this.floatingActionButton, this.floatingActionButtonLocation, this.floatingActionButtonAnimator, this.persistentFooterButtons, this.drawer, this.endDrawer, this.bottomNavigationBar, this.bottomSheet, this.backgroundColor, this.resizeToAvoidBottomPadding, this.resizeToAvoidBottomInset, this.primary = true, this.extendBody = false, this.drawerDragStartBehavior = DragStartBehavior.down, }) : assert(primary != null), assert(extendBody != null), assert(drawerDragStartBehavior != null), super(key: key);

複製代碼

能夠知道,這個也是繼承於StatefulWidget,裏面有不少能夠設置的初始值,這裏使用到了三個:

  • appBar-佈局標題欄
  • body-內容顯示區域
  • floatingActionButton-浮動按鈕
return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
複製代碼

將從MyApp攜帶的title賦值給appBar的title,讓其顯示在界面頂端。內容(body)使用了一個Center居中佈局,讓其child(也是一個widget)只能顯示在當前正中位置。

class Center extends Align {
  /// Creates a widget that centers its child.
  const Center({ Key key, double widthFactor, double heightFactor, Widget child }) : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}
複製代碼

緊接着返回了一個Column的child,這個widget 是縱向排列,可有有一系列的子集,因此在Column 布了兩個Text,一個顯示固定文本,一個顯示可變的文本:

class Column extends Flex {
  Column({
    Key key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    TextDirection textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline textBaseline,
    List<Widget> children = const <Widget>[],
  }) : super(
    children: children,
    key: key,
    direction: Axis.vertical,
    mainAxisAlignment: mainAxisAlignment,
    mainAxisSize: mainAxisSize,
    crossAxisAlignment: crossAxisAlignment,
    textDirection: textDirection,
    verticalDirection: verticalDirection,
    textBaseline: textBaseline,
  );
}
複製代碼

須要注意的是,對變量的佔位符使用的$符號,就跟java中使用%是同樣的。 最後一個就是咱們點擊事件的按鈕floatingActionButton,經過onPressed去調用_incrementCounter方法實現自增計數。 整個運行的流程就到這裏算是講完了。

Hot Reload(熱加載)

在文章開始的時候咱們知道,咱們有一個一道雷同樣的圖標,那就是Hot Reload,這個怎麼個意思呢?就是說你若是更新了你的代碼,不用從新運行整個都從新運行,直接使用這個就能夠了,能夠很迅速的將你更新的內容從新顯示。 在這裏有一個頗有意思的事情: 咱們點擊+讓計數器顯示到1,而後將主題的顏色改爲綠色:primarySwatch: Colors.green,

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
       // 主題從藍色更改成綠色
        primarySwatch: Colors.green,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
複製代碼

而後點擊hot reload 會發現,咱們的主題更改成綠色了,可是個人計數器顯示的數字仍然是1,並無變成0。這也印證了上面的那句話,hot reload只會更改所須要更改的內容,不會影響所有。

總結

這裏在作一個總結,但願對才你有所幫助

  • flutter中,絕大部分可以使用的內容都是widget
  • 若是隻是顯示內容,不涉及更改狀態,就是用StatelessWidget;若是涉及狀態的變動就是用StatefulWidget
  • StatefulWidget的實現須要兩步:一個是須要建立繼承StatefulWidget的類;另外一個就是建立繼承State的類,通常在State中控制整個狀態。
  • 更新狀態必定要調用setState方法,否則不會起做用
  • hot reload只會影響更改的內容
相關文章
相關標籤/搜索