自定义的AutoComplTextView

借鉴:https://github.com/andyxialm/KMPAutoCompleteTextView

但这个工程有个bug就是输入框删除为空的时候,会保留最后一个字符串的过滤数据集显示,我在下面修复了这个bug

KMPAutoComplTextView.java
public class KMPAutoComplTextView extends AppCompatAutoCompleteTextView {

    private static final int DEFAULT_HIGHLIGHT       = Color.parseColor("#FF4081");
    private static final int DEFAULT_TEXTCOLOR       = Color.parseColor("#80000000");
    private static final int DEFAULT_TEXT_PIXEL_SIZE = 40;

    private float mTextSize;
    private boolean mIsIgnoreCase;
    private KMPAdapter mAdapter;

    private ColorStateList mHighLightColor, mTextColor;
    private List<KMPPopupTextBean> mSourceDatas, mTempDatas;
    private OnPopupItemClickListener mListener;

    public KMPAutoComplTextView(Context context) {
        this(context, null);
    }

    public KMPAutoComplTextView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.autoCompleteTextViewStyle);
    }

    public KMPAutoComplTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KMPAutoComplTextView);
            mTextColor = a.getColorStateList(R.styleable.KMPAutoComplTextView_completionTextColor);
            mHighLightColor = a.getColorStateList(R.styleable.KMPAutoComplTextView_completionHighlightColor);
            mTextSize = a.getDimensionPixelSize(R.styleable.KMPAutoComplTextView_completionTextSize, DEFAULT_TEXT_PIXEL_SIZE);
            mIsIgnoreCase = a.getBoolean(R.styleable.KMPAutoComplTextView_completionIgnoreCase, false);
            a.recycle();
        }
        initListener();
    }

    private void initListener() {
        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) {
                onInputTextChanged(s.toString());
            }
        });

    }

    private void onInputTextChanged(String input) {
        matchResult(input);

        if (mAdapter.mList.size() == 0) {
            KMPAutoComplTextView.this.dismissDropDown();
            return;
        }
        mAdapter.notifyDataSetChanged();

        if (!KMPAutoComplTextView.this.isPopupShowing() || mAdapter.mList.size() > 0) {
            showDropDown();
        }

    }

    public void show(){
        if (!KMPAutoComplTextView.this.isPopupShowing() || mAdapter.mList.size() > 0) {
            showDropDown();
        }
    }

    /**
     * 设置数据集
     *
     * @param strings
     */
    public void setDatas(final List<String> strings) {
        mAdapter = new KMPAdapter(getContext(), getResultDatas(strings));
        setAdapter(mAdapter);
    }

    public void setOnPopupItemClickListener(OnPopupItemClickListener listener) {
        mListener = listener;
        this.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (mListener == null) {
                    return;
                }
                mListener.onPopupItemClick(KMPAutoComplTextView.this.getText().toString());
            }
        });

    }

    private void matchResult(String input) {
        List<KMPPopupTextBean> datas = mSourceDatas;
//        if (TextUtils.isEmpty(input) || datas == null || datas.size() == 0) {
//            return;
//        }
        if (datas == null || datas.size() == 0) {
            return;
        }
        List<KMPPopupTextBean> newDatas = new ArrayList<KMPPopupTextBean>();
        List<String> newDataStrings = new ArrayList<String>();
        if(TextUtils.isEmpty(input)){
            for (KMPPopupTextBean resultBean : datas) {
                KMPPopupTextBean bean = new KMPPopupTextBean(resultBean.mTarget);
                newDatas.add(bean);
                newDataStrings.add(resultBean.mTarget);
            }
        }else {
            for (KMPPopupTextBean resultBean : datas) {
                int matchIndex = matchString(resultBean.mTarget, input, mIsIgnoreCase);
                if (-1 != matchIndex) {
                    KMPPopupTextBean bean = new KMPPopupTextBean(resultBean.mTarget, matchIndex, matchIndex + input.length());
                    newDatas.add(bean);
                    newDataStrings.add(resultBean.mTarget);
                }
            }
        }

        mTempDatas = new ArrayList<KMPPopupTextBean>();
        mTempDatas.clear();
        mTempDatas.addAll(newDatas);

        mAdapter.mList.clear();
        mAdapter.mList.addAll(newDataStrings);
    }


    private List<String> getResultDatas(List<String> strings) {
        if (strings == null || strings.size() == 0) {
            return null;
        }

        List<KMPPopupTextBean> list = new ArrayList<KMPPopupTextBean>();
        for (String target : strings) {
            list.add(new KMPPopupTextBean(target));
        }

        mSourceDatas = new ArrayList<KMPPopupTextBean>();
        mSourceDatas.addAll(list);

//        mTempDatas = new ArrayList<KMPPopupTextBean>();
//        mTempDatas.clear();
//        mTempDatas.addAll(list);
        return strings;
    }

    public void setMatchIgnoreCase(boolean ignoreCase) {
        mIsIgnoreCase = ignoreCase;
    }

    public boolean getMatchIgnoreCase() {
        return mIsIgnoreCase;
    }

    class KMPAdapter extends BaseAdapter implements Filterable {
        private List<String> mList;
        private Context mContext;
        private MyFilter mFilter;

        public KMPAdapter(Context context, List<String> list) {
            mContext = context;
            mList = new ArrayList<String>();
            mList.addAll(list);
        }

        @Override
        public int getCount() {
            return mList == null ? 0 : mList.size();
        }

        @Override
        public Object getItem(int position) {
            return mList == null ? null : mList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                holder = new ViewHolder();
                TextView tv = new TextView(mContext);
                int paddingX = DisplayUtil.dip2px(getContext(), 10.0f);
                int paddingY = DisplayUtil.dip2px(getContext(), 5.0f);
                tv.setPadding(paddingX, paddingY, paddingX, paddingY);

                holder.tv = tv;
                convertView = tv;
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            KMPPopupTextBean bean = mTempDatas == null?mSourceDatas.get(position):mTempDatas.get(position);
            SpannableString ss = new SpannableString(bean.mTarget);
            holder.tv.setTextColor(mTextColor == null ? DEFAULT_TEXTCOLOR : mTextColor.getDefaultColor());
            holder.tv.setTextSize(mTextSize == 0 ? DEFAULT_TEXT_PIXEL_SIZE : DisplayUtil.px2sp(getContext(), mTextSize));

            // Change Highlight Color
            if (-1 != bean.mStartIndex) {
                ss.setSpan(new ForegroundColorSpan(mHighLightColor == null ? DEFAULT_HIGHLIGHT : mHighLightColor.getDefaultColor()),
                        bean.mStartIndex, bean.mEndIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                holder.tv.setText(ss);
            } else {
                holder.tv.setText(bean.mTarget);
            }

            return convertView;
        }

        @Override
        public Filter getFilter() {
            if (mFilter == null) {
                mFilter = new MyFilter();
            }
            return mFilter;
        }

        private class ViewHolder {
            TextView tv;
        }

        private class MyFilter extends Filter {

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults results = new FilterResults();
                if (mList == null) {
                    mList = new ArrayList<String>();
                }
                results.values = mList;
                results.count = mList.size();
                return results;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        }
    }

    public interface OnPopupItemClickListener {
        void onPopupItemClick(CharSequence charSequence);
    }

    /**
     * 获得字符串的next函数值
     *
     * @param mode 字符串
     * @return next函数值
     */
    private static int[] next(char[] mode) {
        int[] next = new int[mode.length];
        next[0] = -1;
        int i = 0;
        int j = -1;
        while (i < mode.length - 1) {
            if (j == -1 || mode[i] == mode[j]) {
                i ++;
                j ++;
                if (mode[i] != mode[j]) {
                    next[i] = j;
                } else {
                    next[i] = next[j];
                }
            } else {
                j = next[j];
            }
        }
        return next;
    }

    /**
     * KMP匹配字符串
     *
     * @param source       主串
     * @param modeStr      模式串
     * @param isIgnoreCase 是否忽略大小写
     * @return 若匹配成功,返回下标,否则返回-1
     */
    public int matchString(CharSequence source, CharSequence modeStr, boolean isIgnoreCase) {
        char[] modeArr = modeStr.toString().toCharArray();
        char[] sourceArr = source.toString().toCharArray();
        int[] next = next(modeArr);
        int i = 0;
        int j = 0;
        while (i <= sourceArr.length - 1 && j <= modeArr.length - 1) {
            if (isIgnoreCase) {
                if (j == -1 || sourceArr[i] == modeArr[j] || String.valueOf(sourceArr[i]).equalsIgnoreCase(String.valueOf(modeArr[j]))) {
                    i ++;
                    j ++;
                } else {
                    j = next[j];
                }
            } else {
                if (j == -1 || sourceArr[i] == modeArr[j]) {
                    i ++;
                    j ++;
                } else {
                    j = next[j];
                }
            }
        }
        if (j < modeArr.length) {
            return -1;
        } else
            return i - modeArr.length; // 返回模式串在主串中的头下标
    }

}
KMPPopupTextBean.java
public class KMPPopupTextBean implements Serializable {
    public String mTarget;
    public int mStartIndex = -1;
    public int mEndIndex = -1;

    public KMPPopupTextBean(String target) {
        this.mTarget = target;
    }

    public KMPPopupTextBean(String target, int startIndex) {
        this.mTarget = target;
        this.mStartIndex = startIndex;
        if (-1 != startIndex) {
            this.mEndIndex = startIndex + target.length();
        }
    }

    public KMPPopupTextBean(String target, int startIndex, int endIndex) {
        this.mTarget = target;
        this.mStartIndex = startIndex;
        this.mEndIndex = endIndex;
    }
}

attrs.xml

<declare-styleable name="KMPAutoComplTextView">
        <attr name="completionHighlightColor" format="reference|color" />
        <attr name="completionTextColor" format="reference|color" />
        <attr name="completionTextSize" format="dimension" />
        <attr name="completionIgnoreCase" format="boolean" />
    </declare-styleable>

使用方法:

List<String> data = new ArrayList<String>();
        data.add("Red roses for wedding");
        data.add("Bouquet with red roses");
        data.add("Si4ngle red rose flower");
        data.add("Bouq5uet with red roses");
        data.add("Sin4gle red rose flower");
        data.add("Bou1quet with red roses");
        data.add("Sing0le red rose flower");
        data.add("Bouq7uet with red roses");
        data.add("Sing9le red rose flower");
        data.add("Bou68quet with red roses");
        data.add("S7ingle red rose flower");
        data.add("Bo0uquet with red roses");
        data.add("Sin11gle red rose flower");
        data.add("Bouq22uet with red roses");
        data.add("Sin33gle red rose flower");
        data.add("Bouquet with red roses");
        data.add("Si77ngle red rose flower");
        data.add("Bouq88uet with red roses");
        data.add("Si89ngle red rose flower");

        KMPAutoComplTextView complTextView = (KMPAutoComplTextView) findViewById(R.id.tvAutoCompl);
        //设置输入一个字就自动提示,默认是两个
        complTextView.setThreshold(1);
        complTextView.setDatas(data);
        complTextView.setOnPopupItemClickListener(new KMPAutoComplTextView.OnPopupItemClickListener() {
            @Override
            public void onPopupItemClick(CharSequence charSequence) {
                Toast.makeText(HomeActivity.this, charSequence.toString(), Toast.LENGTH_SHORT).show();
            }
        });
        complTextView.setOnClickListener(v -> complTextView.show());
        mBinding.btnTest.setOnClickListener(v -> complTextView.show());

 扩展:

Android控件之带清空按钮(功能)的AutoCompleteTextView自动提示

posted @ 2020-04-08 16:06  一只呆萌的萌呆  阅读(326)  评论(0编辑  收藏  举报