Android自定义View——自定义搜索框(SearchView)

概述

在Android开发中,当系统数据项比较多时,常常会在app添加搜索功能,方便用户能快速获得需要的数据。搜索栏对于我们并不陌生,在许多app都能见到它,比如豌豆荚

这里写图片描述

在某些情况下,我们希望我们的自动补全信息可以不只是纯文本,还可以像豌豆荚这样,能显示相应的图片和其他数据信息,因此Android给我们提供的AutoCompleteTextView往往就不够用,在大多情况下我们都需要自己去实现搜索框。

分析

根据上面这张图,简单分析一下自定义搜索框的结构与功能,有 
1. 搜索界面大致由三部门组成,如图:输入框+(自动补全)提示框+结果列表。 
2. 提示框的数据与输入框输入的文本是实时联动的,而结果列表只有在每次进行搜索操作时才会更新数据 
3. 输入框的UI应是动态的,即UI随着输入的文本的改变而改变,如:在未输入文本时,清除按钮这里写图片描述应该是隐藏的;只有当框中有文本时才会显示。 
4. 软键盘也应该是动态的,如完成搜索时应自动隐藏。 
5. 选择提示框的选项会自动补全输入框,且自动进行搜索 
6. (external)有热门搜索推荐/记录搜索记录的功能——热门搜索推荐列表只在刚要进行搜索的时候弹出,即未输入文本时,可供用户选择。

根据上面的分析,我们认为一个搜索框应该包含输入框和提示框两个部分。搜索框可以设置一个回调监听接口,当需要进行搜索操作时,调用监听者的search()方法,从而实现具体的搜索操作以及结果列表的数据联动。

演示Demo

这里写图片描述

注意: 
1. 这里,博主图方便没有模拟太多数据,而且提示框和热搜列表也都只是使用String类型的数据,各位看官们可以根据自身需要去设置item_layout和相应的adapter。 
2. 由于个人习惯,博主在这个demo中使用了通用适配器,所以生成和设置adapter的代码比较简略,看官们可以根据传统的ViewHolder模式打造自己的adapter。或者学习一下通用适配器的打造。可以参考这里(鸿神博客Again)学习一下通用适配器的打造,在我的源码里面也有对应的源码。

实现

好了,说了那么多,开始来看代码吧

先看SearchView的布局文件 search_layout.xml

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.               android:background="#eee"  
  4.               android:layout_width="match_parent"  
  5.               android:layout_height="wrap_content"  
  6.               android:orientation="vertical">  
  7.   
  8.     <LinearLayout  
  9.         android:background="#eb4f38"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="wrap_content"  
  12.         android:orientation="horizontal">  
  13.   
  14.   
  15.         <FrameLayout  
  16.   
  17.             android:layout_weight="1"  
  18.             android:layout_width="0dp"  
  19.             android:layout_height="wrap_content">  
  20.   
  21.             <EditText  
  22.                 android:id="@+id/search_et_input"  
  23.                 android:layout_gravity="center_vertical"  
  24.                 android:layout_margin="10dp"  
  25.                 android:drawableLeft="@drawable/search_icon"  
  26.                 android:drawablePadding="5dp"  
  27.                 android:layout_width="match_parent"  
  28.                 android:layout_height="wrap_content"  
  29.                 android:background="@drawable/search_edittext_shape"  
  30.                 android:textSize="16sp"  
  31.                 android:imeOptions="actionSearch"  
  32.                 android:inputType="text"  
  33.                 android:hint="请输入关键字"/>  
  34.   
  35.             <ImageView  
  36.                 android:visibility="gone"  
  37.                 android:layout_marginRight="20dp"  
  38.                 android:src="@drawable/iv_delete_bg"  
  39.                 android:id="@+id/search_iv_delete"  
  40.                 android:layout_gravity="right|center_vertical"  
  41.                 android:layout_width="wrap_content"  
  42.                 android:layout_height="wrap_content"/>  
  43.         </FrameLayout>  
  44.   
  45.         <Button  
  46.             android:id="@+id/search_btn_back"  
  47.             android:layout_marginRight="10dp"  
  48.             android:layout_marginTop="10dp"  
  49.             android:layout_marginBottom="10dp"  
  50.             android:layout_gravity="center_vertical"  
  51.             android:background="@drawable/btn_search_bg"  
  52.             android:layout_width="@dimen/btn_width"  
  53.             android:layout_height="@dimen/btn_height"  
  54.             android:text="返回"  
  55.             android:textColor="@color/color_white"/>  
  56.     </LinearLayout>  
  57.   
  58.     <ListView  
  59.         android:visibility="gone"  
  60.         android:id="@+id/search_lv_tips"  
  61.         android:background="@drawable/lv_search_tips_bg"  
  62.         android:layout_marginLeft="20dp"  
  63.         android:layout_marginRight="20dp"  
  64.         android:layout_marginBottom="10dp"  
  65.         android:layout_width="match_parent"  
  66.         android:layout_height="200dp">  
  67.     </ListView>  
  68. </LinearLayout>  
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:background="#eee"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="vertical">

    <LinearLayout
        android:background="#eb4f38"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">


        <FrameLayout

            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/search_et_input"
                android:layout_gravity="center_vertical"
                android:layout_margin="10dp"
                android:drawableLeft="@drawable/search_icon"
                android:drawablePadding="5dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/search_edittext_shape"
                android:textSize="16sp"
                android:imeOptions="actionSearch"
                android:inputType="text"
                android:hint="请输入关键字"/>

            <ImageView
                android:visibility="gone"
                android:layout_marginRight="20dp"
                android:src="@drawable/iv_delete_bg"
                android:id="@+id/search_iv_delete"
                android:layout_gravity="right|center_vertical"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </FrameLayout>

        <Button
            android:id="@+id/search_btn_back"
            android:layout_marginRight="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:layout_gravity="center_vertical"
            android:background="@drawable/btn_search_bg"
            android:layout_width="@dimen/btn_width"
            android:layout_height="@dimen/btn_height"
            android:text="返回"
            android:textColor="@color/color_white"/>
    </LinearLayout>

    <ListView
        android:visibility="gone"
        android:id="@+id/search_lv_tips"
        android:background="@drawable/lv_search_tips_bg"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginBottom="10dp"
        android:layout_width="match_parent"
        android:layout_height="200dp">
    </ListView>
</LinearLayout>


注意:demo中颜色什么的都直接用的rgb 值去设置,在实际开发时,需要把它们都统一管理到values目录下 。

比较简单,需要注意的是EditText的这个属性

android:imeOptions="actionSearch" 
就是把Enter键设置为Search键,并把点击Enter键的动作设为actionSearch,这样既可在代码中监听何时按下search键

没什么说的,bg属性可以直接看看源码。接下来看模拟的bean类,这里直接就叫Bean.java

  1. public class Bean {  
  2.   
  3.     private int iconId;  
  4.     private String title;  
  5.     private String content;  
  6.     private String comments;  
  7.   
  8.     public Bean(int iconId, String title, String content, String comments) {  
  9.         this.iconId = iconId;  
  10.         this.title = title;  
  11.         this.content = content;  
  12.         this.comments = comments;  
  13.     }  
  14.   
  15.     public int getIconId() {  
  16.         return iconId;  
  17.     }  
  18.   
  19.     public void setIconId(int iconId) {  
  20.         this.iconId = iconId;  
  21.     }  
  22.   
  23.     public String getTitle() {  
  24.         return title;  
  25.     }  
  26.   
  27.     public void setTitle(String title) {  
  28.         this.title = title;  
  29.     }  
  30.   
  31.     public String getContent() {  
  32.         return content;  
  33.     }  
  34.   
  35.     public void setContent(String content) {  
  36.         this.content = content;  
  37.     }  
  38.   
  39.     public String getComments() {  
  40.         return comments;  
  41.     }  
  42.   
  43.     public void setComments(String comments) {  
  44.         this.comments = comments;  
  45.     }  
  46. }  
public class Bean {

    private int iconId;
    private String title;
    private String content;
    private String comments;

    public Bean(int iconId, String title, String content, String comments) {
        this.iconId = iconId;
        this.title = title;
        this.content = content;
        this.comments = comments;
    }

    public int getIconId() {
        return iconId;
    }

    public void setIconId(int iconId) {
        this.iconId = iconId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getComments() {
        return comments;
    }

    public void setComments(String comments) {
        this.comments = comments;
    }
}

接着看主角SearchView.java

  1. public class SearchView extends LinearLayout implements View.OnClickListener {  
  2.   
  3.     /** 
  4.      * 输入框 
  5.      */  
  6.     private EditText etInput;  
  7.   
  8.     /** 
  9.      * 删除键 
  10.      */  
  11.     private ImageView ivDelete;  
  12.   
  13.     /** 
  14.      * 返回按钮 
  15.      */  
  16.     private Button btnBack;  
  17.   
  18.     /** 
  19.      * 上下文对象 
  20.      */  
  21.     private Context mContext;  
  22.   
  23.     /** 
  24.      * 弹出列表 
  25.      */  
  26.     private ListView lvTips;  
  27.   
  28.     /** 
  29.      * 提示adapter (推荐adapter) 
  30.      */  
  31.     private ArrayAdapter<String> mHintAdapter;  
  32.   
  33.     /** 
  34.      * 自动补全adapter 只显示名字 
  35.      */  
  36.     private ArrayAdapter<String> mAutoCompleteAdapter;  
  37.   
  38.     /** 
  39.      * 搜索回调接口 
  40.      */  
  41.     private SearchViewListener mListener;  
  42.   
  43.     /** 
  44.      * 设置搜索回调接口 
  45.      * 
  46.      * @param listener 监听者 
  47.      */  
  48.     public void setSearchViewListener(SearchViewListener listener) {  
  49.         mListener = listener;  
  50.     }  
  51.   
  52.     public SearchView(Context context, AttributeSet attrs) {  
  53.         super(context, attrs);  
  54.         mContext = context;  
  55.         LayoutInflater.from(context).inflate(R.layout.search_layout, this);  
  56.         initViews();  
  57.     }  
  58.   
  59.     private void initViews() {  
  60.         etInput = (EditText) findViewById(R.id.search_et_input);  
  61.         ivDelete = (ImageView) findViewById(R.id.search_iv_delete);  
  62.         btnBack = (Button) findViewById(R.id.search_btn_back);  
  63.         lvTips = (ListView) findViewById(R.id.search_lv_tips);  
  64.   
  65.         lvTips.setOnItemClickListener(new AdapterView.OnItemClickListener() {  
  66.             @Override  
  67.             public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {  
  68.                 //set edit text  
  69.                 String text = lvTips.getAdapter().getItem(i).toString();  
  70.                 etInput.setText(text);  
  71.                 etInput.setSelection(text.length());  
  72.                 //hint list view gone and result list view show  
  73.                 lvTips.setVisibility(View.GONE);  
  74.                 notifyStartSearching(text);  
  75.             }  
  76.         });  
  77.   
  78.         ivDelete.setOnClickListener(this);  
  79.         btnBack.setOnClickListener(this);  
  80.   
  81.         etInput.addTextChangedListener(new EditChangedListener());  
  82.         etInput.setOnClickListener(this);  
  83.         etInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {  
  84.             @Override  
  85.             public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {  
  86.                 if (actionId == EditorInfo.IME_ACTION_SEARCH) {  
  87.                     lvTips.setVisibility(GONE);  
  88.                     notifyStartSearching(etInput.getText().toString());  
  89.                 }  
  90.                 return true;  
  91.             }  
  92.         });  
  93.     }  
  94.   
  95.     /** 
  96.      * 通知监听者 进行搜索操作 
  97.      * @param text 
  98.      */  
  99.     private void notifyStartSearching(String text){  
  100.         if (mListener != null) {  
  101.             mListener.onSearch(etInput.getText().toString());  
  102.         }  
  103.         //隐藏软键盘  
  104.         InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);  
  105.         imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);  
  106.     }  
  107.   
  108.     /** 
  109.      * 设置热搜版提示 adapter 
  110.      */  
  111.     public void setTipsHintAdapter(ArrayAdapter<String> adapter) {  
  112.         this.mHintAdapter = adapter;  
  113.         if (lvTips.getAdapter() == null) {  
  114.             lvTips.setAdapter(mHintAdapter);  
  115.         }  
  116.     }  
  117.   
  118.     /** 
  119.      * 设置自动补全adapter 
  120.      */  
  121.     public void setAutoCompleteAdapter(ArrayAdapter<String> adapter) {  
  122.         this.mAutoCompleteAdapter = adapter;  
  123.     }  
  124.   
  125.     private class EditChangedListener implements TextWatcher {  
  126.         @Override  
  127.         public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {  
  128.   
  129.         }  
  130.   
  131.         @Override  
  132.         public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {  
  133.             if (!"".equals(charSequence.toString())) {  
  134.                 ivDelete.setVisibility(VISIBLE);  
  135.                 lvTips.setVisibility(VISIBLE);  
  136.                 if (mAutoCompleteAdapter != null && lvTips.getAdapter() != mAutoCompleteAdapter) {  
  137.                     lvTips.setAdapter(mAutoCompleteAdapter);  
  138.                 }  
  139.                 //更新autoComplete数据  
  140.                 if (mListener != null) {  
  141.                     mListener.onRefreshAutoComplete(charSequence + "");  
  142.                 }  
  143.             } else {  
  144.                 ivDelete.setVisibility(GONE);  
  145.                 if (mHintAdapter != null) {  
  146.                     lvTips.setAdapter(mHintAdapter);  
  147.                 }  
  148.                 lvTips.setVisibility(GONE);  
  149.             }  
  150.   
  151.         }  
  152.   
  153.         @Override  
  154.         public void afterTextChanged(Editable editable) {  
  155.         }  
  156.     }  
  157.   
  158.     @Override  
  159.     public void onClick(View view) {  
  160.         switch (view.getId()) {  
  161.             case R.id.search_et_input:  
  162.                 lvTips.setVisibility(VISIBLE);  
  163.                 break;  
  164.             case R.id.search_iv_delete:  
  165.                 etInput.setText("");  
  166.                 ivDelete.setVisibility(GONE);  
  167.                 break;  
  168.             case R.id.search_btn_back:  
  169.                 ((Activity) mContext).finish();  
  170.                 break;  
  171.         }  
  172.     }  
  173.   
  174.     /** 
  175.      * search view回调方法 
  176.      */  
  177.     public interface SearchViewListener {  
  178.   
  179.         /** 
  180.          * 更新自动补全内容 
  181.          * 
  182.          * @param text 传入补全后的文本 
  183.          */  
  184.         void onRefreshAutoComplete(String text);  
  185.   
  186.         /** 
  187.          * 开始搜索 
  188.          * 
  189.          * @param text 传入输入框的文本 
  190.          */  
  191.         void onSearch(String text);  
  192.   
  193. //        /**  
  194. //         * 提示列表项点击时回调方法 (提示/自动补全)  
  195. //         */  
  196. //        void onTipsItemClick(String text);  
  197.     }  
  198.   
  199. }  
public class SearchView extends LinearLayout implements View.OnClickListener {

    /**
     * 输入框
     */
    private EditText etInput;

    /**
     * 删除键
     */
    private ImageView ivDelete;

    /**
     * 返回按钮
     */
    private Button btnBack;

    /**
     * 上下文对象
     */
    private Context mContext;

    /**
     * 弹出列表
     */
    private ListView lvTips;

    /**
     * 提示adapter (推荐adapter)
     */
    private ArrayAdapter<String> mHintAdapter;

    /**
     * 自动补全adapter 只显示名字
     */
    private ArrayAdapter<String> mAutoCompleteAdapter;

    /**
     * 搜索回调接口
     */
    private SearchViewListener mListener;

    /**
     * 设置搜索回调接口
     *
     * @param listener 监听者
     */
    public void setSearchViewListener(SearchViewListener listener) {
        mListener = listener;
    }

    public SearchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        LayoutInflater.from(context).inflate(R.layout.search_layout, this);
        initViews();
    }

    private void initViews() {
        etInput = (EditText) findViewById(R.id.search_et_input);
        ivDelete = (ImageView) findViewById(R.id.search_iv_delete);
        btnBack = (Button) findViewById(R.id.search_btn_back);
        lvTips = (ListView) findViewById(R.id.search_lv_tips);

        lvTips.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                //set edit text
                String text = lvTips.getAdapter().getItem(i).toString();
                etInput.setText(text);
                etInput.setSelection(text.length());
                //hint list view gone and result list view show
                lvTips.setVisibility(View.GONE);
                notifyStartSearching(text);
            }
        });

        ivDelete.setOnClickListener(this);
        btnBack.setOnClickListener(this);

        etInput.addTextChangedListener(new EditChangedListener());
        etInput.setOnClickListener(this);
        etInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
                if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                    lvTips.setVisibility(GONE);
                    notifyStartSearching(etInput.getText().toString());
                }
                return true;
            }
        });
    }

    /**
     * 通知监听者 进行搜索操作
     * @param text
     */
    private void notifyStartSearching(String text){
        if (mListener != null) {
            mListener.onSearch(etInput.getText().toString());
        }
        //隐藏软键盘
        InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
    }

    /**
     * 设置热搜版提示 adapter
     */
    public void setTipsHintAdapter(ArrayAdapter<String> adapter) {
        this.mHintAdapter = adapter;
        if (lvTips.getAdapter() == null) {
            lvTips.setAdapter(mHintAdapter);
        }
    }

    /**
     * 设置自动补全adapter
     */
    public void setAutoCompleteAdapter(ArrayAdapter<String> adapter) {
        this.mAutoCompleteAdapter = adapter;
    }

    private class EditChangedListener implements TextWatcher {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
            if (!"".equals(charSequence.toString())) {
                ivDelete.setVisibility(VISIBLE);
                lvTips.setVisibility(VISIBLE);
                if (mAutoCompleteAdapter != null && lvTips.getAdapter() != mAutoCompleteAdapter) {
                    lvTips.setAdapter(mAutoCompleteAdapter);
                }
                //更新autoComplete数据
                if (mListener != null) {
                    mListener.onRefreshAutoComplete(charSequence + "");
                }
            } else {
                ivDelete.setVisibility(GONE);
                if (mHintAdapter != null) {
                    lvTips.setAdapter(mHintAdapter);
                }
                lvTips.setVisibility(GONE);
            }

        }

        @Override
        public void afterTextChanged(Editable editable) {
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.search_et_input:
                lvTips.setVisibility(VISIBLE);
                break;
            case R.id.search_iv_delete:
                etInput.setText("");
                ivDelete.setVisibility(GONE);
                break;
            case R.id.search_btn_back:
                ((Activity) mContext).finish();
                break;
        }
    }

    /**
     * search view回调方法
     */
    public interface SearchViewListener {

        /**
         * 更新自动补全内容
         *
         * @param text 传入补全后的文本
         */
        void onRefreshAutoComplete(String text);

        /**
         * 开始搜索
         *
         * @param text 传入输入框的文本
         */
        void onSearch(String text);

//        /**
//         * 提示列表项点击时回调方法 (提示/自动补全)
//         */
//        void onTipsItemClick(String text);
    }

}

搜索框主要包含两个结构:输入栏+弹出框(自动补全或热门搜素推荐)。

代码不多,实现很简单,主要是需要给EditText(输入框)设置点击监听和文本改变监听,有以下几点: 
1. 当输入框没有文本时,点击输入框,显示热门搜索列表框。 
2. 当输入框有文本时,点击输入框,应显示自动补全列表框。 
3. 当输入框的文本发生改变时,需要更新自动补全列表框的数据。由于这些数据应该是在外部(调用者)中获得的,所以可以通过接口回调的形式,当需要更新时,通知监听者更新数据。 
4. 当输入框的文本从空”“变换到非空时,即有字符时,界面应显示自动补全框,隐藏热门搜索框。 
5. 当输入框的文本从非空变为空时,系统应隐藏自动补全框和热门搜索框。 
6. 需要监听是否按下search键(enter),按下时通知监听者执行search操作

结合以上6点和在上文分析过的内容,就能很轻松地实现该view。

之后来看看搜索界面的布局文activity_main.xml

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.               xmlns:tools="http://schemas.android.com/tools"  
  3.               android:layout_width="match_parent"  
  4.               android:layout_height="match_parent"  
  5.               tools:context=".MainActivity"  
  6.               android:orientation="vertical">  
  7.   
  8.     <com.yetwish.customsearchdemo.activity.widge.SearchView  
  9.         android:id="@+id/main_search_layout"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="wrap_content">  
  12.     </com.yetwish.customsearchdemo.activity.widge.SearchView>  
  13.   
  14.     <ListView  
  15.         android:visibility="gone"  
  16.         android:id="@+id/main_lv_search_results"  
  17.         android:layout_width="match_parent"  
  18.         android:layout_height="wrap_content">  
  19.   
  20.     </ListView>  
  21. </LinearLayout>  
<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"
              tools:context=".MainActivity"
              android:orientation="vertical">

    <com.yetwish.customsearchdemo.activity.widge.SearchView
        android:id="@+id/main_search_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </com.yetwish.customsearchdemo.activity.widge.SearchView>

    <ListView
        android:visibility="gone"
        android:id="@+id/main_lv_search_results"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </ListView>
</LinearLayout>

就是一个SearchView加上一个结果列表,这些我们在上文都分析过了,所以也没什么好说的。布局可根据自身需求去自定义。

最后就是搜索界面调用该view  MainActiviy.java

  1. public class MainActivity extends Activity implements SearchView.SearchViewListener {  
  2.   
  3.     /** 
  4.      * 搜索结果列表view 
  5.      */  
  6.     private ListView lvResults;  
  7.   
  8.     /** 
  9.      * 搜索view 
  10.      */  
  11.     private SearchView searchView;  
  12.   
  13.   
  14.     /** 
  15.      * 热搜框列表adapter 
  16.      */  
  17.     private ArrayAdapter<String> hintAdapter;  
  18.   
  19.     /** 
  20.      * 自动补全列表adapter 
  21.      */  
  22.     private ArrayAdapter<String> autoCompleteAdapter;  
  23.   
  24.     /** 
  25.      * 搜索结果列表adapter 
  26.      */  
  27.     private SearchAdapter resultAdapter;  
  28.   
  29.     /** 
  30.      * 数据库数据,总数据 
  31.      */  
  32.     private List<Bean> dbData;  
  33.   
  34.     /** 
  35.      * 热搜版数据 
  36.      */  
  37.     private List<String> hintData;  
  38.   
  39.     /** 
  40.      * 搜索过程中自动补全数据 
  41.      */  
  42.     private List<String> autoCompleteData;  
  43.   
  44.     /** 
  45.      * 搜索结果的数据 
  46.      */  
  47.     private List<Bean> resultData;  
  48.   
  49.     /** 
  50.      * 默认提示框显示项的个数 
  51.      */  
  52.     private static int DEFAULT_HINT_SIZE = 4;  
  53.   
  54.     /** 
  55.      * 提示框显示项的个数 
  56.      */  
  57.     private static int hintSize = DEFAULT_HINT_SIZE;  
  58.   
  59.     /** 
  60.      * 设置提示框显示项的个数 
  61.      * 
  62.      * @param hintSize 提示框显示个数 
  63.      */  
  64.     public static void setHintSize(int hintSize) {  
  65.         MainActivity.hintSize = hintSize;  
  66.     }  
  67.   
  68.   
  69.     @Override  
  70.     protected void onCreate(Bundle savedInstanceState) {  
  71.         super.onCreate(savedInstanceState);  
  72.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
  73.         setContentView(R.layout.activity_main);  
  74.         initData();  
  75.         initViews();  
  76.     }  
  77.   
  78.     /** 
  79.      * 初始化视图 
  80.      */  
  81.     private void initViews() {  
  82.         lvResults = (ListView) findViewById(R.id.main_lv_search_results);  
  83.         searchView = (SearchView) findViewById(R.id.main_search_layout);  
  84.         //设置监听  
  85.         searchView.setSearchViewListener(this);  
  86.         //设置adapter  
  87.         searchView.setTipsHintAdapter(hintAdapter);  
  88.         searchView.setAutoCompleteAdapter(autoCompleteAdapter);  
  89.   
  90.         lvResults.setOnItemClickListener(new AdapterView.OnItemClickListener() {  
  91.             @Override  
  92.             public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {  
  93.                 Toast.makeText(MainActivity.this, position + "", Toast.LENGTH_SHORT).show();  
  94.             }  
  95.         });  
  96.     }  
  97.   
  98.     /** 
  99.      * 初始化数据 
  100.      */  
  101.     private void initData() {  
  102.         //从数据库获取数据  
  103.         getDbData();  
  104.         //初始化热搜版数据  
  105.         getHintData();  
  106.         //初始化自动补全数据  
  107.         getAutoCompleteData(null);  
  108.         //初始化搜索结果数据  
  109.         getResultData(null);  
  110.     }  
  111.   
  112.     /** 
  113.      * 获取db 数据 
  114.      */  
  115.     private void getDbData() {  
  116.         int size = 100;  
  117.         dbData = new ArrayList<>(size);  
  118.         for (int i = 0; i < size; i++) {  
  119.             dbData.add(new Bean(R.drawable.icon, "android开发必备技能" + (i + 1), "Android自定义view——自定义搜索view", i * 20 + 2 + ""));  
  120.         }  
  121.     }  
  122.   
  123.     /** 
  124.      * 获取热搜版data 和adapter 
  125.      */  
  126.     private void getHintData() {  
  127.         hintData = new ArrayList<>(hintSize);  
  128.         for (int i = 1; i <= hintSize; i++) {  
  129.             hintData.add("热搜版" + i + ":Android自定义View");  
  130.         }  
  131.         hintAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, hintData);  
  132.     }  
  133.   
  134.     /** 
  135.      * 获取自动补全data 和adapter 
  136.      */  
  137.     private void getAutoCompleteData(String text) {  
  138.         if (autoCompleteData == null) {  
  139.             //初始化  
  140.             autoCompleteData = new ArrayList<>(hintSize);  
  141.         } else {  
  142.             // 根据text 获取auto data  
  143.             autoCompleteData.clear();  
  144.             for (int i = 0, count = 0; i < dbData.size()  
  145.                     && count < hintSize; i++) {  
  146.                 if (dbData.get(i).getTitle().contains(text.trim())) {  
  147.                     autoCompleteData.add(dbData.get(i).getTitle());  
  148.                     count++;  
  149.                 }  
  150.             }  
  151.         }  
  152.         if (autoCompleteAdapter == null) {  
  153.             autoCompleteAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, autoCompleteData);  
  154.         } else {  
  155.             autoCompleteAdapter.notifyDataSetChanged();  
  156.         }  
  157.     }  
  158.   
  159.     /** 
  160.      * 获取搜索结果data和adapter 
  161.      */  
  162.     private void getResultData(String text) {  
  163.         if (resultData == null) {  
  164.             // 初始化  
  165.             resultData = new ArrayList<>();  
  166.         } else {  
  167.             resultData.clear();  
  168.             for (int i = 0; i < dbData.size(); i++) {  
  169.                 if (dbData.get(i).getTitle().contains(text.trim())) {  
  170.                     resultData.add(dbData.get(i));  
  171.                 }  
  172.             }  
  173.         }  
  174.         if (resultAdapter == null) {  
  175.             resultAdapter = new SearchAdapter(this, resultData, R.layout.item_bean_list);  
  176.         } else {  
  177.             resultAdapter.notifyDataSetChanged();  
  178.         }  
  179.     }  
  180.   
  181.     /** 
  182.      * 当搜索框 文本改变时 触发的回调 ,更新自动补全数据 
  183.      * @param text 
  184.      */  
  185.     @Override  
  186.     public void onRefreshAutoComplete(String text) {  
  187.         //更新数据  
  188.         getAutoCompleteData(text);  
  189.     }  
  190.   
  191.     /** 
  192.      * 点击搜索键时edit text触发的回调 
  193.      * 
  194.      * @param text 
  195.      */  
  196.     @Override  
  197.     public void onSearch(String text) {  
  198.         //更新result数据  
  199.         getResultData(text);  
  200.         lvResults.setVisibility(View.VISIBLE);  
  201.         //第一次获取结果 还未配置适配器  
  202.         if (lvResults.getAdapter() == null) {  
  203.             //获取搜索数据 设置适配器  
  204.             lvResults.setAdapter(resultAdapter);  
  205.         } else {  
  206.             //更新搜索数据  
  207.             resultAdapter.notifyDataSetChanged();  
  208.         }  
  209.         Toast.makeText(this"完成搜素", Toast.LENGTH_SHORT).show();  
  210.     }  
  211.   
  212. }  
public class MainActivity extends Activity implements SearchView.SearchViewListener {

    /**
     * 搜索结果列表view
     */
    private ListView lvResults;

    /**
     * 搜索view
     */
    private SearchView searchView;


    /**
     * 热搜框列表adapter
     */
    private ArrayAdapter<String> hintAdapter;

    /**
     * 自动补全列表adapter
     */
    private ArrayAdapter<String> autoCompleteAdapter;

    /**
     * 搜索结果列表adapter
     */
    private SearchAdapter resultAdapter;

    /**
     * 数据库数据,总数据
     */
    private List<Bean> dbData;

    /**
     * 热搜版数据
     */
    private List<String> hintData;

    /**
     * 搜索过程中自动补全数据
     */
    private List<String> autoCompleteData;

    /**
     * 搜索结果的数据
     */
    private List<Bean> resultData;

    /**
     * 默认提示框显示项的个数
     */
    private static int DEFAULT_HINT_SIZE = 4;

    /**
     * 提示框显示项的个数
     */
    private static int hintSize = DEFAULT_HINT_SIZE;

    /**
     * 设置提示框显示项的个数
     *
     * @param hintSize 提示框显示个数
     */
    public static void setHintSize(int hintSize) {
        MainActivity.hintSize = hintSize;
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        initData();
        initViews();
    }

    /**
     * 初始化视图
     */
    private void initViews() {
        lvResults = (ListView) findViewById(R.id.main_lv_search_results);
        searchView = (SearchView) findViewById(R.id.main_search_layout);
        //设置监听
        searchView.setSearchViewListener(this);
        //设置adapter
        searchView.setTipsHintAdapter(hintAdapter);
        searchView.setAutoCompleteAdapter(autoCompleteAdapter);

        lvResults.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                Toast.makeText(MainActivity.this, position + "", Toast.LENGTH_SHORT).show();
            }
        });
    }

    /**
     * 初始化数据
     */
    private void initData() {
        //从数据库获取数据
        getDbData();
        //初始化热搜版数据
        getHintData();
        //初始化自动补全数据
        getAutoCompleteData(null);
        //初始化搜索结果数据
        getResultData(null);
    }

    /**
     * 获取db 数据
     */
    private void getDbData() {
        int size = 100;
        dbData = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            dbData.add(new Bean(R.drawable.icon, "android开发必备技能" + (i + 1), "Android自定义view——自定义搜索view", i * 20 + 2 + ""));
        }
    }

    /**
     * 获取热搜版data 和adapter
     */
    private void getHintData() {
        hintData = new ArrayList<>(hintSize);
        for (int i = 1; i <= hintSize; i++) {
            hintData.add("热搜版" + i + ":Android自定义View");
        }
        hintAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, hintData);
    }

    /**
     * 获取自动补全data 和adapter
     */
    private void getAutoCompleteData(String text) {
        if (autoCompleteData == null) {
            //初始化
            autoCompleteData = new ArrayList<>(hintSize);
        } else {
            // 根据text 获取auto data
            autoCompleteData.clear();
            for (int i = 0, count = 0; i < dbData.size()
                    && count < hintSize; i++) {
                if (dbData.get(i).getTitle().contains(text.trim())) {
                    autoCompleteData.add(dbData.get(i).getTitle());
                    count++;
                }
            }
        }
        if (autoCompleteAdapter == null) {
            autoCompleteAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, autoCompleteData);
        } else {
            autoCompleteAdapter.notifyDataSetChanged();
        }
    }

    /**
     * 获取搜索结果data和adapter
     */
    private void getResultData(String text) {
        if (resultData == null) {
            // 初始化
            resultData = new ArrayList<>();
        } else {
            resultData.clear();
            for (int i = 0; i < dbData.size(); i++) {
                if (dbData.get(i).getTitle().contains(text.trim())) {
                    resultData.add(dbData.get(i));
                }
            }
        }
        if (resultAdapter == null) {
            resultAdapter = new SearchAdapter(this, resultData, R.layout.item_bean_list);
        } else {
            resultAdapter.notifyDataSetChanged();
        }
    }

    /**
     * 当搜索框 文本改变时 触发的回调 ,更新自动补全数据
     * @param text
     */
    @Override
    public void onRefreshAutoComplete(String text) {
        //更新数据
        getAutoCompleteData(text);
    }

    /**
     * 点击搜索键时edit text触发的回调
     *
     * @param text
     */
    @Override
    public void onSearch(String text) {
        //更新result数据
        getResultData(text);
        lvResults.setVisibility(View.VISIBLE);
        //第一次获取结果 还未配置适配器
        if (lvResults.getAdapter() == null) {
            //获取搜索数据 设置适配器
            lvResults.setAdapter(resultAdapter);
        } else {
            //更新搜索数据
            resultAdapter.notifyDataSetChanged();
        }
        Toast.makeText(this, "完成搜素", Toast.LENGTH_SHORT).show();
    }

}


使用SearchView比较简单,只要给SearchView设置onSearchViewListener监听接口,实现对应的方法,并给SearchView传入热搜版和自动补全的adapter既可。

这里使用的匹配算法比较简单,也没有考虑多个搜索词的情况,(这些之后都可以再完善),主要实现就是在总数据中匹配每个Bean的Title是否包含搜索词,包含则表示该数据匹配,否则不匹配。然后将所有匹配的Bean显示到结果列表中。

考虑到实际开发中,数据量十分庞大,可以只把结果集的一部分(如前10个)显示出来,上拉到底的时候再加载之后的记录,也就是可以加入上拉加载的机制,使app性能更优化。

自动补全匹配也是采用相同的算法。算法都比较简单,当然也可以弄得复杂点,比如根据“ ”(空格)去分割输入文本,再逐个考虑单个搜索词的匹配项,把匹配次数从多到少排列出结果集等等。这里不细说。

这里有一个问题是进入该搜索界面时需要加载所有的数据项到内存,当数据项很多时,是否会占用大量的内存?如果是应该如何避免?是采用只加载一部分数据的形式,还是直接使用搜索词到数据库中查询更优?还请各位看官大神们给出宝贵的意见~

好了,自定义搜索框到这就打造完成啦,是不是感觉简单过头了。

各位看官如果有任何问题可评论或者发邮件跟我联系yetwish@gmail.com

囧~忘记贴代码了,代码放在github上,各位看官直接download即可 
链接:https://github.com/yetwish/CustomSearchView

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值