【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の中身は別のレイアウトファイルで定義します。
<?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ページ分のサイズになります。
<?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要素をクラス化しているイメージで捉えています。
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
とレイアウトファイルを紐づけます。
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に比べればここは宣言するだけなので非常にシンプルです。
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)
/以上