Android 的搜索机制 I-创建一个搜索框

来源:互联网 发布:java https请求带证书 编辑:程序博客网 时间:2024/05/29 01:53

概述:

搜索是Android为用户提供的核心功能之一. 用户应该可以搜索任何对他们有用的数据, 不管该内容是在本地的还是在网络上的. 为了向用户提供统一风格的搜索体验, Android提供了一个search framework, 可以帮助我们为APP实现搜索功能. 下图是可以给用户提供搜索建议的搜索框快照:


Search framework为我们提供了两种可以用于搜索的方法: 一种是在屏幕顶端的搜索框, 还有就是使用可以嵌入activity layout的SearchView. 我们还可以为搜索框提供搜索建议, 就好像上图显示的那样. Android会为我们的搜索框提供一些辅助功能, 不管使用哪种方式, 我们都可以:

Ø  可以使用声音搜索.

Ø  根据用户最近搜索提供搜索建议.

Ø  根据APP中的特定数据提供搜索建议.

Ø  在Android的全系统搜索框中提供APP的搜索建议.

Search framework并不提供搜索我们的数据的API.想要处理搜索的话, 我们需要对自己的数据提供相应的API. 比如, 如果我们的数据存放在SQLite中, 我们应该使用android.database.sqlite的API来处理搜索. 此外也不能保证一个设备提供一个专门的搜索按钮用于我们的APP的搜索界面. 当使用搜索框或者自定义接口的时候我们必须提供一个自己UI内部的搜索接口.

关于保护用户的隐私:

在实现搜索功能的时候我们应该考虑到用户的隐私, 至少应该考虑到这些:

1.      不要发送用户的个人信息到服务器, 如果必须这样的话, 不要记录它.

个人信息是可以用于识别用户的任何信息, 比如名字, email地址, 账单信息, 或者其他可以连接到这些信息的信息. 如果我们的APP需要服务器的协助完成搜索功能, 请避免搜索信息和个人信息捆绑一起发送. 比如: 如果你需要搜索某个邮政区号内的商家, 我们不应该发送用户ID, 只要发送邮政区号即可. 如果我们必须发送个人信息, 那么不应该记录这些信息. 如果必须记录这些信息, 那么应该非常细心的保护它们, 并且应该尽快删除.

2.      为用户提供删除搜索记录的方法.

Search framework在用户输入的时候帮我们的APP提供了针对具体内容的建议.有时候这些建议是基于之前的搜索或者用户之前的操作的. 用户或许不希望自己的搜索记录被该设备的其他用户知道(比如一起分享设备的朋友家人等). 如果我们的APP有可以根据搜索历史提供的搜索建议, 那么我们应该提供给用户删除之前搜索记录的功能. 如果我们使用的是SearchRecentSuggestions, 那么我们可以直接调用clearHistory()方法来删除这些记录. 如果我们自己实现了自定义的搜索建议, 我们需要提供类似”清除搜索数据”的功能.

创建一个搜索接口:

当我们打算向APP中添加搜索功能的时候, Android会帮助我们实现用户接口, 它提供了两种方式, 一种是出现在activity页面顶部的”search dialog”, 还有一种是可以嵌入在我们的layout中的”search widget”. 这两种方式都可以放在我们APP的任何activity中. 用户可以从任何可以启动”search dialog”和”search widget”的activity中初始化搜索操作, Android会启动合适的activity来处理搜索并显示结果. 其它的搜索功能可以支持的特性有:

声音搜索; 根据最近搜索提供搜索建议; 根据APP内的数据提供搜索建议. 下面将做详细介绍.

基础:

在开始实现搜索功能之前我们首先应该确定自己使用”search dialog”还是使用”search widget”. 它俩都可以实现搜索功能, 但是实现稍微有所区别:

Searchdialog是由Android控制的UI组件, 当被用户激活的时候, 它就会显示出来, 效果如下图. Android将会控制其所有的事件, 当用户提交一个查询的时候, Android会将该查询提交给我们指定的处理搜索的activity. 这个dialog还可以处理搜索建议. 


Search widget则是一个我们可以放在layout中任何位置的SearchView实例. 默认情况下这个控件的行为就像一个标准的EditText控件, 在那里默默的待着. 但是我们可以对其进行配置让Android处理它所有的输入事件, 将查询交付给合适的activity, 然后像search dialog一样提供搜索建议. 但是Search widget只能使用在Android3.0及以上版本中. 如果有必要的话我们可以通过各种监听器处理所有的用户输入事件, 但是在这里我们只聚焦于借助Android提供的帮助来实现搜索功能.

当用户通过search dialog或者search widget来实施搜索操作的时候, Android将会创建一个intent并且将用户的查询保存在里面. 然后启动一个我们指定的activity来处理搜索(这个activity叫”searchable activity”)并将intent传递给它. 要实现这一流程我们必须这样做:

1.      Searchable configuration: 一个为searchdialog或者search widget配置一些设置的XML文件. 包括一些设置项, 比如语音搜索, 搜索建议, 和搜索框的提示文本等.

2.      Searchable activity: 用于接收搜索查询的Activity,用于搜索数据和显示搜索结果.

3.      Searchable interface: 由searchdialog或者SearchView提供. 默认情况下Search dialog是隐藏的, 当我们调用onSearchRequested()方法的时候(当用户点击搜索按钮的时候)显示在屏幕顶端. 而使用searchView可以让我们将搜索框放在activity的任何位置, 但是我们应该将它放在app bar中作为一个action view来处理.

剩下的部分将会对上述三个步骤进行详细介绍.

创建一个searchableconfiguration:

首先需要的是一个叫做”searchable configuration”的XML文件. 它对搜索功能进行了一些配置,比如搜索建议和语音搜索的行为等. 该文件一般命名为searchable.xml, 并且必须位于res/xml/目录下. Android使用该文件来实例化一个SearchableInfo对象, 但是我们不能再运行时手动创建该对象, 必须声明searchable configuration文件来由Android生成它.

下面来介绍该文件的规则, 这个文件必须有一个<searchable>标签作为根节点, 然后指定一个或者多个属性, 栗子:

<?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">
</searchable>

其中android:label是唯一必须的属性, 它指向一个string资源, 这个string资源应该是APP的名字. 在我们使Quick Search Box可用之前, 这个label是不会被用户看到的, 设置为可用之后, 用户将会在系统设置中的Searchable条目中看到这个值.

虽然android:hint不是必须的, 但是官方还是建议我们实现这个属性, 该属性会让用户在输入之前就看到搜索框中的提示文本. 这东西很重要, 因为它可以提示用户他们可以在这里做什么样的搜索. 为了保证Android下搜索操作的一致性, 我们应该为android:hint指定统一的格式”Search <content-or-product>”, 比如”Search songs andartists”或者”Search YouTube”.

<searchable>标签还有其它的属性, 然而如果不使用搜索建议或者语音搜索的话用不到它们, 更多信息将在后文介绍.

创建一个SearchableActivity:

Searchable Activity是APP中用来根据查询字符串处理搜索和展示搜索结果的Activity. 当用户在search dialog或者search widget中执行一次搜索操作的时候, Android会启动我们的Searchable Activity, 并传给它一个带有ACTION_SEARCH的Intent. 然后Searchable Activity可以从intent中名为QUERY的extra中获取到查询的内容, 然后就可以搜索数据并显示结果了. 但是在此之前Android必须知道哪个Activity是用来做Searchable Activity的, 我们应该在manifest文件中声明它.

声明一个SearchableActivity:

目前我们首先需要一个处理搜索和显示结果的Activity, 还不需要实现真正的搜索功能, 只在manifest中声明一个Activity即可, 在这个<activity>中, 我们需要:

1.      通过<intent-filter>声明Activity可以接受action为ACTION_SEARCH的intent.

2.      通过<meta-data>指定要使用哪个searchable configuration.

栗子:

<application ... >
    <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>
    ...
</application>

<meta-data>标签必须包含android:name属性, 并且它的值也是固定的 ”android.app.searchable”,同时android:resource属性也是必须的, 它用来指定一个相关的searchable configuration文件, 在这个栗子里是”res/xml/searchable.xml”.在这里<intent-filter>并不需要一个带有默认值的<category>, 因为Android使用显示intent启动我们的Activity.

处理一个搜索操作:

如果我们已经在manifest文件中声明了Searchable Activity, 那么就可以通过以下三个步骤来处理一个搜索操作:

1.      接收查询.

2.      搜索我们的数据.

3.      展示结果.

通常我们的搜索结果应该展示在一个ListView中, 所以我们可以让我们的Searchable Activity继承自ListActivity. 它会自带一个带有ListView的默认的layout并提供一些方便的操作ListView的方法. 现在分别来介绍上面的三个步骤:

1.      接收查询:

当用户执行一个搜索操作的时候, Android将会启动我们的Searchable Activity并发送一个带有ACTION_SEARCH的intent对象. 这个intent的extra中带有查询字符串. 我们必须在Activity启动的时候获取这些信息, 栗子:

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.search);

    // Get theintent, verify the action and get the query
    Intent intent = getIntent();
    if (Intent.ACTION_SEARCH.equals(intent.getAction())){
      String query = intent.getStringExtra(SearchManager.QUERY);
      doMySearch(query);
    }
}

QUERY总是会在action为ACTION_SEARCH的intent中, 上面的代码里, 在获取到查询内容之后, 它被传给了doMySearch()方法.

2.      搜索我们的数据:

我们可以用很多种方法保存和搜索自己的数据, 保存和搜索数据很重要是我们应该着重考虑的部分, 这里并不做详细介绍但是有两个需要注意的tips:

a.      如果我们的数据是保存在设备的SQLite数据库中, 执行全文搜索(使用FTS3而不是LIKE查询)可以提供更加健壮的搜索结果, 并且可以执行的更加迅速. 更多信息可以参考这里.

b.      如果我们的数据是在线存储的, 那么搜索的性能及体验可能会被用户的网络状态影响. 这时候应该在数据返回之前显示一个进度条, 以保证用户体验, 可以参考这里.

不论我们的数据存在什么地方, 官方建议我们通过一个Adapter返回搜索的结果给searchable activity, 这样我们就可以很轻松的在ListView中显示搜索结果了. 如果数据是存在SQLite中我们可以使用CursorAdapter. 如果数据从别的地方来, 我们可以考虑使用BaseAdapter.

关于Adapter: 一个Adapter可以将一系列数据的每个条目绑定到View对象中. 当Adapter被应用到ListView上, 每个数据片都会作为一个单独的view被插入到list中. Adapter只是接口, 所以我们需要它们的实现, 比如CursorAdapter(用于从Cursor绑定数据), 如果没有为我们的数据实现的现成的Adapter, 我们可以使用BaseAdapter并扩展为我们自己的Adapter.

3.      展示结果:

就好像上面提到的, 用来展示搜索结果的比较合适的UI是ListView, 所以我们可以扩展ListActivity来展示搜索结果, 并调用setListAdapter()方法并传入一个Adapter. 关于LIstView的详细用法可以参考这里.

使用Search Dialog:

Search dialog在屏幕顶端提供了一个悬浮搜索框, 搜索框的左边还有APP的图标. Searchdialog可以提供搜索建议, 并且当用户执行搜索的时候Android将会将搜索的查询发送给search activity. 但是如果我们的APP需要运行在Android3.0的设备上, 我们应该考虑使用search widget.

默认情况下search dialog总是隐藏的, 直到用户激活它. 我们的APP可以通过onSearchRequested()方法来激活search dialog, 然而这之前我们应该指定search dialog为特定的activity可用, 该方法才会生效. 想要让search dialog相对于某个activity可用, 我们必须为其指定searchable activity. 比如前文代码段中声明的SearchableActivity. 如果我们想要指定一个activity来显示search dialog(比如叫OtherActivity), 并且将查询交给SearchableActivity来处理, 那么我们必须在manifest中声明SearchableActivity和OtherActivity之间的关系. 为了指定这样的关系需要在OtherActivity中使用<meta-data>标签, 并为其指定android:name属性和android:value属性, 其中android:value属性表示要指定的searchable activity的类名, 而android:name则需要使用固定的”android.app.default_searchable”. 栗子:

<application ... >
    <!-- this isthe searchable activity; it performs searches -->
    <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>

    <!-- thisactivity enables the search dialog to initiate searches
         in the SearchableActivity -->

    <activity android:name=".OtherActivity" ...>
        <!-- enable the search dialog to send searches to SearchableActivity-->
        <meta-data android:name="android.app.default_searchable"
                   android:value=".SearchableActivity"/>

    </activity>
    ...
</application>

这样OtherActivity就有了一个可用的search dialog. 当用户出在这个activity中, onSearchRequested()方法就可以激活search dialog了. 当用户执行搜索操作的时候, Android启动SearchableActivity并传给它intent.

Searchable activity默认情况下本身自带searchdialog, 所以我们不用为SearchableActivity声明search dialog.

如果我们希望在APP中的每个activity都提供search dialog, 可以为<application>标签添加<meta-data>子标签, 而不是为每个<activity>添加. 这样每个<activity>都继承了该标签, 也就都有了search dialog, 并提供相同的searchable activity. 如果希望为某个activity指定单独的searchable activity, 那么我们可以为其<activity>单独指定<meta-data>. 现在activity已经有可用的search dialog, 那么就可以处理搜索操作了.

调用searchdialog:

尽管有些设备提供了专用的搜索按钮, 但是每种设备的搜索按钮的点击事件可能有所不同, 并且很多设备并不提供这样的按钮, 所以当我们使用search dialog的时候我们必须在UI中提供一个搜索按钮用来调用onSearchRequested()方法激活search dialog. 为了统一Android APP的用户体验, 我们应该使用Android风格的搜索按钮图标, 并放在Action bar上.

如果我们使用了一个app bar, 那么我们应该使用search widget而不是search dialog.

我们也可以使用”type-to-search”功能, 让用户在键盘上点击搜索按钮, 我们可以在activity的onCreate()方法中通过setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL)方法来实现.

Activity的生命周期对search dialog的影响:

Search dialog是一个悬浮在屏幕顶端的对话框, 它不会对activity的堆栈产生任何的影响, 所以当searchdialog出现的时候, 不会有activity的生命周期类的方法被调用(比如onPause()). 所在的activity只是会失去输入焦点而已, 输入焦点会在search dialog上.

如果我们想要在search dialog被激活的时候收到提醒, 那么需要重写onSearchRequested()方法. 当activity失去焦点而search dialog获得焦点的时候, Android将会调用这个方法. 这时候我们可以做一些合适的操作(比如暂停一个游戏). 除非我们正在传输search context data(下面讨论), 否则应该调用该方法的父方法. 栗子:

@Override
public boolean onSearchRequested(){
    pauseSomeStuff();
    return super.onSearchRequested();
}

如果用户通过返回键取消了搜索, search dialog会关闭并返回输入焦点给activity. 我们可以监听search dialog的关闭消息, 使用的方法是setOnDismissListener()和/或setOnCancelListener(). 我们应该只注册OnDismissListener,因为每次search dialog被关闭的时候都会调用它. 但是OnCancelListener只有在用户明确退出的时候才会执行, 所以在用户执行搜索的时候它不会被调用(就是用户点击搜索的时候, 这时候search dialog会自然消失).

如果当前的activity不是searchable activity, 那么当用户执行搜索操作的时候将会执行普通的生命周期事件. 如果当前的activity是searchable activity, 那么将会发生如下两件事:

a.      默认情况下, searchable activity会在onCreate()中收到带有ACTION_SEARCH的intent并且一个新的activity实例会被添加到堆栈顶部. 这样的话就会有两个searchable activity的实例在堆栈中了, 所以点击返回键就会回到前一个activity而不是完全退出.

b.      如果我们将启动模式设置为”singleTop”, 那么searchable activity会通过onNewIntent(Intent)方法收到带有ACTION_SEARCH的intent. 下面的一小段代码描述了在”singleTop”模式下如何处理新的Intent:

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.search);
    handleIntent(getIntent());
}

@Override
protected void onNewIntent(Intent intent){
    setIntent(intent);
    handleIntent(intent);
}

private void handleIntent(Intent intent){
    if (Intent.ACTION_SEARCH.equals(intent.getAction())){
      String query = intent.getStringExtra(SearchManager.QUERY);
      doMySearch(query);
    }
}

对比之前的代码, 区别在于所有的处理intent的操作都在handleIntent,而onCreate()和onNewIntent()方法都可以调用它. Android在调用onNewIntent()的时候, activity并没有被重新启动, 所以使用getIntent()方法还会返回以前的那个旧的intent, 所以在onNewIntent()方法中调用setIntent()方法充值了新的intent, 以防止我们在未来调用getIntent()方法的时候获得的intent是旧的intent.

很明显在”singleTop”模式下用户拥有更好的用户体验, 因为他们不用按两次返回键才能退出searchable activity. 所以官方建议我们使用”singleTop”模式来启动searchable activity. 代码栗子:

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

传递搜索上下文数据:

有时候我们可能希望提供给searchable activity更多的数据来改进我们的搜索操作, 这时候我们可以为带有ACTION_SEARCH的intent添加额外的数据, 数据保存在一个叫APP_DATA的Bundle中. 想要传递这些数据给searchable activity, 需要重写onSearchRequested()方法, 并创建一个Bundle对象, 然后通过startSearch()方法来激活search dialog, 并将Bundle对象给它. 代码栗子:

@Override
public boolean onSearchRequested(){
     Bundle appData = new Bundle();
     appData.putBoolean(SearchableActivity.JARGON,true);
     startSearch(null,false, appData,false);
     return true;
 }

返回true表示我们已经成功的处理了这个事件, 并调用了startSearch()方法来激活search dialog. 当用户提交了一个查询, 这些附带的信息就会被传递给searchable activity. 我们可以从APP_DATA的Bundle中取出这些数据. 栗子:

Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData!=null){
    boolean jargon = appData.getBoolean(SearchableActivity.JARGON);
}

这里有一件要注意的事情, 就是永远都不要在onSearchRequested()方法之外调用startSearch()方法.

使用SearchWidget:

SearchView widget在Android3.0及以上版本中可用, 如果我们使用的版本在3.0以上并且决定使用searchwidget, 那么官方建议我们将search widget作为一个action view置于app bar中, 而不是使用search dialog或者将search widget置于我们的activity layout中. 比如, 这是一个在app bar中的search widget:


Search widget提供了跟search dialog同样的功能, 当用户执行搜索操作的时候,它会启动一个合适的activity, 也同样可以支持搜索建议和语音搜索. 当我们使用search widget的时候, 可能还需要提供对search dialog的支持, 比如某些不能支持search widget的情况.

配置searchwidget:

在我们已经创建了searchable configuration和一个searchableactivity之后, 我们应该对每个SearchView的搜索辅助功能置为可用. 我们可以调用setSearchableInfo()方法并传入SearchableInfo对象来实现这一功能. 我们可以通过SearchManager的getSearchableInfo()方法来获取SearchableInfo对象. 比如如果我们使用一个SearchView作为action view, 我们应该在onCreateOptionsMenu()方法中将widget置为可用:

@Override
public boolean onCreateOptionsMenu(Menu menu){
    // Inflate theoptions menu from XML
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.options_menu, menu);

    // Get theSearchView and set the searchable configuration
    SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
    // Assumescurrent activity is the searchable activity
    searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
    searchView.setIconifiedByDefault(false);// Do noticonify the widget; expand it by default

    return true;
}

上面的代码就是所有我们所需要做的, search widget已经配置完成, Android将会把查询信息发送给我们的searchable activity.

如果我们想要自己处理更多的输入事件, 我们可以在这里(SearchView)找到.

Searchwidget的其他功能:

Search widget还提供了一些我们可能会需要的功能:

1.      一个summit按钮: 默认情况下并没有任何的按钮用来提供提交操作, 所以用户必须点击键盘上的”return”键, 我们可以通过setSubmitButtonEnabled(true)来增加一个”submit”键.

2.      搜索建议详情: 当我们使用搜索建议的时候, 我们经常希望用户简单的选择某一选项, 但是用户可能想要了解更加详细的信息, 这时候我们可以为每个建议增加一个按钮, 为用户提供详细信息. 相关方法: setQueryRefinementEnabled(true).

3.      改变搜索框的可见性: 默认情况下search widget是”iconified”(图表化)的, 意思是它平时只显示为一个搜索图标(一个放大镜样子的图标), 当用户点击它的时候, 它则会扩展成一个搜索框. 我们可以通过setIconifiedByDefault(false)设置为默认情况下显示搜索框. 还可以通过setIconified()方法来切换搜索框是不是该显示.

更多的功能可以到SearchView的文档中查找.

结合使用searchwidget和search dialog:

当我们在Action Bar上使用search widget的时候, 有时候可能APP会运行在一些较小屏幕的设备上, 这时候搜索键就显示不出来了, 而会被放在悬浮的菜单中. 为了处理这种情况我们应该在用户点击悬浮框中的搜索键的时候, 显示出一个search dialog. 如果想要这样做, 我们必须实现onOptionsItemSelected()方法来处理Search菜单项, 然后通过onSearchRequested()打开searchdialog.

应该使用Searchdialog还是使用Search widget?

这个问题的答案最可能由我们使用的版本是不是高于Android3.0来决定. 因为SearchView需要Android3.0及以上的版本才能支持, 所以如果我们需要在Android3.0及以下版本中开发, 我们应该使用search dialog.

如果开发版本是Android3.0及以上, 那么则由我们的需求决定. 大多数情况下我们应该使用search widget作为一个”action view”放在Action Bar上. 然而有些情况这样的做法可能不使用, 比如没空间给我们使用Action Bar. 所以这种情况下我们应该考虑将search widget放在activity的layout中. 如果这些都不能实现, 我们则应该使用search dialog. 事实上我们在某些情况下更应该结合使用search widget和search dialog.

总结:

要完成一个基本的搜索功能, 需要三个要素, 包括搜索的配置(searchable configuration), 展示结果的activity(searchableactivity)还有搜索接口(search dialog或者search widget).

用户在搜索的接口里输入要搜索的信息, Android根据配置将其发送给searchable activity, 并由其处理然后通常在一个ListView中显示出来. 


参考: http://developer.android.com/guide/topics/search/search-dialog.html


0 0
原创粉丝点击