Skip to content

5.x | Search Filter

Davide Steduto edited this page Jul 31, 2016 · 28 revisions

In this page

  • Introduction
  • Configuration
  • Special behaviors on action
  • Performance result
  • Setup the SearchView widget
  • 3rd libraries and floating SearchView

Introduction

To collect items based on the searchText, the item must implement the interface IFilterable or it will be simply skipped. The searchText will be propagated to the custom implementation of the filter() method.

Important note: PASS ALWAYS A COPY OF THE ORIGINAL LIST: due to internal mechanism, items are removed and/or added in order to animate items in the final list.

Items that implement IExpandable interface with a non-empty sub list of children items, will be automatically scanned by the Adapter and sub items picked up if searchText has a match.

If you don't want to implement the IFilterable interface on the items, then, you can override the method filterObject(item, constraint) to have another filter logic!

Configuration

SearchText methods
  • hasSearchText(): Checks if the current searchText is not empty nor null.
  • hasNewSearchText(): Checks if the searchText is changed with a new text.
  • getSearchText(): The current search text.
  • setSearchText(new_text): Sets the new search text.
filterItems with or without delay?
  • filterItems(unfilteredItems): The method filters immediately the provided list with the search text previously set with setSearchText(). This gives a prompt responsiveness but more work and battery will be consumed.
  • filterItems(unfilteredItems, delay): The execution of the filter will be delayed of few milliseconds (values of 200-400ms are acceptable), useful to grab more characters from user before starting the filter.

In general items are always animated according to the limit below here.

setAnimateToLimit (from 5.0.0-b8)

Tunes the limit after the which the synchronization animations, occurred during updateDataSet and filter operations, are skipped and notifyDataSetChanged() will be called instead. Default value is 600 items, number of new items.

setNotifyChangeOfUnfilteredItems

Sometimes it is necessary, while filtering or after the data set has been updated, to rebound the items that remain unfiltered.
If the items have highlighted text, those items must be refreshed in order to change the text back to normal. This happens systematically when searchText is reduced in length by the user.
The notification (notifyItemChanged()) is triggered when items are not added nor deleted. Default value is false.

setNotifyMoveOfFilteredItems (from 5.0.0-b8)

This method performs a further step to nicely animate the moved items. The process is very slow on big list of the order of 3000-5000 items and higher, due to the calculation of the correct position for each item to be shifted. Use with caution!
The slowness is more visible when the searchText is cleared out after filtering or update data set. Default value is false.

highlightText

From class Utils, sets a spannable text with the accent color (if available) into the provided TextView. Accent color is automatically fetched.

Special behaviors on action

  • From beta8, the filter is 100% asynchronous, it uses the internal AsyncTask supporting a very high-volume of items.
  • Sticky Header is disabled during the filter operation and restored when searchText returns empty.
  • If searchText is empty or null, the provided list is the current list.
  • When only sub items are collected, headers/parents are displayed too.
  • Expandable item will collapse/expand only the filtered sub items.
  • You should disable the refresh and the addition of the items.
  • Restoring deleted items with Undo functionality enabled:
    • Deleting items before the filter starts, restoring them while the filter is active, it makes the restored items to be displayed if part of the current filtered collection and at the correct position.
    • Starting the filter and removing items, then restoring them with no filter active, it makes the restored items to be displayed at the correct position too.
  • Screen rotation is also supported but, the search will be performed again, unless you program differently the basic initialization.

Performance result (when animations are active)

  • Tested with 💯000 items in the emulator:
10:58:02.225 5003-5003/D/MainActivity: onQueryTextChange newText: 7
10:58:02.435 5003-5098/I/FilterAsyncTask: doInBackground - started Filter
10:58:02.435 5003-5098/V/FlexibleAdapter: FilterItems with searchText=7
10:58:03.535 5003-5098/V/FlexibleAdapter: Animate changes! oldSize=100000 newSize=40951
10:58:04.917 5003-5098/V/FlexibleAdapter: calculateRemovals total out=59050
10:58:04.940 5003-5098/V/FlexibleAdapter: calculateAdditions total new=0
10:58:04.940 5003-5098/I/FilterAsyncTask: doInBackground - ended Filter
10:58:04.946 5003-5003/V/FlexibleAdapter: Performing 100000 notifications
10:58:05.401 5003-5003/D/MainActivity: onUpdateEmptyView size=40951

From the moment the background process starts it takes ~2.5s + ~0.5s to perform remove/insert notifications.

  • This, instead, is the test with my "old" Samsung S3 running Android 6:
13:06:32.775 28019-28019/D/MainActivity: onQueryTextChange newText: 1
13:06:32.990 28019-28464/I/FilterAsyncTask: doInBackground - started FILTER
13:06:32.990 28019-28464/V/FlexibleAdapter: filterItems with searchText=1
13:06:33.350 28019-28464/V/FlexibleAdapter: Animate changes! oldSize=10000 newSize=3440
13:06:33.445 28019-28464/V/FlexibleAdapter: calculateRemovals total out=6560
13:06:33.465 28019-28464/V/FlexibleAdapter: calculateAdditions total new=0
13:06:33.465 28019-28464/I/FilterAsyncTask: doInBackground - ended FILTER
13:06:33.465 28019-28019/V/FlexibleAdapter: Performing 10000 notifications
13:06:33.690 28019-28019/D/MainActivity: onUpdateEmptyView size=3440

As you see it takes 700ms to select 3.440 items when filtering a character in a list of 10.000 items.

You can play with the FragmentAsyncFilter to familiarize with the options of the filter.

Under the hood
The queue of notifications is executed in onPostExecute().
Also using the LinkedHashSet instead of List made a big improvement itself. Regarding this aspect, it is strongly recommended to implement hashCode() method coherent with your implementation of equals() method in your IFlexible items.

Setup the SearchView widget (code snipped from the demoApp)

To setup the Android widget SearchView the following files must be properly configured:

**manifests/**AndroidManifest.xml

...
<activity android:name=".MainActivity">
	<meta-data
		android:name="android.app.searchable"
		android:resource="@xml/searchable"/>
	<intent-filter>
		<action android:name="android.intent.action.SEARCH"/>
	</intent-filter>
</activity>
...

**xml/**searchable.xml

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
	android:label="@string/app_name"
	android:searchMode="showSearchIconAsBadge"
	android:hint="@string/action_search"/>

**menu/**menu_filter.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">

	<!-- Search, should appear as action button -->
	<item android:id="@+id/action_search"
	      android:title="@string/action_search"
	      android:icon="@drawable/ic_action_search"
	      app:showAsAction="collapseActionView|always"
	      android:animateLayoutChanges="true"
	      app:actionViewClass="android.support.v7.widget.SearchView"/>

</menu>

MainActivity.java

public class MainActivity extends AppCompatActivity implements SearchView.OnQueryTextListener {
...
}
@Override
public boolean onQueryTextChange(String newText) {
	if (mAdapter.hasNewSearchText(newText)) {
		Log.d(TAG, "onQueryTextChange newText: " + newText);
		mAdapter.setSearchText(newText);
		//Fill and Filter mItems with your custom list and automatically animate the changes
		//Watch out! The original list must be a copy
		mAdapter.filterItems(DatabaseService.getInstance().getDatabaseList(), 200L);
	}
	//Disable SwipeRefresh if search is active!!
	mSwipeRefreshLayout.setEnabled(!mAdapter.hasSearchText());
	return true;
}

@Override
public boolean onQueryTextSubmit(String query) {
	Log.v(TAG, "onQueryTextSubmit called!");
	return onQueryTextChange(query);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
	super.onCreateOptionsMenu(menu, inflater);
	inflater.inflate(R.menu.menu_filter, menu);
	initSearchView(menu);
}

private void initSearchView(final Menu menu) {
	//Associate searchable configuration with the SearchView
	SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
	MenuItem searchItem = menu.findItem(R.id.action_search);
	if (searchItem != null) {
		MenuItemCompat.setOnActionExpandListener(
				searchItem, new MenuItemCompat.OnActionExpandListener() {
					@Override
					public boolean onMenuItemActionExpand(MenuItem item) {
						MenuItem listTypeItem = menu.findItem(R.id.action_list_type);
						if (listTypeItem != null)
							listTypeItem.setVisible(false);
						hideFab();
						return true;
					}

					@Override
					public boolean onMenuItemActionCollapse(MenuItem item) {
						MenuItem listTypeItem = menu.findItem(R.id.action_list_type);
						if (listTypeItem != null)
							listTypeItem.setVisible(true);
						showFab();
						return true;
					}
				});
		mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
		mSearchView.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER);
		mSearchView.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_FULLSCREEN);
		mSearchView.setQueryHint(getString(R.string.action_search));
		mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
		mSearchView.setOnQueryTextListener(this);
	}
}

3rd libraries and floating SearchView

I've found several interesting libraries that implement the floating SearchView (below listed with my personal evaluation), they use a custom adapter and a layout to display the result with suggestions too. Still didn't try them out with FlexibleAdapter, but in theory no conflict with this library.

arimorty/floatingsearchview (9/10)
A search view that implements a floating search bar also known as persistent search.

renaudcerrato/FloatingSearchView (7/10)
Yet another floating search view implementation, also known as persistent search.

lapism/SearchView (7/10)
Persistent SearchView Library in Material Design.

crysehillmes/PersistentSearchView (6/10)
A library that implements Google Play like PersistentSearch view.

edsilfer/custom-searchable (6/10)
This repository contains a library that aims to provide a custom searchable interface for android applications.

sahildave/Search-View-Layout (5/10)
Search View Layout like Lollipop Dialer.

Clone this wiki locally