Recyclerview 上拉載入更多

2020-10-13 03:00:22


效果如下

主要步驟

實現上拉載入更多主要有3步

  1. 定義兩個 item , 第一個是正常顯示內容的 item ,第二個是顯示正在載入檢視的 item 。如果 Adapter 中(position + 1 == itemCount),則說明滑到了最下面,此時載入第二個佈局。
  2. 在 onCreateViewHolder 中對 viewType 進行判斷,根據情況返回兩種不同的 ViewHolder。同樣,在onBindViewHolder 中對兩種情況作不同處理。
  3. 在 Activity 中對 recyclerview 的滑動事件進行監聽,如果 recyclerview 滑動到最下面,則進行相應的邏輯處理。

封裝前程式碼

1.adapter中

程式碼如下(範例):

class MyAdapter(val data : List<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(){
    private val footView = 1   // 定義變數,對應不同的情況
    private val normalView = 0
    private val footStart = 2
    private val footEnd = 3
    private val footNoMore = 4
    private var footState = 0

    class MyVH(view: View):RecyclerView.ViewHolder(view) {  // 普通的ViewHolder
        val textNormal = view.findViewById<TextView>(R.id.textView)
    }

    class FootVH(view: View): RecyclerView.ViewHolder(view) {  // 顯示"正在載入中"介面的ViewHolder
        val textFoot = view.findViewById<TextView>(R.id.tv_my_more)
        val progressBar = view.findViewById<ProgressBar>(R.id.progressBar)
    }
    override fun getItemViewType(position: Int): Int {  // 返回應該載入哪種型別的ViewHodler
        if (position + 1 == itemCount) {  // 說明已經滑動到最底部了
            return footView
        } else {
            return normalView
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { // 建立不同的ViewHodler,但是都是繼承自RecyclerView.ViewHolder
        if (viewType == normalView) {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.item_text, parent, false) // 正常顯示內容的 view
            return MyVH(view)
        }else {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.item_more, parent, false) // footView
            return FootVH(view)
        }
    }

    override fun getItemCount(): Int { // 返回資料個數加1,因為多一個用來顯示 「正在載入中」介面的item        
        return data.size + 1
    }


    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is MyVH) {
            holder.textNormal.text = data[position]
        } else if (holder is FootVH) {
            if (footState == 2) { // 正在載入中
                holder.textFoot.visibility = View.VISIBLE
                holder.progressBar.visibility = View.VISIBLE
            }else if (footState == 3) { // 載入結束
                holder.textFoot.visibility = View.GONE
                holder.progressBar.visibility = View.GONE
            }else if (footState == 4) { // 沒有更多資料可以載入了
                holder.progressBar.visibility = View.GONE
                holder.textFoot.text = "沒有更多資料啦"
            }
        }
    }

    fun setFootState(state: Int) { // 設定不同的 footState
        footState = state
        notifyDataSetChanged() // 更新 Adapter 資料
    }
}

2.定義一個抽象類

定義一個抽象類,減少Activity中對RecyclerView的監聽程式碼

程式碼如下(範例):

abstract class EndRecyclerOnScrollListener: RecyclerView.OnScrollListener(){
    private var flag = 0

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)
        val layout = recyclerView.layoutManager as LinearLayoutManager
        val lastPositionCompletely = layout.findLastCompletelyVisibleItemPosition()
        if (lastPositionCompletely == layout.itemCount - 1 && flag == 0) {
            loadMore()
        }
    }

    abstract fun loadMore()

    fun setFlag(flag: Int) {    // 設定標記防止多次向上滑動,多次呼叫 loadMore()
        this.flag = flag
    }
}

3.MainActivity中

class MainActivity : AppCompatActivity() {
    private val data = ArrayList<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        recyclerView.layoutManager = LinearLayoutManager(this)
        getData()
        val adapter = MyAdapter(data)
        recyclerView.adapter = adapter
        recyclerView.addOnScrollListener(object : EndRecyclerOnScrollListener() {// 匿名內部類實現介面
            override fun loadMore() { // 具體獲取資料的邏輯
                setFlag(1)  // 設定flag = 1,向上滑動,監聽事件會繼續觸發,但是不會繼續載入資料
                adapter.setFootState(2) // 設定 FootView 的初始狀態
                Timer().schedule(object : TimerTask() { // 延時執行
                    override fun run() {
                        if (adapter.itemCount < 52) {
                            runOnUiThread{
                                getData()
                                adapter.setFootState(3) // 載入完成
                                setFlag(0) // 此次資料載入完畢,設定flag = 0,以便下次資料可以載入
                            }
                        }else {
                            runOnUiThread { 
                                adapter.setFootState(4) // 沒有更多資料載入了
                            }
                        }
                    }
                }, 1000)
            }

        })
    }

    fun getData() {
        var s = 'A'
        for (i in 0..25) {
            data.add(s.toString())
            s++
        }
    }
}

至此已經實現了上拉載入更多的功能,不過此時如果其他 RecyclerView 也要實現上拉載入更多,就要寫許多重複程式碼在 Adapter 中,為了減少重複程式碼,下面對 Adapter 進行封裝。

封裝後程式碼

1. LoadMoreWrapper

class LoadMoreWrapper(private val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() { // 因為不同Adapter的ViewHolder型別是不同的,但是都是繼承自RecyclerView.ViewHolder,所以泛型指定為RecyclerView.ViewHolder
    private val footView = 1
    private val normalView = 0
    private val footStart = 2
    private val footEnd = 3
    private val footNoMore = 4
    private var footState = 0

    class FootVH(view: View) : RecyclerView.ViewHolder(view) {
        val textFoot = view.findViewById<TextView>(R.id.tv_my_more)
        val progressBar = view.findViewById<ProgressBar>(R.id.progressBar)
    }

    override fun getItemViewType(position: Int): Int {
        if (position + 1 == itemCount) {
            return footView
        } else {
            return normalView
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder{
        if (viewType == normalView) {
            return adapter.onCreateViewHolder(parent, viewType) // 呼叫adapter的onCreateViewHolder返會正常佈局
        } else {
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.item_more, parent, false)
            return FootVH(view)
        }
    }

    override fun getItemCount(): Int {
        return adapter.itemCount + 1
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is FootVH) {
            if (footState == 2) {
                holder.textFoot.visibility = View.VISIBLE
                holder.progressBar.visibility = View.VISIBLE
            } else if (footState == 3) {
                holder.textFoot.visibility = View.GONE
                holder.progressBar.visibility = View.GONE
            } else if (footState == 4) {
                holder.progressBar.visibility = View.GONE
                holder.textFoot.text = "沒有更多資料啦"
            }
        } else {
            adapter.onBindViewHolder(holder, position)
        }
    }

    fun setFootState(state: Int) {
        footState = state
        notifyDataSetChanged()
    }

}

2. Adapter

然後Adapter中的寫法就是一般的寫法了
class MyAdapter(val data : List): RecyclerView.Adapter<RecyclerView.ViewHolder>(){

class MyVH(view: View):RecyclerView.ViewHolder(view) {
    val textNormal = view.findViewById<TextView>(R.id.textView)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyVH {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.item_text, parent, false)
    return MyVH(view)
}

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

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    holder as MyVH
    holder.textNormal.text = data[position]
}

}

3.Activity中

class MainActivity : AppCompatActivity() {
    private val data = ArrayList<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        recyclerView.layoutManager = LinearLayoutManager(this)
        getData()
        val adapter = MyAdapter(data)
        val loadMoreWrapper = LoadMoreWrapper(adapter) // 封裝後的Adapter
        recyclerView.adapter = loadMoreWrapper
        recyclerView.addOnScrollListener(object : EndRecyclerOnScrollListener() { // 新增監聽事件
            override fun loadMore() { // 載入邏輯
                setFlag(1)  // 設定flag = 1,向上滑動,監聽事件會繼續觸發,但是不會繼續載入資料
                loadMoreWrapper.setFootState(2)
                Timer().schedule(object : TimerTask() {
                    override fun run() {
                        if (adapter.itemCount < 100) {
                            runOnUiThread{
                                getData()
                                loadMoreWrapper.setFootState(3)
                                setFlag(0) // 此次資料載入完畢,設定flag = 0,以便下次資料可以載入
                            }
                        }else {
                            runOnUiThread{
                                loadMoreWrapper.setFootState(4)
                            }
                        }
                    }
                }, 1000)
            }
        })
    }

    fun getData() {
        var s = 'A'
        for (i in 0..25) {
            data.add(s.toString())
            s++
        }
    }
}

參考文章 https://www.jianshu.com/p/b502c5b59998