Android 搜索框架使用详解

 更新时间:2022年11月27日 15:33:13   作者:ChenYhong  
这篇文章主要为大家介绍了Android 搜索框架使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

搜索框架简介

App中搜索功能是必不可少的,搜索功能可以帮助用户快速获取想要的信息。对此,Android提供了一个搜索框架,本文介绍如何通过搜索框架实现搜索功能。

Android 搜索框架提供了搜索弹窗和搜索控件两种使用方式。

  • 搜索弹窗:系统控制的弹窗,激活后显示在页面顶部,输入的内容提交后会通过Intent传递到指定的搜索Activity中处理,可以添加搜索建议。
  • 搜索控件(SearchView):系统实现的搜索控件,可以放在任意位置(可以与Toolbar结合使用),默认情况下与EditText类似,需要自己添加监听处理用户输入的数据,通过配置可以达到与搜索弹窗一致的行为。

使用搜索框架实现搜索功能

可搜索配置

在res/xml目录下创建searchable.xml(必须用此命名),如下:

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

android:label是此配置文件必须配置的属性,通常配置为App的名字,android:hint配置用户未输入内容时的提示文案,官方建议格式为“搜索${content or product}”

更多可搜索配置包含的语法和用法可以看官方文档

搜索页面

配置一个单独的Activity用于显示搜索内容,用户可能会在搜索完一个内容后立刻搜索下一个内容,所以建议把搜索页面设置为SingleTop,避免重复创建搜索页面。

AndroidManifest中配置搜索页面,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        ... 
        >
        <activity
            android:name=".search.SearchActivity"
            android:exported="false"
            android:launchMode="singleTop">
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable" />
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Activity中处理搜索数据,代码如下:

class SearchActivity : AppCompatActivity() {
    private lateinit var binding: LayoutSearchActivityBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.layout_search_activity)
        // 当搜索页面第一次打开时,获取搜索内容
        getQueryKey(intent)
    }
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        // 更新Intent数据
        setIntent(intent)
        // 当搜索页面多次打开,并仍在栈顶时,获取搜索内容
        getQueryKey(intent)
    }
    private fun getQueryKey(intent: Intent?) {
        intent?.run {
            if (Intent.ACTION_SEARCH == action) {
                // 用户输入的内容
                val queryKey = getStringExtra(SearchManager.QUERY) ?: ""
                if (queryKey.isNotEmpty()) {
                    doSearch(queryKey)
                }
            }
        }
    }
    private fun doSearch(queryKey: String) {
        // 根据用户输入内容执行搜索操作
    }
}

使用SearchView

SearchView可以放在页面的任意位置,本文与Toolbar结合使用,如何在Toolbar中创建菜单项在上一篇文章中介绍过,此处省略。要使SearchView与搜索弹窗保持一致的行为需要在代码中进行配置,如下:

class SearchActivity : AppCompatActivity() {
    private lateinit var binding: LayoutSearchActivityBinding
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.example_seach_menu, menu)
        menu?.run {
            val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
            val searchView = findItem(R.id.action_search).actionView as SearchView
            //设置搜索配置  
            searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName))
        }
        return true
    }
    ...
}

使用搜索弹窗

Activity中使用搜索弹窗,如果Activity已经配置为搜索页面则无需额外配置,否则需要在AndroidManifest中添加配置,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        ... 
        >
        <activity
            android:name=".search.SearchExampleActivity">
            <!--为当前页面指定搜索页面-->
            <!--如果所有页面都使用搜索弹窗,则将此meta-data移到applicaion标签下-->
            <meta-data
                android:name="android.app.default_searchable"
                android:value=".search.SearchActivity" />
        </activity>
    </application>
</manifest>

Activity中通过onSearchRequested方法来调用搜索弹窗,如下:

class SearchExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: LayoutSearchExampleActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_search_example_activity)
        binding.btnSearchDialog.setOnClickListener { onSearchRequested() }
    }
}

搜索弹窗对Activity生命周期的影响

搜索弹窗的显示隐藏,不会像其他弹窗一样触发ActivityonPauseonResume方法。如果在搜索弹窗显示隐藏的同时需要对其他功能进行处理,可以通过onSearchRequestedOnDismissListener来实现,代码如下:

class SearchExampleActivity : AppCompatActivity() {
    override fun onSearchRequested(): Boolean {
        // 搜索弹窗显示,可以在此处理其他功能
        return super.onSearchRequested()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: LayoutSearchExampleActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_search_example_activity)
        binding.btnSearchDialog.setOnClickListener { onSearchRequested() }
        val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
        searchManager.setOnDismissListener {
            // 搜索弹窗隐藏,可以在此处理其他功能
        }
    }
}

附加额外的参数

使用搜索弹窗时,如果需要附加额外的参数用于优化搜索查询的过程,例如用户的性别、年龄等,可以通过如下代码实现:

// 配置额外参数
class SearchExampleActivity : AppCompatActivity() {
    override fun onSearchRequested(): Boolean {
        val appData = Bundle()
        appData.putString("gender", "male")
        appData.putInt("age", 24)
        startSearch(null, false, appData, false)
        // 返回true表示已经发起了查询
        return true
    }
    ...
}
// 在搜素页面中获取额外参数
class SearchActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)  
        intent?.run {
            if (Intent.ACTION_SEARCH == action) {
                // 用户输入的内容
                val queryKey = getStringExtra(SearchManager.QUERY) ?: ""
                // 额外参数
                val appData = getBundleExtra(SearchManager.APP_DATA)
                appData?.run {
                    val gender = getString("gender") ?: ""
                    val age = getInt("age")
                }
            }
        }
    }
}

语音搜索

语音搜索让用户无需输入内容就可进行搜索,要开启语音搜索,需要在searchable.xml增加配置,如下:

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:hint="@string/search_hint"
    android:label="@string/app_name"
    android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" />

语音搜索必须配置showVoiceSearchButton用于显示语音搜索按钮,配置launchRecognizer指定语音搜索按钮启动一个语音识别程序用于识别语音转录为文本并发送至搜索页面。

更多语音搜索配置包含的语法和用法可以看官方文档

注意,语音识别后的文本会直接发送至搜索页面,无法更改,需要进行完备的测试确保语音搜索功能适合你的App。

搜索记录

用户执行过搜索后,可以将搜索的内容保存下来,下次要搜索相同的内容时,输入部分文字后就会显示匹配的搜索记录。

要实现此功能,需要完成下列步骤:

创建SearchRecentSuggestionsProvider

自定义RecentSearchProvider继承SearchRecentSuggestionsProvider,代码如下:

class RecentSearchProvider : SearchRecentSuggestionsProvider() {
    companion object {
        // 授权方的名称(建议设置为文件提供者的完整名称)
        const val AUTHORITY = "com.chenyihong.exampledemo.search.RecentSearchProvider"
        // 数据库模式 
        // 必须配置 DATABASE_MODE_QUERIES 
        // 可选配置 DATABASE_MODE_2LINES,为搜索记录提供第二行文本,可用于作为详情补充
        const val MODE: Int = DATABASE_MODE_QUERIES or DATABASE_MODE_2LINES
    }
    init {
        // 设置搜索授权方的名称与数据库模式
        setupSuggestions(AUTHORITY, MODE)
    }
}

AndroidManifest中配置Provider,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        ... 
        >
        <!--android:authorities的值与RecentSearchProvider中的AUTHORITY一致-->
        <provider
            android:name=".search.RecentSearchProvider"
            android:authorities="com.chenyihong.exampledemo.search.RecentSearchProvider"
            android:exported="false" />
    </application>
</manifest>

修改可搜索配置

searchable.xml增加配置,如下:

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:hint="@string/search_hint"
    android:label="@string/app_name"
    android:voiceSearchMode="showVoiceSearchButton|launchRecognizer"
    android:searchSuggestAuthority="com.chenyihong.exampledemo.search.RecentSearchProvider"
    android:searchSuggestSelection=" ?"/>

android:searchSuggestAuthority 的值与RecentSearchProvider中的AUTHORITY保持一致。android:searchSuggestSelection的值必须为" ?",该值为数据库选择参数的占位符,自动由用户输入的内容替换。

在搜索页面中保存查询

获取到用户输入的数据时保存,代码如下:

class SearchActivity : BaseGestureDetectorActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        intent?.run {
            if (Intent.ACTION_SEARCH == action) {
                val queryKey = getStringExtra(SearchManager.QUERY) ?: ""
                if (queryKey.isNotEmpty()) {
                    // 第一个参数为用户输入的内容
                    // 第二个参数为第二行文本,可为null,仅当RecentSearchProvider.MODE为DATABASE_MODE_QUERIES or DATABASE_MODE_2LINES时有效。
                    SearchRecentSuggestions(this@SearchActivity, RecentSearchProvider.AUTHORITY, RecentSearchProvider.MODE)
                        .saveRecentQuery(queryKey, "history $queryKey")
                }
            }
        }
    }
}

清除搜索历史

为了保护用户的隐私,官方的建议是App必须提供清除搜索记录的功能。请求搜索记录可以通过如下代码实现:

class SearchActivity : BaseGestureDetectorActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        SearchRecentSuggestions(this, RecentSearchProvider.AUTHORITY, RecentSearchProvider.MODE)
            .clearHistory()
    }
}

示例

整合之后做了个示例Demo,代码如下:

// 可搜索配置
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:hint="@string/search_hint"
    android:label="@string/app_name"
    android:searchSuggestAuthority="com.chenyihong.exampledemo.search.RecentSearchProvider"
    android:searchSuggestSelection=" ?"
    android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" />
// 清单文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        ...
        >
        <activity
            android:name=".search.SearchExampleActivity"
            android:screenOrientation="portrait">
            <!--为当前页面指定搜索页面-->
            <meta-data
                android:name="android.app.default_searchable"
                android:value=".search.SearchActivity" />
        </activity>
        <activity
            android:name=".search.SearchActivity"
            android:exported="false"
            android:launchMode="singleTop"
            android:parentActivityName="com.chenyihong.exampledemo.search.SearchExampleActivity"
            android:screenOrientation="portrait">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.chenyihong.exampledemo.search.SearchExampleActivity" />
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable" />
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
        </activity>
        <provider
            android:name=".search.RecentSearchProvider"
            android:authorities="com.chenyihong.exampledemo.search.RecentSearchProvider"
            android:exported="false" />
    </application>
</manifest>
// 示例Activity
class SearchExampleActivity : BaseGestureDetectorActivity() {
    override fun onSearchRequested(): Boolean {
        val appData = Bundle()
        appData.putString("gender", "male")
        appData.putInt("age", 24)
        startSearch(null, false, appData, false)
        return true
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: LayoutSearchExampleActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_search_example_activity)
        binding.btnSearchView.setOnClickListener { startActivity(Intent(this, SearchActivity::class.java)) }
        binding.btnSearchDialog.setOnClickListener { onSearchRequested() }
        val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
        searchManager.setOnDismissListener {
            runOnUiThread { Toast.makeText(this, "Search Dialog dismiss", Toast.LENGTH_SHORT).show() }
        }
    }
}
class SearchActivity : BaseGestureDetectorActivity() {
    private lateinit var binding: LayoutSearchActivityBinding
    private val textDataAdapter = TextDataAdapter()
    private val originData = ArrayList<String>()
    private var lastQueryValue = ""
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.example_seach_menu, menu)
        menu?.run {
            val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
            val searchView = findItem(R.id.action_search).actionView as SearchView
            searchView.setOnCloseListener {
                textDataAdapter.setNewData(originData)
                false
            }
            searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName))
            if (lastQueryValue.isNotEmpty()) {
                searchView.setQuery(lastQueryValue, false)
            }
        }
        return true
    }
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        if (item.itemId == R.id.action_clear_search_histor) {
            SearchRecentSuggestions(this, RecentSearchProvider.AUTHORITY, RecentSearchProvider.MODE)
                .clearHistory()
        }
        return super.onOptionsItemSelected(item)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.layout_search_activity)
        setSupportActionBar(binding.toolbar)
        supportActionBar?.run {
            title = "SearchExample"
            setHomeAsUpIndicator(R.drawable.icon_back)
            setDisplayHomeAsUpEnabled(true)
        }
        binding.rvContent.adapter = textDataAdapter
        originData.add("test data qwertyuiop")
        originData.add("test data asdfghjkl")
        originData.add("test data zxcvbnm")
        originData.add("test data 123456789")
        originData.add("test data /.,?-+")
        textDataAdapter.setNewData(originData)
        // 获取搜索内容
        getQueryKey(intent, false)
    }
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        // 更新Intent数据
        setIntent(intent)
        // 获取搜索内容
        getQueryKey(intent, true)
    }
    private fun getQueryKey(intent: Intent?, newIntent: Boolean) {
        intent?.run {
            if (Intent.ACTION_SEARCH == action) {
                val queryKey = getStringExtra(SearchManager.QUERY) ?: ""
                if (queryKey.isNotEmpty()) {
                    SearchRecentSuggestions(this@SearchActivity, RecentSearchProvider.AUTHORITY, RecentSearchProvider.MODE)
                        .saveRecentQuery(queryKey, "history $queryKey")
                    if (!newIntent) {
                        lastQueryValue = queryKey
                    }
                    val appData = getBundleExtra(SearchManager.APP_DATA)
                    doSearch(queryKey, appData)
                }
            }
        }
    }
    private fun doSearch(queryKey: String, appData: Bundle?) {
        appData?.run {
            val gender = getString("gender") ?: ""
            val age = getInt("age")
        }
        val filterData = originData.filter { it.contains(queryKey) } as ArrayList<String>
        textDataAdapter.setNewData(filterData)
    }
}

ExampleDemo github

ExampleDemo gitee

效果如图:

以上就是Android 搜索框架使用详解的详细内容,更多关于Android 搜索框架使用的资料请关注脚本之家其它相关文章!

相关文章

  • Kotlin图文讲解多语言支持实现方法

    Kotlin图文讲解多语言支持实现方法

    这篇文章主要介绍了Kotlin多语言支持实现方法,在Android开发中,我们如何支持多语言APP呢,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值
    2023-02-02
  • 详解ASP.NET Core MVC四种枚举绑定方式

    详解ASP.NET Core MVC四种枚举绑定方式

    这篇文章主要介绍了详解ASP.NET Core MVC四种枚举绑定方式, 小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • Android Jetpack组件支持库DataBinding与ViewModel与LiveData及Room详解

    Android Jetpack组件支持库DataBinding与ViewModel与LiveData及Room

    Jetpack是一个由多个技术库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种Android版本和设备中一致运行的代码,让开发者精力集中编写重要的代码
    2022-09-09
  • 微博API常用方法总结(必看篇)

    微博API常用方法总结(必看篇)

    下面小编就为大家带来一篇微博API常用方法总结(必看篇)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • Android使用CountDownTimer类实现倒计时闹钟

    Android使用CountDownTimer类实现倒计时闹钟

    这篇文章主要为大家详细介绍了Android使用CountDownTimer类实现倒计时闹钟,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • Android实现仪表盘控件开发

    Android实现仪表盘控件开发

    这篇文章主要为大家详细介绍了Android实现仪表盘控件开发,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-05-05
  • Android中Activity的生命周期探讨

    Android中Activity的生命周期探讨

    这篇文章主要介绍了Android中Activity的生命周期探讨,本文同时讲解了销毁Activity、暂停与恢复、停止与重启Activity等内容,需要的朋友可以参考下
    2014-10-10
  • Android 分享控件的实现代码

    Android 分享控件的实现代码

    这篇文章主要介绍了Android 分享控件的实现代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • listview 选中高亮显示实现方法

    listview 选中高亮显示实现方法

    当点击左侧ListView后,选中的一行就会一直呈高亮状态显示,图中选中行字的颜色显示为蓝色(注意:是选中行后一直高亮,而不是只是点击时高亮),如果再次点击另外的一行, 则新的那一行就高亮,下面就来实现这个高亮效果的显示
    2012-11-11
  • Android开发环境搭建图文教程 亲测有效!

    Android开发环境搭建图文教程 亲测有效!

    这篇文章主要为大家详细介绍了Android开发环境搭建图文教程,亲自测试有效的搭建方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03

最新评论