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

开篇

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

效果图

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

篇尾

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350