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本身来管理.

注意:学习不是一蹴而就的,想要提高请继续努力。

猜你喜欢

转载自blog.csdn.net/lhk147852369/article/details/81458836