【Kotlin】自作Adapterを使用してListViewに自作クラスの情報を表示

こんにちは。

新年あけましておめでとうございます、ですが、深圳は連日昼は20℃を超えるし、長期連休でもないので全く年末年始感がないまま2020年を迎えております。

日本にいるときは1/1は何もしない、と決めているのですが、上述の通り年末年始感がまったくないので、ブログでも更新しようかと思い立ったわけです。

ListViewに独自の項目を独自レイアウトで表示させたい

さて、前回はTextViewをDrag & Dropで動かしてみましたが、最終的に作りたいのがList形式のTodoリストであり、自由なレイアウトでListViewに表示 + Drag & Dropで並び替え、ということを考えると、一工夫が必要なことがわかりました。なぜなら、そもそもListViewにはonDrag的なメソッドが標準でないため、実行するためにはListViewを継承した独自クラスの作成をしなければならないことがわかったのです。

そこまで飛ぶにはちょっと気合がいるので、まずはListViewに好きな項目を表示できるようにしよう、と思い立ったわけです。

BaseAdapterクラスを継承

やりたいことは、

  • 独自クラスを作ってリストに登録
  • 上記クラス用のAdapterを作成
  • ListViewに表示

の3点。BaseAdapterクラスを継承すれば、やりたいことができることがわかりました。

大ハマリ

で、実際に書いたBaseAdapterを継承した独自Adapterのコードが下記。

class MyAdapter(context: Context, private val tl : ArrayList<TodoTasks>) : BaseAdapter() {

    private val inflater : LayoutInflater = LayoutInflater.from(context)

    override fun getItem(p0: Int): Any {
        return tl[p0]
    }

    override fun getItemId(p0: Int): Long {
        return p0.toLong()
    }

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

    override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
        var view = p1
        var holder: ViewHolder

        if(view == null){
            view = inflater.inflate(row,null)
            holder = ViewHolder(
                view.taskName,
                view.taskType,
                view.taskLimit
            )
            view.tag = holder
        } else {
            holder = view.tag as ViewHolder
        }

        holder.taskName.text = tl[p0].task_name
        holder.taskType.text = tl[p0].task_type
        holder.taskLimit.text = tl[p0].task_limit

        return view!!
    }

    data class ViewHolder(val taskName : TextView, val taskType: TextView, val taskLimit : TextView)

}

※BaseAdapter継承したところ以外は外でも山ほど解説があるので省略します。

これは最終形なのですが、過程で大ハマリしました。エラーが何も出ないのに、画面に何も表示されない。。。

f:id:Shoriencohol:20200101192750j:plain
真っ白なリストですね。。。

大ハマリしすぎて、初めて質問サイトに投稿してみました。

teratail.com

これへの回答で、一言ありがたい言葉をいただきました。

アダプタの各メソッドが何を返すべきなのかをもう一度ご確認されては如何でしょうか.

むむむ。。。こんな一言で解決するはずが。。。した!!!

問題点1 getView()以外のメソッドを甘く見すぎた

問題は、初期に書いたコードでgetCount()の戻り値を超適当に0にしていたことでした。色んなサイト見て、getView()が難関すぎ、他の関数は適当にoverrideしておけば問題ない、と書いてあったので、超適当にやりすぎた!

BaseAdapterの挙動に関係すると思われるのですが、おそらくgetCount()でListのサイズを取得、0より大きければgetView()を使って処理、という流れと思われます。実際Logをとってみると、複数回getCount()が実行されたあとにgetView()が実行されていますね。まさに、ちゃんとメソッドの戻り値を見直しましょう、ということです。

f:id:Shoriencohol:20200101192350j:plain
getCount()をなんでこんなに何回も実行するのかは不明…

※ちなみにその他のgetItem(), getItemID()あたりは超適当に実装しても何も問題なく。特に、Kotlinはgetterを使わなくても大抵動くので、Java解説だとgetItem()使って取得しているリストも直接ゲットできました。

問題点2 ViewHolderの挙動

もう一つはまったのが、ViewHolderの実装。これも色々なサイトで、「getView()は何度も実施されるため、ViewHolderを実装したほうがいいよ!」と書いてあり、あまり深く考えずに(理解せずに)実装したら全然動かない。意味不明。最終的に、下記のブログを読んで解決。

www.usaco-pg.com

要するに、ViewHolderはView本体じゃなくて、Viewが持ってるtag情報を保持させて、それを書き換えることでリストの内容を更新する、ということですな。

多分、例えばAというtag情報の中には、Aの値が格納されているメモリの位置と、それについている名前が入っていて、名前=○○、とすることでメモリ情報を書き換え、View本体が見に行ったときに中身が変わっている、ということなんでしょうね。(すごく初心者感。。。)

自分のプログラムで言うと、holder.taskLimit.text = tl[p0].task_limitあたりが該当。

最初、なんでholderの値書き換えてるのにviewをリターンするんだ!?と大混乱でしたが、上記の理解(間違ってるかもですが)をしたことで解決しました。

とりあえず、やりたいことはできた

で、実際の表示画面はこんな感じ。

f:id:Shoriencohol:20200101193935j:plain
リスト登録するのが面倒だったので、Hogehoge大量生産。

いやー動いた動いた。よかったよかった。

次にやりたいこと

次にやりたいのは、入力したタスクを追加、みたいなことかな。まあ、簡単そうですね。←フラグ

ではではー