RecyclerView で動的リストを作成する Android Jetpack の一部。

Compose の方法を試す
Jetpack Compose は、Android で推奨される UI ツールキットです。Compose でレイアウトを扱う方法について説明します。

RecyclerView を使用すると、大規模なデータセットを簡単かつ効率的に表示できます。データを指定し、各アイテムの表示方法を定義すると、必要に応じて、RecyclerView ライブラリが動的に要素を作成します。

RecyclerView は、その名のとおり、これらの個々の要素をリサイクルします。アイテムが画面外にスクロールされても、RecyclerView はビューを破棄せず、画面上にスクロールされた新しいアイテムのビューを再利用します。RecyclerView を使用すると、パフォーマンスとアプリの応答性が向上し、消費電力が削減されます。

主なクラス

複数のクラスが連携して動的リストをビルドします。

  • RecyclerView は、データに対応するビューを含む ViewGroup です。これはビュー自体であるため、他の UI 要素と同じように RecyclerView をレイアウトに追加できます。

  • リスト内の各要素はビューホルダー オブジェクトによって定義されます。ビューホルダーは、作成されるときにデータが関連付けられることはありません。ビューホルダーは、作成された後で RecyclerView によってそのデータにバインドされます。ビューホルダーを定義するには、RecyclerView.ViewHolder を拡張します。

  • RecyclerView は、アダプターのメソッドを呼び出し、ビューをリクエストしてそのデータにビューをバインドします。アダプターを定義するには、RecyclerView.Adapter を拡張します。

  • レイアウト マネージャーによって、リスト内の個々の要素が配置されます。RecyclerView ライブラリによって提供されるレイアウト マネージャーを使用することも、独自のレイアウト マネージャーを定義することもできます。レイアウト マネージャーはいずれも、ライブラリの LayoutManager 抽象クラスに基づいています。

すべての要素が連携している状態については、RecyclerView サンプルアプリ(Kotlin)または RecyclerView サンプルアプリ(Java)をご覧ください。

RecyclerView を実装する手順

RecyclerView を使用する場合は、いくつかの手順が必要になります。詳細については、以降のセクションで説明します。

  1. リストやグリッドの外観を決めます。通常、RecyclerView ライブラリの標準レイアウト マネージャーを使用できます。

  2. リストの各要素の表示方法や動作方法を設計します。この設計に基づいて、ViewHolder クラスを拡張します。ViewHolder のバージョンでは、リスト項目のすべての機能が提供されます。ビューホルダーは View のラッパーであり、そのビューは RecyclerView によって管理されます。

  3. データを ViewHolder ビューに関連付ける Adapter を定義します。

また、RecyclerView をニーズに合わせて的確に調整できる高度なカスタマイズ オプションも用意されています。

レイアウトの計画

RecyclerView のアイテムは LayoutManager クラスによって並べ替えられます。RecyclerView ライブラリには 3 つのレイアウト マネージャーがあり、レイアウトの標準的な状況を処理します。

  • LinearLayoutManager は 1 次元のリスト内にアイテムを配置します。
  • GridLayoutManager は、次の 2 次元グリッド内にアイテムを配置します。
    • グリッドが垂直方向に配置されている場合、GridLayoutManager は各行のすべての要素が同じ幅と高さになるようにしますが、行ごとに異なる高さを指定することもできます。
    • グリッドが水平方向に配置されている場合、GridLayoutManager は各列のすべての要素の幅と高さが同じになるようにしますが、列ごとに異なる幅を指定することもできます。
  • StaggeredGridLayoutManagerGridLayoutManager と似ていますが、行のアイテムの高さが同じである必要もなく(垂直グリッドの場合)、同じ列のアイテムの幅が同じである必要もありません(水平グリッドの場合)。結果として、行または列内のアイテムが互いにオフセットされることになります。

また、個々のアイテムのレイアウトも設計する必要があります。このレイアウトは、ビューホルダーを設計する際に必要になります。次のセクションで説明します。

アダプタとビューホルダーを実装する

レイアウトが決まったら、AdapterViewHolder を実装する必要があります。これら 2 つのクラスが連携して、データの表示方法を定義します。ViewHolder は、リスト内の各アイテムのレイアウトを含む View のラッパーです。Adapter は、必要に応じて ViewHolder オブジェクトを作成するとともに、これらのビューのデータを設定します。ビューをこれらのデータに関連付けるプロセスは、バインディングと呼ばれます。

アダプターを定義する際は、次の 3 つの主要なメソッドをオーバーライドする必要があります。

  • onCreateViewHolder(): RecyclerView は、新しい ViewHolder を作成する必要があるたびにこのメソッドを呼び出します。このメソッドは、ViewHolder とそれに関連する View を作成して初期化しますが、ビューのコンテンツは埋めませんViewHolder はこの時点で特定のデータにバインドされていません)。

  • onBindViewHolder(): RecyclerView はこのメソッドを呼び出して、ViewHolder をデータに関連付けます。このメソッドは適切なデータを取得し、そのデータを使用してビューホルダーのレイアウトを埋めます。たとえば、RecyclerView が名前のリストを表示する場合、このメソッドはリストの中から適切な名前を見つけて、ビューホルダーの TextView ウィジェットを埋めます。

  • getItemCount(): RecyclerView はこのメソッドを呼び出して、データセットのサイズを取得します。たとえば、アドレス帳アプリの場合、このデータセットのサイズは住所の合計数になります。RecyclerView はこの情報を使用して、表示できるアイテムがほかにないかどうかを判断します。

次に、ネストされ、データのリストを表示する ViewHolder を持つ簡単なアダプターの典型的な例を示します。この例では、RecyclerView はテキスト要素の簡単なリストを表示しています。アダプターには、ViewHolder 要素のテキストを含む文字列の配列が渡されます。

Kotlin

class CustomAdapter(private val dataSet: Array<String>) :
        RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    /**
     * Provide a reference to the type of views that you are using
     * (custom ViewHolder)
     */
    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView

        init {
            // Define click listener for the ViewHolder's View
            textView = view.findViewById(R.id.textView)
        }
    }

    // Create new views (invoked by the layout manager)
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        // Create a new view, which defines the UI of the list item
        val view = LayoutInflater.from(viewGroup.context)
                .inflate(R.layout.text_row_item, viewGroup, false)

        return ViewHolder(view)
    }

    // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {

        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.textView.text = dataSet[position]
    }

    // Return the size of your dataset (invoked by the layout manager)
    override fun getItemCount() = dataSet.size

}

Java

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {

    private String[] localDataSet;

    /**
     * Provide a reference to the type of views that you are using
     * (custom ViewHolder)
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final TextView textView;

        public ViewHolder(View view) {
            super(view);
            // Define click listener for the ViewHolder's View

            textView = (TextView) view.findViewById(R.id.textView);
        }

        public TextView getTextView() {
            return textView;
        }
    }

    /**
     * Initialize the dataset of the Adapter
     *
     * @param dataSet String[] containing the data to populate views to be used
     * by RecyclerView
     */
    public CustomAdapter(String[] dataSet) {
        localDataSet = dataSet;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        // Create a new view, which defines the UI of the list item
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.text_row_item, viewGroup, false);

        return new ViewHolder(view);
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {

        // Get element from your dataset at this position and replace the
        // contents of the view with that element
        viewHolder.getTextView().setText(localDataSet[position]);
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return localDataSet.length;
    }
}

各ビューアイテムのレイアウトは、通常どおり XML レイアウト ファイルで定義されます。この場合、アプリの text_row_item.xml ファイルは次のようになります。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/list_item_height"
    android:layout_marginLeft="@dimen/margin_medium"
    android:layout_marginRight="@dimen/margin_medium"
    android:gravity="center_vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/element_text"/>
</FrameLayout>

次のステップ

次のコード スニペットは、RecyclerView の使用方法を示しています。

Kotlin

class MainActivity : AppCompatActivity() {

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

        val dataset = arrayOf("January", "February", "March")
        val customAdapter = CustomAdapter(dataset)

        val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = customAdapter

    }

}

Java

RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.layoutManager = new LinearLayoutManager(this)
recyclerView.setAdapter(customAdapter);

このライブラリには、実装をカスタマイズするためのさまざまな方法が用意されています。詳細については、高度な RecyclerView のカスタマイズをご覧ください。

参考情報

Android でのテストの詳細については、次のリソースをご覧ください。

サンプルアプリ