I am writing on Kotlin RecyclerView SearchView Example Tutorial.
You will learn to implement Search Filter functionality in the RecyclerView in Android using Kotlin.
When there are many items in the recycler view, search filter functionality helps user to find his desired item quickly.
First, see the following for output reference.
Step 1. New Studio Project
Make a new project in android studio with empty activity as a default one.
Select Kotlin as the primary source language for the entire project.
Step 2. Gradle Changes
Open your build.gradle(Module:app) file and add the following lines in it
implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:cardview-v7:28.0.0'
First line is integrating the classes for recycler view. We will be able to use Recycler view with the help of this line.
Second line is doing the same thing for card view. Card view helps us to make better design with recycler view.
Step 3. XML In Drawable
Navigate to the app->res->drawable folder and create a new XML file in this drawable folder.
Set the name of the XML file as cardview.xml . You need to add the following code lines in this cardview.xml file.
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true"> <shape android:shape="rectangle"> <padding android:left="4dp" android:top="4dp" android:right="4dp" android:bottom="4dp" /> <gradient android:startColor="#b71ca2" android:endColor="#cc2ec1" android:angle="270" /> <corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/> </shape> </item> <item android:state_focused="false" > <shape android:shape="rectangle"> <padding android:left="4dp" android:top="4dp" android:right="4dp" android:bottom="4dp" /> <gradient android:startColor="#b71ca2" android:endColor="#cc2ec1" android:angle="270" /> <corners android:topLeftRadius="4dp" android:topRightRadius="4dp" /> </shape> </item> </selector>
This file creating some gradient effects with various colors. Using this file, we can design our card view more efficiently.
Using this file, we can also add corners to our card view. At any time, you can change the colors or a radius size.
Step 4. Special XML for Recycler Items And Model
Make a new XML resources file in app->res->layout directory and give it a name like rv_item.xml
You should write down the following source line in rv_item.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" tools:context=".MainActivity"> <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="10dp" card_view:cardCornerRadius="4dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/tv2" android:height="40dp" android:background="@drawable/cardview" android:gravity="center_vertical" android:paddingLeft="10dp" android:text="" android:textColor="#fff" android:textStyle="bold" android:textSize="18sp" /> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/tv" android:background="#000" android:height="40dp" android:gravity="center_vertical" android:paddingLeft="10dp" android:text="Image" android:textColor="#fff" android:textStyle="bold" android:textSize="18sp" /> </LinearLayout> </android.support.v7.widget.CardView> </RelativeLayout>
This file will help us to create the look and feel of the every row item of the recycler view.
A file cardview.xml that we have created in the drawable folder is used here in the first text view.
Now make a new Kotlin file and give it a name like SearchModel.kt
Below source lines for SearchModel.kt file.
class SearchModel { var name: String? = null fun getNames(): String { return name.toString() } fun setNames(name: String) { this.name = name } }
A string variable name is defined in this model file.
For this variable, I have written getter and setter methods. These methods will help to maintain the data during search activity is going on.
Step 5. Adapter For Search
Make a new class and give it a name as SearchAdapter.kt
You need to write down the following code snippet in this file.
import android.content.Context import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import java.util.ArrayList import java.util.Locale /** * Created by Parsania Hardik on 26-Jun-17. */ class SearchAdapter(ctx: Context, private val imageModelArrayList: ArrayList<SearchModel>) : RecyclerView.Adapter<SearchAdapter.MyViewHolder>() { private val inflater: LayoutInflater private val arraylist: ArrayList<SearchModel> init { inflater = LayoutInflater.from(ctx) this.arraylist = ArrayList<SearchModel>() this.arraylist.addAll(MainActivity.imageModelArrayList) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchAdapter.MyViewHolder { val view = inflater.inflate(R.layout.rv_item, parent, false) return MyViewHolder(view) } override fun onBindViewHolder(holder: SearchAdapter.MyViewHolder, position: Int) { holder.time.setText(imageModelArrayList[position].getNames()) } override fun getItemCount(): Int { return imageModelArrayList.size } inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var time: TextView init { time = itemView.findViewById(R.id.tv) as TextView } } // Filter Class fun filter(charText: String) { var charText = charText charText = charText.toLowerCase(Locale.getDefault()) MainActivity.imageModelArrayList.clear() if (charText.length == 0) { MainActivity.imageModelArrayList.addAll(arraylist) } else { for (wp in arraylist) { if (wp.getNames().toLowerCase(Locale.getDefault()).contains(charText)) { MainActivity.imageModelArrayList.add(wp) } } } notifyDataSetChanged() } }
See the following first
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchAdapter.MyViewHolder { val view = inflater.inflate(R.layout.rv_item, parent, false) return MyViewHolder(view) }
Above code is of onCreateViewHolder() method. It is inflating the rv_item.xml file. Using this line compiler will create the view for every child item of the recycler view.
Read the following now
override fun onBindViewHolder(holder: SearchAdapter.MyViewHolder, position: Int) { holder.time.setText(imageModelArrayList[position].getNames()) }
A method onBindViewHolder() will set the text into the text view. For this, it will imageModelArayList which is the data source. Adapter is getting this arraylist from the parameter.
We will create this arraylist in the main activity to see which kind of data it contains.
Now check the following method
// Filter Class fun filter(charText: String) { var charText = charText charText = charText.toLowerCase(Locale.getDefault()) MainActivity.imageModelArrayList.clear() if (charText.length == 0) { MainActivity.imageModelArrayList.addAll(arraylist) } else { for (wp in arraylist) { if (wp.getNames().toLowerCase(Locale.getDefault()).contains(charText)) { MainActivity.imageModelArrayList.add(wp) } } } notifyDataSetChanged() }
This function is the main and important portion of the whole example.
As it’s name suggests, filter() method will filter the recycler view on the basis of it’s name.
Whenever the user types the search query, compiler will call the above function. It will send the search query in to the parameter of this method.
Based on this search query, compiler will update the items in the imageModelArraList and then it will call the notifyDataSetChanged() method.
notifyDataSetChanged() method will simply simply update the recycler view as per the new imageModelArraList.
Step 6. Last but Main Activity
Now this is the last step of our example. Go to the activity_main.xml file and add the below code in it
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <SearchView android:id="@+id/search" android:layout_width="fill_parent" android:layout_height="wrap_content" android:iconifiedByDefault="false"> <requestFocus /> </SearchView> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="15dp"/> </LinearLayout>
It has only two UI widgets. One is search view and another is recycler view.
Search view is above the Recycler view so that user can type the search query and it improves over all user experience.
Now in the MainActivity.kt file, you should write the following data lines
import android.content.Context import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.util.Log import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.widget.SearchView import android.widget.Toast import java.util.ArrayList class MainActivity : AppCompatActivity(), SearchView.OnQueryTextListener { private var recyclerView: RecyclerView? = null private var adapter: SearchAdapter? = null private var editsearch: SearchView? = null private val myImageNameList = arrayOf("Benz", "Bike", "Car", "Carrera", "Ferrari", "Harly", "Lamborghini", "Silver") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) recyclerView = findViewById(R.id.recycler) as RecyclerView imageModelArrayList = populateList() Log.d("hjhjh", imageModelArrayList.size.toString() + "") adapter = SearchAdapter(this, imageModelArrayList) recyclerView!!.adapter = adapter recyclerView!!.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false) recyclerView!!.addOnItemTouchListener( RecyclerTouchListener( applicationContext, recyclerView!!, object : ClickListener { override fun onClick(view: View, position: Int) { Toast.makeText(this@MainActivity, imageModelArrayList[position].getNames(), Toast.LENGTH_SHORT) .show() } override fun onLongClick(view: View?, position: Int) { } }) ) editsearch = findViewById(R.id.search) as SearchView editsearch!!.setOnQueryTextListener(this) } override fun onQueryTextSubmit(query: String): Boolean { return false } override fun onQueryTextChange(newText: String): Boolean { adapter!!.filter(newText) return false } private fun populateList(): ArrayList<SearchModel> { val list = ArrayList<SearchModel>() for (i in 0..7) { val imageModel = SearchModel() imageModel.setNames(myImageNameList[i]) list.add(imageModel) } return list } interface ClickListener { fun onClick(view: View, position: Int) fun onLongClick(view: View?, position: Int) } internal class RecyclerTouchListener( context: Context, recyclerView: RecyclerView, private val clickListener: ClickListener? ) : RecyclerView.OnItemTouchListener { private val gestureDetector: GestureDetector init { gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { return true } override fun onLongPress(e: MotionEvent) { val child = recyclerView.findChildViewUnder(e.x, e.y) if (child != null && clickListener != null) { clickListener.onLongClick(child, recyclerView.getChildPosition(child)) } } }) } override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { val child = rv.findChildViewUnder(e.x, e.y) if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) { clickListener.onClick(child, rv.getChildPosition(child)) } return false } override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {} override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { } } companion object { lateinit var imageModelArrayList: ArrayList<SearchModel> } }
See the following
private var recyclerView: RecyclerView? = null private var adapter: SearchAdapter? = null private var editsearch: SearchView? = null private val myImageNameList = arrayOf("Benz", "Bike", "Car", "Carrera", "Ferrari", "Harly", "Lamborghini", "Silver")
Compiler will first create the objects of recycler view, Search adapter and Search View classes.
Then an string array myImageNameList is there. It contains the names of the various vehicles. We will filter the recycler view based on this vehicle names.
See the below
imageModelArrayList = populateList()
This line will use the populateList() method to create the data source. Below is the code for populateList() method
private fun populateList(): ArrayList<SearchModel> { val list = ArrayList<SearchModel>() for (i in 0..7) { val imageModel = SearchModel() imageModel.setNames(myImageNameList[i]) list.add(imageModel) } return list }
It will create one arraylist which contains the objects of the SearchModel class. Then one for loop is there.
This for loop will create an object of the SearchModel class in it’s every iteration and it will bind one vehicle name to each object. Then all objects are added into the arraylist.
Read the below code
recyclerView!!.addOnItemTouchListener( RecyclerTouchListener( applicationContext, recyclerView!!, object : ClickListener { override fun onClick(view: View, position: Int) { Toast.makeText(this@MainActivity, imageModelArrayList[position].getNames(), Toast.LENGTH_SHORT) .show() } override fun onLongClick(view: View?, position: Int) { } }) )
Compiler will run the above when the user clicks the recycler view. It will simply create the Toast. This Toast contains the name of the clicked Vehicle.
Now focus on the below
editsearch = findViewById(R.id.search) as SearchView editsearch!!.setOnQueryTextListener(this)
Compiler will first find the Search View using id. Then it will set the onQuery change.
It will run the following when the user types the query
override fun onQueryTextChange(newText: String): Boolean { adapter!!.filter(newText) return false }
Compiler will execute the method filter() which is written in the SearchAdapter class.
Download Code for Kotlin RecyclerView SearchView
https://github.com/demonuts/Kotlin-RecyclerView-SearchView-Example-Tutorial-Search-Filter