[Kotlin] RecyclerView その1 – アイコン画像のリスト表示

本文上広告1



今回の目標

第一段階

Google公式のRecyclerViewのサンプルを使用して、基本的なRecyclerViewを表示できるようになりたいと思います。いたってシンプルなRecyclerViewです。

第二段階

RecyclerViewにアイコン画像のリストを表示できるようになる。デフォルトで用意されている固定のアイコン画像を使用します。初めてやるので、いろいろ試行錯誤しながらできるようになりました。その経過(私の失敗)も書いておきます。

リストのみの表示

参考にしたページ

 
Android DevelopersのRecylcerView

@gitbokuさんの RecyclerViewの使い方の一例(Kotlin)

実際にやってみる

  1. Android DevelopersのRecyclerViewのページを参考に始めていきます。
  2. “Add the support library”の項目で、”dependencies”にRecyclerViewを付け加えます。左の画面のbuild.gradle(Module:app)と書かれているほうに追加してください。
  3. ちなみに下の画像のようにbuild.gradle(Project:XXXX)の方に誤ってつけ加えると、ビルドもできなくなりますので、ご注意ください。初心者の私はやりましたww
  4. “Add RecyclerView to your layout”とあるので、公式のコードを参考にしながらres\layout\activit_ymainにRecyclerViewをつけ加えます。
  5. 
    
    
  6. ちゃんと追加できていると、この時点で下のように、デザイン側でも表示されています。
  7. Android Developers公式を参照すると、以下のように、「オブジェクトのハンドルを取得し、layout managerにつなげ、データを表示するために、adapterに付け加えます。」となっているので、このコードをMainActivityに追加すればよいことがわかります。

  8. (Android Developers公式より)

  9. 追加すると、以下のようになります。赤い部分は、”Alt+Enter”でインポートをつけ加えると消えるようになります。(一部AdapterとmyDataSetは後ほどなのでそれ以外の部分。)
  10. 次にAdapterを追加します。”Add a list adapter”を参照して、MyAdapter.ktファイルをメニューから追加。
  11. 作成できたら、公式ページのままコピペです。
  12. package com.example.recyclerview_test
    
    import android.support.v7.widget.RecyclerView
    import android.view.LayoutInflater
    import android.view.ViewGroup
    import android.widget.TextView
    
    
    class MyAdapter(private val myDataset: Array<String>) :
        RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
    
        // Provide a reference to the views for each data item
        // Complex data items may need more than one view per item, and
        // you provide access to all the views for a data item in a view holder.
        // Each data item is just a string in this case that is shown in a TextView.
        class MyViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
    
    
        // Create new views (invoked by the layout manager)
        override fun onCreateViewHolder(parent: ViewGroup,
                                        viewType: Int): MyAdapter.MyViewHolder {
            // create a new view
            val textView = LayoutInflater.from(parent.context)
                .inflate(R.layout.my_text_view, parent, false) as TextView
            // set the view's size, margins, paddings and layout parameters
            return MyViewHolder(textView)
        }
    
        // Replace the contents of a view (invoked by the layout manager)
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            // - get element from your dataset at this position
            // - replace the contents of the view with that element
            holder.textView.text = myDataset[position]
        }
    
        // Return the size of your dataset (invoked by the layout manager)
        override fun getItemCount() = myDataset.size
    }
  13. つぎに、リストの各項目のレイアウトを追加します。ただし、先ほど追加したmy_text_viewと関連づける必要があるので、新しい名前を付けて、Adapter側のコードを変更するか、my_text_viewとして名前を付けてください。
  14. // create a new view
    val textView = LayoutInflater.from(parent.context)
        inflate(R.layout.my_text_view, parent, false) as TextView
    

  15. あとは、MainActivity.ktにMyDatasetを追加すれば完成です。
  16. ビルド実行を行うと、以下のようになるかと思います。後で気づきましたが、Hello Worldは、気に入らなかったら消してください。

ソース

MainActivity.kt

package com.example.recyclerview_test

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView


class MainActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var viewAdapter: RecyclerView.Adapter<*>
    private lateinit var viewManager: RecyclerView.LayoutManager

    var myDataset: Array<String> = Array<String>(13, { i -> i.toString() })

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

        viewManager = LinearLayoutManager(this)
        viewAdapter = MyAdapter(myDataset)

        recyclerView = findViewById<RecyclerView>(R.id.my_recycler_view).apply {
            // use this setting to improve performance if you know that changes
            // in content do not change the layout size of the RecyclerView
            setHasFixedSize(true)

            // use a linear layout manager
            layoutManager = viewManager

            // specify an viewAdapter (see also next example)
            adapter = viewAdapter

        }

    }
}

MyAdapter.kt

package com.example.recyclerview_test

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView


class MyAdapter(private val myDataset: Array<String>) :
    RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder.
    // Each data item is just a string in this case that is shown in a TextView.
    class MyViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)


    // Create new views (invoked by the layout manager)
    override fun onCreateViewHolder(parent: ViewGroup,
                                    viewType: Int): MyAdapter.MyViewHolder {
        // create a new view
        val textView = LayoutInflater.from(parent.context)
            .inflate(R.layout.my_text_view, parent, false) as TextView
        // set the view's size, margins, paddings and layout parameters
        return MyViewHolder(textView)
    }

    // Replace the contents of a view (invoked by the layout manager)
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        holder.textView.text = myDataset[position]
    }

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

activity_main.xml

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

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

    <android.support.v7.widget.RecyclerView
            android:id="@+id/my_recycler_view"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

</android.support.constraint.ConstraintLayout>

my_text_view.xml

<?xml version="1.0" encoding="utf-8"?>

    <TextView
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:padding="20dp"/>

アイコン画像のリスト表示

参考にしたページ

初めは、こちらのページを参考にさせてもらってましたが、ActivityとFragmentの違いからかうまくいかず断念。(初心者につきあまり判別もつかず。)
【kotlin】RecyclerViewの簡単な使い方【初心者向け】

次に、こちらのページを参考にして作成しました。なので、両ページを取り入れたようなサンプルになっています。
KotlinでRecyclerViewを使ったリスト表示を行う

実際にやってみる(ダメだった方)

【kotlin】RecyclerViewの簡単な使い方【初心者向け】を参考にさせてもらったのですが、私の力が及ばすで分からなかったので、多分Activityにそのまま使用したのが間違いだったかもと思ってます。

結果だけ書くと、各項目をクリックすると以下のようなメッセージが出て、アプリが落ちました。

ここら辺、周りでエラーが発生しているようでしたが、多分私の使い方が悪かっただけだと思います。

実際にやってみる(うまくいった方)

実際にやってみて、ダメだった方を途中でリカバーする形で最終形態になっているので、参考もとのサンプルとは異なりますが、悪しからず。

  1. RecylcerViewの各項目のLayoutにあたる部分を作るために、my_text_view.xmlをいったん削除して、my_item.xmlを作成。中身を以下の用に作成します。コードは一番下を参照ください。
  2. 次は各項目のデータに当たる部分のクラスを作成・追加します。
  3. クラスの中身を以下のように書き込みます。今回は使いませんが、今後、画像をロードしたい思っているので、画像のメンバ変数もつけ加えています。(ただし、Android的にはuri等でやるのが正なのでそれは後ほど変えます。)
  4. ViewHolder.ktファイルを追加します。
  5. ViewHolderの中身を追加します。
  6. 同様にViewAdapter.ktとViewAdapterの中身を追加します。
  7. MainActivity.ktを少し変更します。まずは、赤線を引いた部分、2ケ所。MainActivityをItemClickListnerに対応させます。リストデータをつくる関数をアダプターに適用。
  8. 先ほどのMainActivity.ktの変更の続き、そこぞれ対応させたItemClickListnerの関数を追加し、リストデータをつくる関数を追加します。
  9. private fun makeList() : List<ItemData> {
        var list = mutableListOf<ItemData>()
    
        for ( i in 1..49){
            var dat = ItemData().also {
                it.category = i.toString() + "のデータのカテゴリです。"
                it.detail = i.toString() + "のデータの詳細記述です。"
            }
            list.add(dat)
        }
    
        return list
    }
    
    override fun onItemClick(view: View, position: Int) {
        Toast.makeText(applicationContext, "position $position was tapped", Toast.LENGTH_SHORT).show()
    }
    
  10. ビルドと実行を行うと以下のようなリストが表示され、クリックをする画面下部にクリックしたインデックスのトーストが表示されます。

ソース

うまくいった方。

my_item.xml(レイアウト)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">


    <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@mipmap/ic_launcher"/>
        <LinearLayout
                android:orientation="vertical"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

            <TextView
                    android:id="@+id/category_textView"
                    android:layout_width="wrap_content"
                    android:layout_height="0dp"
                    android:layout_weight="0.5"/>
            <TextView
                    android:id="@+id/comment_textView"
                    android:layout_width="wrap_content"
                    android:layout_height="0dp"
                    android:layout_weight="0.5"/>

        </LinearLayout>

    </LinearLayout>

</LinearLayout>

MainActivity.kt

package com.example.recyclerview_test

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.Toast

class MainActivity : AppCompatActivity(), ViewHolder.ItemClickListener {

    private lateinit var recyclerView: RecyclerView
    private lateinit var viewAdapter: RecyclerView.Adapter<*>
    private lateinit var viewManager: RecyclerView.LayoutManager

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

        viewManager = LinearLayoutManager(this)
        viewAdapter = ViewAdapter( makeList(), this, this)

        recyclerView = findViewById<RecyclerView>(R.id.my_recycler_view).apply {
            // use this setting to improve performance if you know that changes
            // in content do not change the layout size of the RecyclerView
            setHasFixedSize(true)

            // use a linear layout manager
            layoutManager = viewManager

            // specify an viewAdapter (see also next example)
            adapter = viewAdapter

        }

    }

    private fun makeList() : List<ItemData> {
        var list = mutableListOf<ItemData>()

        for ( i in 1..49){
            var dat = ItemData().also {
                it.category = i.toString() + "のデータのカテゴリです。"
                it.detail = i.toString() + "のデータの詳細記述です。"
            }
            list.add(dat)
        }

        return list
    }

    override fun onItemClick(view: View, position: Int) {
        Toast.makeText(applicationContext, "position $position was tapped", Toast.LENGTH_SHORT).show()
    }
}

ViewAdapter.kt

package com.example.recyclerview_test

import android.content.Context
import android.support.v7.widget.RecyclerView
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup

class ViewAdapter(private val list: List<ItemData>,
                  private val context: Context,
                  private val itemClickListener: ViewHolder.ItemClickListener) : RecyclerView.Adapter<ViewHolder>() {

    private var mRecyclerView : RecyclerView? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        Log.d("Adapter", "onCreateViewHolder")

        val layoutInflater = LayoutInflater.from(context)
        val mView = layoutInflater.inflate(R.layout.my_item, parent, false)

        mView.setOnClickListener { view ->
            mRecyclerView?.let {
                itemClickListener.onItemClick(view, it.getChildAdapterPosition(view))
            }
        }
        return ViewHolder(mView)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        Log.d("Adapter", "onBindViewHolder")
        holder.titleView.text = list[position].category
        holder.detailView.text = list[position].detail
    }

    override fun getItemCount(): Int {
        Log.d("Adapter", "getItemCount")
        return list.size
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        mRecyclerView = recyclerView
    }

    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        super.onDetachedFromRecyclerView(recyclerView)
        mRecyclerView = null

    }
}

ViewHolder.kt

package com.example.recyclerview_test

import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.TextView

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val titleView: TextView = itemView.findViewById(R.id.category_textView)
    val detailView: TextView = itemView.findViewById(R.id.comment_textView)

    // 独自に作成したListener
    interface ItemClickListener {
        fun onItemClick(view: View, position: Int)
    }
}

ItemDataModel.kt

package com.example.recyclerview_test

class ItemData {
    public var category : String  =" "
    public var detail : String = " "
    public var pathImage : String = " "

}

その他疑問に思ったこと

mipmap/ic_launcherとは?

サンプルを引用して、”@mipmap/ic_launcher”を使用していたのですが、これは??と思ったので、調べました。 

  • mimap: 技術の名前。遠くは荒く、近くは細かくして、メモリを減らすような技術。
  • @mipmap:デフォルトで作られるリソースの中にあります。
  • ic_launcher: デフォルトで用意されている起動アイコン。
  • ということで、アイコンを変えたいときにもここら辺のリストを変更すると良。

Kotlinの配列

この表が分かりやすかった。Kotlin のコレクション・配列早見表

英語や、公式の表記に目を慣らす意味も込めて。

Kotlin 公式 – Array

Kotlin 公式 – ArrayList

気になること・やりたいこと

RecyclerViewの基本に模式的に描かれているクラスの関係は分かりやかったので、メモ。そして、初心者には安心と安定のnyanさんのRecylcerViewのページ。

先に参考にさせてもらったページ、「KotlinでRecyclerViewを使ったリスト表示を行う」はCardViewだったと後で気づき、CardViewも機会があれば少し触ってみたいと思いました。

こういうRecylcerViewの使い方もあるんだなぁという例として、「RecyclerViewにリストカードを表示するViewを作る」も面白いかと思いました。

Fragmentでやってなかったので、「【Android】RecyclerViewの基本的な実装」を参考にして、Fragmentでもやってみたい。

MVVMでRecyclerViewも気になるところではありますが、後で振り返るとして、「RecyclerViewとDatabindingとDiffUtilと。」と「Android — RecyclerView using MVVM and DataBinding」だけをメモしておいて、のちほどじっくり見ます。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする