在实施聊天用户界面时,有许多错综复杂的细节需要考虑。要创建一个我们每天都要使用数十次的聊天应用程序,我们需要考虑看似显而易见的细节,并在考虑用户体验(UX)的基础上实现高质量的聊天功能。
本篇文章将介绍如何开发一款聊天应用,将 WhatsApp、KakaoTalk 和 Line 等代表性聊天应用中的用户界面交互逻辑应用到这款应用中。
基本结构
首先,让我们来看看聊天界面的基本结构。
Scaffold(
appBar: AppBar(
title: const Text("Chat"),
backgroundColor: const Color(0xFF007AFF),
), // <-- App bar
body: Column(
children: [
Expanded(
child: ListView.separated(...), // <- Chat list view
),
_BottomInputField(), // <- Fixed bottom TextField widget
],
),
);
一般来说,聊天界面的结构很简单。它由 AppBar
、Chat ListView
(聊天列表视图) 和固定在底部的TextField
(文本字段)组成。
这里有一点很重要,聊天列表视图和文本字段必须封装在一个Column
部件中,而聊天列表视图部分必须封装在一个Expanded
部件中。如下图:
聊天列表视图和包裹在列部件中的输入框是垂直排列的,由于聊天列表视图部分包裹在展开部件中,因此输入框视图自然会固定在底部。这样做的好处是无需使用堆叠和定位部件将输入框固定在底部。
请注意,我将继续展示的示例也是以这种结构排列的。
1. 当检测到虚拟键盘区域时,输入框和聊天列表视图部分会响应变化的交互方式
首先要考虑的聊天交互是当虚拟键盘出现时,输入栏和聊天列表视图部分如何响应变化。当虚拟键盘出现时,输入框和聊天列表视图会自然跟随移动,这对用户体验非常重要。
为此,您需要设置以下两个属性。
- resizeToAvoidBottomInset property
return Scaffold(
resizeToAvoidBottomInset: true, // assign true
appBar: AppBar(
title: const Text("Ximya"),
backgroundColor: const Color(0xFF007AFF),
),
首先,您需要将 Scaffold widget 的 resizeToAvoidBottomInset
属性设置为 true
。当该属性设置为 true 时,Scaffold 部件会自动调整其大小,以避免在虚拟键盘出现时与虚拟键盘重叠。
- reversed property
ListView.separated(
reverse: true,
itemCount: chatList.length,
...
)
其次,需要将 ListView widget 的 reversed
属性设置为 true
。该属性指定是否按相反顺序排列列表项。将 reversed 设置为 true 后,项目将从下往上排列,虚拟键盘的大小变化也会被检测到。
注意:索引和位置当设置为 true 时,ListView 中的项目将从下到上排列。因此,屏幕上项目的索引和位置会颠倒。在操作传递给 ListView 的数据时需要考虑这一点。如果需要进行数据操作,在将数据传递到 ListView 之前再次反转值可能是解决方案。
例如,controller.chatList.reversed.toList()
.
2. 添加聊天并向下滚动时的互动
当一条信息添加到聊天列表时,它应该位于底部并自然滚动。为此,您需要将 ListView 的 reversed
属性设置为 true
。将反转属性设置为 true 后,项目将从下往上排列。因此,当添加一条信息时,ListView 的区域会扩大,滚动位置也会改变。
3. 将聊天信息对齐到顶部
到目前为止,我已经告诉您需要将 ListView widget 的反转属性设置为 true。但是,这会导致聊天列表部分被放置在屏幕的最下方。
Align(
alignment: Alignment.topCenter,
child: ListView.separated(
shrinkWrap: true,
reverse: true,
itemCount: chatList.length,
itemBuilder: (context, index) {
return Bubble(chat: chatList[index]);
},
);
),
由于将 reverse 属性设置为 true
会将聊天列表部分置于屏幕的最下方,因此需要进行一些修改才能使聊天信息显示在屏幕的顶部。用对齐方式包裹 ListView widget,并将对齐方式属性设置为 Alignment.topCenter,使其位于顶部。此外,你还需要在 ListView 上设置 shrinkWrap: true
属性。这样,ListView 就会根据内部内容调整大小,并在 Alignment widget 的影响下位于顶部。
4. 优化发送消息后优化滚动位置:
发送聊天信息时,无论当前滚动位置在哪里,滚动位置都应更改为最底部。为此,您可以使用 ScrollController
控制 ListView 的滚动行为。
final scrollController = ScrollController()
...
ListView.separated(
shrinkWrap: true,
reverse: true,
controller: scrollController
itemCount: chatList.length,
itemBuilder: (context, index) {
return Bubble(chat: chatList[index]);
},
);
首先,初始化 ScrollController 变量。然后,将该变量传递给 ListView 的控制器属性。现在,您可以控制 ListView 的滚动行为了。
Future<void> onFieldSubmitted() async {
addMessage();
// Move the scroll position to the bottom
scrollController.animateTo(
0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
textEditingController.text = '';
}
然后,将 scrollController.animatedTo
事件应用到添加聊天时发生的方法中,以添加滚动到最底部的动画。我们将偏移值 0 传递给 animatedTo 方法的原因是,在 listview.buidler 设置为 reversed:true
时,0 的位置基本上意味着列表的最底部。
5. 点击聊天区时关闭虚拟键盘
最后,在典型的聊天应用程序中,有一种交互方式,即当虚拟键盘在上时点击普通聊天列表区域,虚拟键盘就会隐藏起来。要实现这一点,只需添加一段简单的代码即可。
Expanded(
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus(); // <-- Hide virtual keyboard
},
child: Align(
alignment: Alignment.topCenter,
child: Selector<ChatController, List<Chat>>(
selector: (context, controller) =>
controller.chatList.reversed.toList(),
builder: (context, chatList, child) {
return ListView.separated(
shrinkWrap: true,
reverse: true,
padding: const EdgeInsets.only(top: 12, bottom: 20) +
const EdgeInsets.symmetric(horizontal: 12),
separatorBuilder: (_, __) => const SizedBox(
height: 12,
),
controller:
context.read<ChatController>().scrollController,
itemCount: chatList.length,
itemBuilder: (context, index) {
return Bubble(chat: chatList[index]);
},
);
},
),
),
),
),
使用“GestureDetector”小部件包装聊天列表部分,并将“FocusScope.of(context).unfocus()
”事件传递给 onTap 函数。
// 1. Initialization
final focusNode = FocusNode();
// 2. Passing the focusNode object
TextField(
focusNode : focusNode,
...
),
// 3. When the chat section is tapped
onChatListSectinoTapped() {
focusNode.unfocus()
另一种方法是使用 FocusNode 对象隐藏虚拟键盘。初始化 FocusNode 对象并在文本字段中设置 focusNode
属性。然后,当点击聊天列表部分时,调用 focusNode.unfocus()
隐藏虚拟键盘。
结论
在本文章中,我们介绍了在构建聊天应用程序时需要考虑的交互。虽然这些细节看似微不足道,但我相信考虑到这些交互会大大提高聊天功能的完整性。如果您想了解整体结构,而不仅仅是上面提到的交互,我建议您克隆包含示例代码的 GitHub 代码库。
作者:Ximya
译自medium.
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/im/37186.html