ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Android] RecyclerView ViewHolder ํŒจํ„ด, DiffUtil ํด๋ž˜์Šค
    Android 2022. 3. 22. 02:52
    ๋ฐ˜์‘ํ˜•

    RecyclerView

      ์˜ˆ์ „ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๋ฆฌ์ŠคํŠธ ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๋“ค์„ ํ‘œํ˜„ํ•ด์ฃผ๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค๋Š” ListView ์˜€์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฐ์ดํ„ฐ์˜ ํฌ๊ธฐ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ๋ทฐ๋ฅผ ๊ณ„์† ์ƒ์„ฑํ•จ์œผ๋กœ์จ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๊ณ  getView ๋ฉ”์†Œ๋“œ์—์„œ ๋ฌด๋ถ„๋ณ„ํ•œ findViewById๋ฅผ ์‚ฌ์šฉํ•˜์˜€๊ธฐ์— ๋น„ํšจ์œจ์ ์ด์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๊ฐœ์„ ํ•œ ์œ„์ ฏ์ด RecyclerView๋กœ ํ™”๋ฉด์ด ๋ณด์—ฌ์ง€๋Š” ๋ทฐ๊นŒ์ง€๋งŒ ์ƒ์„ฑํ•œ ํ›„ ์Šคํฌ๋กค ์‹œ์— ๊ฐ€๋ ค์ง€๊ฒŒ ๋˜๋Š” ๋ทฐ๋“ค์€ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, findViewById์˜ ๊ฐ’ ๋น„์‹ผ ๋น„์šฉ์„ ๋ฐฉ์ง€ํ•˜๊ณ ์ž ViewHolder ํŒจํ„ด์„ ๊ฐ•์ œํ™” ์‹œํ‚จ ListView๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

     

     

     

    ViewHolder ํŒจํ„ด์ด๋ž€?

      ViewHolder ํŒจํ„ด์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ViewHolder ํŒจํ„ด์ด ์™œ ํ•„์š”ํ•œ์ง€๋ถ€ํ„ฐ ์ดํ•ดํ•˜๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค. 

    class ListAdapter(private val items : List<String>): BaseAdapter() {
        override fun getCount(): Int = items.size
    
        override fun getItem(position: Int): String = items.get(position)
    
        override fun getItemId(position: Int): Long = position.toLong()
    
        override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
            var view = convertView
            if(view == null){
                view = LayoutInflater.from(parent?.context).inflate(R.layout.item_list, parent, false)
            }
    
            view?.findViewById<TextView>(R.id.textView).text = items.get(position)
            return view!!
        }
    }

     ์œ„์˜ ์ฝ”๋“œ๋Š” ListView์˜ ์–ด๋Œ‘ํ„ฐ์ธ ListAdapter ํด๋ž˜์Šค๋กœ getView๋ฉ”์†Œ๋“œ๋Š” position์— ํ•ด๋‹นํ•˜๋Š” View๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค. getView ๋ฉ”์†Œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ convertView๊ฐ€ ์กด์žฌํ•˜๋Š”๋ฐ convertView๋Š” ์žฌ์‚ฌ์šฉ๋˜๋Š” ๋ทฐ๋กœ์„œ ListView๊ฐ€ ์Šคํฌ๋กคํ•˜์—ฌ ์ด์ „์— ์ƒ์„ฑํ–ˆ๋˜ ๋ทฐ๊ฐ€ ๊ฐ€๋ ค์กŒ์„ ๋•Œ ํ•ด๋‹น ๋ทฐ๋กœ ์žฌ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๋ณด์—ฌ์ฃผ๊ณ ์ž ํ•˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ ๊ฐฏ์ˆ˜๋งŒํผ ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋‚ญ๋น„ํ•˜๊ฒŒ ๋˜๋ฏ€๋กœ ํ™”๋ฉด์— ๋ณด์—ฌ์ง€๋Š” ๋ทฐ๋งŒํผ๋งŒ ์ƒ์„ฑํ•˜๊ณ  ์žฌ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

     

     ๋ทฐ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ๋‚ญ๋น„๋Š” ๋ฐฉ์ง€ํ•˜์˜€์ง€๋งŒ getView ๋ฉ”์†Œ๋“œ๋งˆ๋‹ค findViewById๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•œ ๋ทฐ๋ฉด ํฌ๊ฒŒ ๋ฌธ์ œ๋˜์ง€ ์•Š์ง€๋งŒ ๋ณต์žกํ•œ ๋ทฐ์ผ์ˆ˜๋ก findViewById์˜ ํ˜ธ์ถœ๋„ ๋งŽ์•„์ง€๊ณ  findViewById์˜ ๋น„์šฉ๋„ ์ปค์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. findViewById์˜ ์›๋ฆฌ๊ฐ€ ๊ถ๊ธˆํ•˜์‹œ๋ฉด ์•„๋ž˜ ๋งํฌ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

     

    https://math-coding.tistory.com/251

     

    [Android] findViewById ์›๋ฆฌ

    findViewById ๋ž€? ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์—์„œ findViewById ๋ฉ”์†Œ๋“œ๋Š” ๋ ˆ์ด์•„์›ƒ์— ์žˆ๋Š” ๋ทฐ๋ฅผ ๋ฆฌ์†Œ์Šค id๋ฅผ ํ†ตํ•ด์„œ ์›ํ•˜๋Š” ๋ทฐ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค. ๊ทธ ์ „์— setContentView์™€ ๊ฐ™์€ ๋ฉ”์†Œ๋“œ๋กœ xml์— ์žˆ๋Š”

    math-coding.tistory.com

     

    class CustomAdapter(private val dataSet: Array<String>) :
            RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
    
        /**
         * Provide a reference to the type of views that you are using
         * (custom ViewHolder).
         */
        class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val textView: TextView
    
            init {
                // Define click listener for the ViewHolder's View.
                textView = view.findViewById(R.id.textView)
            }
        }
    
        // Create new views (invoked by the layout manager)
        override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
            // Create a new view, which defines the UI of the list item
            val view = LayoutInflater.from(viewGroup.context)
                    .inflate(R.layout.text_row_item, viewGroup, false)
    
            return ViewHolder(view)
        }
    
        // Replace the contents of a view (invoked by the layout manager)
        override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
    
            // Get element from your dataset at this position and replace the
            // contents of the view with that element
            viewHolder.textView.text = dataSet[position]
        }
    
        // Return the size of your dataset (invoked by the layout manager)
        override fun getItemCount() = dataSet.size
    
    }

     ์œ„์˜ ์ฝ”๋“œ๋Š” Android ๊ณต์‹๋ฌธ์„œ์— ์žˆ๋Š” ์ฝ”๋“œ๋กœ ์ถ”์ƒ ํด๋ž˜์Šค์ธ RecyclerView.Adpater๋ฅผ ์ƒ์†๋ฐ›์€ CustomAdapter ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

    CustomAdapter ๋‚ด์—๋Š” ๋‚ด๋ถ€ ํด๋ž˜์Šค์ธ Viewholder ํด๋ž˜์Šค๊ฐ€ ์กด์žฌํ•˜๋Š”๋ฐ RecyclerView.Adapater๋ฅผ ์ƒ์†๋ฐ›๊ธฐ ์œ„ํ•ด์„œ๋Š” RecyclerView.ViewHolder๊ฐ€ ์ •์˜๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Viewholder ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด Viewholder ํด๋ž˜์Šค๊ฐ€ findViewByid์˜ ๋‹จ์ ์„ ๊ฐœ์„ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๋‹น ํด๋ž˜์Šค์—์„œ ๋ ˆ์ด์•„์›ƒ์— ์กด์žฌํ•˜๋Š” ๋ชจ๋“  ๋ทฐ๋ฅผ ์„ ์–ธํ•˜๊ณ  ํ• ๋‹น๊นŒ์ง€ ํ•œ ์ƒํƒœ๋กœ View๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

     

     ๋จผ์ € RecyclerView์˜ ๋™์ž‘ ์ˆœ์„œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

     

    1. ํ™”๋ฉด์— ๋ณด์—ฌ์งˆ ๋ทฐ๋ณด๋‹ค 1~2๊ฐœ ๋” ๋งŽ๊ฒŒ ๋ทฐํ™€๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.(onCreateViewHolder)
    2. ๊ฐ ๋ทฐํ™€๋”์— position์— ํ•ด๋‹นํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค.(onBindViewHolder)
    3. ์Šคํฌ๋กค ์‹œ์— ๊ฐ€๋ ค์ ธ์„œ ๋ณด์ด์ง€ ์•Š์€ ๋ทฐํ™€๋”์— ์ƒˆ๋กœ์šด position์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค.

     RecyclerView๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์žฌ์‚ฌ์šฉ ๋ทฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š”๋ฐ getView ๋ฉ”์†Œ๋“œ์™€ ์ฐจ์ด์ ์€ findViewById๋ฅผ ํ•˜์ง€ ์•Š์•„๋„ ๋ ˆ์ด์•„์›ƒ ๋‚ด์— ์กด์žฌํ•˜๋Š” ๋ชจ๋“  ๋ทฐ์— ๋Œ€ํ•œ ์ฐธ์กฐ๊ฐ€ ๋˜์–ด ์žˆ์–ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉ๋งŒ ์‹œํ‚ค๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด findViewById๋Š” onCreateViewHolder๊ฐ€ ํ˜ธ์ถœ๋œ ์ˆ˜๋งŒํผ๋งŒ ํ˜ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ ๊ฐœ์„ ์ด ๋˜์—ˆ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜€

     

    ๋ฐ˜์‘ํ˜•

     

     

     

    notifyDataSetChanged

      ์œ„์˜ ๋‚ด์šฉ๋งŒ ๋ณผ ๋•Œ์— ๋ฉ”๋ชจ๋ฆฌ ๋‚ญ๋น„๋„ ๊ฐœ์„ ํ•˜์˜€๊ณ  findViewById๋„ ๊ฐœ์„ ํ•˜์—ฌ์„œ RecyclerView๋Š” ๋ฌธ์ œ์ ์ด ๋ชจ๋‘ ๊ฐœ์„ ์ด ๋˜์—ˆ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ RecyclerView์—์„œ๋„ ๋น„ํšจ์œจ์ ์ธ ๊ตฌ์กฐ๊ฐ€ ์กด์žฌํ•˜๋Š”๋ฐ ๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ๋“ค์„ ์ ์šฉํ•  ๋•Œ ์ž…๋‹ˆ๋‹ค. RecyclerView๋ฅผ ํ•œ ๋ฒˆ์ด๋ผ๋„ ์‚ฌ์šฉํ•˜์‹  ๋ถ„๋“ค์€ 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();
                }
            }

     notifyDataSetChanged() ๋ฉ”์†Œ๋“œ ๋‚ด๋ถ€์—์„œ notifyChanged ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋Š”๋ฐ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋“ค์„ ํ•˜๋‚˜์”ฉ ๋ณ€๊ฒฝ์„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๊ธฐ์กด์— ๋ฐ”์ธ๋”ฉ ํ–ˆ๋˜ ๋ฐ์ดํ„ฐ๋“ค์„ ์ง€์šฐ๊ณ  ๋‹ค์‹œ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ฐ”์ธ๋”ฉํ•˜๊ณ  ๋ Œ๋”๋ง ํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์น˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ 100๊ฐœ์˜ ๋ฐ์ดํ„ฐ ์ค‘ 1๊ฐœ์˜ ๋ฐ์ดํ„ฐ ๊ฐ’๋งŒ ๋ณ€๊ฒฝํ–ˆ์ง€๋งŒ ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜๊ณ  ๋ Œ๋”๋ง ๊ณผ์ •์„ ๊ฑฐ์นœ๋‹ค๋ฉด ๋งค์šฐ ๋น„ํšจ์œจ์ ์ธ ๊ตฌ์กฐ๊ฐ€ ๋˜๊ฒ ์ฃ ? ์ด๋ฅผ ๊ฐœ์„ ์‹œ์ผœ์ค„ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด DiffUtil์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

     

     

     

    DiffUtil

      ์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” DiffUtil ํด๋ž˜์Šค๋Š” Eugene W.Myers's difference algorithm์„ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. 

     

    DiffUtil.Callback

    ์œ„์˜ ์ฝ”๋“œ๋Š” DiffUtil.Callback ์ถ”์ƒํด๋ž˜์Šค๋กœ 4๊ฐœ์˜ ์ถ”์ƒ๋ฉ”์†Œ๋“œ์™€ 1๊ฐœ์˜ ๋ฉ”์†Œ๋“œ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. 

     

    • getOldListSize : ๊ธฐ์กด์— ์žˆ๋˜ ๋ฆฌ์ŠคํŠธ์˜ ํฌ๊ธฐ ๋ฐ˜ํ™˜
    • getNetListSize : ๋ณ€๊ฒฝ๋  ๋ฆฌ์ŠคํŠธ์˜ ํฌ๊ธฐ ๋ฐ˜ํ™˜
    • areItemsTheSame : ๋‘ ๊ฐœ์˜ ๊ฐ์ฒด๊ฐ€  ๊ฐ™์€์ง€ ํ™•์ธ, ์ฃผ์†Œ๊ฐ’์œผ๋กœ ๋น„๊ตํ•˜๊ฑฐ๋‚˜ ๊ณ ์œ ์˜ ๊ฐ’(ID)์„ ํ†ตํ•ด ๋น„๊ต
    • areContentsTheSame : ๋‘ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ™์€์ง€ ํ™•์ธ
    • getChangePayLoad : ๋ณ€๊ฒฝ ๋‚ด์šฉ์— ๋Œ€ํ•œ ํŽ˜์ด๋กœ๋“œ๋ฅผ ๊ฐ€์ ธ์˜ด

     

     ์œ„ ๋ฉ”์†Œ๋“œ์—์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ areItmesTheSame๊ณผ areContentsTheSame  ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌ๋ณ„ํ•  ์ค„ ์•Œ์•„์•ผ ํ•˜๋Š”๋ฐ areItemsTheSame์€ ๊ฐ์ฒด๊ฐ€ ๊ฐ™์€์ง€ ํŒ๋‹จํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์†Œ๊ฐ’์œผ๋กœ ๋น„๊ตํ•˜๊ฑฐ๋‚˜ ๊ฐ์ฒด ๋‚ด ๊ณ ์œ ์˜ ๊ฐ’์œผ๋กœ ๋น„๊ต๋ฅผ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  areItemsTheSame ๋ฉ”์†Œ๋“œ๊ฐ€ true๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ areContentTheSame ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋‹น์—ฐํ•˜๋“ฏ์ด ๊ฐ์ฒด๊ฐ€ ๋‹ค๋ฅด๋ฉด ๊ฐ์ฒด ๋‚ด ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•  ํ•„์š”๋„ ์—†๊ณ  ๊ฐ์ฒด๋Š” ๊ฐ™๋”๋ผ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฅธ ๊ฐ’์œผ๋กœ ์„ธํŒ…ํ•  ์ˆ˜ ์žˆ์–ด์„œ areContentTheSame ๋ฉ”์†Œ๋“œ๋กœ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ๋ก ์ ์œผ๋กœ areItemsTheSame๊ณผ areContentsTheSame ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ชจ๋‘ true์ด๋ฉด ๊ฐ™์€ ์•„์ดํ…œ์œผ๋กœ ํŒ๋‹จํ•˜์—ฌ ์—…๋ฐ์ดํŠธ ํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

    class DiffUtilCallback(
        private val oldList: List<Book>,
        private val newList: List<Book>
    ): DiffUtil.Callback() {
        override fun getOldListSize(): Int = oldList.size
    
        override fun getNewListSize(): Int = newList.size
    
        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldList.get(oldItemPosition).id == newList.get(newItemPosition).id
        }
    
        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            oldList.get(oldItemPosition) == newList.get(newItemPosition)
        }
    }

     

     

     ์œ„์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ DiffUtilCallback ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•˜๊ณ  Adapater ๋‚ด๋ถ€์— changeList๋ผ๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ƒˆ๋กœ์šด ๋ฆฌ์ŠคํŠธ ์—…๋ฐ์ดํŠธ ์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋จผ์ € DiffUtilCallback์œผ๋กœ ์ด์ „ ๋ฆฌ์ŠคํŠธ์™€ ์ƒˆ๋กœ์šด ๋ฆฌ์ŠคํŠธ์˜ Diff๋ฅผ ๊ณ„์‚ฐํ•œ ํ›„ dispatchUpdatesTo ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด Adapter์— ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„์„ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

        fun changeList(books: List<Book>){
            val diffUtilCallback = DiffUtilCallback(this.books,books)
            val diffResult = DiffUtil.calculateDiff(diffUtilCallback)
            
            this.books.apply { 
                clear()
                addAll(books)
                diffResult.dispatchUpdatesTo(this@BookListAdapter)
            }
        }

     

     

     

    AsyncListDiffer

     DiffUtil.Callback ์ถ”์ƒํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด์„œ RecyclerView Adpater์—์„œ ์‚ฌ์šฉํ•ด๋„ ์ข‹์ง€๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์งˆ ์ˆ˜๋ก Eugene W.Myers's difference algorithm์˜ ์‹œ๊ฐ„๋ณต์žก๋„๊ฐ€ ์ปค์ง€๊ธฐ์— ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. AsyncListDiffer๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ๋ฆฌ์ŠคํŠธ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๊ณ„์‚ฐํ•˜๊ณ  ์—…๋ฐ์ดํŠธ๊นŒ์ง€ ์ง„ํ–‰์„ ์‹œ์ผœ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

     

     ๋จผ์ € DiffUtil.ItemCallback ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    DiffUtil.Callback๊ณผ ๋‹ค๋ฅด๊ฒŒ getOldListSize์™€ getNewListSize ๋ฉ”์†Œ๋“œ๋Š” ๋ฉ”์†Œ๋“œ๋กœ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— areItemsTheSame๊ณผ areContentsTheSame๋งŒ ์˜ค๋ฒ„๋ผ์ด๋”ฉ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

    class DiffUtilCallback(): DiffUtil.ItemCallback<Book>(){
    
        override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
            return oldItem.id == newItem.id
        }
    
        override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
            return oldItem == newItem
        }
    }

     

     ๊ทธ๋ฆฌ๊ณ  Adapter์— AsyncListDiffer ๊ฐ์ฒด๋ฅผ ์„ ์–ธํ•œ ๋’ค changeList ๋‚ด๋ถ€์— submitList๋ฅผ ํ˜ธ์ถœ๋งŒ ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

     

     

     

     ์—ฌ๊ธฐ์„œ๋งŒ ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  AsyncListDiffer ์˜ submitList๋ฅผ ๋ถ„์„ํ•ด๋ด…์‹œ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋Š” AsnycListDiffer ํด๋ž˜์Šค์˜ submitList ๋ฉ”์†Œ๋“œ์˜ ์ผ๋ถ€๋กœ newList์™€ mList(์ด์ „ ๋ฆฌ์ŠคํŠธ)์™€ ๋น„๊ต๋ฅผ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ์ ์ด ์žˆ๋Š”๋ฐ newList์™€ mList์˜ ์ฐธ์กฐ๊ฐ€ ๊ฐ™์œผ๋ฉด ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ฒดํฌํ•˜์ง€๋„ ์•Š๊ณ  ์ข…๋ฃŒ์‹œํ‚ต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋Œ€์ž…ํ•  ๋•Œ ์„œ๋ฒ„๋‚˜ DB๋กœ๋ถ€ํ„ฐ ์ƒˆ๋กœ์šด ๋ฆฌ์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์„ธํŒ…ํ•œ ํ›„ submitList๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด ๋ฌธ์ œ ์—†์ง€๋งŒ, ๊ธฐ์กด์— ์žˆ๋˜ ๋ฐ์ดํŠธ๋ฅผ ๊ฐ€์ง€๊ณ  ์ž„์˜๋กœ ์•ฝ๊ฐ„์˜ ์ˆ˜์ •์„ ํ•œ ํ›„ submitList๋ฅผ ํ•˜๋ฉด ๊ฒฐ๊ตญ ๊ฐ™์€ ์ฐธ์กฐ๊ฐ’์„ ๊ฐ€๋ฆฌํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ์—…๋ฐ์ดํŠธ๊ฐ€ ์•ˆ๋˜๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

     

    AsyncListDiffer submitList

      ์ดํ›„ getBackgroundThreadExecutor๋ฅผ ํ†ตํ•ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ์ž‘์—…์„ ์‹คํ–‰ํ•˜๊ณ  DiffUtil.Callback์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. getOldListSize์™€ getNewListSize ๋ฉ”์†Œ๋“œ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋˜๊ณ  areItemsTheSame ๊ณผ areContentsTheSame์€ DiffUitl.ItemCallback์—์„œ ์ •์˜ํ•œ ํด๋ž˜์Šค๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ •๋ฆฌํ•˜์ž๋ฉด DiffUtil.Callback ๋ฉ”์†Œ๋“œ๋ฅผ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๊ณ„์‚ฐํ•œ๋‹ค๊ณ  ๋ณด๋ฉด ๋ฉ๋‹ˆ๋‹ค.

     

     

     

    ListAdapter

      RecyclerView์˜ notifyDataSetChanged ๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ DiffUtil.Callback ์ถ”์ƒํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ฑฐ๋‚˜ AsyncListDiffer ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ตฌ๊ธ€์—์„œ ๋”๋”์šฑ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•˜๋ผ๊ณ  AsyncListDiffer ๋ฅผ ๋ž˜ํ•‘ํ•œ ListAdapter๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

    class BookAdapter(private val itemClickedListener: (Book) -> Unit) : ListAdapter<Book, BookAdapter.BookItemViewHolder>(diffUtil) {
    
        inner class BookItemViewHolder(private val binding: ItemBookBinding) : RecyclerView.ViewHolder(binding.root){
    
            fun bind(bookModel: Book){
                binding.titleTextView.text = bookModel.title
                binding.descriptionTextView.text = bookModel.description
                binding.root.setOnClickListener {
                    itemClickedListener(bookModel)
                }
    
                Glide
                    .with(binding.coverImageView.context)
                    .load(bookModel.converSmallUrl)
                    .into(binding.coverImageView)
            }
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookItemViewHolder {
            return BookItemViewHolder(ItemBookBinding.inflate(LayoutInflater.from(parent.context),parent,false))
        }
    
        override fun onBindViewHolder(holder: BookItemViewHolder, position: Int) {
            holder.bind(currentList[position])
        }
    
    
        companion object {
            val diffUtil = object : DiffUtil.ItemCallback<Book>() {
                override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
                    return oldItem.id == newItem.id
                }
    
                override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
                    return oldItem == newItem
                }
            }
        }
    }

     ์œ„ ์ฝ”๋“œ๋Š” ListAdpater๋ฅผ ํ™•์žฅํ•œ BookAdpater๋กœ ListAdpater์˜ ์ œ๋„ˆ๋ฆญ ํƒ€์ž…์œผ๋กœ ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ ํƒ€์ž…์ธ Book๊ณผ ๋‚ด๋ถ€ ํด๋ž˜์Šค๋กœ ์ •์˜ํ•œ BookItemViewHolder๋ฅผ ์„ ์–ธํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ƒ์„ฑ์ž ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ DiffUtil.ItemCallback ํด๋ž˜์Šค๋ฅผ ๋™๋ฐ˜ ๊ฐ์ฒด๋กœ ์ •์˜ํ•œ ๋ณ€์ˆ˜๊ฐ’์„ ๋Œ€์ž…ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ListAdapter์—์„œ๋Š” ๋‚ด๋ถ€ ์†์„ฑ ๊ฐ’์œผ๋กœ AsyncListDiffer๊ฐ€ ์กด์žฌํ•˜๊ณ  ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋Œ€์ž…๋œ DiffUtil.ItemCallback ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ submitList ์‹œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ฒดํฌํ•˜๊ณ  ์—…๋ฐ์ดํŠธ ์‹œ์ผœ์ฃผ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๐Ÿ˜˜  

     ํ•˜์ง€๋งŒ ์•„์ง๊นŒ์ง€ ListAdapter์— ์ด์Šˆ๊ฐ€ ์กฐ๊ธˆ ๋งŽ์€ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ ๋‚ด์—ญ์„ ๊ณ„์‚ฐํ•˜๊ธฐ๊นŒ์ง€ ์‹œ๊ฐ„์ด ์†Œ์š”๋˜์–ด ์—…๋ฐ์ดํŠธ ์‹œ ๋Š๊น€์ด ๋ฐœ์ƒํ•œ๋‹ค๊ฑฐ๋‚˜, scroll position์ด ์ž๋™์œผ๋กœ ๋งจ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ด๋™ํ•˜๋Š” ์ด์Šˆ๋ฅผ ๊ฒช์–ด๋ณธ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. scrollToPosition๋„ ์ •์ƒ์ ์œผ๋กœ ์ˆ˜์ •์ด ๋˜์ง€ ์•Š์•„ submitList(null) ์ดํ›„ submitList(newList)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ˆ˜์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌ๋ฉด ListAdpater๋ฅผ ์“ฐ๋Š” ์˜๋ฏธ๊ฐ€ ์—†๊ธด ํ•œ๋ฐ... ํ•ด๊ฒฐํ•˜์‹  ์„ ๋ฐฐ๋‹˜๋“ค ์žˆ์œผ์‹œ๋ฉด ๋Œ“๊ธ€ ๋‚จ๊ฒจ์ฃผ์„ธ์š”.๐Ÿ˜‚

     

     

     

    ์ฐธ๊ณ 

     

    RecyclerView๋กœ ๋™์  ๋ชฉ๋ก ๋งŒ๋“ค๊ธฐ  |  Android ๊ฐœ๋ฐœ์ž  |  Android Developers

    RecyclerView๋กœ ๋™์  ๋ชฉ๋ก ๋งŒ๋“ค๊ธฐ   Android Jetpack์˜ ๊ตฌ์„ฑ์š”์†Œ RecyclerView๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ ์„ธํŠธ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ๊ฐ ํ•ญ๋ชฉ์˜ ๋ชจ์–‘์„ ์ •์˜ํ•˜๋ฉด R

    developer.android.com

     

    [Android] RecyclerView DiffUtil

    RecyclerView์— ํ‘œํ˜„ํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด ์ฃผ๋กœ notifyDataSetChanged()๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. notifyDataSetChanged() ๋ฆฌ์ŠคํŠธ์˜ ๋‚ด์šฉ์ด ๋ณ€๊ฒฝ๋˜์–ด notifyDataSetChanged()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, Adapter์—๊ฒŒ RecyclerView..

    voiddani.tistory.com

     

    [Android] DiffUtil ์‚ฌ์šฉ๋ฒ• ์•Œ์•„๋ณด๊ธฐ

    notifyDataSetChanged() ํƒˆ์ถœ์€ ์ง€๋Šฅ์ˆœ (์šฐ๋ผ๋ผ)

    velog.io

     

    ๋ฐ˜์‘ํ˜•
Designed by Tistory.