При реализации пользовательского интерфейса чата необходимо учитывать множество сложных деталей. Чтобы создать чат-приложение, которое мы используем десятки раз в день, нам нужно рассмотреть, казалось бы, очевидные детали и реализовать высококачественную функцию чата, учитывающую пользовательский опыт (UX).
В этом посте объясняется, как разработать приложение для чата, использующее логику пользовательского интерфейса interaction
, используемую в типичных приложениях для чата, таких как 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
.
chat list view
и input field
, обернутые в виджет Column, располагаются вертикально, и, поскольку chat list view section
обернуты в Expanded, представление input field
естественным образом фиксируется внизу. Преимущество этого заключается в том, что нет необходимости исправлять виджет input field
внизу с помощью виджетов Stack & Positioned.
Обратите внимание, что примеры, которые я буду продолжать показывать, также расположены в этой структуре.
1. Взаимодействие, при котором поле ввода и раздел просмотра списка чатов реагируют на изменения при обнаружении области виртуальной клавиатуры.
Первое взаимодействие в чате, которое следует рассмотреть, — это то, как input field
и chat list view section
реагируют на изменения, когда появляется virtual keyboard
. Для удобства пользователя важно, чтобы при появлении виртуальной клавиатуры поле ввода и список чатов естественным образом следовали за движением.
Для этого вам нужно установить следующие два файла properties
.
свойство resizeToAvoidBottomInset
return Scaffold( resizeToAvoidBottomInset: true, // assign true appBar: AppBar( title: const Text("Ximya"), backgroundColor: const Color(0xFF007AFF), ),
Во-первых, вам нужно установить для свойства resizeToAvoidBottomInset
виджета Scaffold значение true
. Когда для этого свойства установлено значение true, виджет Scaffold автоматически регулирует свой размер, чтобы избежать перекрытия с virtual keyboard
при появлении виртуальной клавиатуры.
обратная собственность
ListView.separated( reverse: true, itemCount: chatList.length, ... )
Во-вторых, вам нужно установить для свойства reversed
виджета ListView
значение true
. Это свойство указывает, следует ли упорядочивать элементы списка в обратном порядке. Если задать для параметра reversed значение true, элементы располагаются снизу вверх, и можно обнаружить изменение размера виртуальной клавиатуры.
ПРИМЕЧАНИЕ. Индекс и положение Если для параметра reverse
установлено значение true, элементы в ListView располагаются снизу вверх. В результате индекс и положение элементов на экране меняются местами. Это необходимо учитывать при манипулировании данными, передаваемыми в ListView. Если необходимы манипуляции с данными, решением может быть еще одно изменение значений перед передачей данных в ListView.
Например,controller.chatList.reversed.toList()
.
2. Взаимодействие при добавлении чата и прокрутке вниз
Когда сообщение добавляется в список чата, оно должно располагаться внизу и прокручиваться естественным образом. Для этого вам нужно установить для свойства reversed
ListView значение true
. Если задать для reversed значение true, элементы располагаются снизу вверх. Поэтому при добавлении сообщения область ListView расширяется, а положение прокрутки изменяется.
3. Выравнивание сообщений чата вверху
До сих пор я говорил вам, что вам нужно установить для свойства reversed
виджета ListView значение true
. Однако это приводит к тому, что раздел списка чатов помещается в самый низ экрана.
Align( alignment: Alignment.topCenter, child: ListView.separated( shrinkWrap: true, reverse: true, itemCount: chatList.length, itemBuilder: (context, index) { return Bubble(chat: chatList[index]); }, ); ),
Поскольку установка свойства reversed в значение true помещает раздел списка чатов в самый низ экрана, вам необходимо внести некоторые изменения, чтобы сообщения чата отображались в верхней части экрана. Оберните виджет ListView с помощью Align
и задайте для свойства выравнивания значение Alignment.topCenter, чтобы разместить его вверху. Кроме того, вам необходимо установить свойство shrinkWrap: true
в ListView. Таким образом, ListView регулирует свой размер, чтобы соответствовать своему внутреннему содержимому, и размещается вверху под влиянием виджета «Выравнивание».
4. Оптимизация положения прокрутки после отправки сообщений:
При отправке сообщения чата позиция прокрутки должна измениться на самый низ, независимо от того, где находится текущая позиция прокрутки. Для этого вы можете управлять поведением прокрутки ListView с помощью ScrollController
.
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. Отключение виртуальной клавиатуры в области чата Нажмите
Наконец, в типичном приложении для чата есть interaction where the virtual keyboard hides down
, когда нажимается область общего списка чата, когда виртуальная клавиатура поднята. Чтобы реализовать это, вам просто нужно добавить простой фрагмент кода.
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 с кодом примера.😀
В следующем посте мы рассмотрим, как получать ответы в режиме реального времени с помощью API ChatGPT на основе пользовательского интерфейса функции чата, который мы рассмотрели до сих пор.