우리는 안드로이드 앱을 만들 때 어떠한 목록을 구현해야 될 상황이 생긴다.
구현할 데이터 리스트를 받아 화면에 띄워줘야 하는데, 받는 데이터의 개수는 유동적이며, 내용 또한 모두 다르기 때문에 우리가 하나하나 구현해 줄 수는 없다.
이를 해결하기 위해 처음에 나온 것이 ListView이며 ListView의 단점을 보완하여 다시 나온 것이 RecyclerView이다.
RecyclerView와 ListView의 차이

ListView | RecyclerView |
스크롤로 인해 없어진 객체(목록)은 삭제되며, 반대로 추가로 보여지는 객체는 생성된다. |
스크롤로 인해 없어진 객체는 삭제되지 않고 뒤쪽으로 재배치되어 재사용된다. |
생성과 삭제가 반복되기에 비효율적이다. | 객체를 재사용하기 때문에 효율적이다. |
구조
onCreateViewHolder
ViewHolder 클래스를 생성 및 제공해주는 메서드
onBindViewHolder
각 Position 데이터를 ViewHolder에 넘겨주는 메서드
ViewHolder
ViewHolder 객체의 역할은 각 Item Layout에 데이터를 바인딩해주는 역할을 수행한다.
ViewHolder 객체는 특정 개수까지만 생성되고 이후에는 재활용하며 사용된다.
재활용 시점에는 onBindViewHolder와 ViewHolder 내의 bind 함수만 호출된다.
setOnClickListener와 같이 단 한 번만 초기화 되는 이벤트의 경우 위와 같은 이유 때문에 bind()가 아닌 Init에서 초기화 시켜줘야 한다. bind()에서 초기화 시 재활용 될 때마다 매번 초기화 되기 때문이다.
실행 순서 (총 n번 실행)
onCreateViewHolder >> ViewHolder 생성(init) >> onBindViewHolder >> ViewHolder bind method
재활용 시
onBindViewHolder >> ViewHolder bind method 만 실행된다.
RecyclerView.Adapter vs ListAdapter
ListAdapter는 RecyclerView.Adapter를 상속받아 구현한 Adapter이다.
왜 만들어졌을까?
RecyclerView.Adapter에서의 데이터 갱신은 notifyDataChanged()를 통해 이루어진다.
notifyDataSetChanged()를 실행하면 다음과 같은 코드가 실행된다.
public void notifyChanged() {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
모든 데이터를 갱신하는 메서드이기 때문에 매번 notifyDataChanged()를 호출하기엔 좀 부담스러울 수 있다.
(불필요한 ViewHolder까지 갱신되기 때문)
특정 데이터 변경을 알려주기 위해 아래와 같은 메서드들도 있긴 하지만 상황에 따라 매번 다르기 때문에 설정하기가 매우 번거롭다.
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)
이를 해결하기 위해 ListAdapter를 사용한다.
ListAdapter에서는 DiffUtil을 이용하여 데이터 갱신을 처리한다.
protected ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
new AsyncDifferConfig.Builder<>(diffCallback).build());
mDiffer.addListListener(mListener);
}
DiffUtil.ItemCallback
- areItemsTheSame
- id와 같은 유니크한 데이터를 바탕으로 데이터의 변경을 확인한다.
- areContentsTheSame
- item의 데이터가 모두 동일한지 확인한다.
사용법
시작하기에 앞서 기존의 activity와 layout 외에도 2가지를 생성해야 한다.
1. Adapter
2. Item Layout
RecyclerView는 View인 목록 하나하나가 모여있는 ViewGroup이다.
때문에 우리는 목록, 즉 View를 어떻게 보여줄지 알려주는 Item Layout을 만들어줘야 한다.
또한 공식문서에서는 다음과 같이 나와있다.
"RecyclerView가 뷰를 요청한 다음, 어댑터에서 메서드를 호출하여 뷰를 뷰의 데이터에 바인딩 한다."
즉, 어댑터를 이용하여 뷰와 데이터를 묶어 하나의 목록으로 나타내준다고 볼 수 있다.
1. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
xml에서 recyclerview를 세팅하실 때
app:layoutManager를 설정 안해주시면 다음과 같은 오류를 마주칠 수 있다.
E/RecyclerView: No layout manager attached; skipping layout
2. recyclerview_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="30dp"
android:textColor="#000000"/>
</LinearLayout>
목록(view)을 꾸며주는 레이아웃으로, 해당 레이아웃은 취향 것 꾸며주시면 됩니다.
3. RecyclerAdapter.kt (class)
RecyclerView.Adapter
class RecyclerviewAdapter :
RecyclerView.Adapter<RecyclerviewAdapter.ViewHolder>() {
var adapterList = ArrayList<String>()
inner class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
init{
// Set Listener
}
fun bind(data: String){
val text : TextView = itemView.findViewById(R.id.textView)
text.text = data
}
}
fun setRecyclerview(list: ArrayList<String>){
adapterList = list
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.recyclerview_item, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(adapterList[position])
}
override fun getItemCount(): Int {
return adapterList.size
}
}
ListAdapter
class ItemDiffCallback<T : Any>(
val onItemsTheSame: (T, T) -> Boolean,
val onContentsTheSame: (T, T) -> Boolean
) : DiffUtil.ItemCallback<T>() {
override fun areItemsTheSame(
oldItem: T,
newItem: T
): Boolean = onItemsTheSame(oldItem, newItem)
override fun areContentsTheSame(
oldItem: T,
newItem: T
): Boolean = onContentsTheSame(oldItem, newItem)
}
data class ListData(
val name: String
)
class RecyclerviewAdapter : ListAdapter<ListData, RecyclerviewAdapter.ViewHolder>(
ItemDiffCallback(
onItemsTheSame = { oldItem, newItem -> oldItem.name == newItem.name },
onContentsTheSame = { oldItem, newItem -> oldItem == newItem }
)
) {
inner class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
init{
//TODO Set Listener
}
fun bind(data: ListData){
val text : TextView = itemView.findViewById(R.id.textView)
text.text = data.name
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.recyclerview_item, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
4. MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 테스트용 데이터
val list : ArrayList<String> = arrayListOf("one","two","three","four","five","Six")
val recyclerview = findViewById<RecyclerView>(R.id.recyclerview)
// recyclerview에 어댑터 세팅 및 데이터 넘겨주기
recyclerview.adapter = recyclerviewAdapter().apply {
setRecyclerview(list) // RecyclerView.Adapter
submitList(list) // ListAdapter
}
}
}
'안드로이드 > XML' 카테고리의 다른 글
SearchView (0) | 2024.07.14 |
---|---|
TimePicker (0) | 2024.07.14 |
[Layout] CoordinatorLayout (0) | 2024.07.13 |