[Android 앱 개발 숙련] 3. RecyclerView

📌참고자료: 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
}

RecyclerView 항목 클릭 이벤트 처리하기

  • 콜백 인터페이스(ItemClick) 정의
 	interface ItemClick {
        fun onClick(view : View, position : Int)
    }
  • RecyclerView의 어댑터 클래스
    • 콜백 인터페이스의 onClick 메서드 호출하기
 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) {
    	//...
    }
}
  • MainActivity
    • 콜백 인터페이스(ItemClick)를 구현하는 object 생성
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()
            }
        }
    }
}
profile
Be able to be vulnerable, in search of truth

0개의 댓글