IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 自定义android控件:快速检索QuickSearch -> 正文阅读

[移动开发]自定义android控件:快速检索QuickSearch


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();
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-24 08:03:55  更:2021-11-24 08:04:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:57:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码