1. 程式人生 > >Android自定義View——自定義搜尋框(SearchView) 非常實用的控制元件

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

[html] view plain copy print ?
  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

[java] view plain copy print ?
  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

[java] view plain copy print ?
  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);
        }
    }

    /**