Friday 17 February 2017

Filter RecyclerView Using SearchView In Toolbar

       
           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


<?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.         
  1.  getItemCount(): This method return number of items present in recycler view.
  2.   onCreateViewHolder(): Inside this method we specify the layout that each item of recycler view should use it.
  3. 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.
File: ModelAdapter.java


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.


// 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);

File: MainActivity.java


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>


Spinner example using AppCompatSpinner widget

          When we have a requirement to use spinner(drop down) to show list of objects in your application. Here we will see how to use Ap...