自定义的一个App搜索框

模仿风行App实现的一个自定义搜索框类库,实现了监听文本框输入的变化,清空文本框内容,提示列表,热搜列表、自动保存搜索记录等功能。
实现效果如下:
这里写图片描述
类库的主要代码如下:
分别是

  • CharacterParser:汉字转换为拼音的工具类
  • CommonAdapter:热搜列表和搜索历史列表的Adapter
  • HintAdapter:提示列表的Adapter
  • NoScrollGridView:自定义的GridView,解决只显示一行的问题
  • SearchALG:搜索匹配的算法
  • SearchView:自定义的View,继承LinearLayout,实现了搜索的功能

这里写图片描述

下面将主要代码粘出来,文档最后会附上下载地址

    //继承LinearLayout,实现构造方法
    public SearchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        LayoutInflater.from(context).inflate(R.layout.search_view, this);
        initView();
    }

搜索的历史纪录用sp保存

    /**
     * 用SP存储保存搜索的历史纪录
     */
    if (sp == null) {
        sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
    }

外部可通过keepSearchHistory(true/false)方法设置是否保存搜索记录

/**
 * 设置是否自动保存搜索记录
 *
 * @param isAutoKeep
 */
public void keepSearchHistory(boolean isAutoKeep) {
    this.isAutoKeep = isAutoKeep;

    //如果没有设为自动保存,则返回
    if (!isAutoKeep) {
        return;
    }

    String history_search_datas = sp.getString("history_search_datas", "");
    if (!TextUtils.isEmpty(history_search_datas)) {//不为空
        historySearchDatas = new Gson().fromJson(history_search_datas, List.class);
    } else {//SP中没有数据,直接返回
        return;
    }

    if (historyAdapter == null && isAutoKeep) {
        ll_search_history.setVisibility(VISIBLE);
        historyAdapter = new CommonAdapter(context, historySearchDatas);
        gv_search_history.setAdapter(historyAdapter);
    }
}

外部可设置历史纪录的最大保存个数,默认为6

private int maxHistoryRecordCount = 6;

/**
 * 设置最大的历史纪录数,默认为6条
 *
 * @param maxHistoryRecordCount
 */
public void setMaxHistoryRecordCount(int maxHistoryRecordCount) {
    this.maxHistoryRecordCount = maxHistoryRecordCount;
}

在执行搜索时保存记录,最新搜索的记录放在首位,如果该条记录已经有包含在列表中,则移除原来位置的记录并重新保存在首位,当超过maxHistoryRecordCount 的值时,删除最先保存的历史纪录

/**
 * 保存历史纪录
 *
 * @param text
 */
private void keepSearchRecord(String text) {
    if (historySearchDatas == null) {
        historySearchDatas = new ArrayList<>(maxHistoryRecordCount);
        historySearchDatas.add(0, text);
    } else {
        for (int i = 0; i < historySearchDatas.size(); i++) {
            if ((historySearchDatas.get(i).equals(text))) {//判断数据是否已在历史记录中
                //如果存在就移除
                historySearchDatas.remove(i);
            }
        }

        //超过最大值
        if (historySearchDatas.size() >= maxHistoryRecordCount) {
            //移除最老的一条记录
            historySearchDatas.remove(maxHistoryRecordCount - 1);
        }
        //新添加的记录放在集合的首位
        historySearchDatas.add(0, text);

        //保存搜索的历史记录到sp中
        sp.edit().putString("history_search_datas", new Gson().toJson(historySearchDatas)).commit();

    }

    if (historyAdapter == null) {
        historyAdapter = new CommonAdapter(context, historySearchDatas);
        gv_search_history.setAdapter(historyAdapter);
    } else {
        historyAdapter.updateRecordList(historySearchDatas);
    }
}

当点击“清空”按钮时,删除历史纪录

/**
 * 清空历史纪录
 */
private void clearHistoryRecord() {
    if (historySearchDatas != null && historyAdapter != null) {
        historySearchDatas.clear();//清空列表
        historyAdapter.updateRecordList(historySearchDatas);
        ll_search_history.setVisibility(GONE);
        //删除SP存储的历史纪录
        sp.edit().putString("history_search_datas", "").commit();
    }
}

外部通过setHotSearchDatas(List hotSearchDatas)传入热门搜索的数据,如不调用此方法,则不显示热搜列表

/**
 * 设置热搜的数据集合
 *
 * @param hotSearchDatas
 */
public void setHotSearchDatas(List<String> hotSearchDatas) {
    this.hotSearchDatas = hotSearchDatas;
    if (hotSearchDatas != null && hotSearchDatas.size() > 0) {
        ll_search_hot.setVisibility(VISIBLE);
        hotAdapter = new CommonAdapter(context, hotSearchDatas);
        gv_search_hot.setNumColumns(hotNumColumns);//设置列数
        gv_search_hot.setAdapter(hotAdapter);//设置热搜数据
    } else {
        ll_search_hot.setVisibility(GONE);
    }
}

设置提示列表的最大显示行数和热搜列表的列表

private int maxHintLines = -1;

/**
 * 设置提示的最大显示行数,默认显示所有
 *
 * @param maxHintLines
 */
public void setMaxHintLines(int maxHintLines) {
    this.maxHintLines = maxHintLines;
}

private int hotNumColumns = 2;

/**
 * 设置热搜数据的显示列数,默认为2列
 * 需要在setHotSearchDatas()前设置
 *
 * @param hotNumColumns
 */
public void setHotNumColumns(int hotNumColumns) {
    this.hotNumColumns = hotNumColumns;
}

核心方法,添加EditText文本的改变监听,在文本改变后调用自定义的监听的回调方法,在外部实现刷新提示列表的操作

//刷新提示列表的数据
if (onSearchListener != null) {
onSearchListener.onRefreshHintList(s.toString());
}

et_search_content.addTextChangedListener(new SearchTextChangedListener());
/**
 * 监听EditText文本的改变
 */
class SearchTextChangedListener implements TextWatcher {


    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    /**
     * 当文本改变时调用
     *
     * @param s
     * @param start
     * @param before
     * @param count
     */
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (!TextUtils.isEmpty(s.toString().trim())) {//搜索框不为空
            ll_search_hot.setVisibility(GONE);
            ll_search_history.setVisibility(GONE);
            iv_search_clear.setVisibility(VISIBLE);//显示清除搜索框内容的按钮
            lv_search_hint.setVisibility(VISIBLE);
        } else {
            iv_search_clear.setVisibility(GONE);
            lv_search_hint.setVisibility(GONE);
            if (hotSearchDatas != null && hotSearchDatas.size() > 0) {
                ll_search_hot.setVisibility(VISIBLE);
            }
            if (historyAdapter != null && historySearchDatas.size() > 0 && isAutoKeep) {
                ll_search_history.setVisibility(VISIBLE);
            }
        }

        //刷新提示列表的数据
        if (onSearchListener != null) {
            onSearchListener.onRefreshHintList(s.toString());
        }
    }

    @Override
    public void afterTextChanged(Editable s) {

    }
}

设置自定义监听

/**
 * 自定义searchview的监听
 */
public interface OnSearchListener {
    /**
     * 当进行搜索时回调此方法
     *
     * @param searchText 进行搜索的文本
     */
    void onSearch(String searchText);

    /**
     * 当输入框文本变化,需刷新提示的ListView时调用
     *
     * @param changedText 改变后的文本
     */
    void onRefreshHintList(String changedText);
}

private OnSearchListener onSearchListener;

public void setOnSearchListener(OnSearchListener onSearchListener) {
    this.onSearchListener = onSearchListener;
}

设置返回按钮、搜索按钮、清空搜索框按钮、提示列表Item、热搜列表Item、历史列表Item的点击监听

    iv_search_back.setOnClickListener(new SearchOnClickListener());
    iv_search_clear.setOnClickListener(new SearchOnClickListener());
    tv_search_search.setOnClickListener(new SearchOnClickListener());
    /**
     * 热搜列表的Item点击监听
     */
    gv_search_hot.setOnItemClickListener(new listItemOnClickListener(HOT_ITEM));
    /**
     * 历史纪录的Item点击监听
     */
    gv_search_history.setOnItemClickListener(new listItemOnClickListener(HISTORY_ITEM));
    /**
     * 提示列表的Item点击监听
     */
    lv_search_hint.setOnItemClickListener(new listItemOnClickListener(HINT_ITEM));

    /**
     * 监听软键盘的搜索按钮
     */
    et_search_content.setOnEditorActionListener(new TextView.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                startSearch(et_search_content.getText().toString());
            }
            return true;
        }
    });

    ll_item_delete.setOnClickListener(new SearchOnClickListener());

点击监听的回调方法

/**
 * 点击监听的回调
 */
class SearchOnClickListener implements OnClickListener {

    @Override
    public void onClick(View v) {
        int i = v.getId();
        if (i == R.id.iv_search_back) {
            ((Activity) context).finish();

        } else if (i == R.id.iv_search_clear) {
            et_search_content.setText(null);

        } else if (i == R.id.tv_search_search) {
            startSearch(et_search_content.getText().toString());

        } else if (i == R.id.ll_item_delete) {
            clearHistoryRecord();

        }
    }
}

private static final int HOT_ITEM = 1;
private static final int HINT_ITEM = 2;
private static final int HISTORY_ITEM = 3;
//Item的点击回调
class listItemOnClickListener implements AdapterView.OnItemClickListener {

    private int tag;
    private List<String> datas;

    public listItemOnClickListener(int tag) {
        this.tag = tag;
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        if (tag == HOT_ITEM) {
            datas = hotSearchDatas;
        } else if (tag == HINT_ITEM) {
            datas = hintDatas;
        } else if (tag == HISTORY_ITEM) {
            datas = historySearchDatas;
        }

        String item = datas.get(position);
        et_search_content.setText(item);
        startSearch(item);
    }
}

执行搜索的方法和刷新提示列表和热搜列表的状态

/**
 * 开始执行搜索方法
 *
 * @param text
 */
private void startSearch(String text) {
    if (onSearchListener != null) {
        onSearchListener.onSearch(text);
        refreshListState();
        if (isAutoKeep) {
            //保存搜索记录
            keepSearchRecord(text);
        }
    }
}

/**
 * 刷新热搜和提示列表的状态
 */
private void refreshListState() {
    et_search_content.setSelection(et_search_content.getText().length());
    lv_search_hint.setVisibility(GONE);
    ll_search_hot.setVisibility(GONE);
    //隐藏软键盘
    InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
}

SearchALG类:匹配搜索规则的工具类

搜索规则:支持拼音的首字母搜索和全拼音搜索(注意:不能从中间开始搜索)
eg:爱情买卖
可以通过 aqmm匹配
也可以通过aiqingmaimai匹配
但是不支持直接从中间的字符匹配,如qing…、qmm
也不支持首字母和全拼音的混合搜索,如aqmaim

具体的使用方法如下:

- 关联类库

    compile project(':searchview_library')

- 在布局文件中定义

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <com.example.searchview_library.SearchView
            android:id="@+id/searchView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

- 初始化

    private SearchView searchView;
    searchView = (SearchView) findViewById(R.id.searchView);

- 设置数据

    //热搜数据
    private List<String> hot_datas;
    //提示列表数据
    private List<String> hint_datas;

    private void initData() {
        hot_datas = new ArrayList<>();
        hint_datas = new ArrayList<>();

        searchALG = new SearchALG(this);

        for (int i = 0; i < 10; i++) {
            hot_datas.add("Android Hot " + i);
        }

        //注:若不需要热搜列表,可以不设置
        //设置热搜数据显示的列数
        searchView.setHotNumColumns(2);
        //设置热搜数据
        searchView.setHotSearchDatas(hot_datas);

        /**
         * 设置提示数据的集合
         */
        for (int i = 0; i < 10; i++) {
            hint_datas.add("ts"+"安卓学习" + "Android Hint " + i + " 你好");
        }

        /**
         * 设置自动保存搜索记录
         */
        searchView.keepSearchHistory(true);

        //设置提示列表的最大显示列数
        searchView.setMaxHintLines(8);
        //设置保存搜索记录的个数
        searchView.setMaxHistoryRecordCount(6);

    }

- 设置监听

    private SearchALG searchALG;
    private List<String> changedHintDatas;

    searchView.setOnSearchListener(new MyOnSearchListener());

    /**
     * 设置searview的监听
     */
    class MyOnSearchListener implements SearchView.OnSearchListener {

        /**
         * 搜索回调
         * @param searchText 进行搜索的文本
         */
        @Override
        public void onSearch(String searchText) {
            if (!TextUtils.isEmpty(searchText)) {
                Toast.makeText(MainActivity.this, "完成搜索" + searchText, Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(MainActivity.this, "搜索内容不能为空!", Toast.LENGTH_SHORT).show();
            }
        }

        /**
         * 刷新提示列表
         * @param changedText 改变后的文本
         */
        @Override
        public void onRefreshHintList(String changedText) {
            if (changedHintDatas == null) {
                changedHintDatas = new ArrayList<>();
            } else {
                changedHintDatas.clear();
            }
            if (TextUtils.isEmpty(changedText)) {
                return;
            }
            for (int i = 0; i < hint_datas.size(); i++) {
                String hint_data = hint_datas.get(i);
                boolean isAdd = searchALG.isAddToHintList(hint_data, changedText);
                if (isAdd) {
                    changedHintDatas.add(hint_datas.get(i));
                }
            }

            /**
             * 根据搜索框文本的变化,动态的改变提示的listView
             */
            searchView.updateHintList(changedHintDatas);

        }
    }

demo下载地址:
http://download.csdn.net/detail/benhuo931115/9480849

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您可以按照以下步骤来自定义搜索框: 1. 在您的 HTML 文件中,添加一个 `<form>` 元素,用于容纳搜索框和提交按钮。 ```html <form class="search-form" action="/search" method="GET"> <input type="text" name="query" placeholder="请输入搜索关键字"> <button type="submit">搜索</button> </form> ``` 2. 根据您的需求,自定义搜索框的样式。您可以使用 CSS 来修改搜索框的外观。 ```css .search-form { display: flex; align-items: center; } .search-form input[type="text"] { padding: 8px; border: 1px solid #ccc; border-radius: 4px; } .search-form button[type="submit"] { padding: 8px 12px; background-color: #4caf50; color: white; border: none; border-radius: 4px; cursor: pointer; } ``` 3. 在您的后端代码中处理搜索请求。根据您使用的后端语言和框,您可以获取用户在搜索框中输入的关键字,并在后端进行搜索操作。以下是一个示例,使用 Node.js 中的 Express 框处理搜索请求的代码: ```javascript const express = require('express'); const app = express(); app.get('/search', (req, res) => { const query = req.query.query; // 获取搜索关键字 // 在这里进行搜索操作,并返回相应的结果 }); app.listen(3000, () => { console.log('服务器已启动'); }); ``` 请注意,这只是一个基本的示例,您可能需要根据您的具体要求进行进一步的调整和扩展。同时,确保根据您的项目需要进行安全性和输入验证处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值