Android Kotlin ListViewの次のN件の読み込み


よくあるListViewの最終セルに次のN件の読み込み的なやつ。ページネーションと読んでいいのかどうか。

Activity

class MainActivity : AppCompatActivity() {

    lateinit var myListView:ListView
    lateinit var myAdapter:MyAdapter

    val listItems = ArrayList<String>()
    var columnIndex = 0

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

        //listItemの追加
        addItem()
        myListView = findViewById<ListView>(R.id.postsListView)
        myAdapter = MyAdapter(this, listItems)
        myListView.adapter = myAdapter
    }

    private fun addItem(){
        listItems.add( columnIndex.toString() )
        columnIndex++
    }

    public fun loadNextItem(){
        addItem()
        //itemの変更をadapterに通知
        myAdapter.notifyDataSetChanged()
    }
}

Adapter

ViewHolder
private class ViewHolder(view: View) {
    val name = view.findViewById<TextView>(R.id.name)
}
各セルになるViewの作成メソッド

itemセルはlist_item_cell.xml、次へのセルはlist_next_cell.xmlにlayout定義されているものとする。

次へのセルとなるViewにもタグをつけておくとgetView()時に再利用されるconvertViewの識別に利用できる。

mainActivity.loadNextItem()notifyDataSetChanged()this.notifyDataSetChanged()としてもこのケースでは動作するが、ほとんどのケースでは別スレッドから取得したデータをUIスレッドに渡すと思うので、Activity側でadapterに通知するほうがハマりどころが少ない。

class MyAdapter(
    val context:Context,
    val items: ArrayList<String>
) :BaseAdapter() {

    private val inFlater = LayoutInflater.from(context)

    private fun makeNextView(parent: ViewGroup?):View{
        val view = inFlater.inflate(R.layout.list_next_cell, parent, false)
        view.tag = "nextView"
        view.setOnClickListener {
            val mainActivity = context as MainActivity
            mainActivity.loadNextItem()
        }
        return view
    }

    private fun createView(parent: ViewGroup?):View{
        val view = inFlater.inflate(R.layout.list_item_cell, parent, false)
        view.tag = ViewHolder(view)
        return view
    }
}
getCount()

getCount()は次へセルの分を足してreturnする

override fun getCount(): Int {
    return items.size + 1
}
getView()

positionがitems配列の終端に達した時はページネーションセルを返す。

またconvertViewが次へになるセルのリサイクルになる時がある為、セットされたタグで再利用可能であるViewか確認を行う。

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {

    //ページネーションセル
    if(items.size <= position){
        return makeNextView(parent)
    }
    //コンテンツセル
    var view = convertView ?: createView(parent)
    if(view.tag == "nextView"){
        view = createView(parent)
    }
    val viewHolder = view.tag as ViewHolder
    viewHolder.name.text = items[position].title
    return view
}

MyAdapter

上記を記述したAdapterクラス

class MyAdapter(
    val context:Context,
    val items: ArrayList<String>
) :BaseAdapter() {

    private val inFlater = LayoutInflater.from(context)

    private class ViewHolder(view: View) {
        val name = view.findViewById<TextView>(R.id.name)
    }

    private fun makeNextView(parent: ViewGroup?):View{
        val view = inFlater.inflate(R.layout.list_next_cell, parent, false)
        view.tag = "nextView"
        view.setOnClickListener {
            val mainActivity = context as MainActivity
            mainActivity.loadNextItem()
        }
        return view
    }

    private fun createView(parent: ViewGroup?):View{
        val view = inFlater.inflate(R.layout.list_item_cell, parent, false)
        view.tag = ViewHolder(view)
        return view
    }

    override fun getCount(): Int {
        return items.size + 1
    }


    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {

        //ページネーションセル
        if(items.size <= position){
            return makeNextView(parent)
        }
        //コンテンツセル
        var view = convertView ?: createView(parent)
        if(view.tag == "nextView"){
            view = createView(parent)
        }
        val viewHolder = view.tag as ViewHolder
        viewHolder.name.text = items[position].title
        return view
    }
}

実際にIDEで記述して実行したわけではないので、ところどころTypoだったり、足りないところがあるかもしれません。