AndroidのListViewで再利用されるViewのgetGlobalVisibleRect()が意図した値を返さない

(2020-02-11)

ListViewのViewの再利用

まずはListViewのViewが再利用されていることを確認する。

package com.example.listviewglobalvisiblerect

import android.widget.BaseAdapter
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.app.Activity
import android.graphics.Color
import android.graphics.Rect
import android.util.Log
import android.widget.TextView


data class SampleData(val id: Long, val value: String)

class SampleAdapter: BaseAdapter {
    private val context: Context
    private val items: List<SampleData>
    private val views = ArrayList<View>()

    constructor(context: Context, items: List<SampleData>) {
        this.context = context
        this.items = items
    }

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

    override fun getItem(position: Int): Any {
        return items.get(position)
    }

    override fun getItemId(position: Int): Long {
        return items.get(position).id
    }

    override fun getView(position: Int, convertView: View?, container: ViewGroup?): View {
        var view = convertView ?: (this.context as Activity).layoutInflater.inflate(R.layout.sample_list_item, container, false)
        if (convertView == null) {
            Log.v("SampleAdapter", "view ${position} is created")
            view.tag = position
            if (position == 0) {
                view.setBackgroundColor(Color.LTGRAY)
            }
            views.add(view)
        } else {
            Log.v("SampleAdapter", "view ${convertView.tag} is recycled at ${position}")
        }
        view.findViewById<TextView>(R.id.text_view).setText(items.get(position).value)
        return view
    }
}

position == 0 のCellだけ生成時に背景色を灰色にしたところ、他のpositionでも再利用された灰色のCellが登場した。

再利用されて色が変わったままのCell

画面に表示される分のViewが生成され、スクロールすると画面外に出たViewが再利用された。

V/SampleAdapter: view 0 is created
V/SampleAdapter: view 1 is created
V/SampleAdapter: view 2 is created
V/SampleAdapter: view 3 is created
V/SampleAdapter: view 4 is created
V/SampleAdapter: view 0 is recycled at 5
V/SampleAdapter: view 1 is recycled at 6

各ViewにgetGlobalVisibleRect()を実行した結果

getGlobalVisibleRect()はViewの可視領域を取得できる関数で、領域がない場合falseを返す。

views.forEach { v ->
    var rect = Rect();
    if (v.getGlobalVisibleRect(rect)) {
        Log.v("SampleAdapter", "view ${v.tag} visible rect: ${rect} has_parent: ${v.parent != null}")
    } else {
        Log.v("SampleAdapter", v.tag.toString() + " is not visible")
    }
}

再利用待ち(parent=false)の画面内にないView 1はfalseを返すと思っていたが、実際はtrueを返し正しくない値が入ってしまう。

V/SampleAdapter: view 0 visible rect: Rect(0, 1520 - 1080, 2018) has_parent: true
V/SampleAdapter: view 1 visible rect: Rect(0, 0 - 1185, 498) has_parent: false
V/SampleAdapter: view 2 visible rect: Rect(0, 66 - 1080, 515) has_parent: true
V/SampleAdapter: view 3 visible rect: Rect(0, 518 - 1080, 1016) has_parent: true
V/SampleAdapter: view 4 visible rect: Rect(0, 1019 - 1080, 1517) has_parent: true