自定义android控件:快速检索QuickSearch

本文介绍了一个自定义的快速搜索组件,通过输入过滤大量数据,提高Spinner在候选条目过多情况下的用户体验。组件包括逻辑骨架、UI封装和使用方法,支持前缀和包含匹配,并提供了一个可扩展的抽象基类。此外,还展示了两个XML布局文件用于界面布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Android有自带的下拉选择控件Spinner。问题是,一旦候选条目数过多,从中进行选择就很不友好。

解决这个问题常用的一种方式是:引入一个输入,根据输入过滤出部分结果数据。当然,这样做的前提是,需要为每一个数据项设置一个检索标识。

对于一个输入,判断每一条原始数据的检索标识,是否符合既定的匹配规则(比如前缀或者包含匹配的方式),从而得出符合过滤规则的数据结果。

上效果图

快速检索

逻辑骨架

先来定义控件的逻辑功能部分,核心是根据当前的输入字符串过滤出结果数据,实现见doFilter(String prefix)方法。

/**
 * UI控件,快速检索
 * - 可对原始数据进行快速筛选,筛选结果展示到列表里面
 *
 * @param <T> 需要筛选的数据条目类型
 * @author Liberg
 * @Date 2021年11月15日
 */
public abstract class QuickSearch<T> {
    private static final String TAG = "QuickSearch";
    /**
     * 原始数据
     */
    private ArrayList<T> originList;
    /**
     * 过滤后的数据
     */
    private ArrayList<T> filteredList;

    public QuickSearch(ArrayList<T> datas) {
        this.originList = datas;
    }
    /**
     * 抽象方法:从数据item中拿到检索标识
     */
    protected abstract String getSearchLabel(T item);
    /**
     * 抽象方法:从数据item中拿到显示名称
     */
    protected abstract String getItemName(T item);
    /**
     * 过滤规则:
     * 1. 只输入1~2个字符时,前缀匹配
     * 2. 输入>=3个字符时,包含匹配
     */
    public ArrayList<T> doFilter(String prefix) {
        ArrayList<T> result = new ArrayList<>();
        if (prefix.length() <= 2) {
            for (T d : originList) {
                String label = getSearchLabel(d);
                if (label != null && label.startsWith(prefix)) {
                    result.add(d);
                }
            }
        } else {
            for (T d : originList) {
                String label = getSearchLabel(d);
                if (label != null && label.indexOf(prefix) >= 0) {
                    result.add(d);
                }
            }
        }
        return result;
    }
}

赋予UI

要做一个完整Android控件,离不开对UI部分的封装。

UI封装

public abstract class QuickSearch<T> {
    private boolean isAttachedToParent = false;
    private Activity rootActiviy = null;
    private View containerView = null;
    private ImageButton ibClose = null;
    private EditText etLabel = null;
    private ListView listView = null;
    private MyAdapter listAdapter = null;
    OnQuickSearchListener<T> listener = null;

    public void initUI(Activity rootActivity) {
        this.rootActiviy = rootActivity;
        containerView = rootActivity.getLayoutInflater().inflate(R.layout.quick_search_layout, null);
        ibClose = containerView.findViewById(R.id.ibClose);
        etLabel = containerView.findViewById(R.id.etLabel);
        listView = containerView.findViewById(R.id.list);

        filteredList = originList;
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (listener != null) {
                    listener.onItemSelected(filteredList.get(position));
                }
                hide();
            }
        });
        listAdapter = new MyAdapter(rootActivity);
        listView.setAdapter(listAdapter);

        etLabel.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                filteredList = s.length() == 0 ? originList : doFilter(s.toString());
                if (listAdapter != null) {
                    listAdapter.notifyDataSetChanged();
                }
            }
            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        etLabel.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    UIUtils.hideInputMethod(v);
                }
            }
        });
        ibClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                hide();
                if (listener != null) {
                    listener.onCancelled();
                }
            }
        });
    }
    
    public void show() {
        if (containerView == null) return;
        if (!isAttachedToParent) {
            Window win = rootActiviy.getWindow();
            WindowManager.LayoutParams wlp = win.getAttributes();
            wlp.gravity = Gravity.TOP;
            win.setAttributes(wlp);
            win.addContentView(containerView, wlp);
            isAttachedToParent = true;
        }
        containerView.setVisibility(View.VISIBLE);
    }

    public void hide() {
        if (isAttachedToParent) {
            containerView.setVisibility(View.INVISIBLE);
        }
    }
    /**
     * 控件对外暴露的事件
     * @param <T>
     */
    public interface OnQuickSearchListener<T> {
        void onItemSelected(T item);
        void onCancelled();
    }
    public void setOnQuickSearchListener(OnQuickSearchListener<T> listener) {
        this.listener = listener;
    }
    
    public void resetDatas(ArrayList<T> datas) {
        this.originList = datas;
        filteredList = datas;
        etLabel.setText("");
        listAdapter.notifyDataSetChanged();
    }

    /**
     * 控件内部列表使用的数据适配器
     */
    private class MyAdapter extends BaseAdapter {
        private LayoutInflater mInflater;
        public MyAdapter(Context context) {
            mInflater = LayoutInflater.from(context);
        }
        @Override
        public int getCount() {
            return filteredList.size();
        }
        @Override
        public T getItem(int index) {
            return filteredList.get(index);
        }
        @Override
        public long getItemId(int arg0) {
            return arg0;
        }
        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            T item = getItem(position);
            if (item == null)
                return null;
            ViewHolder holder = null;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.item_ordered_list, null);
                holder.tvIndex = convertView.findViewById(R.id.tvIndex);
                holder.tvTitle = convertView.findViewById(R.id.tvTitle);
                holder.tvSubTitle = convertView.findViewById(R.id.tvSubTitle);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.tvIndex.setText("" + (position + 1));
            holder.tvTitle.setText(getItemName(item));
            holder.tvSubTitle.setText(getSearchLabel(item));
            return convertView;
        }
        public final class ViewHolder {
            public TextView tvIndex;
            public TextView tvTitle;
            public TextView tvSubTitle;
        }
    }
}

其中有2个xml布局文件:

quick_search_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/transparent_40p"
    android:padding="@dimen/dp15">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:padding="5dp"
        android:background="#fff">

        <ImageButton
            android:id="@+id/ibClose"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_alignParentRight="true"
            android:padding="@dimen/dp5"
            android:background="@color/transparent"
            android:src="@drawable/ic_close"
            />

        <LinearLayout
            android:id="@+id/llTop"
            android:paddingEnd="45dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <EditText
                android:id="@+id/etLabel"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:layout_toLeftOf="@+id/ibClose"
                android:background="@drawable/selector_input"
                android:hint="请输入查询标识进行过滤"
                android:paddingStart="@dimen/dp5"
                android:inputType="text"
                android:singleLine="true"
                android:textSize="@dimen/f15" />
        </LinearLayout>
    </RelativeLayout>
    <ListView
        android:id="@+id/list"
        android:background="@color/gray2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        ></ListView>
</LinearLayout>

item_ordered_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="center"
    android:paddingTop="8dp"
    android:paddingBottom="8dp">
    <TextView
        android:id="@+id/tvIndex"
        android:layout_width="36dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="1000" />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center">
        <TextView
            android:id="@+id/tvTitle"
            android:textSize="@dimen/dp18"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="left"
            android:text="" />
        <TextView
            android:id="@+id/tvSubTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/t3"
            android:gravity="left"
            android:text="" />
    </LinearLayout>
</LinearLayout>

使用

Activity中初始化控件,调用show()/hide()来显示/隐藏控件。

private QuickSearch<XxxData> quickSearch = null;
private void initQuickSearch(ArrayList<XxxData> dealers) {
    final Activity rootActivity = this;
    if(quickSearch == null) {
        quickSearch = new QuickSearch<XxxData>(dealers) {
            @Override
            protected String getSearchLabel(XxxData item) {
                return item.markId;
            }
            @Override
            protected String getItemName(XxxData item) {
                return item.name;
            }
        };
        quickSearch.initUI(rootActivity);
        quickSearch.setOnQuickSearchListener(new QuickSearch.OnQuickSearchListener<XxxData>() {
            @Override
            public void onItemSelected(XxxData item) {
                //当前选中了数据item
            }
            @Override
            public void onCancelled() {
                Log.d(TAG, "onCancelled: initQuickSearch()");
            }
        });
    }
}
quickSearch.show();
quickSearch.hide();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值