04
03

๊ธฐ๋ณธ์ ์œผ๋กœ RecyclerView์—์„œ ์ ์šฉํ•˜๋ฉฐ ์„ธ ๋‹จ๊ณ„๋กœ ์ดํ•ดํ•˜๋ฉด ๋œ๋‹ค.

 

  • ์Šคํฌ๋กค์ด ๋์— ๋‹ฟ์•˜์„ ๊ฒฝ์šฐ null์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์–ด๋Œ‘ํ„ฐ์— ์•Œ๋ฆฐ๋‹ค.(onScrollListener)
  • ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„
  • null item ์ œ๊ฑฐ ํ•œ ํ›„ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์–ด๋Œ‘ํ„ฐ์— ์•Œ๋ฆฐ๋‹ค.

๊ฒ€์ƒ‰์„ ํ•ด๋ณด๋‹ˆ ๋Œ€๋‹ค์ˆ˜๊ฐ€ ์„œ๋ฒ„์—์„œ API๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๊ฒŒ ๋Œ€๋‹ค์ˆ˜์˜ ๋‚ด์šฉ์ด์—ˆ๋‹ค. ๋ฌผ๋ก  ๋‚ด๊ฐ€ ๊ฒ€์ƒ‰์„ ์ž˜ ํ•˜์ง€ ๋ชปํ–ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋” ๋†’์ง€๋งŒ ์ผ๋‹จ ๋‚˜๋Š” ์„œ๋ฒ„์™€์˜ ํ†ต์‹ ์—†์ด ๋กœ์ปฌ์—์„œ๋งŒ ๋งŒ๋“ค์–ด๋ดค๋‹ค. ์ฐธ๊ณ ์ž๋ฃŒ๋Š” ์ธ๋„ํ˜•๋‹˜์˜ ์œ ํŠœ๋ธŒ ์˜์ƒ์ด๋‹ค. ์•„๋ž˜ ๊ธ€๋„ ๋งค์šฐ ๋„์›€๋๋‹ค

https://medium.com/@ydh0256/android-recyclerview-%EC%9D%98-%EC%B5%9C%EC%83%81%EB%8B%A8%EA%B3%BC-%EC%B5%9C%ED%95%98%EB%8B%A8-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EA%B0%90%EC%A7%80%ED%95%98%EA%B8%B0-f0e5fda34301

 

[Android] RecyclerView ์˜ ์ตœ์ƒ๋‹จ๊ณผ ์ตœํ•˜๋‹จ ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๊ฐ์ง€ํ•˜๊ธฐ

RecyclerView ๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋ฉด ๊ฐ€๋” ์ตœ์ƒ๋‹จ๊ณผ ์ตœํ•˜๋‹จ์˜ ์ด๋ฒคํŠธ ๊ฐ์ง€๊ฐ€ ํ•„์š”ํ•˜๋‹ค(ํŠนํžˆ ์ฑ„ํŒ…๋ฐฉ์—์„œ์˜ ์˜ˆ์ „์ฑ„ํŒ…์„ ๊ฐ€์ ธ์˜จ๋‹ค๊ฑฐ๋‚˜ ํฌ์ŠคํŒ…์˜ ๋‹ค์Œ ํฌ์ŠคํŒ… ๋กœ๋”ฉ ์ฒ˜๋Ÿผ ํŽ˜์ด์ง• ๋กœ๋”ฉ ์ฒ˜๋ฆฌ์‹œ์— ํ•„์š”ํ•˜๋‹ค)

medium.com

Adapter์—์„œ getItemViewType ๋ฉ”์„œ๋“œ์™€ recyclerview์—์„œ์˜ scrollListener ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

const val ITEM = 1
const val LOADING = 0
        
class RvAdapter(val items: MutableList<String?>) : RecyclerView.Adapter<ViewHolder>() {
	
    // ๋น ๋ฅธ ๊ตฌํ˜„์„ ์œ„ํ•ด ViewHolder๋“ค์„ inner๋กœ ์„ ์–ธํ–ˆ๋‹ค 
    inner class ItemViewHolder(itemView: View) : ViewHolder(itemView) {
        fun bindItems(item: String) {
            val message = itemView.findViewById<TextView>(R.id.rv_item_tv)
            message.text = item
        }
    }
    inner class LoadingViewHolder(itemView: View) : ViewHolder(itemView) {
        val progressBar = itemView.findViewById<ProgressBar>(R.id.rv_loading_pb)!!
    }

    override fun getItemViewType(position: Int): Int {
        return if (items[position] != null) {
            ITEM
        } else {
            LOADING
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return if (viewType == ITEM) {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.rv_item, parent, false)
            ItemViewHolder(view)
        } else {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.rv_loading, parent, false)
            LoadingViewHolder(view)
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        if (holder is ItemViewHolder) {
            holder.bindItems(items[position]!!)
        }
    }

    override fun getItemCount(): Int {
        return items.size
    }
}

ViewHolder๋ฅผ 2๊ฐœ๋ฅผ ๋งŒ๋“ ๋‹ค.

Item์„ ๋„์šธ ๋ทฐํ™€๋”, progressbar๋ฅผ ๋„์šธ ๋ทฐํ™€๋”. ๋งŒ๋“ค์–ด๋‘” ๋ทฐํ™€๋”๋ฅผ onBindViewHolder์—์„œ ์Šค๋งˆํŠธ ์บ์ŠคํŠธ๋ฅผ ์œ„ํ•ด is๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. 

 

onCreateViewHolder์—์„œ viewType์„ ๋ฐ›์„์ˆ˜์žˆ๋Š”๋ฐ, ์ด viewType์„ ๋งŒ๋“ค์–ด์ฃผ๊ธฐ์œ„ํ•ด getItemViewType ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค. ์•„์ดํ…œ์ด null์ด๋ฉด loading์œผ๋กœ, null์ด ์•„๋‹ˆ๋ฉด item์œผ๋กœ ํƒ€์ž…์„ ๋ฐ˜ํ™˜์‹œ์ผœ ๊ฐ๊ฐ์— ๋งž๋Š” ๋ทฐํ™€๋”์— ์—ฐ๊ฒฐ์‹œ์ผœ์ค€๋‹ค.

class MainActivity : AppCompatActivity() {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    var isLoading = false
    private val testItems = mutableListOf<String?>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)


        for(i in 1 until 10){
            testItems.add("dummy item $i")
        }

        binding.rv.layoutManager = LinearLayoutManager(baseContext)
        binding.rv.adapter = RvAdapter(testItems)
        binding.rv.addOnScrollListener(object : RecyclerView.OnScrollListener(){
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                val layoutManager = binding.rv.layoutManager as LinearLayoutManager
                if(!isLoading){
                    if(!binding.rv.canScrollVertically(1)){
                        isLoading = true
                        getMoreData()
                    }
                }
            }
        })

    }

    private fun getMoreData() {
        testItems.add(null)
        binding.rv.adapter?.notifyItemInserted(testItems.size - 1)
        testItems.removeAt(testItems.size - 1)
        val currentSize = testItems.size
        for(i in currentSize+1 until currentSize+10){
            testItems.add("dummy item $i")
        }
        binding.rv.adapter?.notifyDataSetChanged()
        isLoading = false
    }
}

getMoreData ๋ฌดํ•œ์Šคํฌ๋กค์˜ ์ž‘๋™๊ณผ์ •์ด๋‹ค. canScrollVertically์˜ ์ธ์ž๋กœ ๋ฐฉํ–ฅ์ด ๋“ค์–ด๊ฐ€๋Š”๋ฐ, ์ตœ์ƒ๋‹จ์€ -1, ์ตœํ•˜๋‹จ์€ 1์ด ๋“ค์–ด๊ฐ„๋‹ค. 

  1. ๋ฐ์ดํ„ฐ์…‹์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” testItems์— null์„ ์ถ”๊ฐ€ํ•ด์„œ progressbar๋ฅผ ๋„์šด๋‹ค.
  2. notifyItemInserted๋กœ ํ˜„์žฌ progressbar์œ„์น˜์— ์•„์ดํ…œ์ด ๋“ค์–ด์˜ฌ ๊ฒƒ์ด๋ผ๊ณ  ์–ด๋Œ‘ํ„ฐ์— ์•Œ๋ ค์ค€๋‹ค.
  3. null ์ถ”๊ฐ€ํ•œ ๊ฒƒ์„ ์‚ญ์ œํ•˜๊ณ 
  4. ํ˜„์žฌ index๋ฅผ ์ƒˆ ์‹œ์ž‘์ง€์ ์œผ๋กœ ๋งŒ๋“ ๋‹ค.
  5. dataset์— ๋ณ€ํ™”๊ฐ€ ์ƒ๊ฒผ์œผ๋‹ˆ notifyDatasetChanged๋กœ ์–ด๋Œ‘ํ„ฐ์— ๋˜ ์•Œ๋ ค์ฃผ๊ณ 
  6. ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ค ๋“ค์–ด์™”์œผ๋‹ˆ isLoading์„ false๋กœ ๋ฐ”๊ฟ”์ฃผ๋ฉด ๋œ๋‹ค.

์ด๋•Œ ์Šคํฌ๋กค์—์„œ ์‹ค์ˆ˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์ด isLoading์„ getMoreData ์ดํ›„์— ๋†“๋Š” ๊ฒƒ์ด๋‹ค.

isLoading = true๋ฅผ ํ•จ์ˆ˜ ํ˜ธ์ถœ์ดํ›„์— ๋†”๋ฒ„๋ฆฌ๋ฉด ์ตœ์ดˆ 1ํšŒ๋งŒ ์ž‘๋™ํ•˜๊ณ  isLoading๊ฐ’์ด ํ•ญ์ƒ true์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฌดํ•œ์Šคํฌ๋กค์ด ์•ˆ๋œ๋‹ค.  diffutil์„ ์‚ฌ์šฉํ•˜๊ฒŒ๋˜๋ฉด notify๊ณผ์ •์„ ๊ทธ๋ƒฅ submitList๋กœ ๋ฐ”๊ฟ”์ฃผ๋ฉด ๋œ๋‹ค. 

 

"๋Œ“๊ธ€, ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์‹œ๋ฉด ํฐ ํž˜์ด ๋ฉ๋‹ˆ๋‹ค"
 
๋ฐ˜์‘ํ˜•
COMMENT