Android有自带的下拉选择控件Spinner。问题是,一旦候选条目数过多,从中进行选择就很不友好。
解决这个问题常用的一种方式是:引入一个输入,根据输入过滤出部分结果数据。当然,这样做的前提是,需要为每一个数据项设置一个检索标识。
对于一个输入,判断每一条原始数据的检索标识,是否符合既定的匹配规则(比如前缀或者包含匹配的方式),从而得出符合过滤规则的数据结果。
上效果图
逻辑骨架
先来定义控件的逻辑功能部分,核心是根据当前的输入字符串过滤出结果数据,实现见doFilter(String prefix) 方法。
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;
}
protected abstract String getSearchLabel(T item);
protected abstract String getItemName(T item);
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);
}
}
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) {
}
@Override
public void onCancelled() {
Log.d(TAG, "onCancelled: initQuickSearch()");
}
});
}
}
quickSearch.show();
quickSearch.hide();
|