성장, 그 아름다운 향연
article thumbnail

구동 환경

- 최소 sdk : 23
- 현재 sdk : 30
- 언어 : Kotlin

 


 

패스트캠퍼스 강의 중에 중고거래 앱을 만드면서 채팅방을 구현하는 실습이 있었다.

강의에서는 채팅을 보내는 사용자와 상관 없이 우측에만 채팅 기록을 뿌려줬다.

추가적으로 기능을 개선시키기 위해 실제 사용하고 있는 채팅 앱처럼

나는 우측, 상대방은 좌측으로 나타날 수 있게 구현하고 싶었다.

 

첫 번째 방법으로

1. Adapter의 인자로 현재 사용자의 id를 받아온다.

2. 커스텀으로 만든 ViewHolder에서 인자로 받아온 id 와 bind된 id 가 일치할 경우, id만 숨긴다.

3. 일치하지 않을 경우, ConstraintLayout.LayoutParams를 이용해서 일일히 뷰를 조정했다.



이를 구현하고 난 뒤에 초기의 RecyclerView에서는 정상적으로 작동했지만,

채팅을 보내고 나니 상대방의 채팅 기록의 아이디가 자꾸 사라지는 이슈가 발생했다.

 

차라리 2개의 XML 파일로 내가 보여주고 싶은 순간에 적절히 뿌려줄 순 없을까 라는 생각을 가득 안고 구글링의 힘을 빌렸다.

정말 운좋게도 medium 에서 RecyclerView의 ViewHolder를 extend시켜서 구현시킨 자료가 있었다ㅠㅠ

 

하지만 나는 ListAdapter로 구현했기 때문에 가능할지는 의문이었지만 ListAdapter에도 제네릭 타입으로 ViewHolder를 넣기 때문에

충분히 가능할 것이라고 생각됐다!

 

 

아래 사이트가 출처 경로인데 자세한 설명과 다른 이슈를 가지고 있다면 꼭 들어가보자!

 

https://droidbyme.medium.com/android-recyclerview-with-multiple-view-type-multiple-view-holder-af798458763b

 

Android RecyclerView with multiple view type (multiple view holder)

We have explained earlier how to implement Android RecyclerView. But in that article, we explained about a normal RecyclerView means each…

droidbyme.medium.com

 

 

onCreateViewHolder를 들여다보면 인자로 ViewGroup, viewType 이 있는데, 대부분 단일 XML로 RecyclerView를 구현해서 그런지viewType은 왜 존재하는지 생각하지도 않았다.

그렇다면 이것으로 무엇을 할 수 있을까?

 

 

위의 사이트에서는 getItemViewType의 존재를 알려준다.

 

[1] 

 

override fun getItemViewType(position: Int): Int {
        return super.getItemViewType(position)
}

 

 

이는 특정 리스트의 아이템에 type을 설정해주는 것과 같다.

반환형이 Int 이기 때문에 Activity의 RequestCode 를 설정해주는 것과 같이 임의로 적절하게 초기화해준다.

 

 

 

 

[2]

 

companion object {    
        private const val MY_CHAT = 1
        private const val OTHER_CHAT = 2
    }

 

 

다음과 같이 설정했다면 getItemViewType의 쓰임은 다음과 같이 한다.

 

 

 

 

[3]

 

override fun getItemViewType(position: Int): Int {
        return if (auth == currentList[position].senderId)
            MY_CHAT
        else OTHER_CHAT
    }

 

auth는 어댑터에서 인자로 받아온 현재 사용자(나) id이다.

ListAdapter에서 제공하는 currentList을 이용해서

특정 뷰가 지정된 position의 senderId가 auth와 같다면 우측에 그려주겠다! 를 표시하는 것이고,

아니라면 상대방의 것을 좌측에 그려주겠다! 를 표시한다.

 

 

 

 

[4] 다음으로 onCreateViewHolder에 두 개의 ViewHolder를 반환해야 하기 때문에,

MyChatItemViewHolder와 OherChatItemViewHolder를 다음과 같이 작성한다.

 

inner class MyChatItemViewHolder(private val binding: ItemChatBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(chat: ChatItem) {
            val dateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
            val date = Date(chat.time)
            binding.messageTextView.text = chat.message
            binding.timeTextView.text = dateFormat.format(date)
        }
    }

inner class OtherChatItemViewHolder(private val binding: ItemOtherChatBinding) :
       	RecyclerView.ViewHolder(binding.root) {
        fun bind(chat: ChatItem) {
            val dateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
            val date = Date(chat.time)
            binding.senderTextView.text = chat.senderId
            binding.messageTextView.text = chat.message
            binding.timeTextView.text = dateFormat.format(date)
        }
    }

 

binding을 실습하고 있는 단계라 ViewBinding을 적용시켰다. Adapter에서 사용하니 너무나도 깔끔해서 보기 좋다 👍🏻

 

 

 

 

[5]  이제 onCreateViewHolder에서 viewType 을 아까 설정한 상수와 비교해서 그려준다!

 

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if(viewType == MY_CHAT) {
            ChatItemViewHolder(
                ItemChatBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        } else {
            ChatItem2ViewHolder(
                ItemOtherChatBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        }
    }

 

 

 

 

 

[6] 이제 onBindViewHolder로 뿌려주기만 하면 된다.

여기서 잠깐 ListAdapter의 제네릭 타입을 살펴보면,

 

ListAdapter<ChatItem, RecyclerView.ViewHolder>(diffUtil)

 

 

이렇게 설정한 이유는 다수의 ViewHolder들의 반환형이 RecylcerView.ViewHolder이기 때문에 onBindViewHolder에서 형 변환을 통해 사용하도록 하기 위함이다. (특정 뷰홀더를 넣으면 다른 뷰홀더를 사용할 수 없으니까!)

 

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if(getItemViewType(position) == MY_CHAT) {
            (holder as MyChatItemViewHolder).bind(currentList[position])
        } else {
            (holder as OtherChatItemViewHolder).bind(currentList[position])
        }
    }

 

위와 같이 형 변환으로 간단히 해결할 수 있었다!

 

 

 

이번 실습을 혼자 응용해보면서 RecyclerView를 더 유용하고 확장성 있게 구현하는 방법을 알게 된 것 같다.

역시 스스로 개선 사항을 끊임 없이 고민해보고 더 좋은 방향이 있다면 혼자 힘으로 여러 방법을 시도해보고, 만약 안된다면 다른 방법들을 찾아보며 성장하는 것이 큰 도움이 되겠다는 생각이 들었다.

 


전체 코드

 

class ChatItemAdapter(
    private val auth: String
) : ListAdapter<ChatItem, RecyclerView.ViewHolder>(diffUtil) {
    inner class MyChatItemViewHolder(private val binding: ItemChatBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(chat: ChatItem) {
            val dateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
            val date = Date(chat.time)
            binding.messageTextView.text = chat.message
            binding.timeTextView.text = dateFormat.format(date)
        }
    }

    inner class OtherChatItemViewHolder(private val binding: ItemOtherChatBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(chat: ChatItem) {
            val dateFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
            val date = Date(chat.time)
            binding.senderTextView.text = chat.senderId
            binding.messageTextView.text = chat.message
            binding.timeTextView.text = dateFormat.format(date)
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if(viewType == MY_CHAT) {
            MyChatItemViewHolder(
                ItemChatBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        } else {
            OtherChatItemViewHolder(
                ItemOtherChatBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        }
    }
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if(getItemViewType(position) == MY_CHAT) {
            (holder as MyChatItemViewHolder).bind(currentList[position])
        } else {
            (holder as OtherChatItemViewHolder).bind(currentList[position])
        }
    }

    override fun getItemViewType(position: Int): Int {
        return if (auth == currentList[position].senderId)
            MY_CHAT
        else OTHER_CHAT
    }

    companion object {
        val diffUtil = object : DiffUtil.ItemCallback<ChatItem>() {
            override fun areItemsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean {
                return oldItem == newItem
            }

            override fun areContentsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean {
                return oldItem == newItem
            }
        }
        private const val MY_CHAT = 1
        private const val OTHER_CHAT = 2
    }
}

 

 

실행 화면

 

 

profile

성장, 그 아름다운 향연

@dev_minoflower

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...