在您的 Android 应用中创建搜索字段






4.09/5 (3投票s)
Android SDK 提供了一套 API 来在您的应用中实现此模式,在本文中,我们将回顾将其实现到您的应用程序中所需的第一步。
英特尔® 开发者专区提供用于跨平台应用开发的工具和操作指南信息、平台和技术信息、代码示例以及同行专业知识,以帮助开发者创新和成功。加入我们的安卓、物联网、英特尔® 实感™ 技术和Windows社区,下载工具、访问开发工具包、与志同道合的开发者分享想法,并参加黑客马拉松、竞赛、路演和本地活动。
搜索是大量 Android 应用程序中都存在的一项功能。在您的应用内提供一种搜索内容的方式非常重要,因为它能帮助用户找到他们想要的内容。然而,这种搜索必须快速高效,因为获取信息可能是用户打开您应用的主要原因。Android SDK 提供了一套 API 来在您的应用中实现此模式,在本文中,我们将回顾将其实现到您的应用程序中所需的第一步。
开始动手!
我们开始吧!在 Android Studio 中使用“Empty activity”模板创建一个新项目。项目创建后,添加一个新的菜单文件,并将其命名为 res/menu/menu_search.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/search"
android:title="@string/hint_search"
android:icon="@android:drawable/ic_menu_search"
app:showAsAction="collapseActionView|ifRoom"
app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>
我们的菜单文件只包含一个负责显示搜索按钮的项目。当用户按下此按钮时,它会展开并显示一个文本字段,允许用户输入要搜索的词条。负责此任务的控件是 SearchView
,正如我们在 app:actionViewClass
属性中看到的那样。将 app:showAsAction
定义为 collapseActionView
值,可以让 SearchView
在按钮被点击时自行展开。
请注意,我们在这里使用了支持库,以保持在不同 Android 版本间的兼容性。
一旦我们创建了菜单文件,我们将在 MainActivity
中加载它,如下所述。
public class MainActivity extends AppCompatActivity
implements SearchView.OnQueryTextListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_search, menu);
MenuItem searchItem = menu.findItem(R.id.search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setOnQueryTextListener(this);
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
// User pressed the search button
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// User changed the text
return false;
}
}
在 onCreateOptionsMenu()
内部,我们使用 inflate()
加载菜单资源文件。通过 Menu
对象,我们找到包含 SearchView
控件的 MenuItem
。通过为 SearchView
设置一个 OnQueryTextListener
对象,我们的应用可以检测到两个事件。
- 当用户在文本字段中输入每个字符时,会调用
onQueryTextChange
; - 当按下搜索按钮时,会触发
onQueryTextSubmit
。
因此,您可以在这些方法中实现任何您想要的功能来执行搜索(例如,访问 SQLite 数据库或 Web 服务)。就是这样!这是在您的应用中实现搜索功能的最简单方法。
改善搜索体验
前面的方法效果不错,但通常需要搜索操作来帮助用户找到他们想要的东西。一个很好的方法是在用户输入时显示一些建议。为了演示这种方法,我们将在用户在 SearchView
中输入城市名称时显示一个城市列表。这个列表存储在服务器上。我们要做的第一件事是创建一个可搜索的配置。因此,在项目中添加一个新文件 res/xml/searchable.xml
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:hint="@string/hint_search"
android:label="@string/app_name"
android:voiceSearchMode="showVoiceSearchButton|launchRecognizer"
android:searchSuggestAuthority="ngvl.android.demosearch.citysuggestion"
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSuggestIntentData="content://ngvl.android.demosearch.city"/>
让我们来理解一下这个文件中定义的属性
- 使用
android:voiceSearchMode
属性启用了语音搜索按钮。 - 当用户输入时,将会在响应
android:searchSuggestAuthority
的内容提供程序中调用query()
方法(我们稍后会看到这个提供程序)。 - 当用户在列表中选择一个建议时,将使用
android:searchSuggestIntentAction
属性中描述的操作调用一个新的 Activity。 android:searchSuggestIntentData
对前一个属性进行了补充,因为它为用户点击建议时触发的 Intent 定义了一个Uri
模式。
正如我们所知,内容提供程序通常访问 SQLite 数据库来存储和检索数据,但我们也可以用它来访问网络,正如 Android 文档明确指出的那样
内容提供程序“可能同时被多个线程调用,并且必须是线程安全的”。
在这个例子中,建议将从存储在 Web 服务器上的 JSON 文件中检索。创建一个名为 CitySuggestionProvider
的新内容提供程序,只需实现 query()
方法(其他方法为空即可)。
public class CitySuggestionProvider extends ContentProvider {
List<String> cities;
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
if (cities == null || cities.isEmpty()){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://dl.dropboxusercontent.com/u/6802536/cidades.json")
.build();
try {
Response response = client.newCall(request).execute();
String jsonString = response.body().string();
JSONArray jsonArray = new JSONArray(jsonString);
cities = new ArrayList<>();
int lenght = jsonArray.length();
for (int i = 0; i < lenght; i++) {
String city = jsonArray.getString(i);
cities.add(city);
}
} catch (Exception e) {
e.printStackTrace();
}
}
MatrixCursor cursor = new MatrixCursor(
new String[] {
BaseColumns._ID,
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
}
);
if (cities != null) {
String query = uri.getLastPathSegment().toUpperCase();
int limit = Integer.parseInt(uri.getQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT));
int lenght = cities.size();
for (int i = 0; i < lenght && cursor.getCount() < limit; i++) {
String city = cities.get(i);
if (city.toUpperCase().contains(query)){
cursor.addRow(new Object[]{ i, city, i });
}
}
}
return cursor;
}
//
Other methods are empty...
}
当用户开始输入文本时,我们的提供程序会通过类似下面的 URI 被调用。content://ngvl.android.demosearch.citysuggestion/search_suggest_query/santos?limit=50
系统在 URI 的末尾添加 search_suggest_query/<text>
来调用我们的提供程序。默认情况下,结果限制设置为 50,并作为查询参数传递。值得注意的是,搜索操作是在 UI 线程之外完成的,因此我们可以像我们所做的那样执行 HTTP 请求。在这个例子中,我们将城市列表存储在内存中,但在实际应用中,我们可以使用 SQLite 数据库中的一个表。
我们使用 Uri
对象,通过 getLastPathSegment()
获取了文本参数,并通过 getQueryParameter()
获取了要返回的最大建议数。从网络读取内容后,我们构建了一个 MatrixCursor 来表示建议的城市列表。这里一个重要的细节是列名。列“_id”和“suggest_text_1”是必须的。您可以使用更多的列或创建 CursorAdapter 的子类来自定义您的建议列表。
正如我之前所说,我们的建议存储在 Web 服务器上的一个 JSON 文件中。因此,在这里,我们执行请求来检索城市列表,并使用 OkHttp 库将其作为 JSON 文件读取。所以,别忘了在 build.gradle
文件中添加这个依赖。
dependencies { ... compile 'com.squareup.okhttp3:okhttp:3.0.1' }
请确保在 AndroidManifest.xml
中添加 INTERNET 权限,并且内容提供程序的声明如下所示。
<manifest ...
<uses-permission android:name="android.permission.INTERNET"/>
<application...
<provider
android:name=".CitySuggestionProvider"
android:authorities="ngvl.android.demosearch.citysuggestion"
android:enabled="true"
android:exported="true"/>
请确保 android:authorities
与可搜索配置文件中声明的相同。
之后,我们还需要将 Activity 与可搜索配置连接起来。修改 AndroidManifest.xml
中的 Activity 声明。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
通过为 <meta-data>
标签使用 android.app.searchable
值,我们定义了该 Activity 具有一个可搜索的配置。现在我们需要将此信息设置到 SearchView
控件中。将 onCreateOptionsMenu()
修改成如下所示。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_search, menu);
MenuItem searchItem = menu.findItem(R.id.search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setOnQueryTextListener(this);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
searchView.setSearchableInfo(searchManager.getSearchableInfo(
new ComponentName(this, MainActivity.class)));
searchView.setIconifiedByDefault(false);
return true;
}
运行应用程序并在 SearchView 中开始输入一些文本。您会看到一些建议被显示出来。
很好!我们的建议功能正在工作。但是当选择一个建议时会发生什么?或者如果我按下了键盘上的搜索按钮呢?
处理建议和搜索操作
目前,当用户按下搜索按钮或在列表中选择一个建议时,会创建并显示一个 MainActivity 的新实例。为了避免这种情况,我们可以在 AndroidManifest.xml
中将我们 Activity 的启动模式设置为 singleTop
,并实现 onNewIntentMethod()
。
<activity android:name=".MainActivity"
android:launchMode="singleTop">
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
Toast.makeText(this, "Searching by: "+ query, Toast.LENGTH_SHORT).show();
} else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
String uri = intent.getDataString();
Toast.makeText(this, "Suggestion: "+ uri, Toast.LENGTH_SHORT).show();
}
}
当用户点击搜索按钮时,Activity 会使用 ACTION_SEARCH 被调用,但当选择一个建议时,会使用 ACTION_VIEW(正如我们在可搜索配置文件中定义的那样)。如果您现在运行应用程序并执行搜索,将会使用 MainActivity
的同一个实例,而不是创建一个新的实例。
您可以毫无问题地使用这种方法,但您可能需要(或更喜欢)在另一个 Activity 中执行搜索。为此,我们将创建一个新的可搜索 Activity,当用户按下搜索按钮或选择一个建议时,该 Activity 将被调用。
它在 AndroidManifest.xml
中的声明必须是这样的。
<activity android:name=".SearchableActivity">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
记得从 MainActivity
的声明中移除这些相同的配置(<intent-filter>
和 <meta-data>
)。所以现在,我们必须对 MainActivity
做一点小小的改动。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
...
searchView.setSearchableInfo(searchManager.getSearchableInfo(
new ComponentName(this, SearchableActivity.class)));
...
}
// Remove onNewIntent method too
通过这种实现,当用户按下回车/搜索按钮时,会使用 "android.intent.action.SEARCH
" 操作和一个名为 "query
" 的参数启动一个新的 Activity。这个参数代表用户在 SearchView
中输入的文本。下面的代码展示了如何在 SearchableActivity
中处理搜索。
public class SearchableActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_searchable);
TextView txt = (TextView)findViewById(R.id.textView);
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
txt.setText("Searching by: "+ query);
} else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
String uri = intent.getDataString();
txt.setText("Suggestion: "+ uri);
}
}
}
一旦您有了查询参数,您就可以搜索任何您想要的内容。例如,在本地数据库/内容提供程序中或在 Web 服务中搜索。
结论
在本文中,我们在一个 Android 应用中实现了一个搜索功能,并看到了它是多么容易。这些只是第一步,更多详情请查看Android 搜索培训教程。
本示例的源代码可在我的 GitHub 上获取。