개발
안드로이드 코틀린 리사이클러뷰 뷰홀더 사용하기
kks950115
2024. 1. 22. 21:31
728x90
간단한 채팅창 화면을 구현하기 위해 뷰홀더를 사용했다.
다른 블로그들을 찾아보면 잘 설명해준 것들이 많다. 이 글은 그냥 다양한 예시 중 1 이라고 생각하면 된다.
당근마켓을 참고해서 만들었다. 클론코딩이라고 봐도 무방하다.
우선 xml부터...
왼쪽에 표시되는 상대방
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="50dp"
android:layout_height="50dp"
app:cardCornerRadius="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/iv_tv_chatbox_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/background" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/tv_chatbox_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:background="@drawable/black_roundsquare"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="내용"
android:textColor="@color/white"
android:textSize="18sp"
app:layout_constraintStart_toEndOf="@+id/cardView"
app:layout_constraintTop_toTopOf="@+id/cardView" />
<TextView
android:id="@+id/tv_chatbox_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="오휴 12:12"
android:textColor="#444444"
app:layout_constraintBottom_toBottomOf="@+id/tv_chatbox_content"
app:layout_constraintStart_toEndOf="@+id/tv_chatbox_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
오른쪽에 표시되는 나
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/tv_chatbox_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:background="@drawable/box_round_orange"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="내용"
android:textColor="@color/white"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_chatbox_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:text="오휴 12:12"
android:textColor="#444444"
app:layout_constraintBottom_toBottomOf="@+id/tv_chatbox_content"
app:layout_constraintEnd_toStartOf="@+id/tv_chatbox_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
채팅이 표시되는 액티비티
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ChatDetailActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_topMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#222222"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/iv_chatDetail_back"
android:layout_width="20dp"
android:layout_height="40dp"
android:layout_marginStart="5dp"
android:src="@drawable/left_arrow"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/white" />
<TextView
android:id="@+id/tv_chatDetail_name"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="15dp"
android:text="하사끼"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@+id/iv_chatDetail_back"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_chatDetail_temper"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:background="@drawable/hightemper_roundsquare"
android:paddingLeft="5dp"
android:text="85.3℃"
android:textColor="@color/orange"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@+id/tv_chatDetail_name"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="45dp"
android:text="보통 10분 이내 응답"
android:textColor="#aaaaaa"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_chatDetail_name" />
<ImageView
android:id="@+id/iv_telephone"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/telephone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_menuDOts"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/white" />
<ImageView
android:id="@+id/iv_menuDOts"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/dots"
app:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#222222"
android:padding="15dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/layout_topMenu">
<ImageView
android:id="@+id/iv_chatDetail_pic"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_tradeStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="거래 완료"
android:textColor="#FFFFFF"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@+id/iv_chatDetail_pic"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_chatDetailPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="250,000원"
android:textColor="#FFFFFF"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/iv_chatDetail_pic"
app:layout_constraintTop_toBottomOf="@+id/tv_tradeStatus" />
<TextView
android:id="@+id/tv_chatDetail_productName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="자이언트 scr 로드자전거"
android:textColor="#FFFFFF"
app:layout_constraintStart_toEndOf="@+id/tv_tradeStatus"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_chatDetail_chatiing"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toTopOf="@+id/constraintLayout5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/constraintLayout4" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout5"
android:background="@color/baackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:id="@+id/imageView5"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/plus"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_chatDetail_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:background="@drawable/black_roundsquare"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:hint="메시지 보내기"
android:textColorHint="#888888"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_sendMsg"
app:layout_constraintStart_toEndOf="@+id/imageView5"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:src="@drawable/smile"
app:tint="#777777"
app:layout_constraintEnd_toStartOf="@+id/iv_sendMsg"
app:layout_constraintTop_toTopOf="@+id/iv_sendMsg" />
<ImageView
android:id="@+id/iv_sendMsg"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/send"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="#777777" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
클래스
@Parcelize
data class ChatDetail(
val personName : String,
var temper : String,
var tradeStatus : String,
val productName : String,
var productPrice : String,
var productPic : Int
) : Parcelable
@Parcelize
data class ChatLog(
var content : String,
val time : String,
val viewType : Int, //뷰타입을 정해주는 중요한 변수이다.
val img : Int
) : Parcelable
어댑터
class ChatLogAdapter(var chatItems:MutableList<ChatLog>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
//왼쪽 채팅쵸시
inner class Holder(val binding: RvLeftChatBoxBinding) : RecyclerView.ViewHolder(binding.root){
private val img = binding.ivTvChatboxImg
private val content = binding.tvChatboxContent
private val time = binding.tvChatboxTime
fun bind(item:ChatLog){
img.setImageResource(item.img)
content.text = item.content
time.text = item.time
}
}
//오른쪽 채팅표시
inner class Holder2(val binding: RvRightChatBoxBinding) : RecyclerView.ViewHolder(binding.root){
private val content2 = binding.tvChatboxContent
private val time2 = binding.tvChatboxTime
fun bind(item:ChatLog){
content2.text = item.content
time2.text = item.time
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
//Log.d("test","뷰타입은 ${viewType}")
if(viewType == Constants.LEFT){ //뷰타입이 1이면 왼쪽xml로 만든다
return Holder(
RvLeftChatBoxBinding.inflate(LayoutInflater.from(parent.context),
parent,
false
)
)
}else{
return Holder2( //뷰타입이 2이면 오른쪽xml로 만든다
RvRightChatBoxBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
}
override fun getItemCount(): Int {
return chatItems.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (chatItems[position].viewType){
Constants.LEFT -> {
(holder as Holder).bind(chatItems[position])
}
Constants.RIGHT -> {
(holder as Holder2).bind(chatItems[position])
}
}
}
//뷰타입을 판별해주는 중요한 메소드다.
override fun getItemViewType(position: Int): Int {
return chatItems[position].viewType
}
}
액티비티
class ChatDetailActivity : AppCompatActivity() {
private lateinit var binding : ActivityChatDetailBinding
private var chatDetail : ChatDetail? = null
private var chatLog : MutableList<ChatLog>? = null
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityChatDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
val bundle = intent.getBundleExtra("data")
chatDetail = bundle?.getParcelable("chatDetail",ChatDetail::class.java)
chatLog = bundle?.getParcelableArrayList("chatLog",ChatLog::class.java)
binding.ivChatDetailBack.setOnClickListener {
onBackPressed()
}
binding.tvChatDetailName.text = chatDetail?.personName
binding.tvChatDetailTemper.text = chatDetail?.temper
binding.tvTradeStatus.text = chatDetail?.tradeStatus
binding.tvChatDetailProductName.text = chatDetail?.productName
binding.tvChatDetailPrice.text = chatDetail?.productPrice
binding.ivChatDetailPic.setImageResource(chatDetail!!.productPic)
Log.d("test","채팅내역은 ${chatLog}")
//어댑터를 생성하고 데이터를 넣는다.
val chatLogAdapter = ChatLogAdapter (chatLog!!)
binding.rvChatDetailChatiing.adapter = chatLogAdapter
binding.rvChatDetailChatiing.layoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)
}
}
그렇게 완성된 화면
만들면서 RvLeftChatBoxBinding.xml의 높이를 match_parent로 하는 바람에 채팅을 볼려면 화면을 넘겨야하는 의도치 않은 불상사가 있었다.
xml이 잘못된 줄도 모르고 코드 상에서만 이유를 찾느라 내 시간이 살살 녹았다....
꼭 다 해놓고 사소한 실수때문에 몇시간씩 날린다....
뷰가 의도대로 잘 구현되있는지 꼭 꼭 꼭 확인하자.
728x90
반응형