自定义的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());
扩展: