输入文本匹配内容AutoCompleteTextView的使用

简单使用

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方法。这回知道为什么了吧

csdn下载地址
github下载地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值