首页 » Android程序设计:第2版 » Android程序设计:第2版全文在线阅读

《Android程序设计:第2版》搜索界面

关灯直达底部

搜索框架使得应用变得可搜索。要注意,搜索框架只是一个UI框架,并没有提供对真正搜索逻辑的支撑。相反地,它提供了UI部分,支持用户搜索查询并执行它。这又可以调用指定的搜索逻辑,并返回相关结果。为了说明构建搜索逻辑和搜索界面的基础方面,我们将探索一个搜索应用实例,它支持用户通过莎士比亚的“sonnet(十四行诗)”进行搜索。

搜索基础

搜索对应用有些前提要求。首先,它需要真正的逻辑,能够返回搜索结果。它还需要可搜索的配置,建立关于当初始化搜索UI时显示哪些以及如何执行的一些规范。最后,启动可搜索的活动,接收查询,调用搜索逻辑后,显示结果。

搜索逻辑

有很多种方式可以为生成搜索结果创建真正的搜索逻辑。以下我们将探索两个选项:基础的基于索引的搜索以及SQLite数据库支持的搜索,即android.database.sqlite搜索。在任何一种情况下,我们将从基础的构建分块开始:数据对象和SearchLogic接口。

对于数据对象,该实例包含一个主类对象以及一个子类对象。因为用过sonnet进行搜索,我们先定义了一个Sonnet类,它包含标题、sonnet的编号及其行数。当需要在sonnet内检索特定行而不是整个sonnet时,需要用到子对象Sonnet Fragment。该子对象主要用于显示查找结果。


public class Sonnet {    public int num;    public String title;    public String lines;}public class SonnetFragment {    public int num;    public String line;}  

当真正通过UI查找或检索sonnet时,需要为SearchLogic逻辑实现两个方法并调用它们:一是search方法,它接收查询字符串,返回SonnetFragment对象的有序数组;二是getSonnet方法,它根据序号返回特定的Sonnet对象。


public interface SearchLogicInterface {    SonnetFragment search(String query);    Sonnet getSonnet(int i);}  

基于索引的搜索逻辑。在准备工作中,Sonnet会被写到原始文件中,并对每行进行解析。每构成sonnet的几行会作为一个Sonnet对象进行处理,每行的每个词会作为key整合到大索引库中。对Sonnet值进行设置,每个SonnetRef对象都包含sonnet ID,它包含sonnet所包含的单词以及这些单词的序号列表。当然,也可以使用其他更高级的技术,比如基于单词的定位跟踪(sonnet中每行单词的位置作为一个值),它涉及在sonnet内对单词的含义或上下文进行引用的元数据,计算该sonnet内该单词的权重,为每个sonnet创建单词权重排名系统,以及其他方法处理更准确的搜索查询或生成更具体的查询结果。然而,在本节中,我们将简要介绍最基础的索引搜索系统。

当索引构建完成并且应用可以使用时,当查询指定了特定的搜索单词,会很快返回结果,因为需要处理的逻辑仅仅是在索引和引用的sonnet列表和行列表中查找单词,该单词即索引中的值。在以下实例中,我们通过HashMap存储索引,单词作为关键字以及多个包含sonnet号和行号作为值的SonnetRef对象。


// the indexprivate HashMap<String, HashSet<SonnetRef>> termindex;...// adding the term to the indexHashSet<SonnetRef> set = null;if(index.containsKey(word)) {    set = index.get(word);} else set = new HashSet<SonnetRef>;set.add(new SonnetRef(sons.size - 1, i));...  

为了处理多个单词项,这段逻辑获取所有的SonnetRef对象,通过多个对象的交集查找sonnet编号。交集即最终的返回值。

基于数据库的搜索逻辑。在这段实现中,搜索的核心是一个SQL查询,它查找存储在数据库中的sonnet。当读取sonnet数据时,每个sonnet会添加到数据库中。数据库的列包含引用的sonnet编号、sonnet标题、行号以及行本身。

搜索查询可以通过LIKE语句执行。

可搜索的配置文件

一旦建立了搜索逻辑,就需要理解搜索框架。首先,创建可搜索的配置文件(searchable configuration),它是在res/xml目录下的XML文件,通常命名成searchable.xml。可搜索的配置文件包含特定的属性,这些属性最终构成SearchableInfo对象的配置,系统启动时会对该对象进行初始化。

可搜索的XML配置文件必须包含可搜索的元素作为根节点,而且必须包含android:label属性。


<?xml version=/"1.0/" encoding=/"utf-8/"?><searchable xmlns:android=/"http://schemas.android.com/apk/res/android/"    android:label=/"@string/app_label/"    ></searchable> 

完整的可搜索配置文件语法如下:

为了获取更多关于可搜索配置的信息,请查看Android开发者指南的Search Configuration一节(http://developer.android.com/guide/topics/search/searchable-config.html#searchable-element)。

可搜索的活动

定义了可搜索的配置后,必须创建可搜索的活动。该活动最终会调用搜索逻辑并显示结果。当在Search Dialog或Search Widget中执行搜索时,系统会启动ACTION_SEARCH操作的intent来启动该活动。查询是通过intent中的SearchManager.QUERY字符串值来表示的。活动可以通过查询字符串来调用搜索逻辑。该逻辑返回结果,并显示给用户。在前面给出的例子中,查询字符串会传递给搜索逻辑的search方法,并返回SonnetFragment对象数组。该活动后续的逻辑会负责把结果显示给用户。

第一步是在manifest文件中声明可搜索的活动,向系统指定搜索查找应该在该活动上执行。这是通过把android.intent.action.SEARCH操作添加到intent过滤器中,并执行可搜索的配置来完成的。


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

第二步是决定如何显示搜索结果。通常建议采用表格形式显示结果。在这个例子中,我们使用ListView显示搜索结果。为了简单,该活动扩展了ListActivity,因为ListActivity提供了默认的布局方式ListView,通过getListView方法和setListAdapter方法可以很方便地访问。

在这段代码中,处理系统传递给该活动的ACTION_SEARCH intent这段逻辑很重要。从intent中抽取了SearchManager.QUERY字符串的其他信息(通过getStringExtra方法)后,查询就作为搜索逻辑的输入并返回结果。在这个例子中,把SonnetFragment对象的数组放到ArrayAdapter,该ArrayAdapter被设置成List的适配器,对结果进行显示。

以上是最基础的UI工作。下面,我们将涵盖一些UI组件,用户访问这些组件来执行搜索:即Search Dialog和Search Widget。如果你的目标设备运行在Android 3.0(蜂巢Honeycomb/API 11)或更新的版本上,应该使用Search Widget。

Search Dialog

Search Dialog是个用户界面,它支持用户输入文本并执行搜索。当初始化后,该组件出现在屏幕的最上方。Android系统控制Search Dialog中的所有事件。当用户输入查询并提交时,系统会向manifest文件中指定的活动发送ACTION_SEARCH intent。

为了支持Search Dialog从特定的活动给声明的可搜索的活动发送查询,<meta-data>元素必须包含android:value属性,它指定可搜索的活动的类名称。它还必须包含manifest指定的该活动的Search Dialog和android:name属性,后者的值为“android.app.default_searchable”。


<application ... >    <activity          android:label=/"@string/app_name/"          android:name=/".searchdemo.MainActivity/" >          <meta-data android:name=/"android.app.default_searchable/"                    android: />       </activity>    </activity>    ...</application>  

有了元数据引用之后,当用户在前端按下设备的该活动的Search按钮(如果设备有该按钮),或者当活动调用onSearchRequested方法时,会激活Search Dialog。


public class MainActivity extends Activity {      @Override      public void onCreate(Bundle savedInstanceState) {           super.onCreate(savedInstanceState);           setContentView(R.layout.main);           findViewById(R.id.search).setOnClickListener(new OnClickListener {                  @Override                  public void onClick(View v) {                        onSearchRequested; // activates the Search Dialog                   }         });}  

Search Dialog会悬浮在屏幕的上方。它不会给活动栈带来任何变化。因此,当出现Search Dialog时,不会调用生命周期方法onPause。活动会失去Search Dialog的输入光标。如果用户按下Back按钮取消搜索,Search Dialog会关闭,活动会重新获取到输入光标。

Search Widget

Search Widget(具体地说即SearchView类)只在Android 3.0(蜂巢Honeycomb/API 11)或更新的版本中提供。建议在Action Bar中使用SearchView作为action视图,可以对菜单项进行折叠,而不是把Search Widget放到活动布局中。要在ActionBar中使用它,首先创建一个定制的菜单XML文件(在这个例子中名为search_menu.xml),在其中一项上引用android:actionView Class=“android.widget.SearchView”,把该XML文件放到res/menu目录中。


<?xml version=/"1.0/" encoding=/"utf-8/"?><menu xmlns:android=/"http://schemas.android.com/apk/res/android/">      <item android:id=/"@+id/menu_search/"            android:icon=/"@android:drawable/ic_menu_search/"            android:title=/"@string/search/"            android:showAsAction=/"ifRoom|withText/"            android:actionViewClass=/"android.widget.SearchView/"            /></menu>  

有了菜单XML文件后,可以在活动的onCreateOptionsMenu方法中设置。通过SearchableInfo引用SearchView并设置setSearchableInfo方法,表示可搜索的配置。


@Overridepublic boolean onCreateOptionsMenu(Menu menu) {        // Inflate the options menu from XML    MenuInflater inflater = getMenuInflater;        // inflate the search_menu.xml    inflater.inflate(R.menu.search_menu, menu);        // Get the SearchView and set the searchable configuration    SearchManager searchManager =            (SearchManager) getSystemService(Context.SEARCH_SERVICE);    SearchView searchView =            (SearchView) menu.findItem(R.id.menu_search).getActionView;    searchView.setSearchableInfo(            searchManager.getSearchableInfo(getComponentName));        // Do not iconify the widget; expand it by default    searchView.setIconifiedByDefault(false);        // turn on submit button    searchView.setSubmitButtonEnabled(true);        // enable query selection refinement    searchView.setQueryRefinementEnabled(true);    return true;}