编程中的一切都是操作数据,界面上的表现都需要具体数据来支撑,决定界面展现的数据称之为 状态数据。然而:
有些数据中界面中是恒定不变的,比如一些固定的文字、图标、颜色等; 也有些数据会随着用户的交互发生改变,比如计数器项目中,数字会随着用户的点击而增加。
如何通过代码逻辑,维护状态数据在交互过程中的正确性,就是对状态数据的管理。在一个需求中,哪些 状态数据 是可变的,需要具体问题具体分析。
在猜数字的需求之中,点击按钮生成随机数。但在猜测的过程中,我们期望禁止点击来重新生成,否则又要重新猜测。也就是说,在一次猜数字过程中,只能生成一个随机数;同时,猜对时,需要解除禁止,进入下一次游戏。
对于生成随机数的需求,需要一个量来标识是否是在游戏过程中。这就是根据具体需求,来分析必要的状态数据。比如这里通过 bool
类型的 _guessing
对象标识是否在游戏过程中。界面和交互的逻辑表现在:当 _guessing
为 false
时,支持点击,按钮呈蓝色;为 true
时,禁止点击,按钮呈灰色:
标题 | |
---|---|
如下所示,在 _GuessPageState
中定义 _guessing
属性,FloatingActionButton
按钮组件在创建时根据 _guessing
值控制相关属性。比如 _guessing
为 true 时 onPressed
为 null, 表示不响应点击,且背景色是灰色 Colors.grey :
---->[guess_page.dart#_GuessPageState]----
bool _guessing = false;
// 略...
// 按钮组件构建逻辑 :
floatingActionButton: FloatingActionButton(
onPressed: _guessing ? null : _generateRandomValue,
backgroundColor: _guessing ? Colors.grey : Colors.blue,
tooltip: 'Increment',
child: const Icon(Icons.generating_tokens_outlined),
),
注: boolValue ? a : b
称三目运算符,相当于一种简写的赋值语句;boolValue
为 true 时取 a
,反之取 b
。下面是一个小例子:
int a = 5;
int b = 6;
bool boolValue = true;
int c = boolValue ? a : b
// 上行代码等价于下面代码:
int c;
if(boolValue){
c = a;
}else{
c = b;
}
上面通过 _guessing
状态数据控制组件构造,从而达到控制表现的效果。写一个问题就是,如何在交互逻辑中,正确地维护 _guessing
状态数据值。这里的逻辑是:点击之后,表示游戏开始,将 _guessing
置为 true
。所以只需要在 _generateRandomValue
方法中添加一行即可:
void _generateRandomValue() {
setState(() {
_guessing = true; // 点击按钮时,表示游戏开始
_value = _random.nextInt(100);
});
}
大家可以将当前代码自己跑一下,点击操作。体会一下状态数据变化的过程 (业务逻辑),和对界面表现的控制力(界面构建逻辑)。
既然是猜数字,那么随机生成的数字肯定不能明晃晃地摆在那里,而且生成之后,上方的 点击生成随机数值
的提示信息也不需要了。可以看出这些界面表现都是通过 _guessing
状态数据决定的,通过状态数据控制界面呈现,一般称之为 界面构建逻辑 。
也就是在猜数字过程中,要隐藏数字的展示;取除上方的提示字,效果如下:
代码实现也比较简单,和之前一样,通过 _guessing
值,决定组件构造的内容。从这里可以看出,一个状态数据,可以控制界面中很多部件的表现形式。
注: 在列表中可以通过 if(boolValue) 来控制是否添加某个元素。
当前代码提交位置: guess_page.dart
这样我们就实现了随机数字生成的需求,现在需要做的是猜数字需求。 在输入框中输入数字,点击确定按钮,比较后想用户提示大小信息。现在首要问题是知道如何获取输入的数字,以及如何触发按钮的点击事件。
我们之前将头部栏单独封装成一个组件,独立存放。现在想处理头部栏的相关工作,直接看 guess_app_bar.dart
文件即可。先来看一下点击事件的回调:
这里使用 IconButton
,也就是图标按钮,在 onPressed 构造入参中可以传入无参函数,用于回调。也就是说,你点击按钮就会触发一个函数(方法),如下所示,点击一下在控制台输出信息:
在 GuessAppBar
类中,并没有猜数字过程中的相关数据,在这里校验大小并不是很合适。这时就可以通过回调,将事件触发的任务移交给自己的使用者。因为函数本身也可以视为一个对象,如下所示,将函数作为属性成员,通过构造函数进行赋值:
这样在构造 GuessAppBar
组件时,就可以将回调事件交由 _GuessPageState
处理,而这里有我们维护的状态数据。处理如下:
目前,输入框可以进行输入,但如何获取到输入内容呢? 我们可以使用输入控制器 TextEditingController
它可以承载输入的内容。它会作为 TextField
的构造入参,而 TextField
在 GuessAppBar
中;又因为由于核心逻辑的维护在 _GuessPageState
中,所以控制器对象可以交由 _GuessPageState
维护,并可以通过 GuessAppBar 构造函数来传入:
---->[guess_page.dart#GuessAppBar]----
class GuessAppBar extends StatelessWidget implements PreferredSizeWidget {
final VoidCallback onCheck;
final TextEditingController controller;
const GuessAppBar({
Key? key,
required this.onCheck,
required this.controller,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AppBar(
// 略同...
title: TextField(
controller: controller,
这样,在 _GuessPageState
里创建 _guessCtrl
属性,作为 GuessAppBar
构造入参,就可以和输入框进行绑定。当输入文字,点击按钮后,查看控制台,就可以看到输入信息。
另外,注意一下,输入控制器有销毁的方法,需要覆写状态类的 dispose
方法,调用一下:
@override
void dispose() {
_guessCtrl.dispose();
super.dispose();
}
到这里,相关的数据和界面就准备完毕,当前代码提交位置: guess_page.dart 。本章最主要的知识是通过改变数据来修改界面的呈现效果。比如在交互过程中,按钮的禁用、文字的密文展示会发生变化,它们的表现都在构造逻辑中由数据决定。大家可以通过当前的源码好好思考一下,状态数据和界面之间的关系。
下一章,将继续完善功能,处理校验以及提示用户输入值大了还是小了。
© 2019 - 2023 Liangliang Lee. Powered by gin and hexo-theme-book.