とっとこメモ

困った時のエンジニアメモ。Unity, Kotlin, Flutter などなど

【Kotlin】RecyclerViewで無限スクロールを実装する

kotlinでRecyclerView を使って InfiniteScroll を実装するメモ

今回は単純に数字が表示されるレコードを無限スクロールさせるだけの処理

まずはRecycler Viewを使えるようにbuild.gradleに以下を追記する

projectの方ではなくappの方

 dependencies {
    ...
    implementation 'androidx.recyclerview:recyclerview:1.0.0'  <-これ
}

レイアウトファイルを修正

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

row.xml

通常レコードのレイアウトファイル

row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:background="@color/colorPrimaryDark"
    android:layout_height="44dp">
    <TextView
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="8dp"
        android:textStyle="bold"
        android:textSize="17sp"
        android:textColor="@android:color/white"
        android:gravity="center_vertical"
        android:text="Lorem Ipsum"
        />
</LinearLayout>

ローディング中レコードのレイアウトファイル

ローディング中に一覧の一番下に表示するローディング表示のレコード

progressbar.xml

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/progressbar"
            android:layout_width="24dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal" />
    </LinearLayout>
</LinearLayout>

Adapterファイルの追加

Adapterとは?

AdapterViewなどのViewとデータの橋渡しをするためのもの

RecyclerViewAdapter.kt

package com.example.android.sample.recyclerinfinitescroll

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.row.view.*
import java.lang.IllegalArgumentException

class RecyclerViewAdapter(var list: ArrayList<String>) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    companion object{
        private const val VIEW_TYPE_DATA = 0
        private const val VIEW_TYPE_PROGRESS = 1
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when(viewType){
            VIEW_TYPE_DATA -> {
                val view = LayoutInflater.from(parent.context).inflate(R.layout.row, parent, false)
                DataViewHolder(view)
            }
            VIEW_TYPE_PROGRESS -> {
                val view = LayoutInflater.from(parent.context).inflate(R.layout.progressbar, parent, false)
                ProgressViewHolder(view)
            }
            else -> throw IllegalArgumentException("Different View Type")
        }
    }

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

    override fun getItemViewType(position: Int): Int {
        var viewtype = list.get(position)
        return when(viewtype){
            "load" -> VIEW_TYPE_PROGRESS
            else -> VIEW_TYPE_DATA
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if(holder is DataViewHolder)
        {
            holder.textview.text = list.get(position)
        }
    }

    inner class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
        var textview = itemView.textview

        init{
            itemView.setOnClickListener {
                Toast.makeText(itemView.context, list.get(adapterPosition), Toast.LENGTH_LONG)
                    .show()
            }
        }
    }

    inner class ProgressViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
    }
}

MainActivityの修正

MainActivity.kt

package com.example.android.sample.recyclerinfinitescroll

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.widget.LinearLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    var handler : Handler = Handler()
    lateinit var list: ArrayList<String>
    lateinit var adapter : RecyclerViewAdapter
    private var isLoading: Boolean = false
    lateinit var layoutManger: LinearLayoutManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        layoutManger = LinearLayoutManager(this)
        recyclerview.layoutManager = layoutManger

        list = ArrayList()
        load()
        adapter = RecyclerViewAdapter(list)
        recyclerview.adapter = adapter
        addScrollListener()

    }

    private fun addScrollListener() {
        recyclerview.addOnScrollListener(object: RecyclerView.OnScrollListener()
        {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)

                if(!isLoading){
                    if(layoutManger.findLastCompletelyVisibleItemPosition() == list.size - 1){
                        loadMore()
                        isLoading = true
                    }
                }

            }
        })
    }

    private fun loadMore() {
        handler.post(Runnable {
            list.add("load")
            adapter.notifyItemInserted(list.size - 1)
        })
        handler.postDelayed(Runnable {
            list.removeAt(list.size - 1)
            var listSize = list.size

            adapter.notifyItemRemoved(listSize)
            var nextLimit = listSize + 10

            for(i in listSize until nextLimit){
                list.add("Item No $i")
            }

            adapter.notifyDataSetChanged()
            isLoading = false

        }, 2500)
    }

    private fun load() {
        for(i in 0..30){
            list.add("Item No: $i")
        }
    }
}

以上で簡単ではありますが、 RecyclerViewでInfiniteScroll(無限スクロール)を実装できました。 次回は API経由で取得したデータをRecyclerViewで無限スクロール方法をメモります。