RecyclerView란 무엇일까요?
RecyclerView와 ListView는 어떤 차이점이 있을까요?
RecyclerView Adapter는 어떤 구성 요소를 가지고 있을까요?
맨처음 생성된, 화면에 맞는 뷰객체를 홀딩(기억)하고있을 객체.
보통 inner class 로 선언
bind 함수를 따로 선언하는게 편리하다
inner class ViewHolder(private val binding : RecyclerPostBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item : PostData){
binding.ivProfile.setImageResource(item.profileImg)
binding.tvLikeCount.text = item.likeCount
binding.tvCommentCount.text = item.commentCount
...
}
}
가장 먼저 실행되는 함수
View 에 뿌려줄 데이터의 전체길이 리턴하면 됨
override fun getItemCount(): Int {
return datas.size
}
그다음으로 실행되는 함수
ViewHolder를 생성하는 함수.
View객체 만큼 (처음화면에 보이는 개수 + 2개 정도) 만 호출되고 더이상 호출되지 않음
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = RecyclerPostBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return ViewHolder(binding)
}
생성된 ViewHolder 에 data를 바인딩 해주는 함수.
데이터가 스크롤 되어서, 맨위 ViewHolder 객체가 맨 아래로 이동한다면, 해당 Layout을 재사용하며 데이터만 바뀐다. 이때마다 onBindViewHolder 가 호출된다
보여질 데이터의 index 값을 position 값으로 알수 있다.
새롭게 올라오는 데이터가 list의 20번째 데이터라면, position으로 20이 들어옴
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(datas[position])
}
RecyclerView Adapter 실행순서 디버깅 해보기
→ 처음 adapter 메소드의 실행순서
→ 아래 스크롤시 adapter 메소드의 실행순서
position 10부터는 onCreateViewHolder가 호출되지 않는다!!
이는, 화면에서 사라진 position 1, 2 의 View가 재활용 되었다는 뜻.
따라서, ViewHolder는, 화면에 나오는 View + 여분의 View 만 생성시킨채, 더이상 View를 생성하지않고 재활용한다!!
→ 다시 윗스크롤시 adapter 메소드의 실행순서
마찬가지로 더이상 View객체를 생성하지 않는것을 볼 수 있다
RecyclerView를 설정할 때 주의해야 하는 점은 무엇이 있을까요?
→ item 레이아웃과 recyclerview 자체가 들어갈 layout 두가지 필요
<?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=".view.SelectActivity">
...
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/coinListRV"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="120dp"
android:layout_marginBottom="80dp"
app:layout_constraintBottom_toTopOf="@+id/laterTextArea"
app:layout_constraintTop_toBottomOf="@+id/topTextArea" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
<?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="80dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp">
...
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
Adapter 의 매개변수 : Context, Data 두가지
ViewHolder :
onCreateViewHolder :
onBindViewHolder :
getItemCount :
→ view 사용
class SelectRVAdapter(val context : Context, val coinPriceList : List<CurrentPriceResult>)
:RecyclerView.Adapter<SelectRVAdapter.ViewHolder>(){
inner class ViewHolder(view: View)
: RecyclerView.ViewHolder(view){
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
: ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.intro_coin_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return coinPriceList.size
}
}
→ viewBinding 사용
class SelectRVAdapter(val context : Context, val coinPriceList : List<CurrentPriceResult>)
:RecyclerView.Adapter<SelectRVAdapter.ViewHolder>(){
inner class ViewHolder(private val viewBinding: IntroCoinItemBinding)
: RecyclerView.ViewHolder(viewBinding.root){
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
: ViewHolder {
val viewBinding = IntroCoinItemBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(viewBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return coinPriceList.size
}
}
→ onBindViewHolder 에서 삽입(공식홈페이지 내용)
class SelectRVAdapter(val context : Context, val coinPriceList : List<CurrentPriceResult>)
:RecyclerView.Adapter<SelectRVAdapter.ViewHolder>(){
val likedCoinList = ArrayList<String>()
inner class ViewHolder(private val viewBinding: IntroCoinItemBinding)
: RecyclerView.ViewHolder(viewBinding.root){
// 레이아웃의 view 를 viewBinding 으로 연결
val coinName : TextView = viewBinding.coinName
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val viewBinding = IntroCoinItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(viewBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// 데이터 (coinPriceList) 를 view에 삽입
holder.coinName.text = coinPriceList[position].coinName
}
override fun getItemCount(): Int {
return coinPriceList.size
}
}
→ ViewHolder 에서 삽입 (bind 함수 사용. 더 편리함)
class SelectRVAdapter(val context : Context, val coinPriceList : List<CurrentPriceResult>)
:RecyclerView.Adapter<SelectRVAdapter.ViewHolder>(){
val likedCoinList = ArrayList<String>()
inner class ViewHolder(private val viewBinding: IntroCoinItemBinding)
: RecyclerView.ViewHolder(viewBinding.root){
// 레이아웃의 view 를 viewBinding 으로 연결
val coinName : TextView = viewBinding.coinName
// 데이터 (coinPriceList) 를 view에 삽입
fun bind(item : CurrentPriceResult){
coinName.text = item.coinName
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val viewBinding = IntroCoinItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(viewBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(coinPriceList[position])
}
override fun getItemCount(): Int {
return coinPriceList.size
}
}
val adapter = SelectRVAdapter(context, 데이터)
binding.coinListRV.adapter = adapter
binding.coinListRV.layoutManager = LinearLayoutManager(context)
RecyclerView 의 Adapter는, 전체 아이템 개수에 기반하여,
화면표시 아이템개수 + 여분 아이템 개수 의 ViewHolder를 미리 생성하고,
화면에 표시되지 않는 View를 재사용 하는 구조이다.
따라서, 재사용되는 View에 체크표시가 되어있다면, 그것이 초기화가 안된상태로 새롭게 추가되는 것이다! 따라서, 새롭게 추가되는 View에 대해, 조건을 걸어줘야 해결이 가능하다.
별도의 checked list 리스트를 만든 뒤, item을 화면에 띄울때,
checked list에 들어있는 item인지 판별하여 띄운다!!
class MainAdapter(val context : Context, private val datas : List<Int>)
: RecyclerView.Adapter<MainAdapter.ViewHolder>(){
// check 표시여부 저장할 데이터사이즈와 동일한 크기의 배열 생성.
var checkarr : Array<Boolean> = Array(datas.size){false}
inner class ViewHolder(private val viewBinding: RecyclerItemBinding)
: RecyclerView.ViewHolder(viewBinding.root){
fun bind(item : Int){
// check박스가 표시되야하는지 검사
viewBinding.check.isChecked = checkarr[position]
viewBinding.iv.setImageResource(item)
// check 박스 누르면, 해당 상태 arr 에 저장
viewBinding.check.setOnClickListener {
checkarr[position] = viewBinding.check.isChecked
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
: ViewHolder {
val viewBinding = RecyclerItemBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(viewBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(datas[position])
}
override fun getItemCount(): Int {
return datas.size
}
}
→ Step1. Adapter 에 itemClick interface를 생성한다.
class CoinListRVAdapter(val context : Context, val dataSet : List<InterestCoinEntity>)
: RecyclerView.Adapter<CoinListRVAdapter.ViewHolder>() {
interface ItemClick{
fun onClick(view : View, position: Int)
}
var itemClick : ItemClick? = null
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.findViewById<ImageView>(R.id.likeBtn).setOnClickListener{ v->
itemClick?.onClick(v,position)
}
...
}
...
}
→ Step2. Activity 나 Fragment 에서 onClick 함수 구현하기
selectedRVAdapter.itemClick = object : CoinListRVAdapter.ItemClick{
override fun onClick(view: View, position: Int) {
viewModel.updateInterestCoinData(selectedList[position])
}
}