Search code examples
javaandroidautocompleteandroid-actionbarsearchview

Auto complete search view with custom delimiter


[SOLVED] Is it possible to make SearchView(android.support.v7.widget) in the ActionBar(android.support.v7.app) like a MultiAutoCompleteTextView?

Suggesting words like here, after custom delimiter.

I didn't extend MultiAutoCompleteTextView because I didn't want to lose some features like icon, "X" button.

enter image description here

UPDATE:

I tried to extend SearchView:

public class MultiAutoCompleteSearchView extends android.support.v7.widget.SearchView {

    private MultiAutoCompleteSearchView.SearchAutoComplete mSearchAutoComplete;

    public static class SearchAutoComplete extends android.support.v7.widget.SearchView.SearchAutoComplete {

        private String mSeparator = "+";

        public SearchAutoComplete(Context context) {
            super(context);
        }
        public SearchAutoComplete(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
        public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

        @Override
        protected void replaceText(CharSequence text) {
            String newText = getText().toString();
            if (newText.contains(mSeparator)) {
                int lastIndex = newText.lastIndexOf(mSeparator);
                newText = newText.substring(0, lastIndex + 1) + text.toString();
            } else {
                newText = text.toString();
            }
            super.replaceText(newText);
        }

        @Override
        protected void performFiltering(CharSequence text, int keyCode) {
            String newText = text.toString();
            if (newText.indexOf(mSeparator) != -1) {
                int lastIndex = newText.lastIndexOf(mSeparator);
                if (lastIndex != newText.length() - 1) {
                    newText = newText.substring(lastIndex + 1).trim();
                    if (newText.length() >= getThreshold()) {
                        text = newText;
                    }
                }
            }
            super.performFiltering(text, keyCode);
        }
    }

    public void initialize() {
        mSearchAutoComplete = (MultiAutoCompleteSearchView.SearchAutoComplete)
                findViewById(android.support.v7.appcompat.R.id.search_src_text);
        this.setAdapter(null);
        this.setOnItemClickListener(null);
    }

    public MultiAutoCompleteSearchView(Context context) {
        super(context);
        initialize();
    }

    public MultiAutoCompleteSearchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    public MultiAutoCompleteSearchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize();
    }

    public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {
        mSearchAutoComplete.setOnItemClickListener(listener);
    }

    public void setAdapter(ArrayAdapter<?> adapter) {
        mSearchAutoComplete.setAdapter(adapter);
    }
}

And xml file for menu.

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      tools:context=".SearchActivity">
    <item android:id="@+id/action_search"
          android:title="@string/action_search"
          android:icon="@drawable/ic_action_search"
          app:showAsAction="ifRoom|collapseActionView"
          app:actionViewClass="cullycross.com.searchview.MultiAutoCompleteSearchView" />
</menu>

And activity method:

public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_search, menu);

    final MenuItem searchItem = menu.findItem(R.id.action_search);
    final MultiAutoCompleteSearchView searchView = (MultiAutoCompleteSearchView)
            MenuItemCompat.getActionView(searchItem);
    searchView.setQueryHint("Type any word");
    MultiAutoCompleteSearchView.SearchAutoComplete searchAutoComplete =
            (MultiAutoCompleteSearchView.SearchAutoComplete)searchView
                    .findViewById(R.id.search_src_text);
    searchAutoComplete.setAdapter(new ArrayAdapter<String>(
            this,
            android.R.layout.simple_dropdown_item_1line,
            options
    ));
    return true;
}

But for some reason I get a NPE here: searchView.setQueryHint("Type any word");, so it means, that getActionView returns null.


Solution

  • A lot of hours are passed, I found a solution:

    Custom adapter:

    public class DelimiterAdapter extends ArrayAdapter<String> implements Filterable { 
        private final static String [] options = { 
            "Apple","Mango","Peach","Banana","Orange","Grapes","Watermelon","Tomato" 
        }; 
        private final LayoutInflater mInflater; 
        private List<String> mSubStrings; 
    
        private String mMainString; 
        public String getMainString() { return mMainString; } 
    
        private AmazingFilter mFilter; 
    
        public DelimiterAdapter(Context context, int resource) { 
            super(context, -1); 
            mInflater = LayoutInflater.from(context); 
        } 
    
        @Override 
        public View getView(int position, View convertView, ViewGroup parent) { 
            final TextView tv; 
            if (convertView != null) { 
                tv = (TextView) convertView; 
            } else { 
                tv = (TextView) mInflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false); 
            } 
            tv.setText(getItem(position)); 
            return tv; 
        } 
    
        @Override 
        public int getCount() { 
            return mSubStrings.size(); 
        } 
    
        @Override 
        public String getItem(int position) { 
            return mSubStrings.get(position); 
        } 
    
        @Override 
        public long getItemId(int position) { 
            return position; 
        } 
    
    
        @Override 
        public Filter getFilter() { 
            if(mFilter == null) { 
                mFilter = new AmazingFilter(); 
            } 
            return mFilter; 
        } 
    
        private class AmazingFilter extends Filter { 
    
            private final static String DELIMITER = "+"; 
    
            @Override 
            protected FilterResults performFiltering(CharSequence constraint) { 
                final FilterResults filterResults = new FilterResults(); 
                String request; 
                mSubStrings = new ArrayList<String>(); 
                if(constraint != null) { 
                    request = constraint.toString(); 
    
                    //cuts the string with delimiter 
                    if (request.contains(DELIMITER) && 
                            request.lastIndexOf(DELIMITER) != request.length() - 1) { 
                        final String[] splitted = request.split("\\" + DELIMITER); 
                        request = splitted[splitted.length - 1].trim(); 
    
                        //save string before delimiter 
                        int index = constraint.toString().lastIndexOf(request); 
                        mMainString = constraint.toString().substring(0, index); 
                    } else { 
                        request = request.trim(); 
                        mMainString = ""; 
                    } 
    
    
                    //checks for substring of any word in the dictionary 
                    for(String s : options) { 
                        if(s.contains(request)) { 
                            mSubStrings.add(s); 
                        } 
                    } 
                } 
                filterResults.values = mSubStrings; 
                filterResults.count = mSubStrings.size(); 
                return filterResults; 
            } 
    
            @Override 
            protected void publishResults(CharSequence constraint, FilterResults results) { 
                clear(); 
                for (String request : (ArrayList<String>)results.values) { 
                    add(request); 
                } 
                if (results.count > 0) { 
                    notifyDataSetChanged(); 
                } else { 
                    notifyDataSetInvalidated(); 
                } 
            } 
        } 
    } 
    

    Extended SearchView:

    public class MultiAutoCompleteSearchView extends android.support.v7.widget.SearchView { 
    
        private SearchAutoComplete mSearchAutoComplete; 
    
        public void initialize() { 
            mSearchAutoComplete = (SearchAutoComplete) 
                findViewById(android.support.v7.appcompat.R.id.search_src_text); 
            this.setAdapter(null); 
            this.setOnItemClickListener(null); 
        } 
    
        public MultiAutoCompleteSearchView(Context context) { 
            super(context); 
            initialize(); 
        } 
    
        public MultiAutoCompleteSearchView(Context context, AttributeSet attrs) { 
            super(context, attrs); 
            initialize(); 
        } 
    
        public MultiAutoCompleteSearchView(Context context, AttributeSet attrs, int defStyleAttr) { 
            super(context, attrs, defStyleAttr); 
            initialize(); 
        } 
    
        public void setOnItemClickListener(AdapterView.OnItemClickListener listener) { 
            mSearchAutoComplete.setOnItemClickListener(listener); 
        } 
    
        public void setAdapter(ArrayAdapter<?> adapter) { 
            mSearchAutoComplete.setAdapter(adapter); 
        } 
    }
    

    Activity method onCreateMenuOptions

    @Override 
    public boolean onCreateOptionsMenu(Menu menu) { 
        // Inflate the menu; this adds items to the action bar if it is present. 
        getMenuInflater().inflate(R.menu.menu_search, menu); 
    
        final MenuItem searchItem = menu.findItem(R.id.action_search); 
    
        final MultiAutoCompleteSearchView searchView = (MultiAutoCompleteSearchView) 
            MenuItemCompat.getActionView(searchItem); 
    
        searchView.setQueryHint("Type any word"); 
    
        MultiAutoCompleteSearchView.SearchAutoComplete searchAutoComplete = 
            (MultiAutoCompleteSearchView.SearchAutoComplete)searchView 
                    .findViewById(R.id.search_src_text); 
        //since words are very short 
        searchAutoComplete.setThreshold(1); 
    
        searchAutoComplete.setAdapter(new DelimiterAdapter( 
                this, 
                android.R.layout.simple_dropdown_item_1line 
        )); 
    
        searchView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 
            @Override 
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 
    
                String stringBefore, newString; 
                stringBefore = ((DelimiterAdapter)parent.getAdapter()).getMainString(); 
                newString = parent.getAdapter().getItem(position).toString(); 
    
                searchView.setQuery(stringBefore+newString, false); 
            } 
        }); 
    
    
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 
            @Override 
            public boolean onQueryTextSubmit(String s) { 
                if(s.length() > 0) { 
                    mFindString = s; 
    
                    // do smth with string
                    return true; 
                } 
                return false; 
            } 
    
            @Override 
            public boolean onQueryTextChange(String s) { 
                return false; 
            } 
        }); 
    
        return true; 
    } 
    

    XML file of the menu:

    <menu xmlns:android="http://schemas.android.com/apk/res/android" 
          xmlns:app="http://schemas.android.com/apk/res-auto" 
          xmlns:tools="http://schemas.android.com/tools" 
          tools:context=".SearchActivity"> 
        <item android:id="@+id/action_search" 
          android:title="@string/action_search" 
          android:icon="@drawable/ic_action_search" 
          app:showAsAction="ifRoom|collapseActionView" 
          app:actionViewClass="cullycross.com.searchview.MultiAutoCompleteSearchView" /> 
    </menu>