Skip to content

Latest commit

 

History

History
342 lines (263 loc) · 9.53 KB

07. Flutter用户交互 - 添加交互.md

File metadata and controls

342 lines (263 loc) · 9.53 KB

Adding interactivity

学习要点

  • 如果响应点击
  • 如果自定义widget
  • 区分无状态(stateless)和有状态(stateful)的widget

Stateful and stateless widgets

widget分为有状态(stateful)和无状态(stateless)两种. 如果一个部件与用户交互时可以改变, 它就是有状态的部件.

无状态的部件永远不会改变. 如 Icon, IconButton, Text. 继承自StatelessWidget

有状态的部件是动态的. 当用户触发交互或接收到数据时, 它的显示会进行改变. 如Checkbox, Radio, Slider, InkWell, Form, and TextField. 继承自StatefulWidget.

widget的状态存在一个State对象中, 根据显示来区分不同的状态. state对象中包含可变的值, 如slider当前的值, 或者一个checkbox是否勾选. 当widget的状态改变, state对象会调用setState()通知框架重新绘制widget.

Creating a stateful widget

demo

  • stateful widget由两个类实现: StatefulWidget的子类, 和State的子类
  • state类中包含widget的可变状态和build方法
  • 当widget的状态发生变化, state对象会调用setState(), 通知框架重新绘制该widget

Step 0: Get ready

Step 1: Decide which object manages the widget’s state

widget的状态有多种管理方式.

自定义stateful widget需要创建两个类

  • 定义一个widget, 继承自StatefulWidget
  • 定义一个State, 包含了这个widget的状态, 并定义build()方法创建这个widget

Step 2: Subclass StatefulWidget

Step 3: Subclass State

Step 4: Plug the stateful widget into the widget tree

Problems?

Managing state

  • 多种方式可以管理状态
  • 选择哪种方式进行状态管理
  • 如果有疑惑, 先由父widget进行管理

由谁来管理widget的状态, 父部件? 自己? 一起? 或者其他部件? 看情况决定. 有多种方式可以进行widget的交互. 作为widget的设计者, 依据widget的使用方式而定. 下面几种最通用的管理状态的方式:

  • widget管理自己的状态. The widget manages its own state
  • 父部件管理该widget的状态. The parent manages the widget’s state
  • 混合匹配方式. A mix-and-match approach

如果决定使用哪种方式? 可以根据以下规则:

  • 如果state对象关心的是用户数据, 如checkbox的勾选状态, slider的位置, 此时最好由父widget管理该状态
  • 如果state对象关心的是式样, 如动画, 此时最好由自己管理

如果有疑惑, 最开始可以由父widget进行管理.

The widget manages its own state

有时候, 由widget自己管理内部的状态最合适. 如当ListView的内容超过渲染盒子后, 它自动可滚动. 许多开发者不想要管理ListView的滚动行为, 因此ListView自行管理它滚动的偏移量.

// TapboxA manages its own state.

//------------------------- TapboxA ----------------------------------

class TapboxA extends StatefulWidget {
  TapboxA({Key key}) : super(key: key);

  @override
  _TapboxAState createState() => _TapboxAState();
}

class _TapboxAState extends State<TapboxA> {
  bool _active = false;

  void _handleTap() {
    setState(() {
      _active = !_active;
    });
  }

  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            _active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: _active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

//------------------------- MyApp ----------------------------------

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Demo'),
        ),
        body: Center(
          child: TapboxA(),
        ),
      ),
    );
  }
}

The parent widget manages the widget’s state

有时候由父widget告诉子widget何时需要更新比较合适. 如 IconButton允许你将一个图标当成一个可点击的按钮. IconButton是stateless的, 因为父widget需要知道按钮合适被点击来做合适的操作.

// ParentWidget manages the state for TapboxB.

//------------------------ ParentWidget --------------------------------

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: 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 GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            active ? 'Active' : 'Inactive',
            style: TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

使用 @required 注释你代码所依赖的参数. 使用它需要到foundation library

import 'package:flutter/foundation.dart';

A mix-and-match approach

有些widget, 使用混合搭配的方式. 该案例中, stateful widget管理一些状态, 父widget管理其他方面的状态.

TapboxC 案例. 按下的时候有一个深绿色边框. 按回的时候, 边框消失, 盒子的颜色改变.

_ParentWidgetState

  • 管理_active状态
  • 实现_handleTapboxChanged(), 在盒子被点击的时候调用
  • 当触发点击事件, _active状态改变时, 调用setState()更新UI

_TapboxCState

  • 管理_highlight状态
  • GestureDetector监听所有点击事件. 当用户往下点击时, 会增加高亮效果(深绿色边框). 当用户释放点击, 移除高亮效果.
  • 当用户向下点击和释放时, 更改_highlight的状态, 调用setState()来更新UI
  • 在点击事件. 通过widget属性, 将状态的改变传给父widget来做合适的操作.
//---------------------------- ParentWidget ----------------------------

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: 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() => _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 GestureDetector(
      onTapDown: _handleTapDown, // Handle the tap events in the order that
      onTapUp: _handleTapUp, // they occur: down, up, tap, cancel
      onTap: _handleTap,
      onTapCancel: _handleTapCancel,
      child: Container(
        child: Center(
          child: Text(widget.active ? 'Active' : 'Inactive',
              style: TextStyle(fontSize: 32.0, color: Colors.white)),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color:
              widget.active ? Colors.lightGreen[700] : Colors.grey[600],
          border: _highlight
              ? Border.all(
                  color: Colors.teal[700],
                  width: 10.0,
                )
              : null,
        ),
      ),
    );
  }
}

你也可以将高亮状态交给父部件管理, 将有效状态放在内部, 但是这样做没有意义. 开发者关心的是盒子的有效状态, 不关心高亮.

Other interactive widgets

Flutter提供多种按钮和类似交互的部件. 大部分widgets在 Material Design guidelines中实现, 定义一套UI.

Flutter 也提供iOS风格的部件Cupertino

如果需要交互, 可以使用其中一个预设好的部件更加方便

Standard widgets

  • Form
  • FormField

Material Components

  • Checkbox
  • DropdownButton
  • FlatButton
  • FloatingActionButton
  • IconButton
  • Radio
  • RaisedButton
  • Slider
  • Switch
  • TextField