當前位置:
首頁 > 最新 > Android 開源庫:手把手教你實現一個簡單好用的搜索框

Android 開源庫:手把手教你實現一個簡單好用的搜索框

關鍵時刻,第一時間送達!

GIF

前言

Android開發中,類似下圖的搜索功能非常常見

今天,我將手把手教大家實現一款 封裝了 歷史搜索記錄功能 & 樣式 的Android 自定義搜索框 開源庫,希望你們會喜歡。

GIF

已在Github開源:地址:SearchView:https://github.com/Carson-Ho/Search_Layout,歡迎 Star !

目錄

簡介

一款封裝了 歷史搜索記錄功能 & 樣式 的Android自定義搜索框

GIF

需求場景

在開始coding前, 理解好用戶的需求場景 有助於我們更好地設計 & 實現功能

需求場景如下

業務流程圖

根據場景,梳理出來的功能業務流程圖如下:

功能需求

根據功能的業務流程圖,得出功能需求如下

功能列表

功能原型圖

示意圖

GIF

總體設計

下面,將根據功能需求給出特定的技術解決方案

總體解決方案

項目結構說明

項目工程示意圖

結構說明

功能詳細設計

下面將給出詳細的功能邏輯

關鍵字搜索

描述:根據用戶輸入的搜索欄位進行結果搜索

原型圖

註:關鍵字搜索功能是因人而異的,所以本源碼僅留出介面供開發者實現,不作具體實現

源碼分析

分析1:EditText_Clear.Java

作用:自定義EdiText,與系統自帶的EdiText對比:多了左側圖片 & 右側圖片設置、一鍵清空EdiText內容功能

具體代碼如下:

public class EditText_Clear extends android.support.v7.widget.AppCompatEditText {

/**

* 步驟1:定義左側搜索圖標 & 一鍵刪除圖標

*/

private Drawable clearDrawable,searchDrawable;

public EditText_Clear(Context context) {

super(context);

init();

// 初始化該組件時,對EditText_Clear進行初始化 ->>步驟2

}

public EditText_Clear(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

public EditText_Clear(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init();

}

/**

* 步驟2:初始化 圖標資源

*/

private void init() {

clearDrawable = getResources().getDrawable(R.drawable.delete);

searchDrawable = getResources().getDrawable(R.drawable.search);

setCompoundDrawablesWithIntrinsicBounds(searchDrawable, null,

null, null);

// setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom)介紹

// 注1:setCompoundDrawablesWithIntrinsicBounds()傳入的Drawable的寬高=固有寬高(自動通過getIntrinsicWidth()& getIntrinsicHeight()獲取)

// 注2:若不想在某個地方顯示,則設置為null

// 此處設置了左側搜索圖標

// 另外一個相似的方法:setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)介紹

// 與setCompoundDrawablesWithIntrinsicBounds()的區別:可設置圖標大小

// 傳入的Drawable對象必須已經setBounds(x,y,width,height),即必須設置過初始位置、寬和高等信息

// x:組件在容器X軸上的起點 y:組件在容器Y軸上的起點 width:組件的長度 height:組件的高度

}

/**

* 步驟3:通過監聽複寫EditText本身的方法來確定是否顯示刪除圖標

* 監聽方法:onTextChanged() & onFocusChanged()

* 調用時刻:當輸入框內容變化時 & 焦點發生變化時

*/

@Override

protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {

super.onTextChanged(text, start, lengthBefore, lengthAfter);

setClearIconVisible(hasFocus() && text.length() > 0);

// hasFocus()返回是否獲得EditTEXT的焦點,即是否選中

// setClearIconVisible() = 根據傳入的是否選中 & 是否有輸入來判斷是否顯示刪除圖標->>關注1

}

@Override

protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {

super.onFocusChanged(focused, direction, previouslyFocusedRect);

setClearIconVisible(focused && length() > 0);

// focused = 是否獲得焦點

// 同樣根據setClearIconVisible()判斷是否要顯示刪除圖標

}

/**

* 關注1

* 作用:判斷是否顯示刪除圖標

*/

private void setClearIconVisible(boolean visible) {

setCompoundDrawablesWithIntrinsicBounds(searchDrawable, null,

visible ? clearDrawable : null, null);

}

/**

* 步驟4:對刪除圖標區域設置點擊事件,即"點擊 = 清空搜索框內容"

* 原理:當手指抬起的位置在刪除圖標的區域,即視為點擊了刪除圖標 = 清空搜索框內容

*/

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

// 原理:當手指抬起的位置在刪除圖標的區域,即視為點擊了刪除圖標 = 清空搜索框內容

case MotionEvent.ACTION_UP:

Drawable drawable = clearDrawable;

if (drawable != null && event.getX()

&& event.getX() >= (getWidth() - getPaddingRight() - drawable.getBounds().width())) {

setText("");

}

// 判斷條件說明

// event.getX() :抬起時的位置坐標

// getWidth():控制項的寬度

// getPaddingRight():刪除圖標圖標右邊緣至EditText控制項右邊緣的距離

// 即:getWidth() - getPaddingRight() = 刪除圖標的右邊緣坐標 = X1

// getWidth() - getPaddingRight() - drawable.getBounds().width() = 刪除圖標左邊緣的坐標 = X2

// 所以X1與X2之間的區域 = 刪除圖標的區域

// 當手指抬起的位置在刪除圖標的區域(X2=

// 具體示意圖請看下圖

break;

}

return super.onTouchEvent(event);

}

}

對於含有一鍵清空功能 & 更多自定義樣式的EditText自定義控制項具體請看我的另外一個簡單 & 好用的開源組件:Android自定義EditText:http://blog.csdn.net/carson_ho/article/details/77179764

分析2:SearchListView.java

作用:解決 ListView & ScrollView 的嵌套衝突

具體代碼如下:

public class Search_Listview extends ListView {

public Search_Listview(Context context) {

super(context);

}

public Search_Listview(Context context, AttributeSet attrs) {

super(context, attrs);

}

public Search_Listview(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

}

// 通過複寫其onMeasure方法,達到對ScrollView適配的效果

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,

MeasureSpec.AT_MOST);

super.onMeasure(widthMeasureSpec, expandSpec);

}

}

分析3: search_layout.xml

作用:搜索框的布局

具體代碼如下:

xmlns:app="http://schemas.android.com/apk/res-auto"

android:id="@+id/search_block"

// 返回按鈕

android:src="@drawable/back" />

// 搜索框(採用上面寫的自定義EditText

android:id="@+id/et_search"

// 最後2行 = 更換輸入鍵盤按鈕:換行 ->>搜索

// 下方搜索記錄布局 = ScrollView+Listview

// Listview布局(採用上述講解的SearchListView,解決了與ScrollView的衝突)

android:id="@+id/listView"

android:id="@+id/tv_clear"

android:text="清除搜索歷史" />

分析4:ICallBack.java、bCallBack.java

作用:搜索按鍵、返回按鍵回調介面

具體代碼如下:

/**

* ICallBack.java

*/

public interface ICallBack {

void SearchAciton(String string);

}

/**

* bCallBack.java

*/

public interface bCallBack {

void BackAciton();

}

分析5:SearchView.java

作用:涵蓋搜索框中所有功能,此處主要講解 關鍵字搜索 功能實現

具體代碼如下:

/**

* 步驟1:初始化成員變數

*/

// 搜索框組件

private EditText et_search; // 搜索按鍵

private LinearLayout search_block; // 搜索框布局

private ImageView searchBack; // 返回按鍵

// 回調介面

private ICallBack mCallBack;// 搜索按鍵回調介面

private bCallBack bCallBack; // 返回按鍵回調介面

// ListView列表 & 適配器

private SearchListView listView;

private BaseAdapter adapter;

/**

* 步驟2:綁定 搜索框 組件

*/

private void initView(){

// 1. 綁定R.layout.search_layout作為搜索框的xml文件

LayoutInflater.from(context).inflate(R.layout.search_layout,this);

// 2. 綁定搜索框EditText

et_search = (EditText) findViewById(R.id.et_search);

// 3. 搜索框背景顏色

search_block = (LinearLayout)findViewById(R.id.search_block);

// 4. 歷史搜索記錄 = ListView顯示

listView = (Search_Listview) findViewById(R.id.listView);

// 5. 刪除歷史搜索記錄 按鈕

tv_clear = (TextView) findViewById(R.id.tv_clear);

tv_clear.setVisibility(INVISIBLE); // 初始狀態 = 不可見

}

/**

* 步驟3

* 監聽輸入鍵盤更換後的搜索按鍵

* 調用時刻:點擊鍵盤上的搜索鍵時

*/

et_search.setOnKeyListener(new View.OnKeyListener() {

public boolean onKey(View v, int keyCode, KeyEvent event) {

if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN) {

// 點擊搜索按鍵後,根據輸入的搜索欄位進行查詢

// 註:由於此處需求會根據自身情況不同而不同,所以具體邏輯由開發者自己實現,此處僅留出介面

if (!(mCallBack == null)){

mCallBack.SearchAciton(et_search.getText().toString());

}

Toast.makeText(context, "需要搜索的是" + et_search.getText(), Toast.LENGTH_SHORT).show();

}

return false;

}

});

/**

* 步驟4:回調介面

*/

// 搜索按鍵回調介面

public interface ICallBack {

void SearchAciton(String string);

}

// 返回按鍵介面回調

public void setOnClickBack(bCallBack bCallBack){

this.bCallBack = bCallBack;

}

實時顯示歷史搜索記錄

描述:包括 最近搜索記錄 & 相似搜索記錄

原型圖

源碼分析

分析1:RccordSQLiteOpenHelper.java

作用:創建、管理資料庫 & 版本控制

該資料庫用於存儲用戶的搜索歷史記錄

具體代碼如下:

// 繼承自SQLiteOpenHelper資料庫類的子類

public class RecordSQLiteOpenHelper extends SQLiteOpenHelper {

private static String name = "temp.db";

private static Integer version = 1;

public RecordSQLiteOpenHelper(Context context) {

super(context, name, null, version);

}

@Override

public void onCreate(SQLiteDatabase db) {

// 打開資料庫 & 建立一個名為records的表,裡面只有一列name來存儲歷史記錄:

db.execSQL("create table records(id integer primary key autoincrement,name varchar(200))");

}

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

}

分析2:SearchView.java

作用:涵蓋搜索框中所有功能,此處主要講解 實時顯示歷史搜索記錄 功能實現

具體代碼如下:

/**

* 步驟1:初始化變數

*/

// 用於存放歷史搜索記錄

private RecordSQLiteOpenHelper helper ;

private SQLiteDatabase db;

// ListView列表 & 適配器

private SearchListView listView;

listView = (SearchListView) findViewById(R.id.listView);

private BaseAdapter adapter;

// 實例化資料庫SQLiteOpenHelper子類對象

helper = new RecordSQLiteOpenHelper(context);

/**

* 步驟2:搜索框的文本變化實時監聽

*/

et_search.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) {

}

// 輸入文本後調用該方法

@Override

public void afterTextChanged(Editable s) {

// 每次輸入後,模糊查詢資料庫 & 實時顯示歷史搜索記錄

// 註:若搜索框為空,則模糊搜索空字元 = 顯示所有的搜索歷史

String tempName = et_search.getText().toString();

queryData(tempName); // ->>關注1

}

});

/**

* 步驟3:搜索記錄列表(ListView)監聽

* 即當用戶點擊搜索歷史裡的欄位後,會直接將結果當作搜索欄位進行搜索

*/

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

@Override

public void onItemClick(AdapterView parent, View view, int position, long id) {

// 獲取用戶點擊列表裡的文字,並自動填充到搜索框內

TextView textView = (TextView) view.findViewById(android.R.id.text1);

String name = textView.getText().toString();

et_search.setText(name);

Toast.makeText(context, name, Toast.LENGTH_SHORT).show();

}

});

/**

* 關注1

* 模糊查詢數據 & 顯示到ListView列表上

*/

private void queryData(String tempName) {

// 1. 模糊搜索

Cursor cursor = helper.getReadableDatabase().rawQuery(

"select id as _id,name from records where name like "%" + tempName + "%" order by id desc ", null);

// 2. 創建adapter適配器對象 & 裝入模糊搜索的結果

adapter = new SimpleCursorAdapter(context, android.R.layout.simple_list_item_1, cursor, new String[] { "name" },

new int[] { android.R.id.text1 }, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

// 3. 設置適配器

listView.setAdapter(adapter);

adapter.notifyDataSetChanged();

// 當輸入框為空 & 資料庫中有搜索記錄時,顯示 "刪除搜索記錄"按鈕

if (tempName.equals("") && cursor.getCount() != 0){

tv_clear.setVisibility(VISIBLE);

}

else {

tv_clear.setVisibility(INVISIBLE);

};

}

刪除歷史搜索記錄

描述:清空所有歷史搜索記錄

原型圖

源碼分析

/**

* 步驟1:初始化變數

*/

private TextView tv_clear; // 刪除搜索記錄按鍵

tv_clear = (TextView) findViewById(R.id.tv_clear);

tv_clear.setVisibility(INVISIBLE);// 初始狀態 = 不可見

/**

* 步驟2:設置"清空搜索歷史"按鈕

*/

tv_clear.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

// 清空資料庫->>關注2

deleteData();

// 模糊搜索空字元 = 顯示所有的搜索歷史(此時是沒有搜索記錄的) & 顯示該按鈕的條件->>關注3

queryData("");

}

});

/**

* 關注2:清空資料庫

*/

private void deleteData() {

db = helper.getWritableDatabase();

db.execSQL("delete from records");

db.close();

tv_clear.setVisibility(INVISIBLE);

}

/**

* 關注3

* 模糊查詢數據、顯示到ListView列表上 & 確定顯示 「刪除歷史按鈕」條件

*/

private void queryData(String tempName) {

// 步驟1、2、3上面已經提到了,直接看步驟4

// 1. 模糊搜索

Cursor cursor = helper.getReadableDatabase().rawQuery(

"select id as _id,name from records where name like "%" + tempName + "%" order by id desc ", null);

// 2. 創建adapter適配器對象 & 裝入模糊搜索的結果

adapter = new SimpleCursorAdapter(context, android.R.layout.simple_list_item_1, cursor, new String[] { "name" },

new int[] { android.R.id.text1 }, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

// 3. 設置適配器

listView.setAdapter(adapter);

adapter.notifyDataSetChanged();

// 4. 當輸入框為空 & 資料庫中有搜索記錄時,才顯示 "刪除搜索記錄"按鈕

if (tempName.equals("") && cursor.getCount() != 0){

tv_clear.setVisibility(VISIBLE);

}

else {

tv_clear.setVisibility(INVISIBLE);

};

}

保存歷史搜索記錄

描述:將用戶輸入的搜索欄位保存到資料庫中

原型圖

源碼分析

/**

* 監聽輸入鍵盤更換後的搜索按鍵

* 調用時刻:點擊鍵盤上的搜索鍵時

*/

et_search.setOnKeyListener(new View.OnKeyListener() {

public boolean onKey(View v, int keyCode, KeyEvent event) {

if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN) {

// 步驟1已經講解過,直接看步驟2

// 1. 點擊搜索按鍵後,根據輸入的搜索欄位進行查詢

// 註:由於此處需求會根據自身情況不同而不同,所以具體邏輯由開發者自己實現,此處僅留出介面

if (!(mCallBack == null)){

mCallBack.SearchAciton(et_search.getText().toString());

}

Toast.makeText(context, "需要搜索的是" + et_search.getText(), Toast.LENGTH_SHORT).show();

// 2. 點擊搜索鍵後,對該搜索欄位在資料庫是否存在進行檢查(查詢)->> 關注3

boolean hasData = hasData(et_search.getText().toString().trim());

// 3. 若存在,則不保存;若不存在,則將該搜索欄位保存(插入)到資料庫,並作為歷史搜索記錄

if (!hasData) {

insertData(et_search.getText().toString().trim()); // ->>關注4

queryData("");

}

}

return false;

}

});

/**

* 關注3

* 檢查資料庫中是否已經有該搜索記錄

*/

private boolean hasData(String tempName) {

// 從資料庫中Record表裡找到name=tempName的id

Cursor cursor = helper.getReadableDatabase().rawQuery(

"select id as _id,name from records where name =?", new String[]);

// 判斷是否有下一個

return cursor.moveToNext();

}

/**

* 關注4

* 插入數據到資料庫,即寫入搜索欄位到歷史搜索記錄

*/

private void insertData(String tempName) {

db = helper.getWritableDatabase();

db.execSQL("insert into records(name) values("" + tempName + "")");

db.close();

}

至此,關於搜索框的全部源碼講解完畢。

完整源代碼請看:Carson_Ho的Github地址:SearchView

具體使用

具體請看文章:Android開源庫:這裡有一個簡單好用、含歷史搜索記錄的搜索框

完整Demo地址:Carson_Ho的Github地址:SearchView

GIF

貢獻代碼

希望你們能和我一起完善這款簡單 & 好用的SearchView控制項,具體請看:貢獻說明

關於該開源項目的意見 & 建議可在Issue上提出。歡迎 Star !

總結

相信你一定會喜歡上 這款簡單 & 好用的SearchView控制項

已在Github上開源:SearchView(https://github.com/Carson-Ho/Search_Layout),歡迎 Star !

GIF


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員大咖 的精彩文章:

TAG:程序員大咖 |