📌참고자료: RecyclerView | Android Developers
- public class RecyclerView
extends ViewGroup
implements ScrollingView, NestedScrollingChild2, NestedScrollingChild3- build.gradle(:app)
android{ //... buildFeatures { viewBinding true } } dependencies { //... implementation 'com.android.databinding:viewbinding:4.0.1' }
용어 사전
- Position: position of a data item within an Adapter
- Index: index of an attached child view
- Binding: process of preparing a child view to display data
- Recycle (view):
- A view previously used to display data for a specific adapter position
- may be placed in a cache for later reuse to display the same type of data again
- Scrap (view):
- A child view that has entered into a temporarily detached state during layout.
- may be reused without becoming fully detached from the parent RecyclerView
-> unmodified if no rebinding is required
-> modified if the view was considered dirty- Dirty (view): A child view that must be rebound by the adapter before being displayed.
Positions in RecyclerView
- additional level of abstraction between the Adapter and LayoutManager
-> able to detect changes in batches during layout calculation
-> all view bindings happen at the same time- RecyclerView의 2가지 position:
- layout position:
- position of an item in the latest layout calculation
- position from the LayoutManager's perspective
-> what user is currently seeing on the screen- adapter position:
- position of an item in the adapter
- position from the Adapter's perspective
Presenting Dynamic Data
- To display updatable data in a RecyclerView, your adapter needs to signal inserts, moves, and deletions to RecyclerView
-> List diffing with DiffUtil
- ListAdapter: higher-level API that builds in List diffing on a background thread, with minimal code
- AsyncListDiffer: also provides this behavior, but without defining an Adapter to subclass
- DiffUtil: lower-level API you can use to compute the diffs yourself
📌참고자료: DiffUtil | Android Developers
- utility class that calculates the difference between two lists -> outputs a list of update operations that converts the first list into the second one
- 한 리스트를 다른 리스트로 변환하는 연산의 최소 횟수 계산
-> W. Myers's difference algorithm 사용- list 크기가 큰 경우, 연산 시간이 오래 걸릴 수 있음
-> background thread에서 실행 권장
📌참고자료: RecyclerView로 동적 목록 만들기 | Android Developers
- 동적 목록을 만들기 위해 함께 사용되는 클래스:
- RecyclerView: 데이터에 해당하는 뷰가 포함된 ViewGroup
- RecyclerView.Adapter: 어댑터
- RecyclerView.ViewHolder: 목록의 각 요소, 어댑터가 뷰 홀더에 데이터를 바인딩
- RecyclerView.LayoutManager: 목록의 각 요소 정렬
RecyclerView 구현하기
- (1) RecyclerView 모양 정하기
- LayoutManager로 정해짐
- LinearLayoutManager: 1차원 목록으로 항목 정렬
- GridLayoutManager: 2차원 그리드로 항목 정렬
- StaggeredGridLayoutManager: 행의 항목이 동일한 높이(세로 그리드인 경우)이거나 동일한 열의 항목이 동일한 너비(가로 그리드인 경우)일 필요가 없는 그리드로 항목 정렬
- (2) 목록의 각 항목 모양 정하기
- ViewHolder: 각 항목의 레이아웃을 포함하는 View의 Wrapper
- Adapter: ViewHolder 객체 생성 & 데이터 바인딩
- onCreateViewHolder(): ViewHolder 객체 생성
- onBindViewHolder(): ViewHolder 객체에 데이타 바인딩
- getItemCount(): 데이터 셋 크기 반환
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/list_item_height" android:layout_marginLeft="@dimen/margin_medium" android:layout_marginRight="@dimen/margin_medium" android:gravity="center_vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/element_text"/> </FrameLayout>
class CustomAdapter(private val dataSet: Array<String>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() { class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val textView: TextView init { textView = view.findViewById(R.id.textView) } } override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(viewGroup.context) .inflate(R.layout.text_row_item, viewGroup, false) return ViewHolder(view) } override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { viewHolder.textView.text = dataSet[position] } override fun getItemCount() = dataSet.size }
interface ItemClick {
fun onClick(view : View, position : Int)
}
class MyAdapter(val mItems: MutableList<MyItem>) : RecyclerView.Adapter<MyAdapter.Holder>() {
var itemClick : ItemClick? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val binding = ItemRecyclerviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return Holder(binding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.itemView.setOnClickListener {
itemClick?.onClick(it, position)
}
//...
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemCount(): Int {
return mItems.size
}
inner class Holder(val binding: ItemRecyclerviewBinding) : RecyclerView.ViewHolder(binding.root) {
//...
}
}
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val dataList = mutableListOf<MyItem>()
//...
val adapter = MyAdapter(dataList)
binding.recyclerView.adapter = adapter
binding.recyclerView.layoutManager = LinearLayoutManager(this)
adapter.itemClick = object : MyAdapter.ItemClick {
override fun onClick(view: View, position: Int) {
val name: String = dataList[position].aName
Toast.makeText(this@MainActivity," $name 선택!", Toast.LENGTH_SHORT).show()
}
}
}
}