使用自定义分隔符自动完成搜索视图

Auto complete search view with custom delimiter

[已解决] 是否可以在 ActionBar(android.support.v7.app) 像 MultiAutoCompleteTextView?

在自定义分隔符之后建议像这里这样的词。

我没有扩展 MultiAutoCompleteTextView,因为我不想失去一些功能,例如图标、"X" 按钮。

更新:

我尝试扩展 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);
    }
}

和 xml 菜单文件。

<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>

和activity方法:

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;
}

但出于某种原因,我在这里得到了一个 NPE:searchView.setQueryHint("Type any word");,所以这意味着 getActionView returns null

很多小时过去了,我找到了解决方案:

自定义适配器:

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(); 
            } 
        } 
    } 
} 

扩展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方法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 菜单文件:

<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>