Flutter 學習總結(二)添加交互

上一篇我們已經把佈局搭好了,這篇將講解如何交互。

主要內容:

  • 如何響應點擊(tap).
  • 如何創建自定義widget.
  • stateless(無狀態)和 stateful(有狀態)widgets的區別.

重點:

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

創建一個有狀態的widget

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

本章效果:

很簡單,就只是實現點擊更改狀態

 

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

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

Step 2: 創建StatefulWidget子類

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

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

Step 3: 創建State子類

//下劃線(_)開頭的成員或類是私有的
//自定義State類存儲可變信息
//狀態對象存儲這些信息在_isFavorited和_favoriteCount變量
//當文本在40和41之間變化時,將文本放在SizedBox中並設置其寬度可防止出現明顯的“跳躍” ,
//因爲這些值具有不同的寬度。
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'),
          ),
        ),
      ],
    );
  }
}

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

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

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

在相同的位置創建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(
      // ...
    );
  }
}

main.dart代碼:https://raw.githubusercontent.com/flutter/website/master/_includes/code/layout/lakes-interactive/main.dart

 

以上爲此次案例,但是我們所需要學的並不是這一點半點,下面介紹管理方式。

管理狀態:

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

常用管理狀態方式:

1.widget管理自身狀態

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

2.父widget管理widget狀態

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

3.混搭掛管理

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

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

import 'package:flutter/material.dart';

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

//dart2 相比於dart1   少了關鍵字new
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Widget titleSection = Container(
      //內邊距32px
      padding: const EdgeInsets.all(32.0),
      child: Row(
        children: [
          //如果佈局太大而不適合設備
          //Expanded widget,可以將widget的大小設置爲適和行或列
          Expanded(
            //其他兩個widget的兩倍
//            flex: 2,
            child: Column(
              //crossAxisAlignment屬性值爲CrossAxisAlignment.start,這會將將列中的子項左對齊。
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.only(bottom: 8.0),
                  child: Text(
                    'Oeschinen Lake Campground',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                Text(
                  'Kandersteg, Switzerland',
                  style: TextStyle(
                    color: Colors.grey[500],
                  ),
                ),
              ],
            ),
          ),
          Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          Text('41'),
//          new FavoriteWidget(),
        ],
      ),
    );

    //一個顏色爲primary color,包含一個Icon和Text的 Widget 列
    Column buildButtonColumn(IconData icon, String label) {
      Color color = Theme.of(context).primaryColor;

      return Column(
        //mainAxisSize .min 聚集在一起
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(icon, color: color),
          Container(
            margin: const EdgeInsets.only(top: 8.0),
            child: Text(
              label,
              style: TextStyle(
                fontSize: 12.0,
                fontWeight: FontWeight.w400,
                color: color,
              ),
            ),
          ),
        ],
      );
    }

    Widget buttonSection = Container(
      child: Row(
        //mainAxisAlignment和crossAxisAlignment屬性來對齊其子項。
        // 對於行(Row)來說,主軸是水平方向,橫軸垂直方向。
        // 對於列(Column)來說,主軸垂直方向,橫軸水平方向
        //spaceEvenly它會在每個圖像之間,之前和之後均勻分配空閒的水平空間
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          buildButtonColumn(Icons.call, 'CALL'),
          buildButtonColumn(Icons.near_me, 'ROUTE'),
          buildButtonColumn(Icons.share, 'SHARE'),
        ],
      ),
    );
//    softwrap屬性表示文本是否應在軟換行符(例如句點或逗號)之間斷開
    Widget textSection = Container(
      padding: const EdgeInsets.all(32.0),
      child: Text(
        '''
Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese Alps. Situated 1,578 meters above sea level, it is one of the larger Alpine Lakes. A gondola ride from Kandersteg, followed by a half-hour walk through pastures and pine forest, leads you to the lake, which warms to 20 degrees Celsius in the summer. Activities enjoyed here include rowing, and riding the summer toboggan run.
        ''',
        //softwrap屬性表示文本是否應在軟換行符(例如句點或逗號)之間斷開。
        softWrap: true,
      ),
    );

    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
//        appBar: AppBar(
//          title: Text('Top Lakes'),
//        ),

//        界面
        body: ListView(
          children: [
            Image.asset(
              'images/lake.jpg',
              width: 600.0,
              height: 240.0,
  //             BoxFit.cover 告訴框架,圖像應該儘可能小,但覆蓋整個渲染框
              fit: BoxFit.cover,
            ),
            titleSection,
            buttonSection,
            textSection,
          ],
        ),

////      管理自身狀態 TapboxA
//        body: new Center(
//          child: TapboxA(),
//        ),

////      父widget管理widget的state TapboxB
//        body: new Center(
//          child: ParentWidget(),
//        ),
//        body: new Center(
//          child: new ParentWidgetC(),
//        ),
      ),
    );
  }
}

// FavoriteWidget類管理自己的狀態,因此它重寫createState()來創建狀態對象。 框架會在構建widget時調用createState()
class FavoriteWidget extends StatefulWidget {
  _FavoriteWidgetState createState() => new _FavoriteWidgetState();
}

//下劃線(_)開頭的成員或類是私有的
//自定義State類存儲可變信息
//狀態對象存儲這些信息在_isFavorited和_favoriteCount變量
class _FavoriteWidgetState extends State<FavoriteWidget> {
  bool _isFavorite = true;
  int _favoriteCount = 41;

  //切換狀態
  void _toggleFavorite() {
    setState(() {
      if (_isFavorite) {
        _isFavorite = false;
        _favoriteCount -= 1;
      } else {
        _favoriteCount += 1;
        _isFavorite = true;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Row(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        new Container(
          padding: EdgeInsets.all(0.0),
          child: new IconButton(
            icon: (_isFavorite
                ? 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'),
            ))
      ],
    );
  }
}

//TapboxA 管理自身狀態.
//管理TapboxA的狀態.
//定義_active:確定盒子的當前顏色的布爾值.
//定義_handleTap()函數,該函數在點擊該盒子時更新_active,並調用setState()更新UI.
//實現widget的所有交互式行爲
//------------------------- 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],
        ),
      ),
    );
  }
}

//父widget管理widget的state
// ParentWidget 爲 TapboxB 管理狀態.
// TapboxB通過回調將其狀態導出到其父項。
// 由於TapboxB不管理任何狀態,因此它的子類爲StatelessWidget
//------------------------ ParentWidget TapboxB--------------------------------

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: TapboxB(active: _active, onChanged: _handleTapboxChanged),
    );
  }
}

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

//混合管理
//狀態widget管理一些狀態,並且父widget管理其他狀態
//------------------------ ParentWidget TapboxC--------------------------------

class ParentWidgetC extends StatefulWidget {
  @override
  _ParentWidgetCState createState() {
    // TODO: implement createState
    return new _ParentWidgetCState();
  }
}

class _ParentWidgetCState extends State<ParentWidgetC> {
  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,
      ),
    );
  }
}

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管理自己的state
//父widget管理 widget狀態
//混搭管理(父widget和widget自身都管理狀態))

//如何決定使用哪種管理方法
//如果狀態是用戶數據,如複選框的選中狀態、滑塊的位置,則該狀態最好由父widget管理
//如果所討論的狀態是有關界面外觀效果的,例如動畫,那麼狀態最好由widget本身來管理.

注意:學習不是一蹴而就的,想要提高請繼續努力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章