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。或者學習一下通用介面卡的打造。可以參考
實現
好了,說了那麼多,開始來看程式碼吧
先看SearchView的佈局檔案 search_layout.xml
- <?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>
<?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
- 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;
- }
- }
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
- 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);
- }
- }
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);
}
}
/**