LoginSignup
2

More than 5 years have passed since last update.

Android app development with Kotlin: RecyclerView

Posted at

Kotlin: 1.2.71
Android Studio: 3.2.1

RecyclerView

It's a kind of listview.

This is a sample made with RecyclerView
RecyclerView Sample.jpg

What do you need to prepare?

・Adapter (This will hold the Items and View
・Listener (Callback Interface for watching list click event
・ItemsArray (Items to show
・Layout of cell to show (Cell's layout

Create the base of RecyclerView

Android Studio has a feature which helps making base components easier. Right click the package you want to add, go to New -> Fragment -> Fragment(List)
AddRoot.jpg
Then, configure the RecyclerView
ConfigureRecyclerView.jpg
Object Kind: Enter any name, but I don't use it
Object content layout file name: Layout for cell
List layout file name: RecyclerView layout
Adapter class name: Adapter's name. In this case, I prefer ItemAdapter or ItemFragmentAdapter.

Fragment

When the RecyclerView is created in this way, Android Studio will creates Fragment like this

ItemFragment.kt
class ItemFragment : Fragment() {

    // TODO: Customize parameters
    private var columnCount = 1

    private var listener: OnListFragmentInteractionListener? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        arguments?.let {
            columnCount = it.getInt(ARG_COLUMN_COUNT)
        }
    }

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

        // Set the adapter
        if (view is RecyclerView) {
            with(view) {
                layoutManager = when {
                    columnCount <= 1 -> LinearLayoutManager(context)
                    else -> GridLayoutManager(context, columnCount)
                }
                adapter = MyItemRecyclerViewAdapter(DummyContent.ITEMS, listener)
            }
        }
        return view
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnListFragmentInteractionListener) {
            listener = context
        } else {
            throw RuntimeException(context.toString() + " must implement OnListFragmentInteractionListener")
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     *
     *
     * See the Android Training lesson
     * [Communicating with Other Fragments](http://developer.android.com/training/basics/fragments/communicating.html)
     * for more information.
     */
    interface OnListFragmentInteractionListener {
        // TODO: Update argument type and name
        fun onListFragmentInteraction(item: DummyItem?)
    }

    companion object {

        // TODO: Customize parameter argument names
        const val ARG_COLUMN_COUNT = "column-count"

        // TODO: Customize parameter initialization
        @JvmStatic
        fun newInstance(columnCount: Int) =
                ItemFragment().apply {
                    arguments = Bundle().apply {
                        putInt(ARG_COLUMN_COUNT, columnCount)
                    }
                }
    }
}

In my case, I only use these

RecyclerViewSampleFragment.kt
class RecyclerViewSampleFragment : Fragment(), ItemClickListener {

    private lateinit var sampleAdapter: RecyclerViewSampleAdapter
    private var samples = ArrayList<FragmentType>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        samples.add(FragmentType.COUNTER_SAMPLE)
        samples.add(FragmentType.EDITOR_SAMPLE)
        samples.add(FragmentType.IMAGE_VIEW_SAMPLE)
        samples.add(FragmentType.BOTTOM_NAVIGATION_SAMPLE)
        samples.add(FragmentType.WEBVIEW_SAMPLE)
        sampleAdapter = RecyclerViewSampleAdapter(samples, this)
        setHasOptionsMenu(true)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.recyclerview_layout, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recycler_view.apply {
            this.layoutManager = LinearLayoutManager(context)
            this.adapter = sampleAdapter
        }
    }

    override fun onItemSelected(fragmentType: FragmentType) {
        (activity?.application as App).mainActivity?.addFragment(fragmentType)
    }

    companion object {
        // TODO: Customize parameter initialization
        @JvmStatic
        fun newInstance() = RecyclerViewSampleFragment()
    }
}

onCreate(): Making the items to show in RecyclerView and initializing adapter
onCreateView(): specify the layout including RecyclerView
onViewCreated(): Apply layoutManager and adapter to RecyclerView
onItemSelected(): Custom Interface of notifying list click event

Adapter

Android Studio creates Adapter like this

class MyItemRecyclerViewAdapter(
        private val mValues: List<DummyItem>,
        private val mListener: OnListFragmentInteractionListener?)
    : RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {

    private val mOnClickListener: View.OnClickListener

    init {
        mOnClickListener = View.OnClickListener { v ->
            val item = v.tag as DummyItem
            // Notify the active callbacks interface (the activity, if the fragment is attached to
            // one) that an item has been selected.
            mListener?.onListFragmentInteraction(item)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.fragment_item2, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = mValues[position]
        holder.mIdView.text = item.id
        holder.mContentView.text = item.content

        with(holder.mView) {
            tag = item
            setOnClickListener(mOnClickListener)
        }
    }

    override fun getItemCount(): Int = mValues.size

    inner class ViewHolder(val mView: View) : RecyclerView.ViewHolder(mView) {
        val mIdView: TextView = mView.item_number
        val mContentView: TextView = mView.content

        override fun toString(): String {
            return super.toString() + " '" + mContentView.text + "'"
        }
    }
}

init: Add and do something on when adapter created.

onCreateViewHolder(): Specify the layout using as the cell

onBindViewHolder(): Set data to the view

getItemCount(): return the number of Items in array.

ViewHolder Class: Define the view to use (Which need to be defined on cell's layout

On my case:

class RecyclerViewSampleAdapter(private val samples: ArrayList<FragmentType>, private val listener: ItemClickListener) : RecyclerView.Adapter<RecyclerViewSampleAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_item, parent, false))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = samples[position]
        holder.sampleView.text = item.title
        holder.itemView.setOnClickListener { listener.onItemSelected(item) }
    }

    override fun getItemCount() = samples.size

    inner class ViewHolder(mView: View) : RecyclerView.ViewHolder(mView) {
        val sampleView: TextView = mView.sample_name
    }
}

If list itens are selected, ItemClickListener will notify the event and gives selected item.

Cell Layout

Based on Android Studio created

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

    <TextView
        android:id="@+id/sample_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/text_margin"
        android:textAppearance="?attr/textAppearanceListItem" />
</LinearLayout>

RecyclerView Layout

Based on Android Studio created

recyclerview_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView 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:id="@+id/recycler_view"
    android:name="galaxysoftware.sampleapp.fragment.RecyclerViewSamplesFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="16dp"
    android:layout_marginRight="16dp"
    android:background="#FFFFFF"
    app:layoutManager="android.support.v7.widget.LinearLayoutManager"
    tools:context=".fragment.RecyclerViewSampleFragment"
    tools:listitem="@layout/recyclerview_item" />

Ready

Now, it's ready to use RecyclerView
Also I'll recommend to remove DummyContent in dummy package which Android Studio created.

What should you do when the list items are changed?

You need to notify adapter, what is changed.
Here are the methods to notify
AdapterNotifyMethods.png
Example: If you want to refresh everything, then use adapter.notifyDataSetChanged() (On my case, sampleAdapter.notifyDataSetChanged()

Use with Swipe Refresh

You might see many times that list is refreshed by Swiping down. Let's make this possible
First, edit the layout to this

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView 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:id="@+id/refresh_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:name="galaxysoftware.sampleapp.fragment.RecyclerViewSamplesFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:background="#FFFFFF"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        tools:context=".fragment.RecyclerViewSampleFragment"
        tools:listitem="@layout/recyclerview_item" />
</android.support.v7.widget.RecyclerView>

Then add this to fragment

refresh_list.setOnRefreshListener {
    //Refresh items

    sampleAdapter.notifyDataSetChanged()
    refresh_list.isRefreshing = false
}

Refresh items and call notifyDataSetChanged(), refresh_list.isRefreshing = false
When to call notify and end refreshing is depends on how you refresh items.
So the case if you are requesting JSON result from the API, you have to wait for the response before notifying and end refreshing to avoid unexpected behavior.

Project:

Code: https://github.com/GalaxyDevGamer/Android-Samples/blob/master/app/src/main/java/galaxysoftware/androidsamples/fragment/RecyclerViewSampleFragment.kt
Layout: https://github.com/GalaxyDevGamer/Android-Samples/blob/master/app/src/main/res/layout/recyclerview_layout.xml

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2