일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- fastapi를 사용한 파이썬 웹 개발
- PCA
- Stream
- BAEKJOON
- study book
- FastAPI
- bloc
- Algorithm
- MDP
- DART
- 영상처리
- Flutter
- llm을 활용 단어장 앱 개발일지
- Kaggle
- system hacking
- 파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습
- Got
- pytorch
- ML
- BFS
- Widget
- MATLAB
- ARM
- C++
- Computer Architecture
- Image Processing
- BOF
- rao
- 백준
- Dreamhack
- Today
- Total
Bull
[Flutter::Widget] 채팅 UI를 만들어 봅시다! 본문
서론
이번에 만들 앱을 기획하기전에 어떤 UX구조를 해야할 지 고민이 되서 먼처 채팅 UI의 기본적인 구조를 바탕으로 만들어 보았습니다. 채팅 UI는 만들다보면 간단해보일 거 같은 느낌이 드는 데 막상 해보면 조금 많이 어렵다고 느낄 수 있습니다. 왜냐하면 채팅의 히스토리를 어떻게 할 것인지 그 히스토리를 또 어떻게 저장할 것인지 많은 데이터가 담기기 때문에 생각할 게 많아지죠. 따라서 기본적인 틀 구성을 수학의 정석처럼 정석은 어떤지 한 번 살펴 보시다.
혼자 헛소리 조금만 하겠습니다 - (넘어가도 좋습니다)
제가 만들기 위한 앱은 앱을 들어왔을 때 바로 채팅을 칠 수 있는 구조로 만들어야하는데 BottomNavigationBar를 사용하면 아마 채팅을 치는데 불편함이 있겠죠? 그래서 BottomNavigationBar를 사용하지 않고 왼쪽 상단에 drawer를 통해 구현하는 게 좋겠습니다. 광고는 앱바 하단에 달아보죠.
CODE
GPT로 짠 CODE기 때문에 보고 코드를 리뷰하는 형태로 진행해보겠습니다.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Chatbot UI Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChatbotScreen(),
);
}
}
class ChatbotScreen extends StatefulWidget {
@override
_ChatbotScreenState createState() => _ChatbotScreenState();
}
class _ChatbotScreenState extends State<ChatbotScreen> {
final List<Map<String, String>> _messages = [];
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
void _sendMessage() {
if (_controller.text.isNotEmpty) {
setState(() {
_messages.add({'sender': 'user', 'message': _controller.text});
_messages.add({'sender': 'bot', 'message': '${_controller.text}'});
_controller.clear();
});
_scrollToBottom();
}
}
void _scrollToBottom() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chatbot UI Demo'),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: _messages.length,
itemBuilder: (context, index) {
final message = _messages[index];
final isUser = message['sender'] == 'user';
return ListTile(
title: Align(
alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: isUser ? Colors.blueAccent : Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
child: Text(
message['message'] ?? '',
style: TextStyle(color: isUser ? Colors.white : Colors.black),
),
),
),
);
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(
hintText: '메시지를 입력하세요',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: _sendMessage,
),
],
),
),
],
),
);
}
}
배울 만한 개념 (제 기준)
우선 제 기준으로 생소하다고 느낄 만한 내용들을 담아봤습니다. 객관적인 지표로 지식을 설명하기에 기본적인 것을 선별하기가 무척어렵네요.
[목록]
Align
ScrollController
WidgetsBinding.instance.addPostFrameCallback
(... CODE ...)
ListTile(
title: Align(
alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: isUser ? Colors.blueAccent : Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
child: Text(
message['message'] ?? '',
style: TextStyle(color: isUser ? Colors.white : Colors.black),
),
),
),
);
(... CODE ...)
Align class는 반환값을 Widget으로 간주합니다. 따라서 ListTile
은 title
로 Widget을 사용해야겠죠?(ListTile class)
말을 편하게 하기 위해 어떤 공간을 컨테이너라 하겠습니다. Align
은 이제 aligment
프로퍼티를 통해 컨테이너 안에 배열을 원하는 위치에 배열할 수 있습니다.
isUser ? Alignment.centerRight : Alignment.centerLeft
를 통해 만약 상대방이면 왼쪽 중앙에 두고 나의 채팅은 오른쪽 중앙으로 두는 작업을 진행합니다. 이제 이 컨테이너는 채팅을 칠수록 무한이 뻗어진 컨테이너로 나타낼 수 있습니다.
그 아래의 스타일 부분과 Text는 설명을 생략하게습니다.
ScrollController
ListView.builder(
controller: _scrollController,
itemCount: _messages.length,
itemBuilder: (context, index) {
...
return ListTile(
...
);
},
),
우리는 ListView.builder
를 사용했습니다. 이 위젯은 리스트를 나타낼 때 사용하는데요. 여기서 세 프로퍼티 controller
,itemCount
,itemBuilder
가 있는데 itemBuilder
는 required
를 요구하여 필수로 사용되는 부분입니다.
controller
는 컨트롤러라고만 적혀있지만 공식 사이트를 보면 ScrollController
class를 반환해야합니다.itemCount
는 몇개의 리스트를 만들고 싶은 지 개수를 적어야합니다.
itemBuilder
는 NullableIndexedWidgetBuilder
를 반환하는데 이는 Widget을 반환해주는 콜백함수입니다. NullableIndexedWidgetBuilder class는 주어진 인덱스에 대한 위젯을 예를 들어 목록에서 생성하지만 null을 반환할 수 있는 함수에 대한 사인입니다.
말이 어려우니 우리는 ListView.builder
로 ListTile
을 반환해야하는 구나 정도로만 이해하고 넘어갑시다. 이 ScrollController
를 통해 Expanded
되어진 위젯을 스크롤할 수 있도록 만듭니다. 터치를 통해 밀어내면 이 컨트롤러를 통해 움직이는 것이 가능하지만 우리는 세세한 조정도 해야합니다. 이 다음에 나올 내용과 같습니다.
WidgetsBinding.instance.addPostFrameCallback
void _scrollToBottom() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
});
}
_scrollToBottom
는 스크롤을 컨트롤하기 위해 만든 메소드입니다. 예를들어 채팅이 계속 늘어나는데 이 부분이 없으면 채팅창은 채팅이 생성되더라도 새로 생긴 채팅에 대한 포커싱을 자동으로 하지 않습니다. 이를 구현하기 위해 사용되었습니다.
_scrollToBottom
은 채팅을 추가할 때 실행하면 됩니다. 코드를 설명하겠지만 함수명과 같이 스크롤을 제일 하단으로 움직여주는 메소드입니다. 위의 다른 메소드인 _sendMessage
의 코드를 확인해보면 메시지를 추가하고 _scrollToBottom
를 실행하는 모습을 볼 수 있습니다.
이제 세세한 코드를 살펴보겠습니다.
WidgetsBinding:
WidgetsBinding
은 Flutter 애플리케이션에서 위젯 계층과 Flutter 엔진을 연결하는 중요한 역할을 합니다. 위젯 계층은 우리가 작성하는 UI 코드이고, Flutter 엔진은 이 코드를 실제 화면에 렌더링하고 시스템과 상호작용합니다.
WidgetsBinding
은 이 두 계층이 서로 소통할 수 있도록 돕는 접착제와 같은 역할을 합니다.
instance:WidgetsBinding
클래스는 싱글톤으로 구현되어 있으며, instance
는 이 클래스의 유일한 인스턴스에 접근하는 데 사용됩니다.
addPostFrameCallback:addPostFrameCallback
은 프레임이 렌더링된 후 호출될 콜백을 등록합니다. 이 콜백은 특정 작업이 프레임이 완료된 후 실행되도록 예약합니다.
WidgetsBinding.instance.addPostFrameCallback
이를 모아서 해석해보면 실제 렌더링 계층에서 작동하는 시스템에 인스턴스를 통해 접근하여 프레임이 렌더링 된 후 호출될 콜백을 등록하는 과정입니다.
_scrollController.hasClients
는 controller: _scrollController,
프로퍼티와 같이 컨트롤러가 부착되어있는 지 확인합니다. hasClients property의 공식문서에 따르면 컨트롤러 부착없이 위치, 오프셋, 애니메이트 To 및 jumpTo와 같이 ScrollPosition 등을 호출하면 안됩니다.
이제 setState
를 통해 상태를 업데이트 해볼텐데요. 부드러운 효과를 주기 위해 animateTo
애니메이션을 추가했습니다. _scrollController.position.maxScrollExtent
를 통해 스크롤을 끝까지 내립니다. 그 아래의 프로퍼티 설명은 생략하겠습니다.
'Software Framework > Flutter' 카테고리의 다른 글
[Flutter::Window] win32 MessageBox 출력해보기 (Level 1 : 구현) (0) | 2024.07.31 |
---|---|
[Flutter::Widget] Bottom Menu Dropdown 만들어보자. (0) | 2024.07.27 |
[Flutter::Animation] Hero 기법 (1) | 2024.07.25 |
[Flutter::Animation] Fade 기법 (1) | 2024.07.25 |
[Flutter::Animation] implicits animations (ImplicitlyAnimatedWidget) (2) | 2024.07.24 |