用 Android RecyclerView 建立列表

Photo by Zdeněk Macháček on Unsplash
Photo by Zdeněk Macháček on Unsplash
在開發 App 時,不免會需要顯示各式各樣的列表。利用 Android SDK 的 RecyclerView,我們便可以輕鬆地實作各種列表。

在開發 App 時,不免會需要顯示各式各樣的列表。利用 Android SDK 的 RecyclerView,我們便可以輕鬆地實作各種列表。

完整程式碼可以在 下載。

建立專案

建立一個新的 Basic Activity 專案。Basic Activity 專案預設有 MainActivity、FirstFragment、和 SecondFragment。我們會在 FirstFragment 裡實作一個列表。因此,將不會動到 MainActivity 和 SecondFragment 的程式碼。

建立每一列的畫面和資料

假設在列表中的每一列,只有兩個 TextView,一個顯示 Key,一個顯示 Value。在 res/layout/ 下,新增 row.xml,其內容如下:

<?xml version="1.0" encoding="utf-8"?>
<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/keyTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/valueTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

再來,我們為每一個列所需要的資料,建立一個 data class。新增 RowData,其程式碼如下:

package com.waynestalk.androidrecyclerviewexample

data class RowData(val key: String, val value: String)

RecyclerView.Adapter

當 RecyclerView 在畫整個列表時,RecyclerView.Adapter 是用來提供每一列的 view。而 RecyclerView 只是將拿到的 view,一個接著一個畫下去。

新增 FirstAdapter,其程式碼如下:

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class FirstAdapter(private val dataset: Array<RowData>) : RecyclerView.Adapter<FirstAdapter.ViewHolder>() {
    inner class ViewHolder(listItemView: View) : RecyclerView.ViewHolder(listItemView) {
        val keyTextView = itemView.findViewById<TextView>(R.id.keyTextView)
        val valueTextView = itemView.findViewById<TextView>(R.id.valueTextView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val view = inflater.inflate(R.layout.row, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val rowData = dataset[position]
        holder.keyTextView.text = rowData.key
        holder.valueTextView.text = rowData.value
    }

    override fun getItemCount(): Int {
        return dataset.size
    }
}

每一個列的 view 都會被包含在一個 ViewHolder 裡面。所以,在 FirstAdapter 中,我們宣告一個 ViewHolder 並且繼承 RecylerView.ViewHolder,而其建構子裡的參數 listItemView,就是列的 View,而且這個 listItemView 會被設定給 RecyclerView.ViewHolder.itemView。然後,在建構子裡,我們會取出列裡面所有子元件的參照,以便後續使用。

inner class ViewHolder(listItemView: View) : RecyclerView.ViewHolder(listItemView) {
    val keyTextView = itemView.findViewById<TextView>(R.id.keyTextView)
    val valueTextView = itemView.findViewById<TextView>(R.id.valueTextView)
}

我們將所有的資料傳給 FirstAdapter 的建構子。繼承 RecyclerView.Adapter 後,FirstAdapter 必須要實作三個方法:

  • getItemCount():列表中列的總數。
  • onCreateViewHolder():我們要在這方法裡創建列的 View 的實體。
  • onBindViewHolder():在這方法裡,我們會將列的資料設到 View 裡。

就這樣,關於列部分的程式碼,我們都已經準備好了。

RecyclerView

接下來,我們開始要用 RecyclerView 來畫出列表。首先,先修改 fragment_first.xml,我們要放入 <RecyclerView>

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FirstFragment">

    <Button
        android:id="@+id/button_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/next"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/button_first" />

</androidx.constraintlayout.widget.ConstraintLayout>

新增 FirstViewModel 來提供 RowData 資料。

package com.waynestalk.androidrecyclerviewexample

import androidx.lifecycle.ViewModel

class FirstViewModel : ViewModel() {
    val dataset = arrayOf(
            RowData("Side name", "Wayne's Talk"),
            RowData("URL", "https://waynestalk.com"),
    )
}

在 FirstFragment 中,我們必須創建 FirstAdapter,並將 RowData 資料傳給它。然後,將 FirstAdapter 設定給 RecyclerView。

package com.waynestalk.androidrecyclerviewexample

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class FirstFragment : Fragment() {
    private lateinit var viewModel: FirstViewModel

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        viewModel = ViewModelProvider(this).get(FirstViewModel::class.java)

        val root = inflater.inflate(R.layout.fragment_first, container, false)

        val recyclerView = root.findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.adapter = FirstAdapter(viewModel.dataset)
        recyclerView.layoutManager = LinearLayoutManager(activity)

        return root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.findViewById<Button>(R.id.button_first).setOnClickListener {
            findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
        }
    }
}

結論

RecyclerView 是不是很簡單呢!它會負責幫我們一列一列地畫,而我們只需要考慮怎麼畫出每一列就可以了。所以,大部分的邏輯集中在 FirstAdapter 裡,是不是簡單許多!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

You May Also Like
Photo by Hans-Jurgen Mager on Unsplash
Read More

Kotlin Coroutine 教學

Kotlin 的 coroutine 是用來取代 thread。它不會阻塞 thread,而且還可以被取消。Coroutine core 會幫你管理 thread 的數量,讓你不需要自行管理,這也可以避免不小心建立過多的 thread。
Read More