爲你的 Flutter APP 添加交互性

官方文檔markdown

Stateful & stateless

Flutter 中, Widget 分爲兩種,一種是有狀態的稱爲 StatefulWidget ,一種是無狀態的稱爲 StatelessWidget。例如 Checkbox 複選框是 StatefulWidget (具備可變狀態的小部件),而 Text 部件就是 StatelessWidget(不須要可變狀態的小部件)。app

咱們自定義 StatefulWidget 的時候,會重寫 createState() 方法來建立一個 State 對象,在 State 中能夠經過 setState((){...}); 方法來改變當前 Widget 的狀態,而 StatelessWidget 則不能夠。less

建立一個 StatefulWidget 實例的方法以下:ide

class FavoriteWidget extends StatefulWidget {
  // 建立 State 對象
  @override
  State<StatefulWidget> createState() {
    return _FavoriteWidgetState();
  }
}

// 用來管理 FavoriteWidget 的狀態
class _FavoriteWidgetState extends State<FavoriteWidget> {
  bool _isFavorited = true;
  
  void _toggleFavorite() {
    // 改變狀態
    setState(() {
      _isFavorited = !_isFavorited
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
    // 這裏能夠根據 _isFavorited 去改變改變部分 widget 的狀態
    ...
    );
  }
}
複製代碼

在 Dart 中,成員變量或者類名稱以 下劃線 開頭表示該成員或者類爲 private 的。 官方文檔ui

管理 Widget 的狀態

Flutter提供了一下幾種方式管理 widget 的狀態this

  • widget 狀態由本身管理
  • 由父 widget 管理 widget 的狀態
  • 混合管理 widget 的狀態 (本身和父部件各管理一部分)

widget 狀態由本身管理

這個比較簡單,直接在 widget 內部管理本身的狀態spa

// 有可變狀態的 widget
class TapboxA extends StatefulWidget {
  TapboxA({Key key}) : super(key: key) {
    print('TapboxA init');
  }

  // 建立 State 用來管理當前 widget 的狀態
  @override
  _TapboxAState createState() => _TapboxAState();
}

// State
class _TapboxAState extends State<TapboxA> {
  _TapboxAState() {
    print('boxA state init');
  }

  bool _active = false;

  void _handleTap() {
    print('boxA state handleTap is call');
    // 設置狀態
    setState(() {
      _active = !_active;
    });
  }

  Widget build(BuildContext context) {
    print('boxA state build is call');
    return GestureDetector(
      onTap: _handleTap, // 點擊事件
      child: Container(
        child: Center(
          // 根據 _active 來設置不一樣的 Text 值
          child: Text(
            _active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }


}

//------------------------- MyApp ----------------------------------

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Demo'),
        ),
        body: Center(
          child: TapboxA(),
        ),
      ),
    );
  }
}
複製代碼

效果以下:3d

初始化順序:rest

flutter: TapboxA init
flutter: boxA state init
flutter: boxA state build is call
複製代碼

點擊事件被觸發時:code

flutter: boxA state handleTap is call
flutter: boxA state build is call
複製代碼

由父 widget 管理

案例中的 TapboxB 是一個 StatelessWidgetParentWidget 是一個 StatefulWidget

咱們須要使用 ParentWidget 來改變 TapboxB 的狀態。

//------------------------- 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 _handleTapboxChanged(bool newValue) {
    print('parent _handleTapboxChanged is call : $newValue');
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    print('ParentWidgetState build is call');
    return MaterialApp(
      title: 'ParentWidget',
      theme: ThemeData(
          primaryColor: Colors.redAccent
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('ParentWidget'),
        ),
        body: Center(
          child: TapboxB(onChanged: _handleTapboxChanged, active: _active,),
        ),

      ),
    );
  }
}

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

class TapboxB extends StatelessWidget {

  TapboxB({Key key, this.active: false, @required this.onChanged})
      : super(key: key) {
    print('Tap boxB init : ${this.active}');
  }

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    print('child _handleTap : $active');
    onChanged(!active);
  }

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

複製代碼

能夠看到,TapboxB 的構造方法中有一個 onChanged 參數,和 active 參數;onChanged參數類型爲 ValueChanged<bool> 他是一個接受一個參數的回調方法,方法源碼以下:

/// Signature for callbacks that report that an underlying value has changed.
///
/// See also [ValueSetter].
typedef void ValueChanged<T>(T value);
複製代碼

TapboxB 中,當觸發點擊事件的時候就是執行這個回調方法,也就是上述代碼中 _handleTap() 方法中所執行的語句。而 active 用來切換當前 TapboxB 的狀態。

注意:onChanged 和 active 都是由父部件傳遞過來的

效果以下:

而後咱們來看看輸出:

初始化時輸出以下:

Performing hot restart...                                        
flutter: ParentWidget init
Restarted app in 1,976ms.
flutter: ParentWidgetState init
flutter: ParentWidgetState build is call
flutter: Tap boxB init : false
flutter: Tap boxB build method
複製代碼

執行順序以下:

咱們再看看子部件(TapboxB)觸發點擊事件時的輸出

flutter: child _handleTap : false
flutter: parent _handleTapboxChanged is call : true
flutter: ParentWidgetState build is call
flutter: Tap boxB init : true
flutter: Tap boxB build method
複製代碼

執行順序以下:

說明:當咱們觸發子部件上的點擊事件時候,這個時候會執行 _handleTap() 方法,_handleTap() 方法裏面會執行 onChanged(...),接着就會執行父部件裏面的回調方法 _handleTapboxChanged(...),注意 _handleTapboxChanged(...) 方法裏面執行了 setState(() {...}),在這個方法裏面切換了狀態,而後會從新調用 build 方法從新渲染子部件。

setState(() {...}) 會使 widget 重繪,相似於 Android 中調用 Viewinvalidate() 方法

混合管理 widget 的狀態

混合管理就是某些狀態由本身管理,某些狀態由父部件來管理。

下面的例子就是一個混合管理狀態的例子,部件 TabboxC 在被點擊時有三個狀態變換,背景色,文字和邊框

示例中,背景色和文字的狀態交由父部件來管理(和上一個示例相似),而邊框狀態由本身管理。

既然父部件和子部件都能管理狀態,那麼它們都是要繼承StatefulWidget類。

// ------------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 {
// 構造方法
  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 MaterialApp(
      title: 'mix',
      theme: ThemeData(
          primaryColor: Colors.redAccent
      ),
      home: Scaffold(
          appBar: AppBar(
            title: Text('mix'),
          ),
          body: 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 顏色 父控件 控制(經過回調方法)
                    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),),
                ),
              ),
            ),
          )
      ),
    );
  }
}
複製代碼

效果以下:

初始化時候的順序和上面相似,咱們來看看點擊事件被觸發時候的執行順序:

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
複製代碼

執行流程以下:

你們可能會發現,子部件在 Down 事件中調用了 setState(...) 方法,而後執行了一次 build 操做;而在 Up 事件中一樣也調用了 setState(...) 方法,可是爲何沒有執行 build 操做,而是直接執行了 click 操做。這裏面可能和 Android 裏面相似,在 View 的 onTouchEvent 方法裏面,onClick 方法也是在 ACTION_UP 裏面執行的。

若有錯誤,還請指出,謝謝!

相關文章
相關標籤/搜索