65.9K
CodeProject 正在变化。 阅读更多。
Home

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

2016年6月1日

CPOL

6分钟阅读

viewsIcon

11555

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 上获取。

© . All rights reserved.