When a requirement in your application, you want to implement search functionality by using SearchView widget inside the Toolbar for filtering RecyclerView. In this tutorial we will see the logic for filtering items in RecyclerView.
Screen Shots:
1. Build Gradle
Add the following lines to the dependencies in your app build.gradle file and sync. We are using RecyclerView, Design, CardView support libraris.
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.1.0' compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4' testCompile 'junit:junit:4.12' compile 'com.android.support:cardview-v7:25.1.0' compile 'com.android.support:recyclerview-v7:25.1.0' compile 'com.android.support:design:25.1.0' }
2. Menu
Add the following menu_home.xml and menu_search.xml under res/menu folder.
File: menu_home.xml
File: menu_search.xml
File: menu_home.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_search" android:orderInCategory="0" android:title="@string/action_search" android:icon="@drawable/ic_search" app:showAsAction="always"/> <item android:id="@+id/action_status" android:orderInCategory="10" android:title="@string/action_status" app:showAsAction="never"/> <item android:id="@+id/action_settings" android:orderInCategory="11" android:title="@string/action_settings" app:showAsAction="never"/> </menu>
File: menu_search.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_filter_search" android:title="Search" app:actionViewClass="android.support.v7.widget.SearchView" android:icon="@drawable/ic_search" app:showAsAction="collapseActionView|always" /> </menu>
3. XML Layout
Create xml layout file in res/layout and name it toolbar.xml, this includes toolbar widget.
File: toolbar.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:layout_alignParentTop="true" android:background="@color/colorPrimary" app:titleTextColor="@color/colorTextPrimary" />
Create xml layout file in res/layout and name it search_toolbar.xml, this includes toolbar widget.
File: search_toolbar.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/searchtoolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:layout_alignParentTop="true" android:background="@color/colorTextPrimary" app:collapseIcon="@drawable/ic_arrow_back" app:titleTextColor="@color/colorPrimary" />
open activity_main.xml layout and add the following views.
File: activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_search_view_check" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.prasad.searchviewex.MainActivity"> <include layout="@layout/toolbar" /> <include layout="@layout/search_toolbar" android:visibility="gone" /> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/toolbar" android:scrollbars="none" /> </RelativeLayout>
create xml layout file in res/layout and name it item_row.xml, this layout defines item of recycler view.
File: item_row.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="1dp" android:orientation="horizontal" card_view:cardCornerRadius="1dp" card_view:cardUseCompatPadding="true"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/txt_Name" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:padding="10dp" android:text="Item Name" android:textSize="18dp" /> </RelativeLayout> </android.support.v7.widget.CardView>
4. Values
Open colors.xml file under res/values, add the below colors.
File: colors.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#065E52</color> <color name="colorPrimaryDark">#054E43</color> <color name="colorAccent">#96BCB7</color> <color name="colorTextPrimary">#FFFFFF</color> <color name="colorTextDisable">#66FFFFFF</color> </resources>
Open strings.xml file under res/values, and add the below resources.
File: strings.xml
<resources> <string name="app_name">SearchViewEx</string> <string name="action_search">Search</string> <string name="action_status">Status</string> <string name="action_settings">Settings</string> </resources>
Open styles.xml file under res/values, and add the below items.
File: styles.xml
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="windowNoTitle">true</item> <item name="windowActionBar">false</item> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="android:textColorSecondary">@android:color/white</item> <item name="actionBarStyle">@style/AppBarOverlay</item> </style> <style name="AppBarOverlay" parent="Widget.AppCompat.Light.ActionBar.Solid"> <item name="elevation">0dp</item> </style> </resources>
5. Java Class
Create a Model.java POJO class which defines the data for items of recycler view.File: Model.java
package com.prasad.searchviewex; import java.io.Serializable; public class Model implements Serializable { private String name; private String version; public Model(String name, String version) { this.name = name; this.version = version; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } }
6. Adapter
Create a class ModelAdapter.java, this class must extend RecyclerView.Adapter. This adapter follows the view holder design pattern.- Add a constructor to adapter to handle data, by using that data we can displays recycler view.
- RecyclerView.Adapter has three abstract methods, those three methods we must override.
- getItemCount(): This method return number of items present in recycler view.
- onCreateViewHolder(): Inside this method we specify the layout that each item of recycler view should use it.
- onBindViewHolder(): In this method we can set the string value to TextView.
- I have created one custom method name as setFilter(), In this method we can updating adapter with search filter items.
package com.prasad.searchviewex; 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; public class ModelAdapter extends RecyclerView.Adapter<ModelAdapter.ViewHolder> { public ArrayList<Model> os_version; public ModelAdapter(ArrayList<Model> arrayList) { os_version = arrayList; } @Override public ModelAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // create a new view View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_row, null); // create ViewHolder ViewHolder viewHolder = new ViewHolder(view); return viewHolder; } @Override public void onBindViewHolder(ModelAdapter.ViewHolder holder, int position) { holder.nameView.setText(os_version.get(position).getName()); } @Override public int getItemCount() { return os_version.size(); } public static class ViewHolder extends RecyclerView.ViewHolder { TextView nameView; public ViewHolder(View view) { super(view); nameView = (TextView) view.findViewById(R.id.txt_Name); } } public void setFilter(ArrayList<Model> arrayList) { os_version = new ArrayList<>(); os_version.addAll(arrayList); notifyDataSetChanged(); } }
7. MainActivity
Open MainActivity.java class file and follow the custom search view.
- Customizing Search View: The following code snippet helps you to change colors, icons and also the text.
public void initSearchView() { final SearchView searchView = (SearchView) search_menu.findItem(R.id.action_filter_search).getActionView(); // Enable Submit button searchView.setSubmitButtonEnabled(true); // Change search close button image ImageView closeButton = (ImageView) searchView.findViewById(R.id.search_close_btn); closeButton.setImageResource(R.drawable.ic_close); // Set hint and the text colors EditText txtSearch = ((EditText) searchView.findViewById(android.support.v7.appcompat.R.id.search_src_text)); txtSearch.setHint("Search.."); txtSearch.setHintTextColor(Color.DKGRAY); txtSearch.setTextColor(getResources().getColor(R.color.colorPrimary)); // Set the cursor AutoCompleteTextView searchTextView = (AutoCompleteTextView) searchView.findViewById(android.support.v7.appcompat.R.id.search_src_text); try { Field mCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes"); mCursorDrawableRes.setAccessible(true); mCursorDrawableRes.set(searchTextView, R.drawable.search_cursor); //This sets the cursor resource ID to 0 or @null which will make it visible on white background } catch (Exception e) { e.printStackTrace(); } }
- SearchView Events: For handling SearchView events, just add QueryTextListener. In this listener contains the callback methods, which handles the query text change and submit actions.
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { final ArrayList<Model> filteredModelList = filter(os_version, query); mAdapter.setFilter(filteredModelList); searchView.clearFocus(); return true; } @Override public boolean onQueryTextChange(String newText) { final ArrayList<Model> filteredModelList = filter(os_version, newText); mAdapter.setFilter(filteredModelList); return true; } });
- Circular Reveal Animation: Here I am going to implementing code for Circular Animation. This code is applicable only for action bar Search View. Because we need to find out the origin of the circle for the circular animation.
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void circleReveal(int viewID, int posFromRight, boolean containsOverflow, final boolean isShow) {
final View myView = findViewById(viewID);
int width = myView.getWidth();
if (posFromRight > 0)
width -= (posFromRight * getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_material)) - (getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_material) / 2);
if (containsOverflow)
width -= getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_overflow_material);
int cx = width;
int cy = myView.getHeight() / 2;
Animator anim;
if (isShow)
anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, (float) width);
else
anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, (float) width, 0);
anim.setDuration((long) 220);
// make the view invisible when the animation is done
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (!isShow) {
super.onAnimationEnd(animation);
myView.setVisibility(View.INVISIBLE);
}
}
});
// make the view visible and start the animation
if (isShow)
myView.setVisibility(View.VISIBLE);
// start the animation
anim.start();
}
Use the below code you can show and hide the Search View.
File: MainActivity.java// To reveal a previously invisible view
circleReveal(R.id.searchtoolbar,1,true,true);
// To hide a previously visible view
circleReveal(R.id.searchtoolbar,1,true,false);
package com.prasad.searchviewex; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.support.annotation.RequiresApi; import android.support.v4.view.MenuItemCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewAnimationUtils; import android.widget.AutoCompleteTextView; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import java.lang.reflect.Field; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { Toolbar toolbar, searchtollbar; Menu search_menu; private RecyclerView recyclerView; MenuItem item_search; ArrayList<Model> os_version = new ArrayList<>(); ModelAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initControls(); } private void initControls() { toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); setSearchToolbar(); recyclerView = (RecyclerView) findViewById(R.id.recycler_view); os_version.add(new Model("Alpha", "version 1")); os_version.add(new Model("Beta", "version 1")); os_version.add(new Model("Cup Cake", "version 1")); os_version.add(new Model("Donut", "version 1.6")); os_version.add(new Model("Eclair", "version 2.1")); os_version.add(new Model("Froyo", "version 2.2")); os_version.add(new Model("Ginger Bread", "version 2.3")); os_version.add(new Model("Honycomb", "version 3.0")); os_version.add(new Model("Icecream Sandwhich", "version 4.0")); os_version.add(new Model("Jelly Bean", "version 4.1")); os_version.add(new Model("Kitkat", "version 4.4")); os_version.add(new Model("Lolly Pop", "version 5.0")); os_version.add(new Model("Marsh Mallow", "version 6.0")); os_version.add(new Model("Nougat", "version 7.0")); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(this)); mAdapter = new ModelAdapter(os_version); recyclerView.setAdapter(mAdapter); } @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.menu_home, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle item selection switch (item.getItemId()) { case R.id.action_status: Toast.makeText(this, "Home Status Click", Toast.LENGTH_SHORT).show(); return true; case R.id.action_search: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) circleReveal(R.id.searchtoolbar, 1, true, true); else searchtollbar.setVisibility(View.VISIBLE); item_search.expandActionView(); return true; case R.id.action_settings: Toast.makeText(this, "Home Settings Click", Toast.LENGTH_SHORT).show(); return true; default: return super.onOptionsItemSelected(item); } } public void setSearchToolbar() { searchtollbar = (Toolbar) findViewById(R.id.searchtoolbar); if (searchtollbar != null) { searchtollbar.inflateMenu(R.menu.menu_search); search_menu = searchtollbar.getMenu(); searchtollbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) circleReveal(R.id.searchtoolbar, 1, true, false); else searchtollbar.setVisibility(View.GONE); } }); item_search = search_menu.findItem(R.id.action_filter_search); MenuItemCompat.setOnActionExpandListener(item_search, new MenuItemCompat.OnActionExpandListener() { @Override public boolean onMenuItemActionCollapse(MenuItem item) { // Do something when collapsed if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { circleReveal(R.id.searchtoolbar, 1, true, false); } else searchtollbar.setVisibility(View.GONE); return true; } @Override public boolean onMenuItemActionExpand(MenuItem item) { // Do something when expanded return true; } }); initSearchView(); } else Log.d("toolbar", "setSearchtollbar: NULL"); } public void initSearchView() { final SearchView searchView = (SearchView) search_menu.findItem(R.id.action_filter_search).getActionView(); // Enable/Disable Submit button in the keyboard searchView.setSubmitButtonEnabled(false); // Change search close button image ImageView closeButton = (ImageView) searchView.findViewById(R.id.search_close_btn); closeButton.setImageResource(R.drawable.ic_close); // set hint and the text colors EditText txtSearch = ((EditText) searchView.findViewById(android.support.v7.appcompat.R.id.search_src_text)); txtSearch.setHint("Search.."); txtSearch.setHintTextColor(Color.DKGRAY); txtSearch.setTextColor(getResources().getColor(R.color.colorPrimary)); // set the cursor AutoCompleteTextView searchTextView = (AutoCompleteTextView) searchView.findViewById(android.support.v7.appcompat.R.id.search_src_text); try { Field mCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes"); mCursorDrawableRes.setAccessible(true); mCursorDrawableRes.set(searchTextView, R.drawable.search_cursor); //This sets the cursor resource ID to 0 or @null which will make it visible on white background } catch (Exception e) { e.printStackTrace(); } searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { final ArrayList<Model> filteredModelList = filter(os_version, query); mAdapter.setFilter(filteredModelList); searchView.clearFocus(); return true; } @Override public boolean onQueryTextChange(String newText) { final ArrayList<Model> filteredModelList = filter(os_version, newText); mAdapter.setFilter(filteredModelList); return true; } }); } private ArrayList<Model> filter(ArrayList<Model> models, String search_txt) { search_txt = search_txt.toLowerCase(); final ArrayList<Model> filteredModelList = new ArrayList<>(); for (Model model : models) { final String text = model.getName().toLowerCase(); if (text.contains(search_txt)) { filteredModelList.add(model); } } return filteredModelList; } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void circleReveal(int viewID, int posFromRight, boolean containsOverflow, final boolean isShow) { final View myView = findViewById(viewID); int width = myView.getWidth(); if (posFromRight > 0) width -= (posFromRight * getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_material)) - (getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_material) / 2); if (containsOverflow) width -= getResources().getDimensionPixelSize(R.dimen.abc_action_button_min_width_overflow_material); int cx = width; int cy = myView.getHeight() / 2; Animator anim; if (isShow) anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, (float) width); else anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, (float) width, 0); anim.setDuration((long) 220); // make the view invisible when the animation is done anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (!isShow) { super.onAnimationEnd(animation); myView.setVisibility(View.INVISIBLE); } } }); // make the view visible and start the animation if (isShow) myView.setVisibility(View.VISIBLE); // start the animation anim.start(); } }
8. AndroidManifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.prasad.searchviewex"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>