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

相關文章
相關標籤/搜索