Flutter——Widget系列4、widget狀態(tài)分類,有狀態(tài)(StatefulWidget)和無狀態(tài)(StatelessWidget)

image.png

前言

簡單粗暴來說,一個widget,如果在運(yùn)行的過程中需要變化,就是有狀態(tài)的。

Fluter的widget,安卓狀態(tài)來說,可以分為:有狀態(tài)(StatefulWidget)和無狀態(tài)(StatelessWidget)

無狀態(tài)(StatelessWidget)

狀態(tài)組件指的就是其內(nèi)部的狀態(tài)是來自其父組件并使用final類型的變量來存儲,當(dāng)組件被build的時候它們就使用這些不可變的數(shù)據(jù)來構(gòu)建自己的UI。

無狀態(tài)的組件,前面我們已經(jīng)使用無數(shù)次了,重點(diǎn)是看StatefulWidget,有狀態(tài)的組件。

有狀態(tài)(StatefulWidget)

有狀態(tài)的wdiget,其持有狀態(tài)可能在Widget生命周期中發(fā)生變化。

  • Checkbox、Radio、Slider、InkWell、Form和TextField是有狀態(tài)小部件的示例,它們是StatefulWidget的子類。

  • 對于有可變狀態(tài)控件的管理,官方文檔是寫了有3種模式:控件自己管理狀態(tài)、交給父控件管理狀態(tài)以及混合管理 (自己和父部件各管理一部分)。

實(shí)現(xiàn)一個StatefulWidget至少需要兩個類:
一個StatefulWidget
一個State類。

  • StatefulWidget類本身是不變的,但是State類在Widget生命周期中始終存在,當(dāng)在State內(nèi)部改變?nèi)魏巫涌丶枰淖兞繒r,都需要使用setState。

一句話來說,就是一個有狀態(tài)的組件,需要兩個類,分別是StatefulWidget和State,而狀態(tài)組件的內(nèi)容,需要用到setState方法。


一、 有狀態(tài)(StatefulWidget)

實(shí)現(xiàn)StatefulWidget需要的兩個類

實(shí)現(xiàn)一個StatefulWidget至少需要兩個類:
一個StatefulWidget
一個State類。

如何改變StatefulWidget的內(nèi)容

  • StatefulWidget類本身是不變的,但是State類在Widget生命周期中始終存在,當(dāng)在State內(nèi)部改變?nèi)魏巫涌丶枰淖兞繒r,都需要使用setState

例子

例子1 控件自己管理狀態(tài)

一個計(jì)數(shù)器,點(diǎn)擊自身,統(tǒng)計(jì)點(diǎn)擊次數(shù)。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
//    final wordPair = new WordPair.random();

    return MaterialApp(
        title: 'Flutter 測試標(biāo)題',
        theme: new ThemeData(
          primaryColor: Colors.red,
        ),
        home: new Scaffold(
          appBar: AppBar(
            title: Text("測試呀"),
          ),
          body: new Counter(),
        ));
  }
}

class Counter extends StatefulWidget {
  // 經(jīng)典寫法,繼承自StatefulWidget的Widget返回一個自己的State私有類
  _CounterState createState() => new _CounterState();
}

class _CounterState extends State<Counter> {
  // State類里面定義私有變量,我們的狀態(tài)變化,會通過setState方法具體操作
  int _count = 0;

  void _increment() {
    setState(() {
      ++_count;
    });
  }

  Widget build(BuildContext context) {
    return new Container(
        decoration: new BoxDecoration(color: Colors.grey[100]),
        child: new Center(
            child: new RaisedButton(
                // 按下時的事件
                onPressed: _increment,
                // 內(nèi)容不寫死,數(shù)據(jù)動態(tài)顯示,一切都是因?yàn)開increment里面的setState方法
                child: new Text('click count : ${_count}'))));
  }
}

.
.

重要都在備注里面了,如果非要說說邏輯,大概就是:

  • 1、繼承自StatefulWidget的Widget返回一個自己的State私有類
  • 2、State類里面定義私有變量,我們的狀態(tài)變化,會通過setState方法具體操作
  • 3、通過一定的事件或者條件,觸發(fā)setState改變數(shù)據(jù)
  • 4、數(shù)據(jù)展示的邏輯,不寫死,根據(jù)變量的調(diào)整動態(tài)調(diào)整
2019-08-27 13.57.03.gif

.
.

例子2 父控件管理子控件狀態(tài)


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
//    final wordPair = new WordPair.random();

    return MaterialApp(
        title: 'Flutter 測試標(biāo)題',
        theme: new ThemeData(
          primaryColor: Colors.red,
        ),
        home: new Scaffold(
          appBar: AppBar(
            title: Text("測試呀"),
          ),
          body: new ParentWidget(),
        ));
  }
}


//------------------------- parent widget ----------------------------------

class ParentWidget extends StatefulWidget {

  ParentWidget() {
    print('ParentWidget init');
  }

  @override
  State<StatefulWidget> createState() {
    return _ParentWidgetState();
  }
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  _ParentWidgetState() {
    print('ParentWidgetState init');
  }

  void _handleSonChanged(bool newValue) {
    print('parent _handleSonChanged is call : $newValue');
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    print('ParentWidgetState build is call');
    return new Center(
      child: SonWidget(onChanged: _handleSonChanged, isShowAndroid: _active,),
    );
  }
}

//------------------------- child widget ----------------------------------

class SonWidget extends StatelessWidget {

  SonWidget({
    Key key, this.isShowAndroid: false, 
    @required this.onChanged
  }): super(key: key) {
    print('Son SonWidget init : ${this.isShowAndroid}');
  }

  final bool isShowAndroid;
  final ValueChanged<bool> onChanged;

  void _handleSon() {
    print('child _handleSon : $isShowAndroid');
    onChanged(!isShowAndroid);
  }

  @override
  Widget build(BuildContext context) {
    print('Son SonWidget build method');
    return GestureDetector(
      onTap: _handleSon,
      child: Container(
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
            color: isShowAndroid ? Colors.lightGreen[700] : Colors.grey[600]
        ),
        child: Center(
          child: Text(isShowAndroid ? 'Android' : 'Flutter',
            style: TextStyle(fontSize: 32.0, color: Colors.white),),
        ),
      ),
    );
  }
}

核心:子控件的構(gòu)造方法,暴露出事件參數(shù),父控件創(chuàng)建子控件的時候,傳入?yún)?shù),進(jìn)而實(shí)現(xiàn)父控件改變自控內(nèi)容的目的。

所以核心代碼:

image.png
初始化順序

初始化輸出日志:


flutter: ParentWidget init
flutter: ParentWidgetState build is call
flutter: Son SonWidget init : false
flutter: Son SonWidget build method
Reloaded 1 of 432 libraries in 2,751ms.
image.png
點(diǎn)擊回調(diào)執(zhí)行順序

點(diǎn)擊輸出日志:

第一次點(diǎn)擊
flutter: child _handleSon : false
flutter: parent _handleSonChanged is call : true
flutter: ParentWidgetState build is call
flutter: Son SonWidget init : true
flutter: Son SonWidget build method

第二次點(diǎn)擊
flutter: child _handleSon : true
flutter: parent _handleSonChanged is call : false
flutter: ParentWidgetState build is call
flutter: Son SonWidget init : false
flutter: Son SonWidget build method

第三次點(diǎn)擊
flutter: Son SonWidget init : false
flutter: Son SonWidget build method
flutter: child _handleSon : false
flutter: parent _handleSonChanged is call : true
flutter: ParentWidgetState build is call
flutter: Son SonWidget init : true
flutter: Son SonWidget build method

一切是那么重新,在父控件控制子控件的時,刷新ui會讓子控件重新初始化和和build(繪制)。

image.png

.
.

參考:為你的 Flutter APP 添加交互性

2019-08-27 15.56.01.gif

.
.

例子3 混合管理

也就是,父控件管,子控件自己也管,一起管

混合管理就是某些狀態(tài)由自己管理,某些狀態(tài)由父部件來管理。
下面的例子就是一個混合管理狀態(tài)的例子,部件 TabboxC 在被點(diǎn)擊時有三個狀態(tài)變換,背景色,文字和邊框。
示例中,背景色和文字的狀態(tài)交由父部件來管理(和上一個示例類似),而邊框狀態(tài)由自己管理。
既然父部件和子部件都能管理狀態(tài),那么它們都是要繼承StatefulWidget類。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
//    final wordPair = new WordPair.random();

    return MaterialApp(
        title: 'Flutter 測試標(biāo)題',
        theme: new ThemeData(
          primaryColor: Colors.red,
        ),
        home: new Scaffold(
          appBar: AppBar(
            title: Text("測試呀"),
          ),
          body: new ParentWidget2(),
        ));
  }
}


// ------------parent widget-----------
class ParentWidget2 extends StatefulWidget {
  ParentWidget2() {
    print('Parent init');
  }

  @override
  State<StatefulWidget> createState() {
    return _ParentWidgetState2();
  }
}

class _ParentWidgetState2 extends State<ParentWidget2> {

  _ParentWidgetState2() {
    print('_Parent  State init');
  }

  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    print('_Parent _handleTapboxChanged method is called');
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    print('_Parent State build is called');
    return TabboxC(onChanged: _handleTapboxChanged, active: _active,);
  }

}

// ------------child widget-----------
class TabboxC extends StatefulWidget {
//  構(gòu)造方法
  TabboxC({
    Key key,
    this.active: false,
    @required this.onChanged
  }) : super(key: key) {
    print('TabboxC init');
  }

  final bool active;
  final ValueChanged<bool> onChanged;

  @override
  State<StatefulWidget> createState() {
    return _TapboxCState();
  }
}

class _TapboxCState extends State<TabboxC> {

  bool _highlight = false;

  _TapboxCState() {
    print('_TapboxC  State init');
  }

  void _handleTapDown(TapDownDetails details) {
    print('_TapboxC tap down');
    setState(() {
      _highlight = true;
    });
  }

  void _handleTapUp(TapUpDetails details) {
    print('_TapboxC tap up');
    setState(() {
      _highlight = false;
    });
  }

  void _handleTapCancel() {
    print('_TapboxC tap cancel');
    setState(() {
      _highlight = false;
    });
  }

  void _handleTap() {
    print('_TapboxC tap clicked');
    widget.onChanged(!widget.active);
  }

  @override
  Widget build(BuildContext context) {
    print('_TapboxCState build is called');
    return  Center(
      child: GestureDetector(
//              down
        onTapDown: _handleTapDown,
//              up
        onTapUp: _handleTapUp,
//              cancel
        onTapCancel: _handleTapCancel,
//              click
        onTap: _handleTap,
        child: Container(
          width: 200.0,
          height: 200.0,
          decoration: BoxDecoration(
//                  Box 顏色  父控件 控制(通過回調(diào)方法)
              color: widget.active ? Colors.lightGreen[700] : Colors
                  .grey[600],
//                  邊框顏色  自己控制
              border: _highlight ? Border.all(
                  color: Colors.teal[700], width: 10.0) : null
          ),
          child: Center(
            child: Text(widget.active ? 'Active' : 'Inactive',
              style: TextStyle(fontSize: 32.0, color: Colors.white),),
          ),
        ),
      ),
    );
  }
}

2019-08-27 16.10.52.gif

按下釋放后的一瞬間,才有邊框。

初始化

初始化時候的順序和上面類似,我們來看看點(diǎn)擊事件被觸發(fā)時候的執(zhí)行順序:

flutter: _TapboxC tap down
flutter: _TapboxCState build is call
flutter: _TapboxC tap up
flutter: _TapboxC tap clicked
flutter: _Parent _handleTapboxChanged method is call
flutter: _Parent State build is call
flutter: TabboxC init
flutter: _TapboxCState build is call

執(zhí)行流程:


image.png

大家可能會發(fā)現(xiàn),子部件在 Down 事件中調(diào)用了 setState(...) 方法,然后執(zhí)行了一次 build 操作;而在 Up 事件中同樣也調(diào)用了 setState(...) 方法,但是為什么沒有執(zhí)行 build 操作,而是直接執(zhí)行了 click 操作。這里面可能和 Android 里面類似,在 View 的 onTouchEvent 方法里面,onClick 方法也是在 ACTION_UP 里面執(zhí)行的。

參考:為你的 Flutter APP 添加交互性


END

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容