ListViewを簡単なモデルで理解しよう【Android・Kotlin】

ListViewを簡単なモデルで実装する

本記事の完成イメージがこちら。


画像の拡大

標準に搭載されているタイムゾーンの配列をTimeZone.getAvailableIDs()で取得して、一覧表示する単純な仕様となっている。APIなど複雑な処理はないのでリストビューの理解だけに集中できるように進めていく。

ListViewのレイアウトファイルを作る

配置したListViewの行のレイアウトファイルを作りたい場合は、ListViewをクリックして、listitemの項目の...をクリック。開いたダイアログの右上から、New layout Fileを選択して作成すると簡単だ。


画像の拡大

こんな感じにレイアウトする。


画像の拡大

リストの行のタップ領域高さは、48dp以上が推奨されているようだ。あまりに狭いと、ユーザビリティが悪くなるので気をつけよう。


画像の拡大

基本のListView

BaseAdapterクラスを継承した、TimeZoneAdapterクラスを作成する。

class TimeZoneAdapter(private val context: Context,
                      private val timeZones: Array<String> = TimeZone.getAvailableIDs())
    : BaseAdapter() {


    private val inflater = LayoutInflater.from(context)

    // インデックスp0にある行のビューを返す
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val view = convertView ?: inflater.inflate(R.layout.list_time_zone_row, parent, false)
        val timeZoneId = getItem(position)
        val timeZone = TimeZone.getTimeZone(timeZoneId)

        val timeZoneLabel = view.findViewById<TextView>(R.id.timeZone)
        timeZoneLabel.text = "${timeZone.displayName}(${timeZone.id})"

        val clock = view.findViewById<TextClock>(R.id.clock)
        clock.timeZone = timeZone.id

        return view
    }

    // インデックスp0にあるデータを返す
    override fun getItem(position: Int) = timeZones[position]

    // 行を識別するためのユニーク値
    override fun getItemId(position: Int) = position.toLong()

    // リスト表示するデータ件数
    override fun getCount() = timeZones.size

}

getViewの部分が一番混乱するかもしれない。getViewは次のような働きをする。


画像の拡大

getViewは、positionに指定された行のViewを返す。Viewが生成されていないならinflaterで生成し、すでに存在すれば、そのViewを使い回す。また、実際の値をリストに設定する処理もここで書く。

ViewHolderで速度改善

実はさきほどのプログラムは、次の部分に問題がある。

val timeZoneLabel = view.findViewById<TextView>(R.id.timeZone)

スクロールするたびに、findViewByIdを呼び出すのはコストがかかるのだ。つまりスクロール動作が遅くなる。そこで登場するのがViewHolderパターンだ。


画像の拡大

Viewにはtagプロパティがあり、そこに任意のオブジェクトを1つ持つことができる。これを利用して、あらかじめtimeZoneLabelなどのインスタンスを生成して持たせておくのだ。convertViewはリサイクルされるので、tagに持たせたオブジェクトも再利用されるはずである。そして次のコードが、 ViewHolderパターンに書き換えたものだ。

class TimeZoneAdapter(private val context: Context,
                      private val timeZones: Array<String> = TimeZone.getAvailableIDs())
    : BaseAdapter() {


    private val inflater = LayoutInflater.from(context)

    // インデックスp0にある行のビューを返す
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val view = convertView ?: createView(parent)
        val timeZoneId = getItem(position)
        val timeZone = TimeZone.getTimeZone(timeZoneId)

        val viewHolder = view.tag as ViewHolder

        @SuppressLint("SetTextI18n")
        viewHolder.name.text = "${timeZone.displayName}(${timeZone.id})"
        viewHolder.clock.timeZone = timeZone.id

        return view
    }

    // インデックスp0にあるデータを返す
    override fun getItem(position: Int) = timeZones[position]

    // 行を識別するためのユニーク値
    override fun getItemId(position: Int) = position.toLong()

    // リスト表示するデータ件数
    override fun getCount() = timeZones.size


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



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

}

ListViewの動作がなんか重いと思ったら、ここを見直してみよう。

MainActivityにListViewを実装する

最後に、アクティビティにListView実装しよう。

package com.apppppp.trylistview

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.ListView

class MainActivity : AppCompatActivity() {

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


        val list = findViewById<ListView>(R.id.clockList)
        val adapter = TimeZoneAdapter(this)

        list.adapter = adapter

        list.setOnItemClickListener { _, _, position, _ ->

            val timeZone = adapter.getItem(position)
            println(timeZone)
        }

    }
}

これで完成。


画像の拡大

createViewが呼び出される回数を確認

createViewが何回呼び出されるか確認するために、カウンターを追加して測定してみた。Viewがリサイクルされるならば、カウンターの値は一定値以上にはならないはずだ。

private var count = 0

private fun createView(parent: ViewGroup?) : View {
    println(count++)

    val view = inflater.inflate(R.layout.list_time_zone_row, parent, false)
    view.tag = ViewHolder(view)
    return view
}

リストをスクロールすると11回まで呼び出され、それ以上は呼び出されなかった。これで、Viewがリサイクルされていることが確認できた。


画像の拡大

今回のプロジェクトファイルはGitHubへ上げておいたのでご参考に。

デスクワークの負担を減らすアイテム

作業効率をアップさせるには姿勢が大事です!実際に使ってみてどれもオススメなのでよかったら参考に!

Bestandノートパソコンスタンド 11 '' -16 '' Macbook Air Pro/富士通と互換性のある放熱性に優れたアルミニウム合金PCスタンド-シルバー
Bestandノートパソコンスタンド 11 '' -16 '' Macbook Air Pro/富士通と互換性のある放熱性に優れたアルミニウム合金PCスタンド-シルバー

ノートパソコンの高さをあげると、長時間のデスクワークでも作業者の負担を軽減する事が可能です。 アルミニウム製品、純正品、高品質、高級感があ ります。 対応機種:MacBook、MacBook Air、MacBook Pro、富士通と対応11"-16"ノートパソコン スタンド.

Amazon
AKRacing ゲーミングチェア Pro-X V2 GREY
AKRacing ゲーミングチェア Pro-X V2 GREY

AKRacingハイエンドゲーミングチェア。大きめの座面でゆったりとした座り心地。張地を経年劣化に強い高耐久仕様のPUレザーに変更したアップグレードモデル 寸法:背もたれ幅55cm/奥行55cm/高さ95cm、座面下高さ:32~39cm、座面厚さ:13cm、重量:25kg

Amazon
Kensington ExpertMouse ワイヤレストラックボール K72359JP
Kensington ExpertMouse ワイヤレストラックボール K72359JP

接続方式/Bluetooth、2.4GHz USB付属品/レシーバー、パームレスト、日本語取扱説明書 サイズ(本体):W130×D157×H65mm 対応OS:2.4GHzUSB:Windows10/8.1/8/7、MacOS10.8以降/Bluetooth4.0LE:Windows10/8.1、MacOS10.8以降

AmazonRakuten

Anrdoidアプリ開発にオススメの書籍

こちらの本は、図や絵が多くてわかりやすく、Androidアプリ開発初心者でも理解しやすい内容。基本的なことはしっかりおさえられるので、この手の本は一冊読んでおくと良と思う。

基本からしっかり身につくAndroidアプリ開発入門
基本からしっかり身につくAndroidアプリ開発入門

圧倒的な多数のユーザーが使っているヤフーのアプリ。その制作の最前線にいる黒帯エンジニアが、ユーザーが使いやすいアプリの大切な基本をしっかりと解説します。

KindleAmazonRakuten

Kotlinの実践的な使い方が学べる内容の本で、プログラミング技術をもっと磨きたい人向け。センスの良いプログラミングがたくさん紹介されていて、個人的には「目からうろこ」の連続だった本。

Kotlinプログラミング
Kotlinプログラミング

Kotlinは、Javaとの相互運用を可能にし、Android OSでGoogleがフルサポートする静的型プログラミング言語です。この言語は、Javaだけでは十全ではない(Javaだけでは実装に手間がかかりすぎる)、軽量かつ豊かな表現形式や、他言語ではすでに実装されている最新の機能を盛り込んでいます。

KindleAmazonRakuten

Amazonでお得に購入するなら、Amazonギフト券がオススメ!

\Amazonギフトがお得/

コンビニ・ATM・ネットバンキングで¥5,000以上チャージすると、プライム会員は最大2.5%ポイント、通常会員は最大2%ポイントがもらえます!
Amazonギフト券

\この記事をシェアする/