讀古今文學網 > 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 index
private HashMap<String, HashSet<SonnetRef>> termindex;
...
// adding the term to the index
HashSet<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方法,表示可搜索的配置。


@Override
public 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;
}