设计一个简易模糊查询输入框(上)

一般说到“模糊查询”马上广大程序员就想到了SQL模糊查询,那在Android中也有SQLite来实现这个功能,而本次中,我并打算使用这种方法实现,原因就是:麻烦!作为有逼格的程序员,肯定喜欢自己封装一些方法或者组件,去快实现各种各样的功能,目的就是化繁为简。本着“不重复造轮子”和“要造就造一次轮子的模板”的思想出发,自己设计一个基于List的简易模糊查询。

要实现的效果图


图中实现的功能:
1.支持不连续拼音查询
2.支持不连续文字查询
3.根据输入内容自动选择查询方式

设计思路

在看完上述的功能后,首先想哪个实现最简单呢?嗯。3实现最简单,根据输入内容判断是英文还是中文用一个简单的方法就能实现:

    /**
     * 判断字符串是否为英文
     * @param charaString
     * @return
     */
    public static boolean isEnglish(String charaString) {
        return charaString.matches("^[a-zA-Z]*");
    }

这样,就完成了判断输入内容。然后再看一下,前两个功能有点像,其中最需要注意的点就是“不连续”,怎么能实现不连续?理解就是要搜“我们的歌”时,输入“我歌”,也会对应到“我们的歌”上,我的思路就是用索引0查完之后,记录下关键字key在源数据中的位置,截取源数据List长度为剩下部分,以此类推到索引的最大值时候,如果截取剩余的List中仍然包含key值,那就说明是全部包含在内的,也就实现了不连续查询。用代码写出来就是:

    /**
     * 将字符串拆分为List<String>
     * @param s
     * @return
     */
    private static List<String> StringToList(String s){
        List<String> strings = new ArrayList<>();
        for (int i = 0; i < s.length(); i++) {
            strings.add(s.substring(i,i+1));
        }
        return strings;
    }

    /**
     * 按文字顺序查找是否包含关键词
     * @param source    源数据
     * @param str       关键词
     * @return          数据源中是否包含关键词(这里实现了不连续的功能)
     */
    public static boolean containChKeyword(String source,String str){
        boolean flag = false;
        List<String> keys = StringToList(str);
        List<String> data = StringToList(source);

        String key;
        boolean b = true;
        for (int i = 0; i < keys.size(); i++) {
            key = keys.get(i);
            if (b) {
                if (data.contains(key)) {
                    data = data.subList(data.indexOf(key) + 1, data.size());
                    if (i == keys.size() - 1) {
                        flag = true;
                    }
                }else {
                    b = false;
                }
            }
        }
        return flag;
    }

其中StringToList()方法的作用就是把输入的字符串逐个拆分成单字然后用List将其保存起来。然后通过根据上一个key在源数据中的索引逐渐缩小源数据List的大小来实现不连续包含的方法。同理,实现英文的不连续查询:

    /**
     * 按拼音顺序查找是否包含关键词
     * @param source  源数据
     * @param str      关键词
     * @return          数据源中是否包含关键词(这里实现了不连续的功能)
     */
    public static boolean containEnKeyword(String source,String str){
        boolean flag = false;
        List<String> keys = StringToList(str);
        String string =  Chinsese2SpellingUtils.getInstance().getStringPinYin(source);
        List<String> data = StringToList(string);

        String key;
        boolean b = true;
        for (int i = 0; i < keys.size(); i++) {
            key = keys.get(i);
            if (b) {
                if (data.contains(key)) {
                    data = data.subList(data.indexOf(key) + 1, data.size());
                    if (i == keys.size() - 1) {
                        flag = true;
                    }
                }else {
                    b = false;
                }
            }
        }
        return flag;
    }

思路是跟上边基本一样,不同就是在源数据的处理上,因为源数据录入的时候全是中文,所以需要一种方式将其转化为全拼音的方法,这里就需要介绍一个jar包:pinyin4j ;它的功能就是能把汉字转化为对应的汉语拼音,在android studio中的集成方式有两步:
1.在项目的build.gradle中:

2.在app中的build.gradle中:

到这里主干功能都已经完成了,接下来进行UI层面设计。UI界面需要不断监听输入框的内容,来动态显示ListView中的数据。其中监听输入框的接口为:addTextChangedListener()这个方法不大熟悉的朋友就要上网看看了,很实用!动态显示ListView的数据就更简单了直接或间接调用adapter. notifyDataSetChanged()方法即可。首先,我们先看一下特殊设计的adapter:

public class NameAdapter extends BaseAdapter {
    private LayoutInflater mInflater;
    private List<String> all_data;
    private List<String> sort_data = new ArrayList<>();

    public NameAdapter(Context context,List<String> source) {
        this.mInflater = LayoutInflater.from(context);
        this.all_data = source;
        showSortListView("");
    }

    public void showSortListView(String key){
        sort_data.clear();
        if (TextUtils.isEmpty(key)){
            sort_data.addAll(all_data);
        }else {
            String source;
            for (int i = 0; i < all_data.size(); i++) {
                source = all_data.get(i);
                if (StringUtils.isEnglish(key)){
                    String s = Chinsese2SpellingUtils.getInstance().getStringPinYin(source);
                    if (StringUtils.containEnKeyword(s,key)){
                        sort_data.add(source);
                    }
                }else {
                    if (StringUtils.containChKeyword(source,key)){
                        sort_data.add(source);
                    }
                }
            }
        }
        notifyDataSetChanged();
    }

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

    @Override
    public Object getItem(int i) {
        return sort_data.get(i);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        String data = sort_data.get(position);
        View view;
        ViewHolder viewHolder;
        if (convertView==null) {
            view = mInflater.inflate(R.layout.item_name, parent, false);
            viewHolder = new ViewHolder();
            viewHolder.tv_name= view.findViewById(R.id.tv_name);
            view.setTag(viewHolder);
        }else {
            view=convertView;
            viewHolder= (ViewHolder) view.getTag();
        }
        viewHolder.tv_name.setText(data);
        return view;
    }

    static class ViewHolder{
        TextView tv_name;
    }

}

其中跟普通的适配器明显不一样的是有两个数据源:all_data、sort_data;其中前者是全部数据,后者是需要显示的数据。还有一个明显不一样的地方就是:

这个方法的意思就是显示根据输入的关键词改变sort_data的内容,然后调用notifyDataSetChanged()更新UI界面,在构造方法中直接传入空值调用此方法是为了第一次无搜索的情况下全部显示在UI上,此处可以做一些定制,比如不显示,仅作为后台数据存储着。最后在MainActivity中的显示。

public class MainActivity extends AppCompatActivity {
    private LinearLayout ll_search;
    private EditText et_search;
    private ListView lv_name;
    private List<String> names;
    private NameAdapter adapter;
    private static final int EDIT_TEXT_CHANGED = 9999;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case EDIT_TEXT_CHANGED:
                    String s = (String) msg.obj;
                    adapter.showSortListView(s);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initModel();
        initView();
        initController();
    }

    private void initController() {
        adapter = new NameAdapter(this,names);
        lv_name.setAdapter(adapter);
    }

    private void initModel() {
        names = new ArrayList<>();
        names.add("吕良");
        names.add("张楚岚");
        names.add("冯宝宝");
        names.add("诸葛青");
        names.add("王也");
        names.add("贾正瑜");
        names.add("贾正亮");
        names.add("风星潼");
        names.add("夏禾");
        names.add("张灵玉");
        names.add("徐三");
        names.add("徐四");
        names.add("柳妍妍");
        names.add("陆玲珑");
        names.add("陆瑾");
        names.add("风正豪");
        names.add("张锡林");
        names.add("胡杰");
    }

    private void initView() {
        et_search = findViewById(R.id.et_search);
        ll_search = findViewById(R.id.ll_search);
        lv_name = findViewById(R.id.lv_name);

        et_search.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }

            @Override
            public void afterTextChanged(Editable editable) {
                String change = editable.toString();
                if (TextUtils.isEmpty(change)){
                    ll_search.setVisibility(View.VISIBLE);
                }else {
                    ll_search.setVisibility(View.INVISIBLE);
                }
                Message msg = Message.obtain();
                msg.what = EDIT_TEXT_CHANGED;
                msg.obj = change;
                mHandler.sendMessage(msg);
            }
        });
    }
}

也没什么特殊的,简单的MVC模式使用,handler使用。

总结

此仅为演示demo,简单实现了比“精准搜索”稍微宽泛一点点的“模糊查询”,并没有做到SQL中那么好,但主要是想要分享一下“造轮子模型”的方法。这个功能分为上、下两篇,上篇来基本实现大功能,下篇中来修缮上篇中遗留的问题,那么有哪些问题需要在下篇中改进呢?
1.功能上,仅支持全中文、全拼音;这是不完全的,也是不大合理的,最好的实现应该是混合输入,然后内部再自己判断自己的流程。
2.性能上,作为一个演示demo并没有什么问题,但是如果置于一个项目中的话就会留下内存泄露的隐患,在adapter中保存全部数据的all_data什么时机下释放handler的处理。

Demo下载地址


关注我的技术公众号,我会不定期推送关于安卓的文章,内容包含Android日常开发遇到中的问题、常用框架的介绍以及需要熟记的概念等等。
微信扫一扫下方的二维码即可关注:

猜你喜欢

转载自blog.csdn.net/weixin_37309888/article/details/80056164