Flutter 添加交互

2020-08-27 14:46 更新

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

重點(diǎn):

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

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

實(shí)現(xiàn)一個(gè)自定義的有狀態(tài)widget需要?jiǎng)?chuàng)建兩個(gè)類:

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

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


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

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


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

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

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

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


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

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

狀態(tài)對(duì)象也定義了build方法。此build方法創(chuàng)建一個(gè)包含紅色I(xiàn)conButton和Text的行。 該widget使用IconButton(而不是Icon), 因?yàn)樗哂幸粋€(gè)onPressed屬性,該屬性定義了處理點(diǎn)擊的回調(diào)方法。IconButton也有一個(gè)icon的屬性,持有Icon。

按下IconButton時(shí)會(huì)調(diào)用_toggleFavorite()方法,然后它會(huì)調(diào)用setState()。 調(diào)用setState()是至關(guān)重要的,因?yàn)檫@告訴框架,widget的狀態(tài)已經(jīng)改變,應(yīng)該重繪。 _toggleFavorite在: 1)實(shí)心的星形圖標(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之間變化時(shí),將文本放在SizedBox中并設(shè)置其寬度可防止出現(xiàn)明顯的“跳躍” ,因?yà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)

重點(diǎn)是什么?

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

誰(shuí)管理著stateful widget的狀態(tài)?widget本身?父widget?都會(huì)?另一個(gè)對(duì)象?答案是……這取決于實(shí)際情況。 有幾種有效的方法可以給你的widget添加互動(dòng)。作為小部件設(shè)計(jì)師。以下是管理狀態(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)界面外觀效果的,例如動(dòng)畫,那么狀態(tài)最好由widget本身來管理.

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

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

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


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

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

_TapboxAState 類:

  • 管理TapboxA的狀態(tài).
  • 定義_active:確定盒子的當(dāng)前顏色的布爾值.
  • 定義_handleTap()函數(shù),該函數(shù)在點(diǎn)擊該盒子時(shí)更新_active,并調(diào)用setState()更新UI.
  • 實(shí)現(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

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

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

ParentWidgetState 類:

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

TapboxB 類:

  • 繼承StatelessWidget類,因?yàn)樗袪顟B(tài)都由其父widget處理.
  • 當(dāng)檢測(cè)到點(diǎn)擊時(shí),它會(huì)通知父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時(shí),請(qǐng)考慮使用@required為代碼所依賴的任何參數(shù)使用注解。

 'package: flutter/foundation.dart';


混合管理

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

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

_ParentWidgetState 對(duì)象:

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

_TapboxCState 對(duì)象:

  • 管理_highlight state.
  • GestureDetector監(jiān)聽所有tap事件。當(dāng)用戶點(diǎn)下時(shí),它添加高亮(深綠色邊框);當(dāng)用戶釋放時(shí),會(huì)移除高亮。
  • 當(dāng)按下、抬起、或者取消點(diǎn)擊時(shí)更新_highlight狀態(tài),調(diào)用setState()更新UI。
  • 當(dāng)點(diǎn)擊時(shí),將狀態(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,
        ),
      ),
    );
  }
}

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


其他交互式widgets

Flutter提供各種按鈕和類似的交互式widget。這些widget中的大多數(shù)實(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)你需要交互性時(shí),最容易的是使用預(yù)制的widget。這是預(yù)置widget部分列表:

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

Material Components:

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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)