package carbon.widget; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.util.AttributeSet; import java.util.ArrayList; import java.util.List; import carbon.R; import carbon.internal.SimpleTextWatcher; /** * This implementation extends EditText directly and uses TextWatcher for tracking text changes. * This class can be used to create new material search fields with drop down menus separated by a * seam. */ public class SearchEditText extends EditText { private OnFilterListener onFilterListener; private String prevText = ""; public void setDataProvider(SearchDataProvider dataProvider) { this.dataProvider = dataProvider; } public interface SearchDataProvider<Type> { int getItemCount(); Type getItem(int i); String[] getItemWords(int i); } protected TextWatcher searchTextWatcher; SearchDataProvider dataProvider; public SearchEditText(Context context) { super(context, null, R.attr.carbon_searchEditTextStyle); initSearchEditText(); } /** * XML constructor. Gets default parameters from R.attr.carbon_editTextStyle. * * @param context * @param attrs */ public SearchEditText(Context context, AttributeSet attrs) { super(context, attrs, R.attr.carbon_searchEditTextStyle); initSearchEditText(); } public SearchEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, R.attr.carbon_searchEditTextStyle); initSearchEditText(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public SearchEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, R.attr.carbon_searchEditTextStyle, defStyleRes); initSearchEditText(); } private void initSearchEditText() { searchTextWatcher = new SimpleTextWatcher() { @Override public void afterTextChanged(Editable text) { if (!prevText.equals(text.toString())) search(); prevText = text.toString(); } }; addTextChangedListener(searchTextWatcher); } private void search() { if (dataProvider == null) { return; } String currentWord = getText().toString(); if (currentWord.length() == 0) { fireOnFilterEvent(null); return; } filter(currentWord); fireOnFilterEvent(filteredItems); } private void fireOnFilterEvent(List filteredItems) { if (onFilterListener != null) onFilterListener.onFilter(filteredItems); } @Override public void setText(CharSequence text, BufferType type) { prevText = getText().toString(); super.setText(text, type); } @Override public Editable getText() { try { return super.getText(); } catch (ClassCastException e) { return new SpannableStringBuilder(""); } } List filteredItems = new ArrayList(); public void filter(String word) { filteredItems.clear(); if (word.length() == 0) return; for (int i = 0; i < dataProvider.getItemCount(); i++) { String[] itemWords = dataProvider.getItemWords(i); matchItem(word, i, itemWords); } } private void matchItem(String word, int i, String[] itemWords) { for (int j = 0; j < itemWords.length; j++) { String itemText = itemWords[j].toLowerCase(); if (itemText.indexOf(word) == 0) { filteredItems.add(dataProvider.getItem(i)); return; } else if (partialMatch(itemText, word)) { filteredItems.add(dataProvider.getItem(i)); return; } } } private boolean partialMatch(String item, String word) { // item: 'lemon', text: 'le' int i = 0, j = 0; String text = word.toLowerCase(); for (; i < item.length() && j < text.length(); i++) { if (item.charAt(i) == text.charAt(j)) j++; } return j == text.length(); } public interface OnFilterListener { void onFilter(List filterResults); } public void setOnFilterListener(OnFilterListener onFilterListener) { this.onFilterListener = onFilterListener; } public List getFilteredItems() { return filteredItems; } }