【Android App with Kotlin #5】RecyclerViewを使う

RecyclerViewを一番シンプルかつ実践的な機能で使いました。

作るもの

ImageViewとTextViewがリスト表示され、 タップするとTOASTが通知されるシンプルなものです。

環境

このコードは以下の環境で書いています。

  • macOS 10.14.2(Mojave)
  • Android Studio 3.3 前回の記事からバージョンップしました
  • Android SDK 28
  • gradle 4.6

事前準備

特にありません。

レイアウトファイル

activity_recycler.xml

ここにはRecyclerViewのみ置かれます。 RecyclerViewの中身は別のレイアウトファイルで定義します。

activity_reciycler.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <android.support.v7.widget.RecyclerView
            android:id="@+id/rvAvaterList"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>

item_profile.xml

ファイル名はそれっぽい適当な名前です。 RecyclerViewの1要素を表すレイアウトファイルです。 最上位のレイアウト(この場合はConstraintLayout)のheightは wrap_contentもしくは固定値にしないと、1要素が1ページ分のサイズになります。

item_profile.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="wrap_content">

    <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent">
        <ImageView
                android:id="@+id/ivAvater"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                tools:srcCompat="@mipmap/ic_launcher"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"/>
        <TextView
                android:id="@+id/tvAvtName"
                android:layout_width="wrap_content"
                android:layout_height="26dp"
                android:layout_marginTop="8dp"
                android:layout_marginStart="8dp"
                android:layout_marginEnd="8dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toEndOf="@+id/ivAvater"
                app:layout_constraintEnd_toEndOf="parent"
                tools:text="@tools:sample/full_names"/>
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

ViewHolder

AvaterViewHolder.kt

item_profileで定義したレイアウト内のウィジェットを 後述のAdapterから利用出来る様に宣言しておくファイルになります。 また、インターフェースを用意してありますが、 これはタップした際の動作を定義出来るようにしておく受け口になります。 RecyclerViewの1要素をクラス化しているイメージで捉えています。

AvaterViewHolder.kt
class AvaterViewHolder(view: View): RecyclerView.ViewHolder(view) {

    val ivAvater: ImageView = view.findViewById(R.id.ivAvater)
    val tvAvtName: TextView = view.findViewById(R.id.tvAvtName)

    /*
    処理を持たないメソッド(インターフェース)を用意する事で
    AdapterやActivityで処理が実装出来るようになる
    */
    interface ItemInterface{
        fun onClickItem(position: Int)
    }
}

Adapter

AvaterAdapter.kt

Activityによって生成(インスタンス化)されます。 RecyclerViewの要素数分、行データを読み取り、 ViewHolderとレイアウトファイルを紐づけます。

AvaterAdapter.kt
class AvaterAdapter(
        private val avtList:List<String>, 
        private val ItemListener:AvaterViewHolder.ItemInterface
        ): RecyclerView.Adapter<RecyclerView.ViewHolder>(){

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

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

        val layoutInflater = LayoutInflater.from(parent.context)
        val view: View =  layoutInflater.inflate(R.layout.item_profile, parent, false)

        return AvaterViewHolder(view)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val myHolder:AvaterViewHolder = holder as AvaterViewHolder

        myHolder.tvAvtName.text = avtList[position]
        myHolder.ivAvater.setImageResource(R.mipmap.ic_launcher)

        //テキストをクリックされた時の動作を新規にバインドさせる
        myHolder.tvAvtName.setOnClickListener{
            ItemListener.onClickItem(position)
        }

    }
}

コードの説明に入ります。

class AvaterAdapter(
        private val avtList:List<String>, 
        private val ItemListener:AvaterViewHolder.ItemInterface
        ): RecyclerView.Adapter<RecyclerView.ViewHolder>(){

第一引数ではActivityから受け取るリスト要素を、 第二引数では、AvaterViewHolder内に宣言したインターフェースをItemListenerという名前で待ちます。

   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

        val layoutInflater = LayoutInflater.from(parent.context)
        val view: View =  layoutInflater.inflate(R.layout.item_profile, parent, false)

        return AvaterViewHolder(view)
    }

Adapterに与えられたリスト要素(今回はavtList:List<String>)から1要素ずつ受け取り、 viewtypeに応じたRecyclerViewのレイアウトファイルを読み込みます。 最も、今回の場合は全部同じレイアウトのリストなので、viewtypeにかかわらず同じviewを読み込みます。

もし、複数種類のレイアウトファイルを使い分ける場合は、getItemViewTypeを使います。 position(要素の位置)に応じたviewtypeを返却するメソッドです。

サンプル

override fun getItemViewType(position:Int):Int{
        val VIEW_T_HEADER:Int = 0
        val VIEW_T_FOOTER:Int = 1
        val VIEW_T_BODY:Int = 3
        when(position){
                //一番最初の要素の場合
                0 -> {
                        return VIEW_T_HEADER
                }
                //リスト要素と同じサイズ + 1 = 最終行の場合
                avtList.size + 1 -> {
                        return VIEW_T_FOOTER
                }
                else -> {
                        return VIEW_T_BODY
                }
        }
}
  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val myHolder:AvaterViewHolder = holder as AvaterViewHolder

        myHolder.tvAvtName.text = avtList[position]
        myHolder.ivAvater.setImageResource(R.mipmap.ic_launcher)

        //テキストをクリックされた時の動作を新規にバインドさせる
        myHolder.tvAvtName.setOnClickListener{
            ItemListener.onClickItem(position)
        }

    }

要素毎にテキストや画像をバインド(割り当て)しています。 ここで、setOnCLickListener等のメソッドも普通のウィジェット同様に使えるので、 インターフェースItemListenerが持つメソッドonClickItemを実行させます。(AvaterViewHolderで用意しましたね)

Activity

RecyclerActivity.kt

Adapterに比べればここは宣言するだけなので非常にシンプルです。

RecyclerActivity.kt
class RecyclerActivity: AppCompatActivity(), AvaterViewHolder.ItemInterface {

    val avtList: List<String> = listOf("John","Pawl","Amanda")

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

        rvAvaterList.layoutManager = LinearLayoutManager(this)
        rvAvaterList.adapter = AvaterAdapter(avtList, this)
    }

    override fun onClickItem(position: Int) {
        Toast.makeText(this, "Item Positon:" + position + ", Item:" + avtList[position], Toast.LENGTH_SHORT).show()
    }
}
rvAvaterList.layoutManager = LinearLayoutManager(this)

activirty_recycler.xmlで用意したRecyclerViewのウィジェットをIDから使用しています。 layoutManagerというものを定義しています。 LinearLayoutManagerと、GridLayoutManagerの2種類があります。 前者はListViewのような1行形式のレイアウトで、 後者は縦横の格子状に要素を持つ事が出来るレイアウトです。 今回は前者を使います。

rvAvaterList.adapter = AvaterAdapter(avtList, this)

先程までで作成したAdapterをこのRecyclerViewに割り当てています。 1つ目の引数では、このアクティビティクラスのメンバ変数として宣言しているavtListを、 2つ目の引数ではアクティビティクラスの戻り値として持つAvaterViewHolderを割り当てています。 (この辺のつながりが自分でもまだよくわかっていません。わかる方いたらコメントやDMで教えてください。)

長くなりましたが、ここまでで一旦、シンプルなRecyclerViewを 扱う事が出来たかと思います。

リンク

次の記事

前回の記事

【Android App with Kotlin #4】ListViewを使う(2)

/以上