



我们将首先使用一些硬编码文本为应用程序创建基本用户界面,以测试用户界面是否正确更新。然后,我们将集成Dialogflow代理,以便它可以回答查询并告诉用户他们的幸运数字,然后添加麦克风选项,以便可以利用语音转文本功能。
该应用程序的整体小部件树如图3-13所示。
图3-13
接下来,我们将详细讨论每个小部件的实现。
首先,让我们在名为chat_screen.dart的新dart文件中创建一个名为ChatScreen的StatefulWidget。请按照以下步骤操作。
(1)创建一个文本框(在Flutter术语中,这称为TextField,它允许用户输入文本)。要创建TextField,需要定义createTextField()。
注意:
onSubmitted属性用作该文本域(TextField)的回调,以便在用户指示他们已将文本输入TextField时进行处理。当按下键盘上的Enter键时触发该属性。
在上面的TextField小部件中,当用户完成输入文本时即调用_handleSubmitted()。下文将对_handleSubmitted()进行详细介绍。
我们还将decoration属性指定为collapsed,以删除可能出现在文本域中的默认边框。我们指定了一个hintText属性,即Enter your message(请输入你的消息)。为了侦听更改并更新TextField,我们还附加了一个TextEditingController实例。可以通过执行以下代码来创建实例。
final TextEditingController _textController = new TextEditingController();
注意:
与Java不同,Dart没有public、private或protected等关键字来定义变量的使用范围。相反,它在标识符名称之前使用下画线(_)来指定该标识符是某个类私有的。
(2)在createSendButton()函数中创建一个发送按钮,该按钮可用于向代理发送查询:
在Flutter中,可以使用Icons类轻松添加类似于发送按钮的图形图标。为此,我们创建了一个新的Icon实例并指定Icons.send,以便将小部件用作发送按钮。这可以作为icon属性的参数。我们还设置了onPressed属性,当用户单击Send(发送)按钮时调用该属性。在这里,我们再次调用_handleSubmitted。
注意:
=>有时也称为箭头,是一种速记符号,用于定义仅包含一行的方法。例如,方法fun(){ return 10; }可以写成下面的形式。
fun() => return 10;
(3)文本域和Send(发送)按钮应该并排显示,因此可以将它们作为子项添加到Row小部件,以便将它们包装在一行中。包装好的Row小部件位于屏幕底部。
可以在_buildTextComposer()中创建这个小部件。
_buildTextComposer()函数可返回一个IconTheme小部件,这个小部件还有一个Container,是其子项。在该Container中,又包含一个Row小部件,由我们在步骤(1)和(2)中创建的文本域和发送按钮组成。
接下来,我们将构建一个ChatMessage小部件,用于显示用户与聊天机器人的交互。
来自用户的查询和来自代理的响应可以被认为是单个组件的两个不同部分。我们将为它们创建两个不同的容器,然后将它们添加到一个名为ChatMessage的单个单元中。这样可以确保每个查询及其答案的显示顺序与用户输入的顺序相同。
我们首先在名为chat_message.dart的新dart文件中创建一个名为ChatMessage的有状态小部件。如图3-14所示,该ChatMesage被划分为Query(查询)和Response(响应)两个部分。
图3-14
要创建屏幕的用户界面,请按照下列步骤操作。
(1)创建一个包含一些文本的Container(容器),它将在屏幕上显示用户输入的查询。
我们首先为该容器提供8.0的上边距,容器中包含一个字符串,每当用户输入查询时都会显示该字符串。在调用_handleSubmitted()时,此硬编码字符串会被修改为字符串参数。我们还将fontSize属性修改为16.0,并将颜色设置为black45(深灰色),以帮助用户区分查询和响应。
(2)创建一个容器来显示响应字符串。
该容器的上边距属性为8.0,它也包含一个硬编码的响应字符串。这个容器稍后会被修改,以便它可以适应用户的响应。
(3)将这两个容器包装在一个Column(列)中,并将它作为一个容器从build()方法返回,该容器将在有状态小部件(即ChatMessage)内被覆盖。
提示:
在Flutter中,文本被包装在一个Container中。一般来说,当它们太长而无法水平放入屏幕时,它们往往会从屏幕溢出。这可以看到在屏幕角落出现了一个红色标记。为避免文本溢出,请确保将Container与Text包装在Flexible中,以便文本可以垂直占据可用空间并自行调整。
(4)为了存储和显示所有字符串(查询和响应),我们将使用一个ChatMessage类型的List。
final List<ChatMessage> _messages = <ChatMessage>[];
此列表应出现在我们之前创建的TextField上方,以接收用户的输入。
(5)为了确保文本域以垂直顺序正确显示,我们需要将它们包装在列中,并从ChatScreen.dart的Widget build()方法返回它们。该列的3个子项是一个灵活的列表视图、一个分隔符和一个带有文本域的容器。
该用户界面是通过覆盖build()方法创建的,如下所示。
ListView包含ChatMessages并将其作为其子项,它被设置为Flexible,以允许其在放置分隔符(Divider)和文本域的容器后在垂直方向上占据屏幕的整个可用空间。它在所有4个主要方向上的填充为8.0。
此外,reverse属性被设为true,以使其可在从下到上的方向上滚动。itemBuilder属性被分配了索引的当前值,以便它可以构建子项。
另外,还为itemCount分配了一个值,以帮助列表视图正确估计最大可滚动内容。该列的第二个子项创建了一个分隔符。这是一个devicePixel粗水平线,用于标记列表视图和文本域的分隔。
在列的最底部位置,我们放置了一个带有文本域的容器作为其子项。这是通过调用之前定义的_buildTextComposer()方法来构建的。
(6)在ChatScreen.dart方法中定义_handleSubmitted()以正确响应用户的发送消息Action。
该方法的字符串参数包含用户输入的查询字符串的值。该查询字符串以及一个硬编码的响应字符串用于创建ChatMessage的实例,并插入到_messages列表中。
(7)在ChatMessage中定义一个构造函数,以便正确传递和初始化参数值、查询和响应。
final String query, response;
ChatMessage({this.query, this.response});
(8)分别修改ChatMessages.dart中query和response容器内Text属性的值,使显示在屏幕上的文本与用户输入的文本一致。
// 修改查询文本
child: new Text(query,
style:...
)
// 修改响应文本
child: new Text(response,
style:...
)
成功编译到目前为止所编写的代码之后,屏幕应如图3-15所示。
图3-15
从图3-15中可以看到,虚拟查询文本(显示为灰色)将由用户编写,而响应字符串(显示为黑色)则来自聊天机器人。
注意:
整个chat_message.dart文件的网址如下。
https://github.com/PacktPublishing/Mobile-Deep-Learning-Projects/blob/master/Chapter3/ActionsOnGoogleWithFlutter-master/lib/chat_message.dart
在下一节中,我们将集成Dialogflow代理,以便可以对用户查询进行实时响应。