Android 中的 Searchable 使用,及删除记录

本文介绍了如何在Android应用中使用Searchable接口,特别是如何在不跳转新界面的情况下实现搜索dialog,并详细讲解了如何设置searchable.xml文件和AndroidManifest.xml以实现搜索功能。同时,文章还讨论了如何添加和实现删除搜索历史记录的功能,通过在searchQuery()方法中添加相应代码来完成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       Android为程序的搜索功能提供了统一的搜索接口,search dialog和search widget,这里简单介绍search dialog使用。search dialog 只能为于activity窗口的上方。下面以点击EditText输入框启动search dialog搜索框为例,效果如下:

       该效果是在点击 EditText 时,自动跳转到 google 自带的搜索框界面。 google 自带的搜索框,其左边的图标为该 app 的图标。无论是点击该图标或者是该图标前面的箭头都是可以回到 EditText 所在的界面。

       由于点击 EditText 会自动跳转界面,所以可以在跳转后的界面中自行布局,丰富界面。而本例是为了让其在当前界面实现跳转搜索,不需要额外的启动一个界面。所以在启动模式中使用“单顶模式”,即:singleTop 模式。一定要注意 google 自带的搜索框是位于 Activity 的顶部的(没办法修改)。


       实现效果图中的大致步骤:

     1. 在 res/xml目录下新建 searchable.xml 文件:

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:hint="@string/search_hint">
</searchable></span>

  

        注意:在新建 searchable.xml 时,带上后缀名。上面的代码是最简单的搜索配置代码,其中的 android:lable="@string/app_name" 必须要有,且 lable 的属性一定要为 @string/app_name。 hint 属性是提示的信息,不是必须的,这点和 EditText 类似。其他的属性可以参考 google 的官方文档。



       2. 在 AndroidManifest.xml 中声明搜索的 Activity。

       由于本例不需要在搜索时启动一个 Activity 所以,将其设置为 singleTop 模式。所以:

在 activity 根标签外添加 :
<span style="font-size:18px;"><meta-data
            android:name="android.app.default_searchable"
            android:value=".MainActivity"/></span>

       在 activity 根标签内添加(因为启动模式为 singleTop 不会加载其他的界面,所以需要在 activity 标签内,如果需要在其他界面显示,则将其添加到注册该节目的 activity 根标签内):

<span style="font-size:18px;"><intent-filter>
        <action android:name="android.intent.action.SEARCH" />
   </intent-filter>

   <meta-data
        android:name="android.app.searchable"
        android:resource="@xml/searchable" /></span>

       其中的 intent-filter 标签的 name 值必须为 android.intent.action.SEARCH,meta-data标签的 resource 值为 res/xml目录下的searchable.xml 文件;



       3. 启动搜索框:
       使用 onSearchRequested() 方法即可启动搜索框;

       当然也可以重写 onSearchRequested(),这样可以在搜索的时候去执行其他的操作,可以实现更多的扩展功能:
<span style="font-size:18px;">@Override
public boolean onSearchRequested() {
   //做其他的事
   doOther();
   return super.onSearchRequested();
}</span>

        如果想要传入参数:
<span style="font-size:18px;"> public boolean onSearchRequested() {
     
     Bundle data = new Bundle();
     data.putString("key", "your info");
     startSearch(null, true, data , false);
     return true;
 }</span>




         4. 取得搜索的关键字及处理搜索结果:
<span style="font-size:18px;">    /**
     *  由于 Activity 的启动模式为 singleTop ,搜索的 Action 必须为:
     *  android.intent.action.SEARCH,所以需要重写该方法,以便获取 Action 的“动作”。
     *  如果不重写,Action 的返回值为 android.intent.action.MAIN
     */
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // 设置 intent 这样就能得到正确的 Action 了
        setIntent(intent);
    }

    /**
     *  该方法在 onNewIntent() 方法后执行,可以获取 onNewIntent() 方法设置的 intent,
     *  也可以直接 onNewIntent() 方法进行得到 intent 等的操作
     */
    @Override
    protected void onResume() {
        super.onResume();
        Intent intent = getIntent();
        if (intent.ACTION_SEARCH.equals(intent.getAction())) {
            String query = intent.getStringExtra(SearchManager.QUERY);
            searchQurey(query, intent);
        }
    }</span>

       在 Activity 中重写 onNewIntent() 方法,该方法为启动模式为 singleTop 下得到 Action 值为 android.intent.action.SEARCH 的intent,在 onNewIntent() 方法中设置 intent 即:setIntent(intent);可以重写 onResume() 方法,可在该方法中得到 onNewIntent() 方法中设置的 intent 即:getIntent();


       这样在增加相应的点击事件过后就能实现搜索了,当然了这还与我们的效果图还有差距。上面的步骤还没有实现记录搜索结果、查询搜索结果、删除搜索记录。




下面是实现其剩下的步骤:

       5. 使用 ContentProvider 来保存每次搜索的记录:
可以新建一个 SearchSuggestionSampleProvider 类继承 SearchRecentSuggestionsProvider 类,该类可用于保存最近的搜索等;
<span style="font-size:18px;">package com.crazy.gemi.ui.cheaper;

import android.content.SearchRecentSuggestionsProvider;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.CancellationSignal;

import com.crazy.gemi.R;

/**
 *  该类用于记录最近的搜索,并将其存入数据库
 */
public class SearchSuggestionSampleProvider
        extends SearchRecentSuggestionsProvider {

    public final static String AUTHORITY="com.crazy.gemi.ui.cheaper.SearchSuggestionSampleProvider";
    public final static int MODE=DATABASE_MODE_QUERIES;

    public SearchSuggestionSampleProvider(){
        super();
        setupSuggestions(AUTHORITY, MODE);
    }

}
</span>
 
       该类的构造方法中使用 setupSuggestions(AUTHORITY, MODE);其中第一个参数为该类的权限名,第二个参数值为 DATABASE_MODE_QUERIES 常量(该常量为 1 )。

       由于该类本质上是 ContentProvider 所以需要注册:
<span style="font-size:18px;"> <provider
            android:authorities="com.crazy.gemi.ui.cheaper.SearchSuggestionSampleProvider"
            android:name=".ui.cheaper.SearchSuggestionSampleProvider"/></span>


        这样就完成了所有在 AndroidManifest.xml 中的工作,其中的完整代码如下:

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.crazy.gemi">

    <uses-permission android:name="android.permission.INTERNET"/>


    <application
        android:allowBackup="true"
        android:icon="@mipmap/naryou_logo"
        android:name=".ui.utils.MyApplication"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <meta-data
            android:name="android.app.default_searchable"
            android:value=".MainActivity"/>

        <activity android:name=".MainActivity" android:launchMode="singleTop">
            <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>

        <provider
            android:authorities="com.crazy.gemi.ui.cheaper.SearchSuggestionSampleProvider"
            android:name=".ui.cheaper.SearchSuggestionSampleProvider"/>
    </application>
    
</manifest>
</span>


        现在还需要在 res/xml目录下的 searchable.xml 文件添加如下属性用于记录搜索记录和查询搜索记录:
<span style="font-size:18px;">android:searchSuggestAuthority="com.crazy.gemi.ui.cheaper.SearchSuggestionSampleProvider"
    android:searchSuggestSelection=" ? "</span>

        其中:android:searchSuggestAuthority="com.crazy.gemi.ui.cheaper.SearchSuggestionSampleProvider"为保存搜索关键字时的 ContentProvider;android:searchSuggestSelection=" ? " 为下次输入时,自动查询以前搜索记录的字段,该参数必须要有。


        这样也就完成了 searchable.xml 的完整代码,代码如下:
<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:hint="@string/search_hint"

    android:searchSuggestAuthority="com.crazy.gemi.ui.cheaper.SearchSuggestionSampleProvider"
    android:searchSuggestSelection=" ? ">

</searchable></span>



       6. 保存搜索记录:
<span style="font-size:18px;">    private void searchQurey(String query , Intent intent) {
        //保存搜索记录
        SearchRecentSuggestions suggestions=new SearchRecentSuggestions(this,
                SearchSuggestionSampleProvider.AUTHORITY, SearchSuggestionSampleProvider.MODE);

        suggestions.saveRecentQuery(query, null);
        
    }</span>





        这样通过上面的 6 大步骤就可以实现搜索记录的查询了。但是还差了删除记录的功能了。怎样实现删除功能呢?或许有多种方法。这里提供我的办法来实现该功能。



        7. 实现删除历史记录:

       要删除记录是不是需要在搜索记录的最低端要有“删除历史记录”这样的字眼呢?这是肯定的,那么就在其中插入该字眼。我们不可能在搜索框中输入“删除历史记录”这样的字眼吧,这样的行为就显得太傻了一样。我们的 SearchSuggestionSampleProvider 类不是继承 SearchRecentSuggestionsProvider 类吗?该类不是有 query() 方法可以提供查询吗,这样我们在每次的查询过程中就在其底部添加“删除历史记录”的字眼不就可以了吗。好,该类的完整代码如下:
SearchSuggestionSampleProvider.java (完整代码):
<span style="font-size:18px;">package com.crazy.gemi.ui.cheaper;

import android.content.SearchRecentSuggestionsProvider;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.CancellationSignal;

import com.crazy.gemi.R;

/**
 *  该类用于记录最近的搜索,并将其存入数据库
 */
public class SearchSuggestionSampleProvider
        extends SearchRecentSuggestionsProvider {

    public final static String AUTHORITY="com.crazy.gemi.ui.cheaper.SearchSuggestionSampleProvider";
    public final static int MODE=DATABASE_MODE_QUERIES;

    public SearchSuggestionSampleProvider(){
        super();
        setupSuggestions(AUTHORITY, MODE);
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
        String[] items = null;
        Cursor cursor = super.query(uri, projection, selection, selectionArgs, sortOrder);
        int arrayLength = cursor.getCount();
        if (arrayLength != 0) {
            items = new String[arrayLength + 1];
            cursor.moveToFirst();
            int i = 0;
            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                int number = cursor.getColumnIndex("suggest_intent_query");
                items[i] = cursor.getString(number);
                i++;
            }
        //    items[i] = "清空历史记录";
            items[i] = getContext().getString(R.string.clear_search_keyword);
        }else {
            return null;
        }

        // suggest_format 等字段不能够改变
        String[] columns = new String[]{"suggest_format", "suggest_text_1", "suggest_intent_query", "_id"};
        // ContentProvider对外共享数据的时候,如果没有数据库,需要对外共享数据时则使用 MatrixCursor
        MatrixCursor stringCursor = new MatrixCursor(columns);
        String row[] = new String[4];
        int i = 0;
        for (CharSequence item : items) {
            row[0] = "" + 0;
            row[1] = item.toString();
            row[2] = item.toString();
            row[3] = "" + (++i);
            stringCursor.addRow(row);
        }
        return stringCursor;
    }
}
</span>



        这样是不是就可以实现删除功能了呢?如果按照上面的步骤去执行,那就会看见当我们每次点击“删除历史记录”时,就会在搜索框中多出“删除历史记录”这个字眼。由此可以我们并没有实现该功能,只实现了添加“删除历史记录”字眼的功能。到这一步离实现“删除历史记录”只有一步之遥的距离了。执行在 searchQurey() 方法中添加如下代码即可:

<span style="font-size:18px;"> // 点击 “清空历史记录” 删除记录
        if(query.equals(getString(R.string.clear_search_keyword))){
            suggestions.clearHistory();
        }else{
            suggestions.saveRecentQuery(query, null);
        }</span>



         完整的 MainActivity.java 代码如下:

<span style="font-size:18px;">package com.crazy.gemi;

import android.app.SearchManager;
import android.content.Intent;
import android.graphics.Color;
import android.provider.SearchRecentSuggestions;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import android.widget.TextView;

import com.crazy.gemi.ui.cheaper.CheaperFragment;
import com.crazy.gemi.ui.cheaper.SearchSuggestionSampleProvider;
import com.crazy.gemi.ui.favor.FavorFragment;
import com.crazy.gemi.ui.more.MoreFragment;
import com.crazy.gemi.ui.near.NearFragment;
import com.crazy.gemi.ui.pocket.PocketFragment;

public class MainActivity extends FragmentActivity
        implements View.OnClickListener, CheaperFragment.SearchResult{

    private TextView[] textView = new TextView[5];
    private View[] views = new View[5];
    // 其中的 firstFragment 相当于是个中间变量
    private Fragment firstFragment, nearFragment, cheaperFragment, favorFragment, pocketFragmnet, moreFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();
        initFragment();
    }

    private void init() {

        textView[0] = (TextView)findViewById(R.id.near);
        textView[1] = (TextView)findViewById(R.id.search_cheaper);
        textView[2] = (TextView)findViewById(R.id.favor);
        textView[3] = (TextView)findViewById(R.id.pocket);
        textView[4] = (TextView)findViewById(R.id.more);

        views[0] = findViewById(R.id.near_top_line);
        views[1] = findViewById(R.id.cheaper_top_line);
        views[2] = findViewById(R.id.favor_top_line);
        views[3] = findViewById(R.id.pocket_top_line);
        views[4] = findViewById(R.id.more_top_line);

        textView[0].setOnClickListener(this);
        textView[1].setOnClickListener(this);
        textView[2].setOnClickListener(this);
        textView[3].setOnClickListener(this);
        textView[4].setOnClickListener(this);

    }

    private void initFragment() {
        firstFragment = FavorFragment.newInstance();
        favorFragment = firstFragment;
        // 最先加载的 fragment
        getSupportFragmentManager().beginTransaction().
                add(R.id.frame_layout, favorFragment).commit();
        textView[2].setTextColor(Color.BLACK);
        views[2].setBackgroundColor(Color.parseColor("#FF6600"));
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.near:
//                getSupportFragmentManager().beginTransaction().
//                        replace(R.id.frame_layout, NearFragment.newInstance()).commit();

                if(nearFragment==null){
                    nearFragment= NearFragment.newInstance();
                }
                switchContent(firstFragment, nearFragment, getSupportFragmentManager().beginTransaction());
                firstFragment = nearFragment;

                selectStringAndBackgroundColor(0);
                break;
            case R.id.search_cheaper:
                if(cheaperFragment==null){
                    cheaperFragment= CheaperFragment.newInstance();
                }
                switchContent(firstFragment, cheaperFragment, getSupportFragmentManager().beginTransaction());
                firstFragment = cheaperFragment;

                selectStringAndBackgroundColor(1);
                break;
            case R.id.favor:
                if(favorFragment==null){
                    favorFragment= FavorFragment.newInstance();
                }
                switchContent(firstFragment, favorFragment, getSupportFragmentManager().beginTransaction());
                firstFragment = favorFragment;

                selectStringAndBackgroundColor(2);
                break;
            case R.id.pocket:
                if(pocketFragmnet==null){
                    pocketFragmnet= PocketFragment.newInstance();
                }
                switchContent(firstFragment, pocketFragmnet, getSupportFragmentManager().beginTransaction());
                firstFragment = pocketFragmnet;

                selectStringAndBackgroundColor(3);
                break;
            case R.id.more:
                if(moreFragment==null){
                    moreFragment= MoreFragment.newInstance();
                }
                switchContent(firstFragment, moreFragment, getSupportFragmentManager().beginTransaction());
                firstFragment = moreFragment;

                selectStringAndBackgroundColor(4);
                break;
        }
    }

    /**
     *  通过 position 的位置改变文字和 View 的颜色
     * @param position
     */
    private void selectStringAndBackgroundColor(int position){
        int sum = textView.length;
        for (int i = 0; i < sum; i++) {
            if (position == i) {
                textView[i].setTextColor(Color.BLACK);
                views[i].setBackgroundColor(Color.parseColor("#FF6600"));
            } else {
                textView[i].setTextColor(Color.GRAY);
                views[i].setBackgroundColor(Color.parseColor("#f0f0f0"));
            }
        }
    }

    /**
     * 判断是否添加了界面,以保存当前状态
     */
    public void switchContent(Fragment from, Fragment to,
                              FragmentTransaction transaction) {

        if (!to.isAdded()) { // 先判断是否被add过

            transaction.hide(from).add(R.id.frame_layout, to)
                    .commit(); // 隐藏当前的fragment,add下一个到Activity中
        } else {
            transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
        }

    }

    /**
     *  由于 Activity 的启动模式为 singleTop ,搜索的 Action 必须为:
     *  android.intent.action.SEARCH,所以需要重写该方法,以便获取 Action 的“动作”。
     *  如果不重写,Action 的返回值为 android.intent.action.MAIN
     */
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // 设置 intent 这样就能得到正确的 Action 了
        setIntent(intent);
    }

    /**
     *  该方法在 onNewIntent() 方法后执行,可以获取 onNewIntent() 方法设置的 intent,
     *  也可以直接 onNewIntent() 方法进行得到 intent 等的操作
     */
    @Override
    protected void onResume() {
        super.onResume();
        Intent intent = getIntent();
        if (intent.ACTION_SEARCH.equals(intent.getAction())) {
            String query = intent.getStringExtra(SearchManager.QUERY);
            searchQurey(query, intent);
        }
    }

    @Override
    public void doSearch() {
        onSearchRequested();
    }

    private void searchQurey(String query , Intent intent) {
        //保存搜索记录
        SearchRecentSuggestions suggestions=new SearchRecentSuggestions(this,
                SearchSuggestionSampleProvider.AUTHORITY, SearchSuggestionSampleProvider.MODE);

        // 点击 “清空历史记录” 删除记录
        if(query.equals(getString(R.string.clear_search_keyword))){
            suggestions.clearHistory();
        }else{
            suggestions.saveRecentQuery(query, null);
        }

    }

//    private void clearSearchHistory() {
//        SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
//                SearchSuggestionSampleProvider.AUTHORITY, SearchSuggestionSampleProvider.MODE);
//        suggestions.clearHistory();
//    }
}
</span>


       该类中有许多跟实现该功能无关的代码,如 CheaperFragment 中的接口回调等(由于 EditText 在CheaperFragment ,而 Fragment 中没有 onNewIntent() 的重写方法,所以需要使用接口)。这就需要读者自行去去掉了,呵呵!!







评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值