Flutter入門三部曲(2) - 界面開發基礎 | 掘金技術徵文

image.png

上一節咱們熟悉了初始化後的flutter的界面。這一節,咱們就來重點了解一下這部分的內容。html

StatelessWidgets and StatefulWidgets

  • Flutter中的Widget都必須從Flutter庫中繼承。 你將使用的兩個幾乎老是StatelessWidgetStatefulWidget。顧名思義,咱們只要若是是不須要根據狀態變化的組件,咱們能夠直接繼承StatelessWidget.若是和狀態有關係的組件就必須繼承StatefulWidget
  • Flutter中的Widget都是不可變的狀態。 可是實際上,總要根據對應的狀態,視圖發生變化,因此就有了state。用它來保持咱們的狀態。 這樣,一個Stateful Widget,其實是兩個類:狀態對象stateWidget組成的。 以下代碼
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),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
複製代碼
  • setState & build _MyHomePageState繼承於State.一方面須要管理本身的狀態_counter,一方面須要build來構造組件。 改變狀態後,須要經過setState來從新構建widget,就是會從新調用build方法,來獲得狀態同步。

最多見的Widget

接着先看看一些經常使用的組件,這些是隨時可用的小部件,開箱即用,你會很是滿意:算法

  • Text - 用於簡單地在屏幕上顯示文本的小部件。
  • Image - 用於顯示圖像。
  • Icon - 用於顯示Flutter的內置Material和Cupertino圖標。
  • Container - 在Flutter中,至關於div。容許在其中進行添加填充,對齊,背景,力大小以及其餘東西的加載。空的時候也會佔用0px的空間,這很方便。
  • TextInput - 處理用戶反饋。
  • Row, Column- 這些小部件顯示水平或垂直方向的子項列表。
  • Stack - 堆棧顯示一個孩子的列表。這個功能很像CSS中的'position'屬性。
  • Scaffold - 爲應用提供基本的佈局結構。它能夠輕鬆實現底部導航,appBars,後退按鈕等。

更多的能夠看目錄segmentfault

**注意:**若是您熟悉基於組件的框架(如React或Vue),則可能不須要閱讀此內容。Widget就是組件。bash

封裝組件

這樣的話,實際開發中,也是經過不斷對組件的封裝,來提升工做效率。 好比簡單的封裝一個原型的圖片組件(實際上,應該這個width和height均可以封裝進去的。)網絡

class CircleImage extends StatelessWidget {
  final String renderUrl;

  CircleImage(this.renderUrl);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100.0,
      height: 100.0,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        image: new DecorationImage(
          fit: BoxFit.cover,
          image: NetworkImage(renderUrl ?? ''),
        ),
      ),
    );
  }
}

//直接使用
new CircleImage('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533638174553&di=6913961a358faf638b6233e5d3dcc2b2&imgtype=0&src=http%3A%2F%2Fimage.9game.cn%2F2015%2F3%2F5%2F10301938.png')

複製代碼

運行效果(中間皮卡丘)app

image.png

Stateful Widget 的生命週期

如今讓咱們深刻一點, 先來思考一下 - 爲何Stateful Widget會將StateWidget分開呢?框架

  • 答案就只有一個:性能。
  • State管理着狀態,它是常駐的。然而,Widget是不可變的,當配置發生變化,它會立馬發生重建。因此這樣的重建的成本是極低的。 由於State在每次重建時都沒有拋棄,因此能夠維護它而且沒必要每次重建某些東西時都要進行昂貴的計算以得到狀態屬性。
  • 此外,這是容許Flutter動畫存在的緣由。由於State沒有丟棄,它能夠不斷重建它的Widget以響應數據變化。

1. createState()

當建立一個StatefulWidget時。當即調用。一般都是以下,這樣簡單的操做。less

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

複製代碼

2. mounted is true

當這個Widget調用createState 後, 會將buildContext 傳入。 BuildContext 內有本身在widget tree上相關的信息。異步

全部的widgets 都有 bool this.mounted 這個屬性. 當BuildContext傳入時,它將會被標記成 true。若是這個屬性不是true的話,調用setState會報錯。ide

注意:你能夠在調用setState前,檢查一下這個變量。

if (mounted) {...` to make sure the State exists before calling `setState()
複製代碼

3 . initState()

這個方法只會調用一次,在這個Widget被建立以後。它必須調用super.initState(). 在這裏能夠作:

  1. 初始化根據對應BuildContext的狀態
  2. 初始化根據在樹上的父節點的屬性肯定的值
  3. 註冊Streams ChangeNotifiers或者其餘會改變的數據的監聽。
@override
initState() {
  super.initState();
  // Add listeners to this class
  cartItemStream.listen((data) {
    _updateWidget(data);
  });
}

複製代碼

4. didChangeDependencies()

它是在initState 方法後,就會調用。 當Widget依賴的一些數據(好比說是InheritedWidget,後面會介紹)更新時,它會當即被調用。 同時build方法,會自動調用。 須要注意的是,你須要經過調用BuildContext.inheritFromWidgetOfExactType,手動去註冊InheritedWidget的監聽後,這個方法纔會起做用。

文檔還建議,當InheritedWidget更新時,若是須要進行網絡調用(或任何其餘昂貴的操做),它可能會頗有用。

5.build()

這個方法會常常被調用。

6. didUpdateWidget(Widget oldWidget)

若是父組件發生變化,並且必須去重建widget時,並且被相同的runtimeType重建時,這個方法會被調用。

由於Flutter是複用state的。因此,你可能須要從新初始化狀態。 若是你的Widget是須要根據監聽的數據,發生變化的,那麼你就須要從舊的對象中反註冊,而後註冊新的對象。

注意:若是您但願重建與此狀態關聯的Widget,則此方法基本上是'initState'的替代!

這個方法,會自動調用build,因此不須要去調用setState

@override
void didUpdateWidget(Widget oldWidget) {
  if (oldWidget.importantProperty != widget.importantProperty) {
    _init();
  }
}

複製代碼

7. setState()

這個方法會被framework和開發者不斷調用。用來通知組件刷新。

這個方法的不能有異步的回調。其餘,就能夠隨便使用。

void updateProfile(String name) {
 setState(() => this.name = name);
}

複製代碼

8. deactivate()

(這個狀態暫時不是很理解) State從樹中刪除時會調用Deactivate但可能會在當前幀更改完成以前從新插入。此方法的存在主要是由於State對象能夠從樹中的一個點移動到另外一個點。

這不多使用。

9. dispose()

State刪除對象時調用Dispose ,這是永久性的。 在此方法取消訂閱並取消全部動畫,流等

10. mounted is false

state對象被移除了,若是調用setState,會拋出的錯誤。

一些疑問

BuildContext

- 1. 每一個widget都有本身的context。這個context是父組件經過build方法給他返回的。

首先,先看下面代碼。咱們將在四個地方打印context的hashCode,來看看有什麼不一樣

//...
 _MyHomePageState() {
   //1. constructor
    print('constructor context hashcode = ${context.hashCode}');
  }

  void _incrementCounter() {
     //2. member method
    print('_incrementCounter context hashcode = ${context.hashCode}');
    setState(() {
      _counter++;
    });
  }

@override
  void initState() {
    super.initState();
    //3. initState
    print('initState context hashcode = ${context.hashCode}');
  }


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
     //...
      floatingActionButton: new FloatingActionButton(
       onPressed: () {
          //4.floattingbutton
          print(
              'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
          _incrementCounter();
        },
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
複製代碼

image.png

很明顯能夠看到,咱們在initState方法時,已經分配拿到了父組件的BuildContext.接下來的直接使用context,也都是同一個。

咱們知道能夠經過Scaffold的context來彈出一個SnackBar。這裏想經過點擊彈出這個。 修改代碼以下:

//...
floatingActionButton: new FloatingActionButton(
        onPressed: () {
          print(
              'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
          Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('I am context from Scaffold'),
              ));
        },
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), 
複製代碼

運行,可是運行報錯信息以下:

image.png

很明顯。經過上面的測試,咱們知道這裏的context,確實不是Scaffold。那咱們要如何在這裏拿到Scaffold的context呢?

2. 經過builder方法

修改代碼以下,經過Builder方法,獲得這個context.

//...
floatingActionButton: new Builder(
        builder: (context) {
          return new FloatingActionButton(
            onPressed: () {
              print(
                  'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
              Scaffold.of(context).showSnackBar(SnackBar(
                    content: Text('I am context from Scaffold'),
                  ));
              _incrementCounter();
            },
            tooltip: 'Increment',
            child: new Icon(Icons.add),
          );
        },
      )
複製代碼

運行結果

image.png

咱們能夠看到,咱們確實拿到了Scaffold分配的Context,並且彈出了SnackBar.

後續過程當中,必定要注意這個Context的使用。

注意:這裏其實還有另一個方法,來獲得這個BuildContext。就是將FloatingActionButton分離出來,寫成另一個組件,就能經過build方法拿到了。

方法以下:

  • 添加類
class ScaffoldButton extends StatelessWidget {
  ScaffoldButton({this.onPressedButton});

  final VoidCallback onPressedButton;

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        print(
            'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
        Scaffold.of(context).showSnackBar(
            SnackBar(content: Text('I am context from Scaffold')));
        onPressedButton();
      },
      tooltip: 'Increment',
      child: new Icon(Icons.add),
    );
  }
}
複製代碼

再將floatingActionButton修改爲這個類

//...
        floatingActionButton: ScaffoldButton(
          onPressedButton: () {
            _incrementCounter();
          },
        ));

複製代碼

不知所云的構造參數 Key

隨意點開一個Widget,就會發現,能夠傳遞一個參數Key.那這個Key究竟是幹啥子,有什麼用呢?

image.png

Flutter是受React啓發的,因此Virtual Dom的diff算法也參考過來了(應該是略有修改),在diff的過程當中若是節點有Key來比較的話,可以最大程度重用已有的節點(特別在列表的場景),除了這一點這個Key也用在不少其餘的地方這個之後會總結一下。總之,這裏咱們能夠知道key可以提升性能,因此每一個Widget都會構建方法都會有一個key的參數可選,貫穿着整個框架。

一般狀況下,咱們不須要去傳遞這個Key。由於framework會在內部自處理它,來區分不一樣的widgets 下面有幾種狀況,咱們可使用它

- 使用ObjectKeyValueKey來對組件進行區分。

能夠看PageStorageKey, 和另一個例子,這個例子是deletion: flutter.io/cookbook/ge….

簡單的來講,當咱們使用Row或者Column時,想要執行一個remove的動畫

new AnimatedList(
  children: [
    new Card(child: new Text("foo")),
    new Card(child: new Text("bar")),
    new Card(child: new Text("42")),
  ]
)
複製代碼

當咱們移除"bar"後

new AnimatedList(
  children: [
    new Card(child: new Text("foo")),
    new Card(child: new Text("42")),
  ]
)
複製代碼

由於咱們沒有定義Key,因此可能flutter並不知道,咱們那個item發生了改變,因此可能發生在位置1上的動畫,可能發生在其餘位置。 正確的修改以下:

new AnimatedList(
  children: [
    new Card(key: new ObjectKey("foo"), child: new Text("foo")),
    new Card(key: new ObjectKey("bar"), child: new Text("bar")),
    new Card(key: new ObjectKey("42"), child: new Text("42")),
  ]
)
複製代碼

這樣當咱們移除"bar"的時候,flutter就能準確的區別到正確的位置上。 Key雖然不是Index,可是對於每個元素來講,是獨一無二的。

- 使用GlobalKey
  1. 使用GlobalKey的場景是,從父控件和跨子Widget來傳遞狀態時。 須要注意的是:不要濫用GlobalKey,若是有更好的方式的,請使用其餘方式來傳遞狀態。

這裏有一個例子是 經過給Scaffold添加GolbalKey。而後通widget.GolbalKey.state來調用showSnackBar

class _MyHomePageState extends State<MyHomePage> {
  final globalKey =
      new GlobalKey<ScaffoldState>();

  void _incrementCounter() {
    globalKey.currentState
        .showSnackBar(SnackBar(content: Text('I am context from Scaffold')));
  }

 @override
  Widget build(BuildContext context) {
    return new Scaffold(
        key: globalKey,
      //...
      )
}
}
複製代碼

這樣就能夠直接從父控件調用子Widget的狀態。

  1. 還有一個場景是,過渡動畫,當兩個頁面都是相同的Widget時,也可使用GlobalKey。

總結

這邊文章,咱們對StateFulWidget有了升入的認識。

  • 認識了通用的控件
  • 瞭解了StatefulWidget的生命週期
  • 對BuildContext 瞭解。
  • 對Key的場景進行了瞭解。獲得了使用GlobalKey來跨子組件傳遞狀態的方式。

下一遍文章:咱們將更加深刻的對Flutter的界面開發的一些原理

參考文章

Flutter Widgets

Flutter中的Key,LocalKey,GlobalKey... And More

what-are-keys-used-for-in-flutter-framework


從 0 到 1:個人 Flutter 技術實踐 | 掘金技術徵文,徵文活動正在進行中

相關文章
相關標籤/搜索