简介程序交互

给应用程序添加交互,就是指的是我们点击某个控件的时候,控件的事件能够触发,并且执行一段逻辑。就比较像我们在 HTML 上面的 click事件 。由于是使用 flutter ,在写法上面有比较大的差别。

准备工作

在官方网站中完成构建布局。官方的地址:https://flutterchina.club/tutorials/layout/
完成这个布局可以对组件的嵌套有一个比较深刻的理解了。

最终完成的图示如下:

android flutter交互 flutter 硬件交互_控件

stateless(无状态)和 stateful(有状态)widgets的区别

重点内容

  • 如果自定义的控件可以与用户进行交互,比如通过键盘输入内容、通过滑动屏幕移动滑块点击时改变状态,又或者是随着时间的推移而变化,比如数据Feed会更新状态。这时我们应该选择使用StatefulWidget创建一个有状态控件。
  • 如果自定义的控件仅依赖于对象本身的配置信息,仅仅是用于展示给定的信息。那我们应该选择使用StatelessWidget创建一个无状态控件。

stateless widget 没有内部状态。 IconIconButton, 和Text 都是无状态widget, 他们都是 StatelessWidget的子类。

stateful widget 是动态的. 用户可以和其交互 (例如输入一个表单、 或者移动一个slider滑块),或者可以随时间改变 (也许是数据改变导致的UI更新)。CheckboxRadioSliderInkWellForm, 以及 TextField 都是 stateful widgets,他们都是 StatefulWidget的子类。

由上面的说法,我们可以暂时做一个简单的总结:

  • 如果我们希望 widget 只是作为展示内容渲染出来,就使用 stateless(无状态)widgets。
  • 如果我们希望 widget 可以有事件触发,比如输入,以及点击就使用 stateful(有状态)widgets

自定义有状态的widget

实现一个自定义的有状态widget需要创建两个类:

  • 定义一个widget类,继承自StatefulWidget
  • 包含该widget状态并定义该widget build()方法的类,它继承自State。自定义State类存储可变信息 - 可以在widget的生命周期内改变逻辑和内部状态

代码实例

完成的结果

当点击图标的时候,动态改变图标以及展示数字。

android flutter交互 flutter 硬件交互_控件_02

创建带有状态的 Widget

以下是固定的写法。首先需要创建一个 Widget(下面代码中为 FavoriteWidget ) 继承于 StatefulWidget 。然后给创建的 Widget (下面代码中为 FavoriteWidget ) 创建一个 状态管理的子类 (下面代码中的 _FavoriteWidgetState )继承于 State,并且泛型为 FavoriteWidget

FavoriteWidget类管理自己的状态,因此它重写createState()来创建状态对象。 框架会在构建widget时调用createState()

// 创建一个带有状态的 Widget 需要两个类:StatefulWidget 以及 state
class FavoriteWidget extends StatefulWidget {
  // 由于是需要自己管理状态,需要重写 createState()方法
  @override
  _FavoriteWidgetState createState() => new _FavoriteWidgetState();
}

class _FavoriteWidgetState extends State<FavoriteWidget> {
  @override
  Widget build(BuildContext context) {}
}

修改State子类

状态对象也定义了build方法。此build方法创建一个包含红色IconButtonText的行。 该widget使用IconButton(而不是Icon), 因为它具有一个onPressed属性,该属性定义了处理点击的回调方法。IconButton也有一个icon的属性,持有Icon

我们定义出两个变量,用来动态的改变图标(_isFavorited )以及展示的数字(_favoriteCount )。
setState 状态方法为固定的写法,格式为:setState( (){} )
通过使用 '$'+变量名 的方式来展示我们的变量值

注意: 以下划线(_)开头的成员或类是私有的。

class _FavoriteWidgetState extends State<FavoriteWidget> {
  // 定义出我们交互时,记录状态的变量
  bool _isFavorited = true; // 是否喜欢
  int _favoriteCount = 41;  // 喜欢的计数

  void _toggleFavorite() {
    // setState 状态方法为固定的写法,格式为:setState( (){} )
    setState(() {
      // If the lake is currently favorited, unfavorite it.
      if (_isFavorited) {
        _favoriteCount -= 1;
        _isFavorited = false;
        // Otherwise, favorite it.
      } else {
        _favoriteCount += 1;
        _isFavorited = true;
      }
    });
  }

  // 将控件设置为 stateful 时,需要在 state方法里面 build 处构建
  @override
  Widget build(BuildContext context) {
    return new Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          //由于图标是可以点击的,所以需要使用 IconButton,并且需要判断展示出不同的图标,需要写上逻辑
          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'),
            ),
          ),
        ]);
  }
}

将有stateful widget插入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(
    // ...
    );
  }
}

启动完成就可以看到效果了。

完整的代码

import 'package:flutter/material.dart';

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

// 创建一个带有状态的 Widget 需要两个类:StatefulWidget 以及 state
class FavoriteWidget extends StatefulWidget {
  // 由于是需要自己管理状态,需要重写 createState()方法
  @override
  _FavoriteWidgetState createState() => new _FavoriteWidgetState();
}

class _FavoriteWidgetState extends State<FavoriteWidget> {
  // 定义出我们交互时,记录状态的变量
  bool _isFavorited = true; // 是否喜欢
  int _favoriteCount = 41;  // 喜欢的计数

  void _toggleFavorite() {
    // setState 状态方法为固定的写法,格式为:setState( (){} )
    setState(() {
      // If the lake is currently favorited, unfavorite it.
      if (_isFavorited) {
        _favoriteCount -= 1;
        _isFavorited = false;
        // Otherwise, favorite it.
      } else {
        _favoriteCount += 1;
        _isFavorited = true;
      }
    });
  }

  // 将控件设置为 stateful 时,需要在 state方法里面 build 处构建
  @override
  Widget build(BuildContext context) {
    return new Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          //由于图标是可以点击的,所以需要使用 IconButton,并且需要判断展示出不同的图标,需要写上逻辑
          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'),
            ),
          ),
        ]);
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Column buildButtonColumn(IconData icon, String label) {
      Color color = Theme.of(context).primaryColor;

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

    Widget buttonSection = new Container(
      child: new Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          buildButtonColumn(Icons.call, 'CALL'),
          buildButtonColumn(Icons.near_me, 'ROUTE'),
          buildButtonColumn(Icons.share, 'SHARE'),
        ],
      ),
    );

    Widget titleSection = new Container(
      padding: const EdgeInsets.all(32.0),
      child: new Row(
        children: [
          new Expanded(
            child: new Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                new Container(
                  padding: const EdgeInsets.only(bottom: 8.0),
                  child: new Text(
                    'Oeschinen Lake Campground',
                    style: new TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                new Text(
                  'Kandersteg, Switzerland',
                  style: new TextStyle(
                    color: Colors.grey[500],
                  ),
                ),
              ],
            ),
          ),

          new FavoriteWidget(),
        ],
      ),
    );

    Widget textSection = new Container(
      padding: const EdgeInsets.all(32.0),
      child: new 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: true,
      ),
    );

    // 创建一个 material 风格的 app
    return new MaterialApp(
        title: 'Flutter Demo',
        theme: new ThemeData(
          primarySwatch: Colors.blue,
        ),

        // Material 库中的 Scaffold widget 提供了默认的应用栏 (app bar),标题和构成主页面 widget 树结构的 body 属性。 widget 的子树可以非常复杂。
        home: new Scaffold(
            body: new ListView(
          children: <Widget>[
            new Image.asset(
              'images/test01.jpg',
              height: 240.0,
              fit: BoxFit.cover,
            ),
            titleSection,
            buttonSection,
            textSection,
          ],
        )));
  }
}

附:
官方的文档连接:https://flutterchina.club/tutorials/interactive/