為Flutter應用程序添加交互

本文詳細的介紹了如何給Flutter應用程序添加交互和事件,以及了解Flutter中的State、StatefulWidget、stateless等概念。

你將會學到:
1. 如何響應點擊(tap).
2. 如何創建自定義widget.
3. stateless(無狀態)和 stateful(有狀態)widgets的區別.

如何修改你的應用程序,使其對用戶動作做出反應?在本教程中,您將為widget添加交互。 具體來說,您將通過創建一個管理兩個無狀態widget的自定義有狀態的widget來使圖標可以點擊。

內容

準備

如果您已經根據在Flutter中構建布局一節的構建好了布局,請跳過本塊。

一旦你有一個連接和啟用的設備,或者你已經啟動了iOS模擬器(Flutter安裝一節介紹過),就會很容易開始!

在Flutter中構建布局一節展示了如何構建下面截圖所示的布局。

The starting Lakes app that we will modify

當應用第一次啟動時,這顆星形圖標是實心紅色,表明這個湖以前已經被收藏了。星號旁邊的數字表示41個人對此湖感興趣。 完成本教程后,點擊星形圖標將取消收藏狀態,然后用輪廓線的星形圖標代替實心的,并減少計數。再次點擊會重新收藏,并增加計數。

the custom widget you'll create

為了實現這個,您將創建一個包含星號和計數的自定義widget,它們都是widget。 因為點擊星形圖標會更改這兩個widget的狀態,所以同一個widget應該同時管理這兩個widget。

Stateful(有狀態) 和 stateless(無狀態) widgets

什么是重點?
1. 有些widgets是有狀態的, 有些是無狀態的
2. 如果用戶與widget交互,widget會發生變化,那么它就是有狀態的.
3. widget的狀態(state)是一些可以更改的值, 如一個slider滑動條的當前值或checkbox是否被選中.
4. widget的狀態保存在一個State對象中, 它和widget的布局顯示分離。
5. 當widget狀態改變時, State 對象調用setState(), 告訴框架去重繪widget.

stateless widget 沒有內部狀態. IconIconButton, 和Text 都是無狀態widget, 他們都是 StatelessWidget的子類。

stateful widget 是動態的. 用戶可以和其交互 (例如輸入一個表單、 或者移動一個slider滑塊),或者可以隨時間改變 (也許是數據改變導致的UI更新). Checkbox, Radio, Slider, InkWell, Form, and TextField 都是 stateful widgets, 他們都是 StatefulWidget的子類。

創建一個有狀態的widget

重點:
1. 要創建一個自定義有狀態widget,需創建兩個類:StatefulWidget和State
2. 狀態對象包含widget的狀態和build() 方法。
3. 當widget的狀態改變時,狀態對象調用setState(),告訴框架重繪widget

在本節中,您將創建一個自定義有狀態的widget。 您將使用一個自定義有狀態widget來替換兩個無狀態widget - 紅色實心星形圖標和其旁邊的數字計數 - 該widget用兩個子widget管理一行:IconButton和Text。

實現一個自定義的有狀態widget需要創建兩個類:

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

本節展示如何為Lakes應用程序構建一個名為FavoriteWidget的StatefulWidget。第一步是選擇如何管理FavoriteWidget的狀態。

Step 1: 決定哪個對象管理widget的狀態

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

管理狀態中了解更多關于widget和狀態的分離以及如何管理狀態的信息。

Step 2: 創建StatefulWidget子類

FavoriteWidget類管理自己的狀態,因此它重寫createState()來創建狀態對象。 框架會在構建widget時調用createState()。在這個例子中,createState()創建_FavoriteWidgetState的實例,您將在下一步中實現該實例。

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

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

Step 3: 創建State子類

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

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

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

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

  [[highlight]]void _toggleFavorite()[[/highlight]] {
    [[highlight]]setState(()[[/highlight]] {
      // 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(
            [[highlight]]icon: (_isFavorited[[/highlight]]
                [[highlight]]? new Icon(Icons.star)[[/highlight]]
                [[highlight]]: new Icon(Icons.star_border)),[[/highlight]]
            color: Colors.red[500],
            [[highlight]]onPressed: _toggleFavorite,[[/highlight]]
          ),
        ),
        new SizedBox(
          width: 18.0,
          child: new Container(
            [[highlight]]child: new Text('$_favoriteCount'),[[/highlight]]
          ),
        ),
      ],
    );
  }
}

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

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

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

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

在相同的位置創建stateful widget:

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

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


好了! 當您熱重載應用程序后,星形圖標就會響應點擊了.

遇到問題?

如果您的代碼無法運行,請在IDE中查找可能的錯誤。調試Flutter應用程序可能會有所幫助。如果仍然無法找到問題,請根據GitHub上的示例檢查代碼。


本頁面的其余部分介紹了可以管理widget狀態的幾種方式,并列出了其他可用的可交互的widget。

管理狀態

重點是什么?
1. 有多種方法可以管理狀態.
2. 選擇使用何種管理方法
3. 如果不是很清楚時, 那就在父widget中管理狀態吧.

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

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

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

如果有疑問,首選是在父widget中管理狀態

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

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

這些示例使用GestureDetector捕獲Container上的用戶動作。

widget管理自己的狀態

有時,widget在內部管理其狀態是最好的。例如, 當ListView的內容超過渲染框時, ListView自動滾動。大多數使用ListView的開發人員不想管理ListView的滾動行為,因此ListView本身管理其滾動偏移量。

_TapboxAState 類:

  • 管理TapboxA的狀態.
  • 定義_active:確定盒子的當前顏色的布爾值.
  • 定義_handleTap()函數,該函數在點擊該盒子時更新_active,并調用setState()更新UI.
  • 實現widget的所有交互式行為.

代碼示例如下:


// TapboxA 管理自身狀態.

//------------------------- 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(),
        ),
      ),
    );
  }
}

Dart code: lib/main.dart


父widget管理widget的state

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

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

ParentWidgetState 類:

  • 為TapboxB 管理_active狀態.
  • 實現_handleTapboxChanged(),當盒子被點擊時調用的方法.
  • 當狀態改變時,調用setState()更新UI.

TapboxB 類:

  • 繼承StatelessWidget類,因為所有狀態都由其父widget處理.
  • 當檢測到點擊時,它會通知父widget.

代碼示例如下:

// ParentWidget 為 TapboxB 管理狀態.

//------------------------ 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],
        ),
      ),
    );
  }
}

Dart code: lib/main.dart

提示:在創建API時,請考慮使用@required為代碼所依賴的任何參數使用注解。 要使用@required注解,請導入foundation library(該庫重新導出Dart的meta.dart)。


混合管理

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

在TapboxC示例中,點擊時,盒子的周圍會出現一個深綠色的邊框。點擊時,邊框消失,盒子的顏色改變。 TapboxC將其_active狀態導出到其父widget中,但在內部管理其_highlight狀態。這個例子有兩個狀態對象_ParentWidgetState_TapboxCState

_ParentWidgetState 對象:

  • 管理_active 狀態.
  • 實現 _handleTapboxChanged(), 當盒子被點擊時調用.
  • 當點擊盒子并且_active狀態改變時調用setState()更新UI

_TapboxCState 對象:

  • 管理_highlight state.
  • GestureDetector監聽所有tap事件。當用戶點下時,它添加高亮(深綠色邊框);當用戶釋放時,會移除高亮。
  • 當按下、抬起、或者取消點擊時更新_highlight狀態,調用setState()更新UI。
  • 當點擊時,將狀態的改變傳遞給父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,
        ),
      ),
    );
  }
}

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

Dart code: lib/main.dart


其他交互式widgets

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

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

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

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

標準 widgets:

Material Components:

資源

給您的應用添加交互時,以下資源可能會有所幫助


所屬標簽

無標簽

官方入門指南

Flutter官方發布的入門指導,包括了如何在不同的平臺(Windows, Mac, Linux)上搭建開發環境,以及一些入門級的指導,以便您從零開始進入Flutter的世界,同時,一些Flutter的框架API,也是您開發時必不可少的工具書。

從這里進入


25选5玩法中奖