Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách triển khai danh sách vô hạn (Endless List) với RecyclerView trong Android Kotlin

Giới thiệu

Bài viết này hướng dẫn cách triển khai danh sách vô hạn (endless/infinite scrolling) với RecyclerView trong Android sử dụng Kotlin.

Các bước thực hiện

Bước 1: Tạo dự án mới

Mở Android Studio, chọn File → New Project và điền đầy đủ thông tin để tạo dự án mới.

Bước 2: Thiết kế item layout (res/layout/item_row.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardElevation="2dp"
    app:cardUseCompatPadding="true">

    <TextView
        android:id="@+id/textViewItem"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:textColor="@android:color/holo_blue_bright"
        android:textStyle="bold" />
</androidx.cardview.widget.CardView>

Bước 3: Tạo loading layout (res/layout/item_loading.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:indeterminate="true"
        android:paddingLeft="8dp"
        android:paddingRight="8dp" />
</LinearLayout>

Bước 4: Viết RecyclerViewAdapter (RecyclerViewAdapter.kt)

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.NonNull
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder

class RecyclerViewAdapter internal constructor(private val itemList: List<String>) : RecyclerView.Adapter<ViewHolder>() {
    private val viewItemType = 0

    @NonNull
    override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder {
        return if (viewType == viewItemType) {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.item_row, parent, false)
            ItemViewHolder(view)
        } else {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.item_loading, parent, false)
            LoadingViewHolder(view)
        }
    }

    override fun onBindViewHolder(@NonNull viewHolder: ViewHolder, position: Int) {
        if (viewHolder is ItemViewHolder) {
            populateItemRows(viewHolder, position)
        } else if (viewHolder is LoadingViewHolder) {
            showLoadingView(viewHolder, position)
        }
    }

    override fun getItemViewType(position: Int): Int {
        return viewItemType
    }

    private inner class ItemViewHolder internal constructor(@NonNull itemView: View) : ViewHolder(itemView) {
        internal var tvItem: TextView = itemView.findViewById(R.id.textViewItem)
    }

    private class LoadingViewHolder internal constructor(itemView: View) : ViewHolder(itemView) {
        var progressBar: ProgressBar = itemView.findViewById(R.id.progressBar)
    }

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

    private fun showLoadingView(viewHolder: LoadingViewHolder, position: Int) {}

    private fun populateItemRows(viewHolder: ItemViewHolder, position: Int) {
        val item = itemList[position]
        viewHolder.tvItem.text = item
    }
}

Bước 5: Viết MainActivity (src/MainActivity.kt)

import android.os.Bundle
import android.os.Handler
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {
    lateinit var recyclerView: RecyclerView
    lateinit var recyclerViewAdapter: RecyclerViewAdapter
    var rowsArrayList: ArrayList<String> = ArrayList()
    var isLoading = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        title = "KotlinApp"
        
        recyclerView = findViewById(R.id.recyclerView)
        populateData()
        initAdapter()
        initScrollListener()
    }

    private fun initScrollListener() {
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                val linearLayoutManager = recyclerView.layoutManager as LinearLayoutManager?
                
                if (!isLoading) {
                    if (linearLayoutManager != null && linearLayoutManager.findLastCompletelyVisibleItemPosition() == rowsArrayList.size - 1) {
                        loadMore()
                        isLoading = true
                    }
                }
            }
        })
    }

    private fun initAdapter() {
        recyclerViewAdapter = RecyclerViewAdapter(rowsArrayList)
        recyclerView.layoutManager = LinearLayoutManager(applicationContext)
        recyclerView.adapter = recyclerViewAdapter
    }

    private fun populateData() {
        for (i in 0..9) {
            rowsArrayList.add("Number $i")
        }
    }

    private fun loadMore() {
        rowsArrayList.add("") // placeholder loading
        recyclerViewAdapter.notifyItemInserted(rowsArrayList.size - 1)
        
        val handler = Handler()
        handler.postDelayed(Runnable {
            rowsArrayList.removeAt(rowsArrayList.size - 1)
            val scrollPosition = rowsArrayList.size
            recyclerViewAdapter.notifyItemRemoved(scrollPosition)
            
            var currentSize = scrollPosition
            val nextLimit = currentSize + 10
            while (currentSize - 1 < nextLimit) {
                rowsArrayList.add("Number $currentSize")
                currentSize++
            }
            recyclerViewAdapter.notifyDataSetChanged()
            isLoading = false
        }, 2000)
    }
}

Bước 6: Cấu hình Manifest (androidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android" package="com.example.q11">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị Android thực với máy tính. Trong Android Studio, mở file activity và nhấn biểu tượng Run trên thanh công cụ. Chọn thiết bị di động của bạn và kiểm tra màn hình hiển thị.

Cải tiến hiện đại: Thay vì Handler, nên dùng Coroutine + Flow hoặc Paging 3 Library (androidx.paging:paging-runtime) cho pagination chuẩn, hỗ trợ load state, retry, refresh.