LAST UPDATE [February 1st, 2022]
Today, I will show you how to search for items in a RecyclerView using SearchView.
In this example, we have a RecyclerView already set up, and the only thing we do is to add the searching functionality.
We have a list of countries as sample data and using the search field we get the country we want faster.
Contents
Adding SearchView in RecyclerView
Add the SearchView in your layout, inside a LinearLayout with a vertical orientation.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/country_content_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.appcompat.widget.SearchView
android:id="@+id/country_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:textCursorDrawable="@null"
app:iconifiedByDefault="false"
app:queryBackground="@null" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/country_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark" />
</LinearLayout>
Code language: HTML, XML (xml)
In the RecyclerView adapter (In this example, RecyclerViewAdapter.kt), create an ArrayList with the name countryFilterList, and pass all the items…
class RecyclerViewAdapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var countryFilterList = ArrayList<String>()
init {
countryFilterList = countryList
}
// ...
}
Code language: Kotlin (kotlin)
Return the size of countryFilterList so every time will return the right amount of items that match the characters you type in the SearchView.
class RecyclerViewAdapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// ...
override fun getItemCount(): Int {
return countryFilterList.size
}
// ...
}
Code language: Kotlin (kotlin)
Now, in the onBindViewHolder get the item for each row from the countryFilterList array.
class RecyclerViewAdapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// ...
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// ...
val countryHolder = holder as CountryHolder
countryHolder.viewBinding.selectCountryText.text = countryFilterList[position]
// ...
}
}
Code language: Kotlin (kotlin)
Add the Filterable class in the RecyclerViewAdapter and the method getFilter()
class RecyclerViewAdapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {
override fun getFilter(): Filter {
}
}
Code language: Kotlin (kotlin)
Inside the getFilter() method, return a Filter() object:
class RecyclerViewAdapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
}
@Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
}
}
}
}
Code language: Kotlin (kotlin)
The performFiltering method checks if we have typed a text in the SeachView.
If there is not any text, will return all items.
If there is a text, then we check if the characters match the items from the list and return the results in a FilterResults type.
The publishResults get these results, pass them to the countryFilterList array, and update the RecyclerView.
class RecyclerViewAdapter(private var countryList: ArrayList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val charSearch = constraint.toString()
if (charSearch.isEmpty()) {
countryFilterList = countryList
} else {
val resultList = ArrayList<String>()
for (row in countryList) {
if (row.lowercase(Locale.ROOT).contains(charSearch.lowercase(Locale.ROOT))) {
resultList.add(row)
}
}
countryFilterList = resultList
}
val filterResults = FilterResults()
filterResults.values = countryFilterList
return filterResults
}
@Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
countryFilterList = results?.values as ArrayList<String>
notifyDataSetChanged()
}
}
}
}
Code language: Kotlin (kotlin)
Go back to the Activity file (MainActivity.kt) and add the setOnQueryTextListener.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// ...
country_search.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
adapter.filter.filter(newText)
return false
}
})
// ...
}
}
Code language: Kotlin (kotlin)
The onQueryTextChange method is called every time we type on the SearchView and update the RecyclerView with the new results.
Customizing the SearchView
If you want to change the color of the search icon (magnifying glass):
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// ...
val searchIcon = binding.countrySearch.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
searchIcon.setColorFilter(Color.WHITE)
// ...
}
}
Code language: Kotlin (kotlin)
For the color of the cancel button:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// ...
val cancelIcon = binding.countrySearch.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
cancelIcon.setColorFilter(Color.WHITE)
// ...
}
}
Code language: Kotlin (kotlin)
And to change the text color of the TextView:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
// ...
val textView = binding.countrySearch.findViewById<TextView>(androidx.appcompat.R.id.search_src_text)
textView.setTextColor(Color.WHITE)
// If you want to change the color of the cursor, change the 'colorAccent' in colors.xml
// ...
}
}
Code language: Kotlin (kotlin)
You can find the final project here
If you have any questions, please feel free to leave a comment below