Flutter 添加交互

2020-08-27 14:46 更新

創(chuàng)建一個有狀態(tài)的widget

重點:

  • 要創(chuàng)建一個自定義有狀態(tài)widget,需創(chuàng)建兩個類:StatefulWidget和State
  • 狀態(tài)對象包含widget的狀態(tài)和build() 方法。
  • 當(dāng)widget的狀態(tài)改變時,狀態(tài)對象調(diào)用setState(),告訴框架重繪widget

在本節(jié)中,您將創(chuàng)建一個自定義有狀態(tài)的widget。 您將使用一個自定義有狀態(tài)widget來替換兩個無狀態(tài)widget - 紅色實心星形圖標(biāo)和其旁邊的數(shù)字計數(shù) - 該widget用兩個子widget管理一行:IconButton和Text。

實現(xiàn)一個自定義的有狀態(tài)widget需要創(chuàng)建兩個類:

  • 定義一個widget類,繼承自StatefulWidget.
  • 包含該widget狀態(tài)并定義該widget build()方法的類,它繼承自State.

本節(jié)展示如何為Lakes應(yīng)用程序構(gòu)建一個名為FavoriteWidget的StatefulWidget。第一步是選擇如何管理FavoriteWidget的狀態(tài)。


Step 1: 決定哪個對象管理widget的狀態(tài)

Widget的狀態(tài)可以通過多種方式進行管理,但在我們的示例中,widget本身(FavoriteWidget)將管理自己的狀態(tài)。 在這個例子中,切換星形圖標(biāo)是一個獨立的操作,不會影響父窗口widget或其他用戶界面,因此該widget可以在內(nèi)部處理它自己的狀態(tài)。


Step 2: 創(chuàng)建StatefulWidget子類

FavoriteWidget類管理自己的狀態(tài),因此它重寫createState()來創(chuàng)建狀態(tài)對象。 框架會在構(gòu)建widget時調(diào)用createState()。在這個例子中,createState()創(chuàng)建_FavoriteWidgetState的實例,您將在下一步中實現(xiàn)該實例。

class FavoriteWidget extends StatefulWidget {
  @override
  _FavoriteWidgetState createState() => new _FavoriteWidgetState();
}

注意: 以下劃線(_)開頭的成員或類是私有的。有關(guān)更多信息,請參閱Dart語言參考中的庫和可見性部分 。


Step 3: 創(chuàng)建State子類

自定義State類存儲可變信息 - 可以在widget的生命周期內(nèi)改變邏輯和內(nèi)部狀態(tài)。 當(dāng)應(yīng)用第一次啟動時,用戶界面顯示一個紅色實心的星星形圖標(biāo),表明該湖已經(jīng)被收藏,并有41個“喜歡”。狀態(tài)對象存儲這些信息在_isFavorited和_favoriteCount變量。

狀態(tài)對象也定義了build方法。此build方法創(chuàng)建一個包含紅色IconButton和Text的行。 該widget使用IconButton(而不是Icon), 因為它具有一個onPressed屬性,該屬性定義了處理點擊的回調(diào)方法。IconButton也有一個icon的屬性,持有Icon。

按下IconButton時會調(diào)用_toggleFavorite()方法,然后它會調(diào)用setState()。 調(diào)用setState()是至關(guān)重要的,因為這告訴框架,widget的狀態(tài)已經(jīng)改變,應(yīng)該重繪。 _toggleFavorite在: 1)實心的星形圖標(biāo)和數(shù)字“41” 和 2)虛心的星形圖標(biāo)和數(shù)字“40”之間切換UI。

class _FavoriteWidgetState extends State<FavoriteWidget> {
  bool _isFavorited = true;
  int _favoriteCount = 41;

  void _toggleFavorite() {
    setState(() {
      // If the lake is currently favorited, unfavorite it.
      if (_isFavorited) {
        _favoriteCount -= 1;
        _isFavorited = false;
        // Otherwise, favorite it.
      } else {
        _favoriteCount += 1;
        _isFavorited = true;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        new Container(
          padding: new EdgeInsets.all(0.0),
          child: new IconButton(
            icon: (_isFavorited
                ? new Icon(Icons.star)
                : new Icon(Icons.star_border)),
            color: Colors.red[500],
            onPressed: _toggleFavorite,
          ),
        ),
        new SizedBox(
          width: 18.0,
          child: new Container(
            child: new Text('$_favoriteCount'),
          ),
        ),
      ],
    );
  }
}

提示: 當(dāng)文本在40和41之間變化時,將文本放在SizedBox中并設(shè)置其寬度可防止出現(xiàn)明顯的“跳躍” ,因為這些值具有不同的寬度。


Step 4: 將有stateful widget插入widget樹中

將您自定義stateful widget在build方法中添加到widget樹中。首先,找到創(chuàng)建圖標(biāo)和文本的代碼,并刪除它:

// ...
new Icon(
  Icons.star,
  color: Colors.red[500],
),
new Text('41')
// ...

在相同的位置創(chuàng)建stateful widget:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Widget titleSection = new Container(
      // ...
      child: new Row(
        children: [
          new Expanded(
            child: new Column(
              // ...
          ),
          new FavoriteWidget(),
        ],
      ),
    );

    return new MaterialApp(
      // ...
    );
  }
}


管理狀態(tài)

重點是什么?

  • 有多種方法可以管理狀態(tài).
  • 選擇使用何種管理方法
  • 如果不是很清楚時, 那就在父widget中管理狀態(tài)吧.

誰管理著stateful widget的狀態(tài)?widget本身?父widget?都會?另一個對象?答案是……這取決于實際情況。 有幾種有效的方法可以給你的widget添加互動。作為小部件設(shè)計師。以下是管理狀態(tài)的最常見的方法:

  • widget管理自己的state
  • 父widget管理 widget狀態(tài)
  • 混搭管理(父widget和widget自身都管理狀態(tài)))

如何決定使用哪種管理方法?以下原則可以幫助您決定:

  • 如果狀態(tài)是用戶數(shù)據(jù),如復(fù)選框的選中狀態(tài)、滑塊的位置,則該狀態(tài)最好由父widget管理
  • 如果所討論的狀態(tài)是有關(guān)界面外觀效果的,例如動畫,那么狀態(tài)最好由widget本身來管理.

如果有疑問,首選是在父widget中管理狀態(tài)

我們將通過創(chuàng)建三個簡單示例來舉例說明管理狀態(tài)的不同方式:TapboxA、TapboxB和TapboxC。 這些例子功能是相似的 - 每創(chuàng)建一個容器,當(dāng)點擊時,在綠色或灰色框之間切換。 _active確定顏色:綠色為true,灰色為false。

a large green box with the text, 'Active'      a large grey box with the text, 'Inactive'


widget管理自己的狀態(tài)

有時,widget在內(nèi)部管理其狀態(tài)是最好的。例如, 當(dāng)ListView的內(nèi)容超過渲染框時, ListView自動滾動。大多數(shù)使用ListView的開發(fā)人員不想管理ListView的滾動行為,因此ListView本身管理其滾動偏移量。

_TapboxAState 類:

  • 管理TapboxA的狀態(tài).
  • 定義_active:確定盒子的當(dāng)前顏色的布爾值.
  • 定義_handleTap()函數(shù),該函數(shù)在點擊該盒子時更新_active,并調(diào)用setState()更新UI.
  • 實現(xiàn)widget的所有交互式行為.
// TapboxA 管理自身狀態(tài).

//------------------------- TapboxA ----------------------------------

class TapboxA extends StatefulWidget {
  TapboxA({Key key}) : super(key: key);

  @override
  _TapboxAState createState() => new _TapboxAState();
}

class _TapboxAState extends State<TapboxA> {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }

  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: _handleTap,
      child: new Container(
        child: new Center(
          child: new Text(
            _active ? 'Active' : 'Inactive',
            style: new TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

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

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


父widget管理widget的state

對于父widget來說,管理狀態(tài)并告訴其子widget何時更新通常是最有意義的。 例如,IconButton允許您將圖標(biāo)視為可點按的按鈕。 IconButton是一個無狀態(tài)的小部件,因為我們認(rèn)為父widget需要知道該按鈕是否被點擊來采取相應(yīng)的處理。

在以下示例中,TapboxB通過回調(diào)將其狀態(tài)導(dǎo)出到其父項。由于TapboxB不管理任何狀態(tài),因此它的父類為StatelessWidget。

ParentWidgetState 類:

  • 為TapboxB 管理_active狀態(tài).
  • 實現(xiàn)_handleTapboxChanged(),當(dāng)盒子被點擊時調(diào)用的方法.
  • 當(dāng)狀態(tài)改變時,調(diào)用setState()更新UI.

TapboxB 類:

  • 繼承StatelessWidget類,因為所有狀態(tài)都由其父widget處理.
  • 當(dāng)檢測到點擊時,它會通知父widget.
// ParentWidget 為 TapboxB 管理狀態(tài).

//------------------------ ParentWidget --------------------------------

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => new _ParentWidgetState();
}

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

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
  TapboxB({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: _handleTap,
      child: new Container(
        child: new Center(
          child: new Text(
            active ? 'Active' : 'Inactive',
            style: new TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

提示: 在創(chuàng)建API時,請考慮使用@required為代碼所依賴的任何參數(shù)使用注解。

 'package: flutter/foundation.dart';


混合管理

對于一些widget來說,混搭管理的方法最有意義的。在這種情況下,有狀態(tài)widget管理一些狀態(tài),并且父widget管理其他狀態(tài)。

在TapboxC示例中,點擊時,盒子的周圍會出現(xiàn)一個深綠色的邊框。點擊時,邊框消失,盒子的顏色改變。 TapboxC將其_active狀態(tài)導(dǎo)出到其父widget中,但在內(nèi)部管理其_highlight狀態(tài)。這個例子有兩個狀態(tài)對象_ParentWidgetState和_TapboxCState。

_ParentWidgetState 對象:

  • 管理_active 狀態(tài).
  • 實現(xiàn) _handleTapboxChanged(), 當(dāng)盒子被點擊時調(diào)用.
  • 當(dāng)點擊盒子并且_active狀態(tài)改變時調(diào)用setState()更新UI

_TapboxCState 對象:

  • 管理_highlight state.
  • GestureDetector監(jiān)聽所有tap事件。當(dāng)用戶點下時,它添加高亮(深綠色邊框);當(dāng)用戶釋放時,會移除高亮。
  • 當(dāng)按下、抬起、或者取消點擊時更新_highlight狀態(tài),調(diào)用setState()更新UI。
  • 當(dāng)點擊時,將狀態(tài)的改變傳遞給父widget.
//---------------------------- ParentWidget ----------------------------

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => new _ParentWidgetState();
}

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

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new TapboxC(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//----------------------------- TapboxC ------------------------------

class TapboxC extends StatefulWidget {
  TapboxC({Key key, this.active: false, @required this.onChanged})
      : super(key: key);

  final bool active;
  final ValueChanged<bool> onChanged;

  _TapboxCState createState() => new _TapboxCState();
}

class _TapboxCState extends State<TapboxC> {
  bool _highlight = false;

  void _handleTapDown(TapDownDetails details) {
    setState(() {
      _highlight = true;
    });
  }

  void _handleTapUp(TapUpDetails details) {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTapCancel() {
    setState(() {
      _highlight = false;
    });
  }

  void _handleTap() {
    widget.onChanged(!widget.active);
  }

  Widget build(BuildContext context) {
    // This example adds a green border on tap down.
    // On tap up, the square changes to the opposite state.
    return new GestureDetector(
      onTapDown: _handleTapDown, // Handle the tap events in the order that
      onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: new Container(
        child: new Center(
          child: new Text(widget.active ? 'Active' : 'Inactive',
              style: new TextStyle(fontSize: 32.0, color: Colors.white)),
        ),
        width: 200.0,
        height: 200.0,
        decoration: new BoxDecoration(
          color:
              widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: _highlight
              ? new Border.all(
                  color: Colors.teal[700],
                  width: 10.0,
                )
              : null,
        ),
      ),
    );
  }
}

另一種實現(xiàn)可能會將高亮狀態(tài)導(dǎo)出到父widget,同時保持_active狀態(tài)為內(nèi)部,但如果您要求某人使用該TapBox,他們可能會抱怨說沒有多大意義。 開發(fā)人員只會關(guān)心該框是否處于活動狀態(tài)。開發(fā)人員可能不在乎高亮顯示是如何管理的,并且傾向于讓TapBox處理這些細(xì)節(jié)。


其他交互式widgets

Flutter提供各種按鈕和類似的交互式widget。這些widget中的大多數(shù)實現(xiàn)了Material Design 指南, 它們定義了一組具有質(zhì)感的UI組件。

如果你愿意,你可以使用GestureDetector來給任何自定義widget添加交互性。 您可以在管理狀態(tài)Flutter Gallery中找到GestureDetector的示例。

注意: Futter還提供了一組名為Cupertino的iOS風(fēng)格的小部件 。

When you need interactivity, it’s easiest to use one of the prefabricated widgets. Here’s a partial list: 當(dāng)你需要交互性時,最容易的是使用預(yù)制的widget。這是預(yù)置widget部分列表:

標(biāo)準(zhǔn) widgets:

Material Components:

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號