どっこと備忘録群

アウトプットしないとインプットできない私が Androidアプリ開発をメインとした備忘録を載せています。

ListViewを扱う

ListViewでコンテンツ一覧を表示する

実装方法

RecyclerViewの実装と似ているので、すでにそちらを学んでいる方はスムーズに進められます。

  1. ListViewをレイアウトに配置
  2. ArrayAdapterを継承したCustomAdapterクラスを実装
  3. 表示要素を設定する
  4. ListViewCustomAdapterを接続する
  5. その他のカスタマイズ(区切り線、クリックリスナーなど)

1. ListViewをレイアウトに配置

画面のレイアウトXMLファイルにListViewを配置する。

<ListView
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

2. ArrayAdapterを継承したCustomAdapterクラスを実装

ArrayAdapterを継承したCustomAdapterを作成する。 コンストラクタで表示するデータのリストを受け取り、ArrayAdapterのジェネリクスにそのデータの型を指定する。

class CustomAdapter(context: Context, values: List<Int>) :
    ArrayAdapter<Int>(context, 0, values) {
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        TODO("このあと実装")
    }
}

3. 表示要素を設定する

表示する各アイテムのレイアウトを作成し、CustomAdapterでそのViewを生成・設定する。

レイアウトファイルの用意 各リストアイテムのレイアウトをXMLファイルで定義する。ここではlist_view_item.xmlとする。

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:gravity="center"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

getViewメソッドの実装

CustomAdaptergetViewメソッドをオーバーライドし、Viewを生成する。

上記のレイアウトを使ってViewを生成し、データを設定する。 なお、convertViewを利用することで、Viewの再利用が可能になりパフォーマンスが向上する。

class CustomAdapter(context: Context, values: List<Int>) :
    ArrayAdapter<Int>(context, 0, values) {
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        // Viewの再利用を試みる。convertViewがなければ新しく生成する。
        val view = convertView
            ?: LayoutInflater.from(parent.context).inflate(R.layout.list_view_item, parent, false)
        
        // TextViewにデータを設定する
        val textView: TextView = view.findViewById(R.id.text_view)
        val value = getItem(position)
        textView.text = value.toString()
        return view
    }
}

4. ListViewとCustomAdapterを繋ぎこむ

作成したListViewCustomAdapterをセットする。

val listView: ListView = findViewById(R.id.list_view)
listView.adapter = CustomAdapter(this, listOf(12, 23, 34, 45))

参考

タップ時の処理を実装する

OnItemClickListenerを実装する。

listView.setOnItemClickListener { parent, view, position, id ->
    // 要素がタップされた時の処理を記述
}

区切り線を非表示にする

ListViewはデフォルトでアイテム間に区切り線を表示する。 不要な場合はandroid:divider="@null"を設定することで非表示にできる。

<ListView
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="@null" />

高速スクロールバーを有効にする

listView.setFastScrollEnabled(true)

参考

追加読み込み機能を実装する

実装手順

  1. 読み込み表示ビューを作成する
  2. 読み込み表示ビューを追加する
  3. スクロールの終端を検知する

1. 読み込み表示ビューの作成

リストの最下部に表示される読み込み中を示すビューを作成する。 今回は、プログレスバーを配置したレイアウトを使う。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp">

    <ProgressBar
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

2. 読み込み表示ビューの追加

ListViewには、リストの最後に追加で表示できる FooterView という機構があり、ここに先ほど作成した読み込み表示ビューを追加する。

// フッタービューを作成し、ListViewに追加
listView.addFooterView(createFooterView())

...

fun createFooterView(): View {
    return layoutInflater.inflate(R.layout.view_progress, null)
}

3. スクロールの終端を検知する

ListViewOnScrollListenerを使用して、ユーザーがリストの最下部までスクロールしたタイミングを検知する。 onScroll内で、現在表示されているアイテム数が全アイテム数と等しいかどうかを判定し、追加読み込みの処理を動作させるか制御する。

listView.setOnScrollListener(object : OnScrollListener {
    override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
        // このメソッドでは特に処理しない
    }
    
    override fun onScroll(view: AbsListView?, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
        if (totalItemCount == firstVisibleItem + visibleItemCount) {
            // 最下部までスクロールされたので、追加読み込みを開始する
        }
    }
})

この判定ロジックを、再利用可能なインターフェースにまとめることもできる。

interface OnBottomScrolledListener : OnScrollListener {
    /**
     * 最下部までスクロールされたことを検知
     */
    fun onScrolledToBottom()

    override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {}
    
    override fun onScroll(view: AbsListView?, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
        if (totalItemCount == firstVisibleItem + visibleItemCount) {
            onScrolledToBottom()
        }
    }
}

このインターフェースを使えば、リスナーの実装がシンプルにできる。

listView.setOnScrollListener(object : OnBottomScrolledListener {
    override fun onScrolledToBottom() {
        // 最下部までスクロールされたことを検知したので、追加読み込みを実行
    }
})

また読み込み完了した結果、追加で表示するアイテムがない場合は、 無限ループを避けるためにフラグなどを使って制御することも重要。

listView.setOnScrollListener(object : OnBottomScrolledListener {
    override fun onScrolledToBottom() {
        if (!isLoadNecessary) {
            // 追加で読み込むデータがない場合は、フッタービューを削除
            listView.addFooterView(null)
            return
        }
        // 追加読み込みの処理
    }
})

参考

最終更新: 2025.9.7