駆け出しエンジニアぱかぱかの成長記録

引くほど忘れっぽい新卒2年目駆け出しSEぱかぱかの備忘録です。

【Android】RecyclerViewを使ったリストの実装

こんにちは!ぱかぱかです!
今日は現在開発を進めている飲食店記録アプリ「グルメノート」の画面作成第2弾で、前回作成したViewPager2内に表示するFragmentの中身を作っていきたいと思います!

前回の記事

radish-se.hatenablog.com

前回はViewPager2を実装し、スワイプやタブバーのアイコンタップで画面が切り替わるようになりました。

作りたい画面のイメージ図

デザインが微妙な件はいつかどうにかしたいのですが、今のイメージは大体こんな感じです。
行きたい店リスト・行った店リスト・お気に入り店リストを切り替えたいと思っています。

タブバーの位置とかタブの順番とか、どうするのがいいかはのちのち試行錯誤するとします!

レイアウトの作成

作りたい画面は行きたい店リスト・行った店リスト・お気に入り店リストの3つです。
とりあえずこの記事では情報量が多めの行った店リストの部分だけ取り上げます。

RecyclerViewの大枠

まず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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

RecyclerViewのセル

次にRecyclerView1つ1つのセルのレイアウトを作ります。
ここはパーツが多くて長くなってしまうのでコードは省略します!
イメージとしては以下のような感じ。

今回はCardViewというカードっぽい角丸/影付きのビューを使ってみました。

データクラスの作成

VisitedRestaurantListItemクラスを作成しアダプターに引き渡すデータ項目をデータオブジェクトとして管理します。

data class VisitedRestaurantListItem(
    val restaurantName: String,
    val restaurantEvaluation: Evaluation,
    val restaurantPlace: String,
    val restaurantGenre: String,
    val restaurantRegistrationDate: String,
    val restaurantMemo: String,
    var restaurantPhoto: Drawable?,
    val restaurantFavorite: Boolean
)

ビューホルダーの作成

アダプターで利用するためのビューホルダー
ビューホルダーはビューを保持するためのクラスです。
findViewByIdで配下のウィジェットを毎度取得するのは無駄なので、ViewHolderで個々のウィジェットへの参照を準備しておくという考え方。
あくまでウィジェットを保持することが目的で、自身が処理を受け持つことはありません。

class VisitedRestaurantCardViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
    // ウィジェットへの参照をプロパティに格納
    val restaurantName = itemView.findViewById<TextView>(R.id.restaurant_name)
    val restaurantEvaluation = itemView.findViewById<TextView>(R.id.restaurant_evaluation)
    val restaurantPlace = itemView.findViewById<TextView>(R.id.restaurant_place)
    val restaurantGenre = itemView.findViewById<TextView>(R.id.restaurant_genre)
    val restaurantRegistrationDate = itemView.findViewById<TextView>(R.id.restaurant_registration_date)
    val restaurantMemo = itemView.findViewById<TextView>(R.id.restaurant_memo)
    val restaurantPhoto = itemView.findViewById<ImageView>(R.id.visited_restaurant_photo)
    val restaurantFavorite = itemView.findViewById<ImageView>(R.id.visited_restaurant_favorite)
}

アダプターの作成

アダプターはRecyclerViewにデータを橋渡しするためのクラスです。
RecyclerView.Adapter派生クラスを定義する際、まずは型パラメータとしてアダプターで利用するビューホルダーを割り当てておく必要があります。
コンストラクタでリスト表示に必要な情報を上で作成したデータクラスVisitedRestaurantListItem型として受け取ります。

RecyclerView.Adapterでは以下3つのメソッドを実装する必要があります。

onCreateViewHolder リスト個々の項目を生成するためのビューホルダー生成
onBindViewHolder ビューホルダーに値を割り当て、個々のリスト項目を生成
getItemCount リストの項目数を取得

お気に入り登録していないものはハートが灰色になるようにonBindViewHolderの中で設定しています。

class VisitedRestaurantListAdapter(private val data: List<VisitedRestaurantListItem>): RecyclerView.Adapter<VisitedRestaurantCardViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VisitedRestaurantCardViewHolder {
        return VisitedRestaurantCardViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.visited_restaurant_list_item, parent, false))
    }

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

    override fun onBindViewHolder(holder: VisitedRestaurantCardViewHolder, position: Int) {
        holder.restaurantName.text = data[position].restaurantName
        holder.restaurantEvaluation.text = data[position].restaurantEvaluation.star
        holder.restaurantPlace.text = data[position].restaurantPlace
        holder.restaurantGenre.text = data[position].restaurantGenre
        holder.restaurantRegistrationDate.text = data[position].restaurantRegistrationDate
        holder.restaurantMemo.text = data[position].restaurantMemo
        holder.restaurantPhoto.setImageDrawable(data[position].restaurantPhoto)
        if (!data[position].restaurantFavorite) {
            holder.restaurantFavorite.setColorFilter(Color.rgb(125, 125, 125), PorterDuff.Mode.SRC_ATOP)
        }
    }
}

Fragment内でデータ生成

今後データベースから取得したいのですが、今はひとまずコードにベタ打ちでVisitedRestaurantListItemをたくさん作ってリストにし、Adapterに渡します。

class VisitedRestaurantFragment: Fragment()  {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_visited_restaurant,container,false)

        val data = listOf(
            VisitedRestaurantListItem("吉野家",Evaluation.THREE_STAR,"東京","和食","2023/11/01","新メニューが美味しかった",
                ContextCompat.getDrawable(requireContext(), R.drawable.gyudon),true),
            VisitedRestaurantListItem("サイゼリヤ",Evaluation.FIVE_STAR,"名古屋","イタリアン","2023/11/01","安すぎ",ContextCompat.getDrawable(requireContext(), R.drawable.doria),true),
            VisitedRestaurantListItem("牛角",Evaluation.FOUR_STAR,"大阪","焼肉","2023/11/01","牛タンうまい",ContextCompat.getDrawable(requireContext(), R.drawable.yakiniku),false),
            VisitedRestaurantListItem("CoCo壱",Evaluation.THREE_STAR,"京都","カレー","2023/11/01","辛かった",ContextCompat.getDrawable(requireContext(), R.drawable.curry),true),
            VisitedRestaurantListItem("天下一品",Evaluation.ONE_STAR,"神戸","ラーメン","2023/11/01","味が濃い",ContextCompat.getDrawable(requireContext(), R.drawable.ramen),false),
            VisitedRestaurantListItem("コメダ珈琲",Evaluation.TWO_STAR,"広島","カフェ","2023/11/01","量多い",ContextCompat.getDrawable(requireContext(), R.drawable.komeda),false),
            VisitedRestaurantListItem("スシロー",Evaluation.THREE_STAR,"沖縄","寿司","2023/11/01","サーモン3皿食べた",ContextCompat.getDrawable(requireContext(), R.drawable.sushiro),true),
            VisitedRestaurantListItem("餃子の王将",Evaluation.FIVE_STAR,"札幌","中華","2023/11/01","餃子テイクアウトした",ContextCompat.getDrawable(requireContext(), R.drawable.gyoza),false),
            VisitedRestaurantListItem("やよい軒",Evaluation.THREE_STAR,"仙台","和食","2023/11/01","健康的",ContextCompat.getDrawable(requireContext(), R.drawable.yayoi),true),
            VisitedRestaurantListItem("マクドナルド",Evaluation.FOUR_STAR,"青森","ファストフード","2023/11/01","ポテト高くなってた",ContextCompat.getDrawable(requireContext(), R.drawable.mac),false)
        )

        val rv = view.findViewById<RecyclerView>(R.id.recycler_view)
        rv.setHasFixedSize(true)
        rv.layoutManager = LinearLayoutManager(context).apply {
            orientation = LinearLayoutManager.VERTICAL
        }
        rv.adapter = VisitedRestaurantListAdapter(data)
        return view
    }
}

完成

こんな感じに出来上がりました!

もちろんリストはスクロールできるようになっています!
行きたい店リストもお気に入り店リストも微妙にレイアウトが違うのでそれぞれ作っていますよ。
(データ書き換えるのが面倒で同じような画面に見えてしまうので写真は割愛します…)

タブバーは上と下どちらがいいんでしょうかね〜
当初は下にしていたけど、上でも悪くないような…これからまた考えるとします!
写真を見てるとお腹が空いてきますね!

次回は実際にDBからデータを取得したり登録したりというところを作っていきたいですね!

それでは今日はこの辺で!