使用自定义分隔符自动完成搜索视图
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>
[已解决] 是否可以在 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>