feat(chat-room-ui): 사용자 메시지, AI 메시지 아이템 레이아웃, 타이핑 인디케이터 아이템 레이아웃 및 애니메이션 추가

item_chat_user_message.xml
- 오른쪽 정렬된 메시지 버블 구현
- 버블 왼쪽에 시간 텍스트(tv_time) 배치
- bg_chat_user_message 배경 및 패딩 적용
- 텍스트 접근성과 가독성 향상을 위한 속성 설정

item_chat_ai_message.xml
- 왼쪽 정렬된 메시지, 프로필 이미지와 이름, 오른쪽 시간 표시 구조 구현
- 그룹화 대응을 위한 조건부 표시(View visibility) 구조 마련
- bg_chat_ai_message 배경과 가독성 개선 속성 적용

item_chat_typing_indicator.xml, typing_dots_animation.xml
- AI 메시지와 동일한 좌측 정렬 구조에 3개 점 애니메이션 영역 구현
- 600ms alpha 애니메이션 반복으로 로딩 상태 시각화
- 추후 ViewHolder에서 점별 startOffset 설정을 통해 순차 반짝임 완성 예정
This commit is contained in:
2025-08-13 20:30:07 +09:00
parent 760cbb8228
commit 9bb8dcd881
4 changed files with 271 additions and 8 deletions

View File

@@ -1,12 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
타이핑 인디케이터용 알파 애니메이션
- duration: 600ms
- fromAlpha: 0.3 -> toAlpha: 1.0
- repeat: infinite, reverse
- 3개의 점을 순차적으로 반짝이게 하려면, 이 애니메이션을 각 점에 적용하고 startOffset을 다르게 지정하세요.
예) dot1: 0ms, dot2: 200ms, dot3: 400ms
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"> <set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha <alpha
android:duration="600" android:duration="600"

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
AI 메시지 아이템 레이아웃 (3.3)
- 왼쪽 정렬된 메시지 버블
- 프로필 이미지, 이름(조건부), 메시지, 시간(오른쪽, 조건부)
- 배경: @drawable/bg_chat_ai_message
- 그룹화 지원: iv_profile, tv_name, tv_time의 가시성을 어댑터에서 제어
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="6dp"
android:paddingBottom="6dp">
<!-- 프로필 이미지 (그룹 첫 메시지에서만 보임) -->
<ImageView
android:id="@+id/iv_profile"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="8dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_placeholder_profile"
android:contentDescription="AI 프로필 이미지"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<!-- 메시지 그룹: 이름 + 메시지 버블 -->
<LinearLayout
android:id="@+id/message_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toEndOf="@id/iv_profile"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/tv_time"
app:layout_constraintHorizontal_bias="0"
android:layout_marginEnd="8dp">
<!-- 보낸이 이름 (그룹의 첫 메시지에서만 보임) -->
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="12sp"
android:includeFontPadding="false"
android:layout_marginBottom="2dp"
android:maxLines="1"
android:ellipsize="end" />
<!-- 메시지 버블 컨테이너 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/message_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_chat_ai_message"
android:paddingStart="14dp"
android:paddingEnd="14dp"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:id="@+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="15sp"
android:lineSpacingExtra="2dp"
android:includeFontPadding="false"
android:hyphenationFrequency="normal"
android:breakStrategy="balanced"
android:ellipsize="end"
android:maxLines="1000"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<!-- 전송 시간: 버블의 오른쪽에 표시 (그룹의 마지막 메시지에서만 보임) -->
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/color_777777"
android:textSize="12sp"
android:includeFontPadding="false"
app:layout_constraintBottom_toBottomOf="@id/message_group"
app:layout_constraintStart_toEndOf="@id/message_group"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="8dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
타이핑 인디케이터 아이템 레이아웃 (3.4)
- AI 메시지 레이아웃과 동일한 좌측 정렬 구조
- 메시지 영역 대신 3개 점(•••) 애니메이션 표시
- 애니메이션: @anim/typing_dots_animation (순차 반짝임은 추후 ViewHolder에서 startOffset 제어 가능)
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="6dp"
android:paddingBottom="6dp">
<!-- 프로필 이미지 (AI) -->
<ImageView
android:id="@+id/iv_profile"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="8dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_placeholder_profile"
android:contentDescription="AI 프로필 이미지"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<!-- 이름 + 타이핑 버블 그룹 -->
<LinearLayout
android:id="@+id/message_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toEndOf="@id/iv_profile"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="56dp">
<!-- 보낸이 이름 (필요 시 표시) -->
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="12sp"
android:includeFontPadding="false"
android:layout_marginBottom="2dp"
android:maxLines="1"
android:ellipsize="end" />
<!-- 타이핑 표시 컨테이너 (AI 버블 배경 재사용 가능) -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/typing_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_chat_ai_message"
android:paddingStart="14dp"
android:paddingEnd="14dp"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<!-- 3개 점 애니메이션 -->
<LinearLayout
android:id="@+id/ll_typing_dots"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/dot1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="\u2022"
android:textSize="18sp"
android:textColor="@android:color/white"
android:includeFontPadding="false"
android:animation="@anim/typing_dots_animation" />
<TextView
android:id="@+id/dot2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="\u2022"
android:textSize="18sp"
android:textColor="@android:color/white"
android:includeFontPadding="false"
android:animation="@anim/typing_dots_animation" />
<TextView
android:id="@+id/dot3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="\u2022"
android:textSize="18sp"
android:textColor="@android:color/white"
android:includeFontPadding="false"
android:animation="@anim/typing_dots_animation" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
사용자 메시지 아이템 레이아웃
- 오른쪽 정렬된 메시지 버블
- 버블의 왼쪽에 시간 표시
- 배경: @drawable/bg_chat_user_message
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="6dp"
android:paddingBottom="6dp">
<!-- 전송 시간: 버블의 왼쪽에 표시 -->
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/color_777777"
android:textSize="12sp"
android:includeFontPadding="false"
app:layout_constraintBottom_toBottomOf="@id/message_container"
app:layout_constraintEnd_toStartOf="@id/message_container"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="8dp"/>
<!-- 메시지 버블 컨테이너: 오른쪽 정렬 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/message_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_chat_user_message"
android:paddingStart="14dp"
android:paddingEnd="14dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_time">
<TextView
android:id="@+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="15sp"
android:lineSpacingExtra="2dp"
android:includeFontPadding="false"
android:hyphenationFrequency="normal"
android:breakStrategy="balanced"
android:ellipsize="end"
android:maxLines="1000"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>