0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Android】 Databinding + ListView で可変リストの高さいっぱいに合わせる

Last updated at Posted at 2020-12-13

AndroidでScrollViewなどにListViewを入れるようなケースのお話です。この場合、ListViewのアイテム数によって動的に高さを変える必要があります。

[ Android ] コピペでListViewの高さを動的に設定 (初学者向け)
ListViewの要素数に応じてViewの高さを変える
【Android】ScrollViewにListViewを入れる
などで、「viewを取得して高さを足していけばいいよ!」という解決策が提示されています。

しかし、Databindingを使用しているときは、これらの方法を使用すると高さが合わなかったり(なぜか match_parent なのに wrap_contents で計算されたり?)、バインドする前の高さになってしまったりしました。Databindingのバインド後でTextViewが2行になっているのに、高さが1行分しか確保できていないような感じですね。

そこで、ListViewの高さをバインド後の高さに合わせるべく、アダプタに高さのリストを持たせ、データバインディングが終わったときにListViewの高さを変えるAdapterクラスを作ってみました。

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding
import java.lang.IllegalStateException

abstract class AutoSizeAdapter<T: ViewDataBinding>
    (@LayoutRes val res: Int, open val context: Context) : BaseAdapter() {

    private val measuredHeightList: MutableList<Int> = mutableListOf()
    private var parent: ViewGroup? = null
    lateinit var binding: T

    override fun notifyDataSetChanged() {
        //データセットが変更されたとき、計測済みの高さをクリアする
        measuredHeightList.clear()
        measuredHeightList.addAll( (0 until count).toMutableList())
        measuredHeightList.fill(-1)

        //データセットが空のときは高さを0にする
        if(count == 0){
            parent?.layoutParams?.height = 0
            parent?.requestLayout()
        }

        super.notifyDataSetChanged()
    }

    //getViewの変わりに実装する処理
    abstract fun onInflateView(position: Int, binding: T)

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

        parent = _parent

        binding = if(convertView == null){
            DataBindingUtil.inflate(LayoutInflater.from(context), res, parent, false)
        }else{
            DataBindingUtil.getBinding(convertView) ?: throw IllegalStateException()
        }

        if(measuredHeightList.size < position){
            measuredHeightList.add(position, 0)
        }
        binding.root.tag = position

        binding.addOnRebindCallback( object: OnRebindCallback<T>() {
            override fun onPreBind(binding: T): Boolean {
                binding.root.also { view ->
                    measuredHeightList[view.tag as Int] = view.measuredHeight
                    (parent as ListView).setHeightBasedOnChildren(measuredHeightList)
                }
                return super.onPreBind(binding)
            }
        })

        onInflateView(position, binding)

        return binding.root
    }

}

private fun ListView.setHeightBasedOnChildren(list: List<Int>){

    var sum = list.sum()

    //既にBinding済みのものがあって、全部が終わっていないとき、
    //残りのgetViewを呼ぶために残りのサイズを推定する
    if(list.count { it == -1 } > 0 && list.count {it != -1} > 0){
        sum += list.count { it == -1 } * list.filter {it != -1 }.max()
    }

    val params = layoutParams
    params.height = paddingTop + paddingBottom + sum + dividerHeight * (adapter.count) - 1
    layoutParams = params
    requestLayout()
}

課題

いずれか 0 のときは呼ばなくてよくない? !measuredHeightList.any { it != 0 } みたいなとき。
そうすると他の行のgetViewが呼ばれないんですよん なんでだろ
これのせいでアイテムが少しずつ描画されちゃう どうしよう とりあえず高さは合ってるけどうん

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?