您的位置:首页 > 其它

实现搜索框(含历史搜索记录)

2017-04-24 15:06 393 查看


前言

像下图的搜索功能在Android开发中非常常见



今天我将手把手教大家如何实现具备历史搜索记录的搜索框


目录




1. 使用场景

在敲下代码前,理解用户的功能使用场景是非常重要的,这样有助于我们更好地去进行功能的实现,使用场景如下:
用户需要进行某类事物的搜索(通过文字输入进行精确搜索)
在搜索框输入时,通过显示搜索历史从而降低用户二次搜索的成本

简单来说,就是输入过字段会保存,当用户再次搜索该字段时,能快速帮助用户输入


2. 功能业务流程




3. 明确功能点

功能1:关键字搜索
功能2:实时显示历史搜索记录
功能3:历史搜索记录保存
功能4:将软键盘上的”回车按钮“改为”搜索按钮“


4. 涉及到的知识点




4.1 SQLite数据库的增删改查操作

在数据库建立一个叫records的表用于存储搜索历史记录 
 里面只有一列name来存储历史记录

db.execSQL("create table records(id integer primary key autoincrement,name varchar(200))");
1
1
通过插入和删除操作数据库里面的数据
通过查询操作来显示数据库里面的数据到ListView上 

已搜索的关键字再次搜索不重复添加到数据库

注:对于SQLlite数据库的操作详情请看我写的文章:Android:SQLlite数据库操作最详细解析


4.2 ListView和ScrollView的嵌套冲突

问题:ListView和ScrollView一起使用会有冲突,导致ListView显示不全

解决方案:本人采用继承ListView并重写它的onMeasure()方法来解决冲突。

至于两者的滑动冲突则暂时不需要处理(默认拉动ScrollView),这样用户就会感觉里面的搜索历史项和清空搜索记录项是在ListView里面一样,符合设计。
更多解决方法请看:ListView和ScrollView的嵌套冲突解决方案

具体代码如下:

//解决ListView和ScrollView的冲突
public class Search_Listview extends ListView {
public Search_Listview(Context context) {
super(context);
}

//通过复写其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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14


4.3 监听软键盘回车按钮设置为搜索按钮

给搜索框EditText添加一个OnKeyListener监听器,重写里面的回车键,实现按下回车键搜索。
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
3
4
5
6
7
8
1
2
3
4
5
6
7
8


4.4 使用TextWatcher实时筛选

给搜索框EditText添加一个TextWatcher来检测EditText里面的每个字符变化,根据变化之后的内容查询数据库得到结果。
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) {
//设置查询数据库并显示在列表的操作
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


5. 实例Demo

接下来,我将给出详细代码手把手教你实现具备历史搜索记录的搜索框

先下载Demo再进行阅读效果会更好:Carson的Search_Layout_Demo地址


5.1 目录结构




5.2 具体实现如下:

MainActivity.Java
作用:显示搜索框
具体代码如下:
package scut.carson_ho.search_layout;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14

RccordSQLiteOpenHelper.java

作用:继承自SQLiteOpenHelper数据库类的子类,用于创建、管理数据库和版本控制
具体代码如下: 

对于SQLlite数据库的操作详情请看我写的文章:Android:SQLlite数据库操作最详细解析

package scut.carson_ho.search_layout;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
* Created by Carson_Ho on 16/11/15.
*/
//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) {

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

Search_ListView.java
作用:用于解决ListView和ScrollView的嵌套冲突
具体代码如下:
package scut.carson_ho.search_layout;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;

/**
* Created by Carson_Ho on 16/11/15.
*/
//解决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);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Search_View.java
作用:用于封装搜索框功能的所有操作(涵盖历史搜索记录的插入、删除、查询和显示)
具体代码如下:
package scut.carson_ho.search_layout;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.Toast;

/**
* Created by Carson_Ho on 16/11/15.
*/
public class Search_View extends LinearLayout {

private Context context;

/*UI组件*/
private TextView tv_clear;
private EditText et_search;
private TextView tv_tip;
private ImageView iv_search;

/*列表及其适配器*/
private Search_Listview listView;
private BaseAdapter adapter;

/*数据库变量*/
private RecordSQLiteOpenHelper helper ;
private SQLiteDatabase db;

/*三个构造函数*/
//在构造函数里直接对搜索框进行初始化 - init()
public Search_View(Context context) {
super(context);
this.context = context;
init();
}

public Search_View(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}

public Search_View(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init();
}

/*初始化搜索框*/
private void init(){

//初始化UI组件
initView();

//实例化数据库SQLiteOpenHelper子类对象
helper = new RecordSQLiteOpenHelper(context);

// 第一次进入时查询所有的历史记录
queryData("");

//"清空搜索历史"按钮
tv_clear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

//清空数据库
deleteData();
queryData("");
}
});

//搜索框的文本变化实时监听
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) {

if (s.toString().trim().length() == 0) {
//若搜索框为空,则模糊搜索空字符,即显示所有的搜索历史
tv_tip.setText("搜索历史");
} else {
tv_tip.setText("搜索结果");
}

//每次输入后都查询数据库并显示
//根据输入的值去模糊查询数据库中有没有数据
String tempName = et_search.getText().toString();
queryData(tempName);

}
});

// 搜索框的键盘搜索键
// 点击回调
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) {
// 隐藏键盘,这里getCurrentFocus()需要传入Activity对象,如果实际不需要的话就不用隐藏键盘了,免得传入Activity对象,这里就先不实现了
//                    ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(
//                            getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);

// 按完搜索键后将当前查询的关键字保存起来,如果该关键字已经存在就不执行保存
boolean hasData = hasData(et_search.getText().toString().trim());
if (!hasData) {
insertData(et_search.getText().toString().trim());

queryData("");
}
//根据输入的内容模糊查询商品,并跳转到另一个界面,这个需要根据需求实现
Toast.makeText(context, "点击搜索", Toast.LENGTH_SHORT).show();

}
return false;
}
});

//列表监听
//即当用户点击搜索历史里的字段后,会直接将结果当作搜索字段进行搜索
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();

}
});

//点击搜索按钮后的事件
iv_search.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
boolean hasData = hasData(et_search.getText().toString().trim());
if (!hasData) {
insertData(et_search.getText().toString().trim());

//搜索后显示数据库里所有搜索历史是为了测试
queryData("");
}
//根据输入的内容模糊查询商品,并跳转到另一个界面,这个根据需求实现
Toast.makeText(context, "clicked!", Toast.LENGTH_SHORT).show();
}
});

}

/**
* 封装的函数
*/

/*初始化组件*/
private void initView(){
LayoutInflater.from(context).inflate(R.layout.search_layout,this);
et_search = (EditText) findViewById(R.id.et_search);
tv_clear = (TextView) findViewById(R.id.tv_clear);
tv_tip = (TextView) findViewById(R.id.tv_tip);
listView = (Search_Listview) findViewById(R.id.listView);
iv_search = (ImageView) findViewById(R.id.iv_search);
}

/*插入数据*/
private void insertData(String tempName) {
db = helper.getWritableDatabase();
db.execSQL("insert into records(name) values('" + tempName + "')");
db.close();
}

/*模糊查询数据 并显示在ListView列表上*/
private void queryData(String tempName) {

//模糊搜索
Cursor cursor = helper.getReadableDatabase().rawQuery(
"select id as _id,name from records where name like '%" + tempName + "%' order by id desc ", null);
// 创建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);
// 设置适配器
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}

/*检查数据库中是否已经有该条记录*/
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[]{tempName});
//判断是否有下一个
return cursor.moveToNext();
}

/*清空数据*/
private void deleteData() {
db = helper.getWritableDatabase();
db.execSQL("delete from records");
db.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242

search_layout.xml
作用:搜索框的布局
具体代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true"
android:orientation="vertical">

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="50dp"
android:background="#E54141"
android:orientation="horizontal"
android:paddingRight="16dp">

<ImageView
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="center_vertical"
android:padding="10dp"
android:src="@drawable/back" />

<EditText
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="264"
android:background="@null"
android:drawablePadding="8dp"
android:gravity="start|center_vertical"
android:hint="输入查询的关键字"
android:imeOptions="actionSearch"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="16sp" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/search"
android:layout_gravity="center_vertical"
android:id="@+id/iv_search"/>

</LinearLayout>

<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
>

<TextView
android:id="@+id/tv_tip"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="left|center_vertical"
android:text="搜索历史" />

<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#EEEEEE"/>

<scut.carson_ho.search_layout.Search_Listview
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content">

</scut.carson_ho.search_layout.Search_Listview>

</LinearLayout>

<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#EEEEEE"/>

<TextView
android:id="@+id/tv_clear"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#F6F6F6"
android:gravity="center"
android:text="清除搜索历史" />

<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="20dp"
android:background="#EEEEEE"/>
</LinearLayout>

</ScrollView>
</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

activity_main.xml
作用:显示搜索框
具体代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="scut.carson_ho.search_layout.MainActivity">

<scut.carson_ho.search_layout.Search_View
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/search_layout"/>
</RelativeLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


5.3 测试结果




5.4 Demo地址

Carson的Github地址:Search_Layout_Demo
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  搜索 历史