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

某个项目须要作关键词筛选自动补全的功能,而且须要对文本中间的文字也能过滤,固然作这个功能首选的控件固然是AutoCompleteTextView,正常状况该控件只能对前面的字符进行匹配,若是要文本中间的文字也进行匹配就须要本身实现自定义规则了,大概搜了一下,自定义规则网上已经有了,可是相对完整的资料很少,而且大部分都是直接用ArrayAdapter配合String类型来使用,但这样使用相对来讲,实用性不强,由于开发中,每每须要过滤的是class中某一个字段的数据,例以下面的实体类:git

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

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

1、涉及到的知识点

  • 自定义Adapter
  • 泛型
  • 抽象
  • 自定义Filterable实现数据过滤规则

2、最终效果图

3、实现代码

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);
}

说明:ide

  • 这里用到了抽象和泛型,主要是为了提升代码的复用性(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的用法this

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;
    }
}

说明:google

  • 有没有发现,此时的adapter简洁了不少,比通常adapter的写法少了不少代码,哈哈哈
  • 由于咱们是须要对PersonInfo的名字进行数据过滤填充,因此泛型直接传PersonInfo
  • onFilterRule方法,在该方法中实现了本身的过滤规则,这里用的是name字段,固然同时也能够添加多个判断条件
  • onBindDataToView方法,绑定数据到view

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

@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());//设置光标到末尾
    }

4、总结:这样设计代码,结构清晰,简单明了。同时又提升了复用性,之后若是有相似的需求,写adapter时直接继承BaseFilterAdapter,实现本身的过滤逻辑便可,无须再从新写一遍处理代码和逻辑,能够省下很大的工做量。设计

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

相关文章
相关标签/搜索