简单使用
AutoCompleteTextView是当用户输入一个字的时候,该控件会自动搜索与输入内容匹配的内容,并以列表项呈现。AutoCompleteTextView的实现是,继承EditText,当输入文本时弹出一个ListPopupWindow。
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<AutoCompleteTextView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/search_title_edit_bg_shape"
android:drawableLeft="@mipmap/search_title_search_icon"
android:drawablePadding="5dp"
android:imeOptions="actionSearch"
android:inputType="text"
android:padding="10dp"
android:textColor="#666666" />
</LinearLayout>
Activity
String[] NAME = {"Android", "Java", "Android", "Java"};
private AutoCompleteTextView mAutoCompleteTextView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple);
mAutoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.search_view);
mAutoCompleteTextView.setAdapter(new ArrayAdapter<>(this,
android.R.layout.simple_dropdown_item_1line, NAME));
}
使用起来就是这么简单,就这些代码。刚才看到输入2个字才会弹出ListPopupWindow,可以通过setThreshold()方法来改变输入的字数
当然除了这个还可以设置很多其他的属性
@attr ref android.R.styleable#AutoCompleteTextView_completionHint
@attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
@attr ref android.R.styleable#AutoCompleteTextView_completionHintView
@attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
@attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
@attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
@attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
@attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset
@attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset
自定义一个Adapter来使用
有时ArrayAdapter不能满足需求时,只能通过自定义来实现了
要实现一个这样的效果,当没有输入文本时显示历史搜索的商品,当输入文本时去做网络请求,把后台传过来的数据展示出来,并把,头部的“历史搜索,搜索到的商品”改成“搜索到的商品”和去掉尾部的“清除历史记录”。
首先需要继承AutoCompleteTextView实现父类的几个方法。如果输入文本要弹出窗口,也就是说需要调用setThreshold(0),然而这样并不行,看一下AutoCompleteTextView方法的实现就知道为什么了
public void setThreshold(int threshold) {
if (threshold <= 0) {
threshold = 1;
}
mThreshold = threshold;
}
所以需要自定义,改变threshold
public class CustomAutoComplete extends AutoCompleteTextView {
private int threshold;
public CustomAutoComplete(Context context) {
super(context);
}
public CustomAutoComplete(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomAutoComplete(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onFocusChanged(boolean focused, int direction,
Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
if (focused) {
performFiltering(getText(), 0);
showDropDown();
}
}
@Override
public boolean enoughToFilter() {
return true;
}
@Override
public void setThreshold(int threshold) {
if (threshold < 0) {
threshold = 0;
}
this.threshold = threshold;
}
@Override
public int getThreshold() {
return threshold;
}
@Override
public void onFilterComplete(int count) {
// super.onFilterComplete(count);
}
}
自定义Adapter必须要实现Filterable接口,并在getFilter提供一个Filter。我把这个Adapter分为了3部分,头部(搜索到的商品)、中间(显示搜索到的数据)、尾部(清除历史记录)
先贴一下Filter
@Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new ArrayFilter();
}
return mFilter;
}
private class ArrayFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (mOriginalValues == null || mOriginalValues.isEmpty()) {
results.values = null;
results.count = 0;
return results;
}
if (prefix == null || prefix.length() == 0) {
ArrayList<String> list = new ArrayList<String>(mOriginalValues);
results.values = list;
results.count = list.size();
return results;
} else {
String prefixString = prefix.toString().toLowerCase();
final int count = mOriginalValues.size();
final ArrayList<String> newValues = new ArrayList<String>(count);
for (int i = 0; i < count; i++) {
final String value = mOriginalValues.get(i);
final String valueText = value.toLowerCase();
newValues.add(value);
// if (valueText.startsWith(prefixString)) { //源码 ,匹配开头
// newValues.add(value);
// }
// else {
// final String[] words = valueText.split(" ");//分隔符匹配,效率低
// final int wordCount = words.length;
//
// for (int k = 0; k < wordCount; k++) {
// if (words[k].startsWith(prefixString)) {
// newValues.add(value);
// break;
// }
// }
// }
}
results.values = newValues;
results.count = newValues.size();
}
return results;
}
@Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
mObjects = mOriginalValues;
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
注释掉的部分是不同的匹配规则,由于我是后台传过来什么,我就显示什么,所以我这里只是模拟了一下筛选。
public class SearchListAdapter extends BaseAdapter implements Filterable {
private Context mContext;
private String mHeaderText;//头部的提示文本
private boolean mHasFooter;//是否显示底部
private AdapterView.OnItemClickListener mOnItemClickListener;
private View.OnClickListener mClearHistoryOnClick;
private ArrayFilter mFilter;
private List<String> mOriginalValues;//所有的Item
private List<String> mObjects;//过滤后的item
public SearchListAdapter(Context context, List<String> date) {
mContext = context;
mOriginalValues = date;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ListViewHolder helper = ListViewHolder.get(mContext, convertView, parent, R.layout.list_item_search, position);
View headerRootView = helper.getView(R.id.search_header_root_view);
TextView headerTv = helper.getView(R.id.search_header_tv);
View centerRootView = helper.getView(R.id.search_center_root_view);
TextView centerTv = helper.getView(R.id.search_tv);
View footerRootView = helper.getView(R.id.search_footer_root_view);
TextView footerTv = helper.getView(R.id.search_clear_history_tv);
if (position == 0) {//头部
headerTv.setText(mHeaderText);
headerRootView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
headerRootView.setVisibility(View.VISIBLE);
centerRootView.setVisibility(View.GONE);
footerRootView.setVisibility(View.GONE);
} else if (position == getCount() - 1 && mHasFooter) {//尾部
footerRootView.setVisibility(View.VISIBLE);
centerRootView.setVisibility(View.GONE);
headerRootView.setVisibility(View.GONE);
footerTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mClearHistoryOnClick != null) {
mClearHistoryOnClick.onClick(v);
}
}
});
} else {//中间
centerTv.setText(mObjects.get(position - 1));//减一,减去头部
centerRootView.setVisibility(View.VISIBLE);
headerRootView.setVisibility(View.GONE);
footerRootView.setVisibility(View.GONE);
centerTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(null, v, helper.getPosition(), v.getId());
}
}
});
}
return helper.getConvertView();
}
/** 更新请求到的数据 */
public void upData(List<String> data) {
mOriginalValues = data;
mFilter.filter("");
}
public void setHeaderText(String text) {
mHeaderText = text;
}
public void setHasFooter(boolean hasFooter) {
if (mHasFooter != hasFooter) {
mHasFooter = hasFooter;
}
}
private int getFooterCount() {
return mHasFooter ? 1 : 0;
}
@Override
public String getItem(int position) {
if (mObjects != null && mObjects.size() > position - 1 && position > 0) {
return mObjects.get(position - 1);
} else {
return "";
}
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public int getCount() {
if (mObjects == null || mObjects.isEmpty()) {
return 0;
}
return mObjects.size() + 1 + getFooterCount();
}
public void setOnItemClickListener(AdapterView.OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
public void setClearHistoryOnClick(View.OnClickListener onClick) {
this.mClearHistoryOnClick = onClick;
}
}
给头部设置一个,没有任何意义的OnClickListener是因为AutoCompleteTextView的ListPopupWindow有默认的onItemClickListener,如果这里不拦截的话就会调用默认的onItemClickListener了
Activity
public class CustomActivity extends AppCompatActivity {
public static final String NAME_LIST = "name_list";
private CustomAutoComplete mCustomAutoComplete;
private View mLine;
private SearchListAdapter mAdapter;
private String mSearchStr;
private boolean isOnItemClick;//当点击搜索后请求到的商品名称不需要再弹popupWindow
private SharedPreferences mSp;
private SharedPreferences.Editor mEditor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom);
mSp = getSharedPreferences(getPackageName() + "_sp", Context.MODE_PRIVATE);
mEditor = mSp.edit();
mCustomAutoComplete = (CustomAutoComplete) findViewById(R.id.search_view);
mLine = findViewById(R.id.title_search_line_view);
mAdapter = new SearchListAdapter(this, getHistorySearchStr());
//设置popupWindow的宽度
mCustomAutoComplete.setDropDownWidth(getScreenWidth());
mCustomAutoComplete.setAdapter(mAdapter);
mCustomAutoComplete.setThreshold(0);
mLine.post(new Runnable() {
@Override
public void run() {
//计算popupWindow显示的位置
float offset = mLine.getBottom() - mCustomAutoComplete.getBottom();
mCustomAutoComplete.setDropDownVerticalOffset((int) offset);
}
});
mCustomAutoComplete.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mCustomAutoComplete.getText().toString().length() == 0){
mAdapter.setHeaderText(getResources().getString(R.string.search_goods_history));
mAdapter.setHasFooter(true);
mAdapter.upData(getHistorySearchStr());
}
return false;
}
});
mCustomAutoComplete.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
mSearchStr = s.toString();
if (mSearchStr.length() == 0){
mAdapter.setHeaderText(getResources().getString(R.string.search_goods_history));
mAdapter.setHasFooter(true);
mAdapter.upData(getHistorySearchStr());
if (!mCustomAutoComplete.isPopupShowing()){
mCustomAutoComplete.showDropDown();
}
}else {
//访问接口
if (!isOnItemClick) {
refresh();
}
isOnItemClick = false;
}
}
});
//当点击键盘上的搜索时
mCustomAutoComplete.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
//搜索
String searchStr = mCustomAutoComplete.getText().toString().trim();
if (searchStr.length() > 0){
mCustomAutoComplete.dismissDropDown();
saveSearchStr(searchStr);
mSearchStr = searchStr;
//搜索数据
refresh();
}
}
return false;
}
});
mAdapter.setHeaderText(getResources().getString(R.string.search_goods_history));
mAdapter.setHasFooter(true);
mAdapter.setClearHistoryOnClick(new View.OnClickListener() {
@Override
public void onClick(View v) {
clearHistorySearchStr();
mAdapter.upData(null);
}
});
mAdapter.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
isOnItemClick = true;
mSearchStr = mAdapter.getItem(position);
mCustomAutoComplete.setText(mSearchStr);
saveSearchStr(mSearchStr);
//设置光标
Editable spannable = mCustomAutoComplete.getText();
Selection.setSelection(spannable, spannable.length());
mCustomAutoComplete.dismissDropDown();
//搜索数据
refresh();
}
});
}
private void saveSearchStr(String text){
String historyText = mSp.getString(NAME_LIST, "");
if (!historyText.contains(text + ",")) {
StringBuilder sb = new StringBuilder(historyText);
sb.insert(0, text + ",");
mEditor.putString(NAME_LIST, sb.toString());
mEditor.commit();
}
}
private List<String> getHistorySearchStr(){
String text = mSp.getString(NAME_LIST, null);
if (text == null || text.length() == 0){
return null;
}else {
return Arrays.asList(text.split(","));
}
}
private void clearHistorySearchStr(){
mEditor.remove(NAME_LIST);
mEditor.commit();
}
private void refresh(){
//模拟网络搜索
String [] date = {"aa", "ab", "ac", "ad", "ba", "bb", "bc", "bd"};
List<String> responseDate = new ArrayList<>();
for (String text : date){
if (text.contains(mSearchStr)){
responseDate.add(text);
}
}
response(responseDate);
}
private void response(List<String> response){
mAdapter.setHeaderText(getResources().getString(R.string.search_goods));
mAdapter.setHasFooter(false);
mAdapter.upData(response);
}
public void cancel(View view){
finish();
}
/** 获得屏幕宽度 */
public int getScreenWidth() {
return getResources().getDisplayMetrics().widthPixels;
}
}
AutoCompleteTextView实现过程
在输入文本的时候弹出PopupWindow的过程
在AutoCompleteTextView构造方法里面有这么一行代码,添加了文本改变文本时监听
addTextChangedListener(new MyWatcher());
private class MyWatcher implements TextWatcher {
public void afterTextChanged(Editable s) {
doAfterTextChanged();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
doBeforeTextChanged();
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}
看一下doAfterTextChanged的关键代码
if (enoughToFilter()) {
if (mFilter != null) {
mPopupCanBeUpdated = true;
performFiltering(getText(), mLastKeyCode);
}
}
enoughToFilter方法就是判断当前文本的长度,是不是大于我们调用setThreshold的大小
public boolean enoughToFilter() {
return getText().length() >= mThreshold;
}
protected void performFiltering(CharSequence text, int keyCode) {
mFilter.filter(text, this);
}
调用了adapter提供的Filter的filter方法,看一下改方法的实现
public final void filter(CharSequence constraint, FilterListener listener) {
//省略一些代码
if (mThreadHandler == null) {
mThreadHandler = new RequestHandler(thread.getLooper());
}
Message message = mThreadHandler.obtainMessage(FILTER_TOKEN);
RequestArguments args = new RequestArguments();
args.constraint = constraint != null ? constraint.toString() : null;
args.listener = listener;
message.obj = args;
mThreadHandler.sendMessageDelayed(message, delay);
}
在看一下RequestHandler,就看一下RequestHandler类的handleMessage吧
public void handleMessage(Message msg) {
int what = msg.what;
Message message;
switch (what) {
case FILTER_TOKEN:
RequestArguments args = (RequestArguments) msg.obj;
try {
//这个performFiltering就是在自定义adapter时提供的Filter的方法
args.results = performFiltering(args.constraint);
} catch (Exception e) {
args.results = new FilterResults();
} finally {
message = mResultHandler.obtainMessage(what);
message.obj = args;
message.sendToTarget();
}
//省略...
break;
}
}
在finally有调用了ResultHandler,在看一下ResultHandler类的handleMessage
public void handleMessage(Message msg) {
RequestArguments args = (RequestArguments) msg.obj;
//这个publishResults也是在自定义adapter时提供的Filter的方法
publishResults(args.constraint, args.results);
if (args.listener != null) {
int count = args.results != null ? args.results.count : -1;
args.listener.onFilterComplete(count);
}
}
到这里Filter就算执行完了,这个方法有一个listener,是一从mFilter.filter(text, this)一直传到这里的,那就去AutoCompleteTextView看一下方法实现都写了什么
public void onFilterComplete(int count) {
updateDropDownForFilter(count);
}
private void updateDropDownForFilter(int count) {
if ((count > 0 || dropDownAlwaysVisible) && enoughToFilter) {
if (hasFocus() && hasWindowFocus() && mPopupCanBeUpdated) {
showDropDown();
}
}
}
日!到这里总算是显示出来了。
哦对了!当调用onFilterComplete才能弹出PopupWindow,那前面的自定义的View都已经把父类的调用注释掉了,为什么还能弹出来
@Override
public void onFilterComplete(int count) {
// super.onFilterComplete(count);
}
那是因为在调用setAdapter的时候AutoCompleteTextView注册了这么一个东西
adapter.registerDataSetObserver(mObserver);
就是当adapter数据改变是mObserver会收到通知
private static class PopupDataSetObserver extends DataSetObserver {
private final WeakReference<AutoCompleteTextView> mViewReference;
private PopupDataSetObserver(AutoCompleteTextView view) {
mViewReference = new WeakReference<AutoCompleteTextView>(view);
}
@Override
public void onChanged() {
final AutoCompleteTextView textView = mViewReference.get();
textView.post(updateRunnable);
}
}
private final Runnable updateRunnable = new Runnable() {
@Override
public void run() {
final AutoCompleteTextView textView = mViewReference.get();
if (textView == null) {
return;
}
final ListAdapter adapter = textView.mAdapter;
if (adapter == null) {
return;
}
textView.updateDropDownForFilter(adapter.getCount());
}
};
}
在updateRunnable 里面又调用了updateDropDownForFilter方法。这回知道为什么了吧