package com.cleveroad.sample; import android.content.Context; import android.database.DataSetObserver; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.widget.Filter; import java.util.Collections; import java.util.List; /** * Base filter that can be easily integrated with {@link BaseRecyclerViewAdapter}.<br/><br/> * For iterating through adapter's data use {@link #getNonFilteredCount()} and {@link #getNonFilteredItem(int)}. */ abstract class BaseFilter<T> extends Filter { private FilterableAdapter<T> adapter; private CharSequence lastConstraint; private FilterResults lastResults; private DataSetObserver dataSetObserver; private RecyclerView.AdapterDataObserver adapterDataObserver; private int highlightColor; public BaseFilter(@NonNull Context context) throws AssertionError { highlightColor = ContextCompat.getColor(context, R.color.colorAccent); } public BaseFilter(int highlightColor) throws AssertionError { setHighlightColor(highlightColor); } public BaseFilter setHighlightColor(int highlightColor) throws AssertionError { this.highlightColor = highlightColor; return this; } void init(@NonNull FilterableAdapter<T> adapter) throws AssertionError { this.adapter = adapter; dataSetObserver = new DataSetObserver() { @Override public void onChanged() { super.onChanged(); if (!isFiltered()) return; performFiltering(lastConstraint); } @Override public void onInvalidated() { super.onInvalidated(); if (!isFiltered()) return; lastResults = new FilterResults(); lastResults.count = -1; lastResults.values = Collections.emptyList(); } }; adapterDataObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { super.onChanged(); if (!isFiltered()) return; performFiltering(lastConstraint); } }; } protected int getNonFilteredCount() { return adapter.getNonFilteredCount(); } protected T getNonFilteredItem(int position) { return adapter.getNonFilteredItem(position); } @NonNull @Override protected final FilterResults performFiltering(CharSequence constraint) { return performFilteringImpl(constraint); } /** * Perform filtering as always. Returned {@link FilterResults} object must be non-null. * @param constraint the constraint used to filter the data * @return filtering results. <br /> * You can set {@link FilterResults#count} to -1 to specify that no filtering was applied.<br /> * {@link FilterResults#values} must be instance of {@link List}. */ @NonNull protected abstract FilterResults performFilteringImpl(CharSequence constraint); @Override protected final void publishResults(CharSequence constraint, FilterResults results) throws AssertionError { lastConstraint = constraint; lastResults = results; adapter.notifyDataSetChanged(); } public boolean isFiltered() { return lastResults != null && lastResults.count > -1; } @SuppressWarnings("unchecked") public T getItem(int position) throws ArrayIndexOutOfBoundsException { return ((List<T>)lastResults.values).get(position); } public int getCount() { return lastResults.count; } public DataSetObserver getDataSetObserver() { return dataSetObserver; } public RecyclerView.AdapterDataObserver getAdapterDataObserver() { return adapterDataObserver; } public Spannable highlightFilteredSubstring(String name) { SpannableString string = new SpannableString(name); if (!isFiltered()) return string; String filteredString = lastConstraint.toString().trim().toLowerCase(); String lowercase = name.toLowerCase(); int length = filteredString.length(); int index = -1, prevIndex; do { prevIndex = index; index = lowercase.indexOf(filteredString, prevIndex + 1); if (index == -1) { break; } string.setSpan(new ForegroundColorSpan(highlightColor), index, index + length, 0); } while (true); return string; } interface FilterableAdapter<T> { int getNonFilteredCount(); T getNonFilteredItem(int position); void notifyDataSetChanged(); void withFilter(@Nullable BaseFilter<T> filter); boolean isFiltered(); Spannable highlightFilteredSubstring(String text); } }