Android AutoCompleteTextView之高亮关键字解决方案

开篇

  AutoCompleteTextView通常配上ArrayAdapter就能解决大部分的需求。但是ArrayAdapter是以boolean startsWith(String prefix)方式去匹配搜索项,且没有给我们配置关键字高亮。它的这种匹配规则往往不能满足我们实际中的需求。因此,我们需要进行改造。

效果图

《Android AutoCompleteTextView之高亮关键字解决方案》

ArrayAdapter源码

ArrayAdapter的匹配原理是同一个实现一个过滤器来达到过滤的效果。
ArrayAdapter源码:

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

过滤器:

class ArrayFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence prefix) {
            //我们输入过滤就是在这个方法里实现。
            final FilterResults results = new FilterResults();

            if (mOriginalValues == null) {
                synchronized (mLock) {
                    mOriginalValues = new ArrayList<>(mObjects);
                }
            }

            if (prefix == null || prefix.length() == 0) {
                final ArrayList<T> list;
                synchronized (mLock) {
                    list = new ArrayList<>(mOriginalValues);
                }
                results.values = list;
                results.count = list.size();
            } else {
                final String prefixString = prefix.toString().toLowerCase();

                final ArrayList<T> values;
                synchronized (mLock) {
                    values = new ArrayList<>(mOriginalValues);
                }

                final int count = values.size();
                final ArrayList<T> newValues = new ArrayList<>();
                
                //这里开始循环匹配,以startsWith()方式匹配
                for (int i = 0; i < count; i++) {
                    final T value = values.get(i);
                    final String valueText = value.toString().toLowerCase();

                    // First match against the whole, non-splitted value
                    if (valueText.startsWith(prefixString)) {
                        newValues.add(value);
                    } else {
                        final String[] words = valueText.split(" ");
                        for (String word : words) {
                            if (word.startsWith(prefixString)) {
                                newValues.add(value);
                                break;
                            }
                        }
                    }
                }

                results.values = newValues;
                results.count = newValues.size();
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
        //这里输出过滤后的结果, notifyDataSetChanged()刷新列表
       //noinspection unchecked
            mObjects = (List<T>) results.values;
            if (results.count > 0) {
                notifyDataSetChanged();
            } else {
                notifyDataSetInvalidated();
            }
}

}

改造

为了满足我们的业务需求,进行改造!!!
暴露接口:OnItemTextListener接口

    public interface OnItemTextListener<T> {
        /**
         * 获取T对象中的文本
         * @param item
         * @return
         */
        CharSequence selectedItemText(@Nullable T item);

        /**
         * 过滤列表item的显示文本
         * @param item
         * @param mPrefix
         * @return
         */
        CharSequence ListItemText(@Nullable T item, String mPrefix);

        /**
         * 过滤规则
         * @param item
         * @param mPrefix
         * @return true,此item符合过滤规则。
         */
        boolean filterItem(@NonNull T item, String mPrefix);
    }
  • 1、我们把ArrayAdapter拷贝一份,命名为FilterAdapter
public class FilterAdapter<T> extends BaseAdapter implements Filterable, ThemedSpinnerAdapter {
    private String mPrefix = "";
    private OnItemTextListener<T> onItemTextListener = null;

    public void setOnItemTextListener(OnItemTextListener<T> onItemTextListener) {
        this.onItemTextListener = onItemTextListener;
    }
  //此处为省略部分
}
  • 2、重写performFiltering(CharSequence prefix)方法:
        @Override
        protected FilterResults performFiltering(CharSequence prefix) {
            final FilterResults results = new FilterResults();

            if (mOriginalValues == null) {
                synchronized (mLock) {
                    mOriginalValues = new ArrayList<>(mObjects);
                }
            }

            //这里我们记录下过滤关键字,以便在刷新列表时高亮显示
            //add custom
            mPrefix = prefix == null ? "" : prefix.toString().trim();

            if (mPrefix.length() == 0) {
                final ArrayList<T> list;
                synchronized (mLock) {
                    list = new ArrayList<>(mOriginalValues);
                }
                results.values = list;
                results.count = list.size();
            } else {
                final String prefixString = mPrefix.toLowerCase();

                final ArrayList<T> values;
                synchronized (mLock) {
                    values = new ArrayList<>(mOriginalValues);
                }

                final int count = values.size();
                final ArrayList<T> newValues = new ArrayList<>();

                for (int i = 0; i < count; i++) {
                    final T value = values.get(i);
                    //我们把匹配规则用接口暴露出来给用户,让用户实现自定义化的匹配规则。如果用户没有提供匹配规则接口,我们给一个默认的匹配规则实现。
                    if (onItemTextListener != null) {
                        if (onItemTextListener.filterItem(value, prefixString))
                            newValues.add(value);
                    } else {
                        final String valueText = value.toString().toLowerCase();
                        if (valueText.contains(prefixString.toLowerCase())) {
                            newValues.add(value);
                        }
                    }
                }
                //回填匹配出来的列表以及匹配到的数量。
                results.values = newValues;
                results.count = newValues.size();
            }

            return results;
        }
  • 3、重写CharSequence convertResultToString(Object resultValue)方法:
        @Override
        public CharSequence convertResultToString(Object resultValue) {
          //如果不重写这个方法,默认通过Object.toString()方法显示。
          //而在实际的需求开发过程中,我们可能只用到Object中的某一field,所以这里以接口的形式暴露,使得有更强的扩展性。
            return onItemTextListener == null ? super.convertResultToString(resultValue) : onItemTextListener.selectedItemText((T) resultValue);
        }
  • 4、在过滤列表中高亮显示过滤关键字,重写Private View createViewFromResource(@NonNull LayoutInflater inflater, int position, @Nullable View convertView, @NonNull ViewGroup parent, int resource)方法:
    View createViewFromResource(@NonNull LayoutInflater inflater, int position,
                                @Nullable View convertView, @NonNull ViewGroup parent, int resource) {
        //省略部分
        final T item = getItem(position);
        //这里暴露高亮显示过滤关键字的接口,让用可以自由实现高亮显示。
        CharSequence value = onItemTextListener == null ? (item == null ? "" : item.toString()) : onItemTextListener.ListItemText(item, mPrefix);
        text.setText(value);
        return view;
    }

👌,到这里改造完成。下面说说使用。

使用

private AutoCompleteTextView autoCompleteTextView;

FilterAdapter<T> adapter = new FilterAdapter<>(getContext(), R.layout.common_drop_down_item_layout, R.id.tv_content, list);
adapter.setOnItemTextListener(new FilterAdapter.OnItemTextListener<T>() {
            @Override
            public CharSequence selectedItemText(@Nullable T item) {
                return item == null ? "" : item.getEarNo();
            }

            @Override
            public CharSequence ListItemText(@Nullable T item, String mPrefix) {
                Log.i(TAG, "ListItemText: " + mPrefix);
                String value = item == null ? "" : item.getEarNo();
                if (mPrefix.length() > 0) {
                    int index = value.indexOf(mPrefix);
                    if (index == -1)
                        index = value.toLowerCase().indexOf(mPrefix.toLowerCase());
                    if (index > -1) {
                        return SpannableUtil.setTextColor(value, index, index + mPrefix.length(), Color.GREEN);
                    }
                }
                return value;
            }

            @Override
            public boolean filterItem(@NonNull T item, String mPrefix) {
                final String valueText = item.getEarNo().toLowerCase();
                return valueText.contains(mPrefix.toLowerCase());
            }
        });
autoCompleteTextView.setAdapter(adapter);
    public static SpannableString setTextColor(String text, int start, int end, int color) {
        SpannableString spannable = new SpannableString(text);
        spannable.setSpan(new ForegroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        return spannable;
    }

最后附上源码:
FilterAdapter

微信:eoy9527QQ:1006368252

篇尾

天才出于勤奋。 —— 高尔基

    原文作者:SwitchLife
    原文地址: https://www.jianshu.com/p/63d8b68402fd
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞