AutoCompleteTextView,实现自定义规则的自动补全功能

某个项目需要做关键词筛选自动补全的功能,并且需要对文本中间的文字也能过滤,当然做这个功能首选的控件当然是AutoCompleteTextView,正常情况该控件只能对前面的字符进行匹配,如果要文本中间的文字也进行匹配就需要自己实现自定义规则了,大概搜了一下,自定义规则网上已经有了,但是相对完整的资料不多,并且大部分都是直接用ArrayAdapter配合String类型来使用,但这样使用相对来说,实用性不强,因为开发中,往往需要过滤的是class中某一个字段的数据,例如下面的实体类:

public class PersonInfo  {
    private String name;
    private String phone;
}

需求分析:我们需要对上面实例的name属性进行筛选过滤,如果是用ArrayAdapter,那么我们就需要用一个集合去遍历保存所有PersonInfo的name数据,然后再填充到ArrayAdapter,这样处理起来很麻烦,并且扩展性不强,如果客户要求筛选的下拉列表,要同时显示姓名和手机号,那么此时直接使用ArrayAdapter就不再满足需求了。那么最简单的办法当然是继承BaseAdapter然后自定义一个adapter了,这个adapter和我们平常自定义ListView的adapter有一点区别,因为要实现自定义过滤规则,所以必须实现Filterable接口。

一、涉及到的知识点
  • 自定义Adapter
  • 泛型
  • 抽象
  • 自定义Filterable实现数据过滤规则
二、最终效果图

14093431_7yz5.gif

三、实现代码

1.先看看自定义Filterable的代码,CustomFilterRule类

public abstract class CustomFilterRule<T> extends Filter {
    private List<T> mUnfilteredData;

    public CustomFilterRule(List<T> data) {
        this.mUnfilteredData = new ArrayList<>(data);
    }


    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        FilterResults results = new FilterResults();
        String prefixString = constraint.toString().toLowerCase();
        ArrayList<T> newValues = (ArrayList<T>) onFilterData(prefixString, mUnfilteredData);
        results.values = newValues;
        results.count = newValues.size();
        return results;
    }

    /**
     * 如果存在动态添加过滤数据,重新调用该方法,set数据即可
     */
    public void setmUnfilteredData(List<T> data) {
        this.mUnfilteredData = new ArrayList<>(data);
    }


    /**
     * 因为筛选规则不是完全确定的,所以公开一个抽象方法,让子类去实现
     */
    public abstract List<T> onFilterData(String prefixString, List<T> unfilteredValues);
}

说明:

  • 这里用到了抽象和泛型,主要是为了提高代码的复用性(Tabstract),如果不了解或者忘记了,就google吧。。。。

2.增强复用型的adapter,国际惯例,先看代码

public abstract class BaseFilterAdapter<T> extends BaseAdapter implements Filterable, ListCallback<T> {
    private CustomFilterRule<T> filter;
    private List<T> data;
    private int layoutId = -1;

    public BaseFilterAdapter(int layoutId) {
        this.layoutId = layoutId;
        initList();
    }

    private void initList() {
        if (data == null) {
            data = new ArrayList<>();
        }
    }

    @Override
    public CustomFilterRule<T> getFilter() {
        if (filter == null) {
            filter = getCostomFliter();
        }
        return filter;
    }

    public int getLayoutId() {
        if (layoutId != -1) {
            return layoutId;
        }
        return R.layout.item_complete_textview;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public T getItem(int position) {
        return data.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        DataHodler dataHodler;
        if (convertView == null) {
            convertView = LayoutInflater.from(parent.getContext()).inflate(getLayoutId(), null);
            dataHodler = new DataHodler(convertView);
            convertView.setTag(dataHodler);
        } else {
            dataHodler = (DataHodler) convertView.getTag();
        }
        onBindDataToView(dataHodler, getItem(position));
        return convertView;
    }

    @Override
    public T get(int position) {
        return data.get(position);
    }

    @Override
    public void add(T t) {
        data.add(t);
    }

    @Override
    public void add(int position, T t) {
        data.add(position, t);
    }

    @Override
    public void addAll(List<T> allData) {
        data.addAll(allData);
    }

    @Override
    public void remove(T t) {
        data.remove(t);
    }

    @Override
    public void remove(int position) {
        data.remove(position);
    }

    @Override
    public int size() {
        return data.size();
    }

    @Override
    public List<T> getData() {
        return data;
    }


    /**
     * 动态添加改变数据时,需要调用该方法重新设置Filter中的数据,否则下拉列表显示的是旧数据
     */
    public void onRefreshFilterData() {
        getFilter().setmUnfilteredData(data);
    }

    /**
     * 复用ViewHodler
     */
    public class DataHodler {
        private View convertView;
        private SparseArray viewRes = new SparseArray();

        public DataHodler(View convertView) {
            this.convertView = convertView;
        }

        /**
         * 获取View,提高复用性,设计知识点,抽象、View缓存
         */
        public <V extends View> V getView(int viewId) {
            V view = (V) viewRes.get(viewId);
            if (view == null) {
                view = (V) convertView.findViewById(viewId);
                viewRes.put(viewId, view);
            }
            return view;
        }
    }

    /**
     * 创建Filter筛选器
     */
    private CustomFilterRule<T> getCostomFliter() {
        CustomFilterRule<T> customFilter = new CustomFilterRule<T>(data) {
            @Override
            public List<T> onFilterData(String prefixString, List<T> 
            unfilteredValues) {
                //在这里调用子类实现的数据过滤规则
                return onFilterRule(prefixString, unfilteredValues);
            }

            @Override
            protected void publishResults(CharSequence constraint, 
            Filter.FilterResults results) {
                data = (List<T>) results.values;
                if (results.count > 0) {
                    notifyDataSetChanged();
                } else {
                    notifyDataSetInvalidated();
                }
            }
        };
        return customFilter;
    }


    /**
     * 抽象方法,绑定数据。因为不知道子类会绑定哪些数据,所以公开一个抽象方法让子类去实现数据绑定
     *View
     */
    public abstract void onBindDataToView(DataHodler hodler, T t);

    /**
     * 抽象方法,自定义数据过滤规则。作用同上
     */
    public abstract List<T> onFilterRule(String prefixString, List<T> 
    unfilteredValues);
}

说明:

  • Filterable接口: adapter必须实现该接口后再配合AutoCompleteTextView用,否则是无法筛选填充数据的
  • ListCallback<T>接口:该接口中的方法对应List集合部分方法实现,因为想到要提高复用性,所以直接adapter内部添加了一个List集合,之后使用该adapter就不用再添加List集合了,直接调用adapter.add(data),adapter.addAll(listData)等方法即可添加、修改数据,这样相对来说可以减少adapter使用时的代码数量,提高复用;
  • onFilterRule方法:抽象方法,让子类去实现自己的过滤规则,因为实际开发中,每个adapter的数据和过滤规则,不一定一样。
  • onBindDataToView方法:。用于绑定listView item数据

3.BaseFilterAdapter的用法

public class PersonFilterAdapter extends BaseFilterAdapter<PersonInfo> {
    public PersonFilterAdapter(int layoutId) {
        super(layoutId);
    }

    @Override
    public void onBindDataToView(DataHodler hodler, PersonInfo personInfo) {
        TextView name = hodler.getView(R.id.tv_name);
        TextView phone = hodler.getView(R.id.tv_phone);
        name.setText(personInfo.getName());
        phone.setText(personInfo.getPhone());
    }

    /**
     * 自定义筛选规则
     *
     * @param unfilteredValues 要过滤的数据
     * @param prefixString     关键词
     */
    @Override
    public List<PersonInfo> onFilterRule(String prefixString, List<PersonInfo> 
    unfilteredValues) {
        ArrayList<PersonInfo> newValues = new ArrayList<>();
        for (PersonInfo info : unfilteredValues) {
            if (info.getName().contains(prefixString)) {
                newValues.add(info);
            }
        }
        return newValues;
    }
}

说明:

  • 有没有发现,此时的adapter简洁了很多,比一般adapter的写法少了很多代码,哈哈哈
  • 因为我们是需要对PersonInfo的名字进行数据过滤填充,所以泛型直接传PersonInfo
  • onFilterRule方法,在该方法中实现了自己的过滤规则,这里用的是name字段,当然同时也可以添加多个判断条件
  • onBindDataToView方法,绑定数据到view

4.具体的使用代码,列出几个主要的方法

    @Override
    public void initView() {
         tv_seach = (AutoCompleteTextView) findViewById(R.id.tv_seach);
        //设置多少个字开始显示下拉列表
        tv_seach.setThreshold(1);
        //初始化adapter,R.layout.item_complete_textview为下拉列表显示的布局文件
        filterAdapter = new PersonFilterAdapter(R.layout.item_complete_textview);
        tv_seach.setAdapter(filterAdapter);
    }

    @Override
    public void initData() {
        //添加测试数据
        for (int i = 0; i < name.length; i++) {
            filterAdapter.add(new PersonInfo(name[i], "13337589632" + i));
        }
        //刷新Filter数据
        filterAdapter.onRefreshFilterData();
        filterAdapter.notifyDataSetChanged();
    }

    @Override
    public void initListener() {
        //下拉列表点击事件
        tv_seach.setOnItemClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        tv_seach.setText(filterAdapter.get(position).getName());
        tv_seach.setSelection(tv_seach.getText().length());//设置光标到末尾
    }

四、总结:这样设计代码,结构清晰,简单明了。同时又提高了复用性,以后如果有类似的需求,写adapter时直接继承BaseFilterAdapter,实现自己的过滤逻辑即可,无须再重新写一遍处理代码和逻辑,可以省下很大的工作量。

实例代码https://github.com/wangzhiyuan888/SampleExample/tree/master

转载于:https://my.oschina.net/u/2933456/blog/787119

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值