
1. Build Gradle
file : build.gradle
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.1.0' }
2. Menu
file :main_ menu.xml
<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:icon="@android:drawable/ic_menu_search" android:title="@string/action_search" app:actionViewClass="androidx.appcompat.widget.SearchView" app:showAsAction="always|collapseActionView" /> </menu>
I this tutorial we will create RecyclerView in one of Tabs of TabLayout , Creating TabLayout is outside the scope of this tutorial , In the previous tutorial I have covered how to create TabLayout you can refer for it .
3. XML Layout
Create XML layout file in res/layout and name it tab_one_fragment.xml and Add RecyclerView inside RelativeLayout.
file : tab_one_fragment.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/fragment_bg"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> </RelativeLayout>
Create XML Layout file in res/layout and name it list_row.xml , This Layout defines the layout for Items of RecyclerView . Here In this example we will add two TextView inside LinearLayout .
file : list_row.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:padding="16dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/country_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Name" android:textSize="20sp" /> <TextView android:id="@+id/country_iso" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="ISo" android:textSize="16sp"/> </LinearLayout>
4. Java Model
Create a Bean Class CountryModel which defines the data for items of recyclerview .
file : CountryModel.java
package com.tutorialsbuzz.recyclerview.TabFragments; public class CountryModel { String name; String isocode; CountryModel(String name, String isocode){ this.name=name; this.isocode=isocode; } public String getName() { return name; } public String getisoCode() { return isocode; } }
5. RecylerViewAdapter
file : RVAdapter.java
package com.tutorialsbuzz.recyclerview.TabFragments; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.recyclerview.widget.RecyclerView; import com.tutorialsbuzz.recyclerview.R; import java.util.ArrayList; import java.util.List; public class RVAdapter extends RecyclerView.Adapter<ItemViewHolder> { private List<CountryModel> mCountryModel; public RVAdapter(List<CountryModel> countryModel) { mCountryModel = new ArrayList<>(countryModel); } @Override public void onBindViewHolder(ItemViewHolder itemViewHolder, int i) { final CountryModel model = mCountryModel.get(i); itemViewHolder.bind(model); } @Override public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_row, viewGroup, false); return new ItemViewHolder(view); } @Override public int getItemCount() { return mCountryModel.size(); } /** Filter Logic**/ public void animateTo(List<CountryModel> models) { applyAndAnimateRemovals(models); applyAndAnimateAdditions(models); applyAndAnimateMovedItems(models); } private void applyAndAnimateRemovals(List<CountryModel> newModels) { for (int i = mCountryModel.size() - 1; i >= 0; i--) { final CountryModel model = mCountryModel.get(i); if (!newModels.contains(model)) { removeItem(i); } } } private void applyAndAnimateAdditions(List<CountryModel> newModels) { for (int i = 0, count = newModels.size(); i < count; i++) { final CountryModel model = newModels.get(i); if (!mCountryModel.contains(model)) { addItem(i, model); } } } private void applyAndAnimateMovedItems(List<CountryModel> newModels) { for (int toPosition = newModels.size() - 1; toPosition >= 0; toPosition--) { final CountryModel model = newModels.get(toPosition); final int fromPosition = mCountryModel.indexOf(model); if (fromPosition >= 0 && fromPosition != toPosition) { moveItem(fromPosition, toPosition); } } } public CountryModel removeItem(int position) { final CountryModel model = mCountryModel.remove(position); notifyItemRemoved(position); return model; } public void addItem(int position, CountryModel model) { mCountryModel.add(position, model); notifyItemInserted(position); } public void moveItem(int fromPosition, int toPosition) { final CountryModel model = mCountryModel.remove(fromPosition); mCountryModel.add(toPosition, model); notifyItemMoved(fromPosition, toPosition); } }
We have already familiar with RecyclerView Adapter Creation ,So in this part we will concentrate on Filter logic.
You can filter items of ListView and RecyclerView By calling notifyDatasetChanged() on the Adapter , But the RecyclerView brings a whole new element to the table which we can take advantage off: out-of-the-box support for item animations!
Define three helper methods removeItem() , addItem() and moveItem() which enable us to add, remove or move items respectively around in the Adapter and Inside These methods we will call the required notify method on the adapter notifyItemRemoved() , notifyItemInserted() and notifyItemMoved() Respectively to trigger the item animation that goes along with it.
Create a method animateTo() that takes the filtered list of data object as parameter and makes call to three methods applyAndAnimateRemovals() , applyAndAnimateAdditions() and applyAndAnimateMovedItems() by passing the filtered list as parameter. three methods do all the work here, but the order is important .
What animateTo() method do ?
The first step is to remove all items which do not exist in the filtered List anymore.
The next step is to add all items which did not exist in the original List but do in the filtered List.
The final step is to move all items which exist in both Lists. This is exactly what the three methods in animateTo() do.
- applyAndAnimateRemovals() : Inside this method iterate through the internal List of the Adapter backwards and checks if each item is contained in the new filtered List. If it is not it calls removeItem() .
- applyAndAnimateAdditions() : Inside this method iterate through the internal List of the Adapter it iterates through the filtered List and checks if the item exists in the internal List. If it does not it calls addItem().
- applyAndAnimateMovedItems() : Inside this method iterate through the filtered List backwards and looks up the index of each item in the internal List. If it detects a difference in the index it calls moveItem() to bring the internal List of the Adapter in line with the filtered List .
6. View Holder
file : ItemViewHolder.javapackage com.tutorialsbuzz.recyclerview.TabFragments; import android.view.View; import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import com.tutorialsbuzz.recyclerview.R; public class ItemViewHolder extends RecyclerView.ViewHolder { private TextView name_TextView; private TextView iso_TextView; public ItemViewHolder(View itemView) { super(itemView); name_TextView = (TextView) itemView.findViewById(R.id.country_name); iso_TextView = (TextView) itemView.findViewById(R.id.country_iso); } public void bind(CountryModel countryModel) { name_TextView.setText(countryModel.getName()); iso_TextView.setText(countryModel.getisoCode()); } }
6. MainActivity
- Create a TabOneFragment which extends Fragment .
- Inside the onCreateView method get the reference to recyclerview and set the LayoutManager as LayoutLayout.
- Inside the OnActivityCreate method call to setAdpter on recyclerview instance and pass the instance of above define RVAdapter as argument.
7. Filtering Logic
- Inside the onCreateOptionMenu get the reference of searchView widget and set onQueryTextListener on it .
- Upon setting onQueryTextListener on SearchView override the onQueryTextChange and onQueryTextSubmit method.
- I have Created a method filter which takes list of CountryModel and query string as parameter , inside this method we will filter the CountryModel list on one of the constraints of CountryModel (here in this example i am filter the list on name constrain , if you want you can filter on isocode ) and return the filtered list .
- Inside the onQueryTextChange we make a call to filter method and get the return filtered list this which in-turn passed as a argument while make call to setFilter of RVAdapter .
Note : When you perform filter and then Switch between tabs in TabLayout In such scenario the recyclerview won't return the original set of data so to overcome this you have set the onActionExapndListener on MenuItem and then inside the onMenuItemActionCollapse make a call to animateTo() method on adapter instance by passing the original list as argument .
file : TabOneFragment.java
package com.tutorialsbuzz.recyclerview.TabFragments; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import androidx.appcompat.widget.SearchView; import androidx.core.view.MenuItemCompat; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.tutorialsbuzz.recyclerview.R; import java.util.ArrayList; import java.util.List; import java.util.Locale; public class TabOneFragment extends Fragment implements SearchView.OnQueryTextListener { private RecyclerView recyclerview; private List<CountryModel> mCountryModel; private RVAdapter adapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.tab_one_fragment, container, false); recyclerview = (RecyclerView) view.findViewById(R.id.recyclerview); LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); recyclerview.setLayoutManager(layoutManager); return view; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setHasOptionsMenu(true); String[] locales = Locale.getISOCountries(); mCountryModel = new ArrayList<>(); for (String countryCode : locales) { Locale obj = new Locale("", countryCode); mCountryModel.add(new CountryModel(obj.getDisplayCountry(), obj.getISO3Country())); } adapter = new RVAdapter(mCountryModel); recyclerview.setAdapter(adapter); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_main, menu); final MenuItem item = menu.findItem(R.id.action_search); final SearchView searchView = (SearchView) MenuItemCompat.getActionView(item); searchView.setOnQueryTextListener(this); item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionCollapse(MenuItem item) { // Do something when collapsed adapter.animateTo(mCountryModel); return true; // Return true to collapse action view } @Override public boolean onMenuItemActionExpand(MenuItem item) { // Do something when expanded return true; // Return true to expand action view } }); } @Override public boolean onQueryTextChange(String newText) { final List<CountryModel> filteredModelList = filter(mCountryModel, newText); adapter.animateTo(filteredModelList); recyclerview.scrollToPosition(0); return true; } @Override public boolean onQueryTextSubmit(String query) { return false; } private List<CountryModel> filter(List<CountryModel> models, String query) { query = query.toLowerCase(); final List<CountryModel> filteredModelList = new ArrayList<>(); for (CountryModel model : models) { final String text = model.getName().toLowerCase(); if (text.contains(query)) { filteredModelList.add(model); } } return filteredModelList; } }
8 comments:
Excellent tutorial, thanks
Brililant tutorial, thanks!
One quation, after searching, by clicking back button I want to return initial RecyclerView with all item without filter
Excelent! Very well explained!
How to use custom list in search view...
How to implement image with text in searchview
Please have to show how to implement onclick event thank you.
[…] RecyclerView Item Filter With Animation. […]
simply nice... thanks
Post a Comment