일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- onMeasure
- Android
- activity
- kotlin
- 코틀린
- hilt
- CoordinatorLayout
- 알고리즘
- onLayout
- Coroutine
- sqlite
- Algorithm
- DataBinding
- BOJ
- lifecycle
- room
- notification
- 백준
- 알림
- CollapsingToolbarLayout
- Behavior
- LiveData
- 안드로이드
- AppBarLayout
- Navigation
- View
- recyclerview
- CustomView
- ViewModel
- HTTP
- Today
- Total
개발일지
Android in A..Z - RecyclerView (기본) 본문
RecyclerView
제한된 화면에 여러가지 데이터를 리스트 형식으로 표현하는 뷰이다.
ListView보다 성능이 좋으며 다양한 LayoutManager를 제공하므로 다양한 형식의 리스트로 데이터를 표현할 수 있다.
Dependency
dependencies {
implementation "androidx.recyclerview:recyclerview:1.1.0"
// For control over item selection of both touch and mouse driven selection
implementation "androidx.recyclerview:recyclerview-selection:1.1.0-rc03"
}
장점
- DataBinding과 MVVM 패턴을 활용하여 다양한 데이터의 형식을 쉽게 표현할 수 있다.
- 생성된 ViewHolder를 재사용하므로 리소스를 아낄 수 있다.
- ItemDecoration, ItemAnimator 등 다양한 효과를 줄 수 있다.
구성
- Adapter : 데이터 관리, Holder 관리, 데이터와 Holder를 연결해주는 역할을 한다.
- ViewHolder : 데이터가 실제로 표시되는 객체이다.
- LayoutManager : RecyclerView의 Layout을 관리한다.
ViewHolder
실제로 데이터가 표현되는 객체이며 여러개의 ViewHolder를 구현하여 RecyclerView에 다양한 데이터를 여러 종류의 형식으로 표현할 수 있다.
RecyclerView.ViewHolder를 상속받아서 구현한다.
=> 자신의 채팅을 표현하는 MyChatHolder, 상대방의 채팅을 표현하는 OtherChatHolder
BaseHolder
abstract class BaseHolder<VB: ViewDataBinding, E: Any>(protected val binding: VB) : RecyclerView.ViewHolder(binding.root) {
val context: Context
get() { return itemView.context }
lateinit var element: E
open fun bind(element: E) {
this.element = element
}
}
MyChatHolder
class MyChatHolder(binding: HolderMyChatBinding) : BaseHolder<HolderMyChatBinding, Chat>(binding) {
override fun bind(element: Chat) {
super.bind(element)
binding.chat = element
}
}
holder_my_chat.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="chat"
type="com.taetae98.recyclerview.data.Chat" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_margin="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/message"
style="@style/Chat"
android:background="@color/chat_my"
android:text="@{chat.text}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
OtherChatHolder
class OtherChatHolder(binding: HolderOtherChatBinding) : BaseHolder<HolderOtherChatBinding, Chat>(binding) {
override fun bind(element: Chat) {
super.bind(element)
binding.chat = element
}
}
holder_other_chat.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="chat"
type="com.taetae98.recyclerview.data.Chat" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_margin="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/writer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{chat.writer}"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView"
android:layout_width="60dp"
android:layout_height="60dp"
android:contentDescription="@string/profile"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_launcher_foreground" />
<TextView
style="@style/Chat"
android:id="@+id/message"
android:text="@{chat.text}"
android:background="@color/chat_other"
app:layout_constraintTop_toBottomOf="@id/writer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Adapter
ViewHolder관리는 내부적으로 해주기 때문에 데이터에 따른 ViewHolder와 데이터만 관리해주면 된다.
ListAdapter을 상속받아서 구현한다. ListAdapter는 데이터의 타입, ViewHolder의 타입을 설정해야하고, ItemCallback을 생성자 매개변수로 받아야한다.
BaseAdapter
abstract class BaseAdapter<E: Any>(diffCallback: DiffUtil.ItemCallback<E>) : ListAdapter<E, BaseHolder<out ViewDataBinding, E>>(diffCallback) {
override fun onBindViewHolder(holder: BaseHolder<out ViewDataBinding, E>, position: Int) {
holder.bind(getItem(position))
}
}
ChatAdapter
class ChatAdapter : BaseAdapter<Chat>(ChatDiffCallback()) {
init {
setHasStableIds(true)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseHolder<out ViewDataBinding, Chat> {
return when(viewType) {
R.layout.holder_my_chat -> {
MyChatHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.context), viewType, parent, false))
}
R.layout.holder_other_chat -> {
OtherChatHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.context), viewType, parent, false))
}
else -> {
throw IllegalStateException("존재하지 않는 viewType : $viewType")
}
}
}
override fun getItemId(position: Int): Long {
return getItem(position).id
}
override fun getItemViewType(position: Int): Int {
return when(getItem(position).writer) {
"My" -> {
R.layout.holder_my_chat
}
else -> {
R.layout.holder_other_chat
}
}
}
private class MyChatHolder(binding: HolderMyChatBinding) : BaseHolder<HolderMyChatBinding, Chat>(binding) {
override fun bind(element: Chat) {
super.bind(element)
binding.chat = element
}
}
private class OtherChatHolder(binding: HolderOtherChatBinding) : BaseHolder<HolderOtherChatBinding, Chat>(binding) {
override fun bind(element: Chat) {
super.bind(element)
binding.chat = element
}
}
private class ChatDiffCallback : DiffUtil.ItemCallback<Chat>() {
override fun areItemsTheSame(oldItem: Chat, newItem: Chat): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Chat, newItem: Chat): Boolean {
return oldItem.writer == newItem.writer && oldItem.text == newItem.text
}
}
}
- onBindViewHolder : ViewHolder와 데이터가 연결되는 함수이다. ViewHolder에 데이터를 넘겨주고, ViewHolder에서 데이터에 맞게 View를 설정하면 된다. => 이미 생성된 ViewHolder를 재사용할 수도 있기 때문에 전에 데이터로 설정된 View도 다시 설정해야 한다.
- onCreateViewHolder : ViewHolder를 생성하는 부분 ViewHolder를 재활용하지 않고 생성할 때 호출되는 함수. viewType에 맞게 ViewHolder를 생성하면 된다.
- getItemId : 해당 item의 id를 얻을 때 호출되는 함수이다.
- getItemViewType : 해당 item의 ViewType을 얻을 때 호출되는 함수이다. => 다양한 데이터를 다양한 형태의 ViewHolder로 표현할 때 사용한다.
- submitList : 데이터 리스트를 adapter에 알려주는 함수이다.
- notifyItem~ : 데이터의 변화를 알려주는 함수이다.
DiffUtil.ItemCallback
RecyclerView의 성능을 개선시킨 유틸리티로 기존의 데이터 리스트에서 업데이트할 데이터를 찾는다.
=> notifyDataSetChanged같은 데이터 변경 알림 함수를 사용할 때 모든 데이터를 ViewHolder에 업데이트하는 대신 변경된 데이터만 ViewHolder에 업데이트하면서 불필요한 리소스를 아낄 수 있다.
ChatDiffCallback
private class ChatDiffCallback : DiffUtil.ItemCallback<Chat>() {
override fun areItemsTheSame(oldItem: Chat, newItem: Chat): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Chat, newItem: Chat): Boolean {
return oldItem.writer == newItem.writer && oldItem.text == newItem.text
}
}
- areItemsTheSame : 해당 아이템이 같은 아이템인지 비교할 때 호출되는 함수이다.
- areContentsTheSame : 해당 아이템의 내용이 같은지 확인할 때 호출되는 함수이다.
- 처음에 areItemsTheSame 함수로 비교하고, areItemsTheSame에서 true를 반환하면 areContentsTheSame 함수를 호출하여 비교한다.
RecyclerView
ChatFragment
class ChatFragment : BaseFragment<FragmentChatBinding>(R.layout.fragment_chat) {
private val adapter by lazy { ChatAdapter().apply {
submitList(list)
}}
private val list by lazy { ArrayList<Chat>().apply {
add(Chat(0, "Hi", "Other"))
add(Chat(1, "Nice to meet you!!"))
}}
override fun init() {
super.init()
initRecyclerView()
initSendButton()
}
private fun initRecyclerView() {
binding.recyclerView.adapter = adapter
binding.recyclerView.setHasFixedSize(true)
}
private fun initSendButton() {
binding.setSendAction {
list.add(Chat(list.size.toLong(), binding.message.text.toString(), "My"))
adapter.notifyItemInserted(list.lastIndex)
binding.message.text.clear()
}
}
}
fragment_chat.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="sendAction"
type="android.view.View.OnClickListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/chat_background"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<EditText
android:id="@+id/message"
android:layout_width="0dp"
android:minHeight="50dp"
android:maxHeight="300dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:autofillHints=""
android:background="@color/white"
android:hint="@string/message"
android:inputType="textMultiLine" />
<ImageButton
android:id="@+id/button"
android:onClick="@{sendAction}"
android:layout_width="50dp"
android:src="@drawable/ic_send"
android:layout_height="match_parent"
android:background="@color/chat_send_box"
android:contentDescription="@string/send" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Git (예제코드)
github.com/KangTaeJong98/Example/tree/main/Android/RecyclerView
'Android (안드로이드) > RecyclerView' 카테고리의 다른 글
Android in A..Z - RecyclerView (setHasStableIds) (0) | 2021.01.10 |
---|---|
Android in A..Z - RecyclerView (ItemDecoration) (0) | 2021.01.10 |
Android in A..Z - RecyclerView (AdapterDataObserver) (0) | 2021.01.10 |
Android in A..Z - Recyclerview (ItemTouchHelper) (0) | 2021.01.10 |
Android in A..Z - RecyclerView (Layout Manager) (0) | 2021.01.10 |