Android搜索框架

在Android的官方文档里提供了一个搜索框架,如果要实现搜索功能,只需严格按照此框架步骤实现即可.需要注意的是必须是严格按照此框架介绍实现,有时候甚至是一个空格的缺失都有可能造成整个搜索功能的失效.

下面就以一个短信应用项目里的短信搜索为例,对搜索功能的实现进行总结.

##激活搜索对话框
首先,需要在清单文件里配置,添加显示搜索结果的Activity;同时需要增加meta-data属性,配置对应的provider,让项目里所有的Activity都具有搜索能力;根据项目需要,在搜索框输入字符后,需要根据模糊匹配提供搜索建议,因此也需要在清单文件里配置搜索建议列表的内容提供者.

当然,下面的代码也是官方文档提供的模板,接下来的操作,我们只需要根据这里的配置,再进行不断的完善即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<! 配置搜索结果activity -->
<activity
android:name=".SearchableActivity"
android:label="搜索结果" >

<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />

</activity>
<!-- 让所有的Activity都有能力执行搜索 (如果需要让特定的Activity具有搜索能力,则需将该标签放置在该特定Activity里) -->
<meta-data
android:name="android.app.default_searchable"
android:value=".SearchableActivity" />

<!-- 搜索建议列表的内容提供者 -->
<provider
android:name="cn.easydone.smartsms.provider.MySuggestionProvider"
android:authorities="cn.easydone.smartsms.provider.MySuggestionProvider"
android:exported="true" >

</provider>

在SearchableActivity的配置里,intent-filter里的action和meta-data的名称都必须是代码里指定的值,在meta-data元数据里还有一个resource资源,在这里需要新建这样的一个xml资源,资源类型选择searchable,内容如下:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_label"
android:hint="@string/search_hint"
android:searchSuggestAuthority="cn.easydone.smartsms.provider.MySuggestionProvider"
android:searchSuggestSelection=" ?" >

</searchable>

label和hint都需要在values里的strings.xml里进行配置,下面的两行都是关于搜索建议的,有其需要注意的是,搜索建议条件必须是“ ?”,这个空格一定不能少.

完成了配置,接着就需要创建SearchableActivity,继承ListActivity,用以显示搜索结果,点击搜索按钮之后的一系列操作和显示都要在这个Activity里完成.当然,这都需要根据具体的业务进行相应的处理.

接着就是激活搜索对话框,需要在Options Menu或者UI layout里调用onSearchRequested()方法.

1
onSearchRequested();

##处理搜索结果界面
处理搜索结果,其实就是在SearchableActivity的onCreate()方法里调用doMySearch()方法,并且需要把意图里查询关键字传递过去.当然doMySearch()方法需要自己来实现.

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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adapter = new MyAdapter(this, null);
setListAdapter(adapter);
//获取意图,并把关键字"query"作为参数传递给方法doMySearch()
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
//String android.app.SearchManager.QUERY = "query"
String query = intent.getStringExtra(SearchManager.QUERY);
doMySearch(query);
}
}

private void doMySearch(String query) {
QueryHandler mQueryHandler = new QueryHandler(getContentResolver());
Uri uri = Uri.parse("content://sms");
String selection = "body like ?";
String[] selectionArgs = new String[]{"%"+query+"%"};
mQueryHandler.startQuery(0, adapter, uri, projection, selection, selectionArgs, null);
}

private class MyAdapter extends CursorAdapter{

public MyAdapter(Context context, Cursor c) {
super(context, c);
}

/**
* 实例化条目的布局
* Xutils是一个工具类,getDisplayName()是根据号码查询联系人名字的方法,formatTime()是格式化时间的方法
* 如果短信时间为今天,就显示短信发送接收时刻;如果不是今天,就显示短信发送接收的日期
*/

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = getLayoutInflater().inflate(R.layout.activity_conversation_list_item, null);
ViewHolder holder = new ViewHolder();
holder.iv = (ImageView) view.findViewById(R.id.iv);
holder.tv_name = (TextView) view.findViewById(R.id.tv_name);
holder.tv_body = (TextView) view.findViewById(R.id.tv_body);
holder.tv_date = (TextView) view.findViewById(R.id.tv_date);
view.setTag(holder);
return view;
}

//把cursor里面的值绑定给控件 cursor系统已经帮你移动到了指定的位置
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
//取值
String address = cursor.getString(ADDRESS_COLUM_INDEX);
String body = cursor.getString(BODY_COLUM_INDEX);
long date = cursor.getLong(DATE_COLUM_INDEX);

String displayName = Xutils.getDisplayName(address, context);
if(TextUtils.isEmpty(displayName)){
//处理图片
holder.iv.setImageResource(R.drawable.ic_unknow_contact_picture);
holder.tv_name.setText(address);
}else{
Bitmap bitmap = Xutils.getContactPhoto(address, context);
if(bitmap != null){
holder.iv.setImageBitmap(bitmap);
}else{
holder.iv.setImageResource(R.drawable.ic_contact_picture);
}
holder.tv_name.setText(displayName);
}

//短信内容
holder.tv_body.setText(body);

//时间
String dateStr = Xutils.formatTime(date, context);
holder.tv_date.setText(date);

}
}

static class ViewHolder{
ImageView iv;
TextView tv_name;
TextView tv_body;
TextView tv_date;
}

这里的QueryHandler继承了AsyncQueryHandler类,这是一个非常有用的类.查询内容提供者时,如果加载的数据比较多,就会是耗时操作,Android提供了AsyncQueryHandler这个API,使用这个API可以进行内容的异步查询操作.

mQueryHandler执行了startQuery()方法后,会执行onQueryComplete()方法.这里面需要创建MyAdapter类,继承CursorAdapter,然后实例化,将adapter作为startQuery()的一个参数传进去,同时在onQueryComplete()方法中,将参cookie数强转为adapter,然后再将参数cursor传给adapter.这个cursor就是查询的结果,最终会显示在ListView中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.database.Cursor;
import android.widget.CursorAdapter;

public class QueryHandler extends AsyncQueryHandler {
public QueryHandler(ContentResolver cr) {
super(cr);
}

@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
super.onQueryComplete(token, cookie, cursor);
CursorAdapter adapter = (CursorAdapter) cookie;
adapter.changeCursor(cursor);
}
}

##处理搜索建议列表
添加搜索建议,首先需要创建一个内容提供者MySuggestionProvider,这个我们在上面的清单文件里,已经配置过了,而且也在xml资源文件里添加了搜索建议的相关属性.同时,这个内容提供者要继承SearchRecentSuggestionsProvider,内容提供者一定要对其进行授权处理,授权使用其全类名.

而且,非常重要的一点是,内容提供者的授权,以下三个地方都必须一致:

  • MySuggestionProvider
  • AndroidManifest.xml
  • searchable.xml

同时,这里需要自己手动处理query()方法,搜索框架会把参数selectionArgs带到该方法中.

还有一点,由于搜索结果的cursor对于数据列名有要求,必须是一些特定列名的数据才能被识别,如果是从其他地方获取的数据,则需要将其格式化.能够识别的列名常用的,也是官方API列出的有以下几种(当然也有其他的一些):

  • _ID
  • SUGGEST_COLUMN_TEXT_1
  • SUGGEST_COLUMN_TEXT_2
  • SUGGEST_COLUMN_ICON_1
  • SUGGEST_COLUMN_ICON_2
  • SUGGEST_COLUMN_INTENT_ACTION
  • SUGGEST_COLUMN_INTENT_DATA
  • SUGGEST_COLUMN_INTENT_DATA_ID
  • SUGGEST_COLUMN_INTENT_EXTRA_DATA
  • SUGGEST_COLUMN_QUERY
  • SUGGEST_COLUMN_SHORTCUT_ID
  • SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING

下面是具体的代码实现:

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
import com.example.smsmanager.utils.Constants;

import android.app.SearchManager;
import android.content.ContentResolver;
import android.content.SearchRecentSuggestionsProvider;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.provider.BaseColumns;

public class MySuggestionProvider extends SearchRecentSuggestionsProvider {
public final static String AUTHORITY = "cn.easydone.smartsms.provider.MySuggestionProvider";
public final static int MODE = DATABASE_MODE_QUERIES | DATABASE_MODE_2LINES;

public MySuggestionProvider() {
setupSuggestions(AUTHORITY, MODE);
}

private String[] projectionIn = new String[]{"_id","address","body"};
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
System.out.println(selectionArgs[0]);//搜索框架携带的参数
//手动的查询短信的内容提供者
ContentResolver cr = getContext().getContentResolver();
String where = "body like ?";
String[] whereArgs = new String[]{"%"+selectionArgs[0]+"%"};
Cursor cursor = cr.query(Uri.parse("content://sms"), projectionIn, where, whereArgs, null);
return CreateCursor(cursor);
}

//cursor新的列
private String[] columnNames = new String[]{BaseColumns._ID,
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,
SearchManager.SUGGEST_COLUMN_QUERY//传值
};
/**
* 组拼新的cursor
* @param cursor 只要是一行一行的的数据,都可以组拼成cursor
* @return
*/

private Cursor CreateCursor(Cursor cursor){
MatrixCursor mc = new MatrixCursor(columnNames);
//迭代老的cursor
while(cursor.moveToNext()){
//取值 取的一行值
Object[] columnValues = new Object[]{
cursor.getInt(0),
cursor.getString(1),
cursor.getString(2),
cursor.getString(2)
};
//添加给新的cursor
mc.addRow(columnValues);
}
cursor.close();
return mc;
}
}