# 1. 简单使用
可以直接结合`Toolbar`使用,也就是在添加的`item.xml`中直接使用,比如:
~~~
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/bt2"
android:title="@string/note_page_description"
android:icon="@drawable/ic_baseline_search_24"
app:actionViewClass="android.widget.SearchView"
app:showAsAction="always" />
<item
android:id="@+id/bt3"
android:title="@string/note_page_setting"
android:icon="@drawable/ic_baseline_settings_24"
app:showAsAction="ifRoom"/>
</menu>
~~~
然后可以简单设置一下显示格式。
参考:https://blog.csdn.net/jaynm/article/details/107172544
![](https://img.kancloud.cn/e0/bf/e0bff67276f12dcd24df00500ccd9331_1224x607.png)
使用 `SearchView` 时可使用如下常用方法。
- `setIconifiedByDefault(Boolean iconified)`:设置该搜索框默认是否自动缩小为图标。
- `setSubmitButtonEnabled(Boolean enabled)`:设置是否显示搜索按钮。
- `setQueryHint(CharSequence hint)`:设置搜索框内默认显示的提示文本。
- `setOnQueryTextListener(SearchView.OnQueryTextListener listener)`:为该搜索框设置事件监听器。
比如:
~~~
// 添加Toolbar菜单栏按钮
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar_item, menu)
// 找到搜索框
val item = menu?.findItem(R.id.bt2)
search = item?.actionView as SearchView
// 设置搜索框
search.let {
it.isSubmitButtonEnabled = true
it.queryHint = "请输入关键字"
it.imeOptions = EditorInfo.IME_ACTION_DONE
it.maxWidth = 800
}
return true
}
~~~
然后可以返回结果列表:
根据`setSuggestionsAdapter()`方法可以实现输入搜索内容,自动提示的功能。`setSuggestionsAdapter(CursorAdapter adapter)`方法需要一个 `CursorAdapter` 参数,这里看到` Cursor`,很多人就应该清楚,`Cursor` 光标或者游标。正常情况下这里应该采用 `Cursor` 操作数据库,可以实现查询筛选功能。
# 2. 搜索提示功能
一般开发中遇到的需求是:输入关键字就显示搜索结果,所以需要监听搜索框的文字输入,一旦文字变化就查询数据库,更新搜索结果。这里为了模拟,创建一个数据库。这里使用`Room`框架来构建,也就是三步:
- 创建表对应的实体类;
- 创建`Dao`层操作接口类;
- 创建继承自`RoomDatabase`的数据库`Dao`层接口获取类;
至于更多`Room`操作细节,可以查看我的看云的`Kotlin`笔记处。这里不再给出。当然,对应于这里我们需要模糊查询,这里给出一个示例:
~~~
@Query("select * from MFNote where (title like '%' || :words || '%') or (content like '%' || :words || '%') or (first_submit like '%' || :words || '%') or (last_modify like '%' || :words || '%') or (label like '%' || :words || '%') or (category like '%' || :words || '%') LIMIT 10")
fun getNodesByKeyWords(words: String): Cursor
~~~
这里没有指定具体类型`List<MFNote>`,因为我们这里需要一个`Cursor`对象。然后为查询后显示的`item`创建布局文件(`search_item_layout.xml`):
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:background="@color/white"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/search_item_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="主标题"
android:textStyle="bold"
android:padding="10dp"
android:textColor="@color/black"
android:gravity="center_vertical|start"
>
</TextView>
<TextView
android:id="@+id/search_item_subTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:text="副标题"
android:paddingStart="10dp"
android:paddingBottom="10dp"
android:paddingEnd="10dp"
android:maxEms="20"
android:singleLine="true"
android:ellipsize="end"
android:textColor="@color/gray"
android:gravity="center_vertical|start"
tools:ignore="RtlSymmetry">
</TextView>
</LinearLayout>
~~~
然后找到这个`SearchView`,进行设置:
~~~
// 添加Toolbar菜单栏按钮
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar_item, menu)
// 找到搜索框
val item = menu?.findItem(R.id.bt2)
search = item?.actionView as SearchView
// 设置搜索框
search.let {
it.isSubmitButtonEnabled = true
it.queryHint = "请输入关键字"
it.imeOptions = EditorInfo.IME_ACTION_DONE
it.maxWidth = 800
}
// 设置输入监听函数
search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
if(mfDao == null){
mfDao = MFNoteDataBase.getInstance(this@MainActivity)?.mfDao()
}
val cursor = mfDao?.getNodesByKeyWords(newText?:"测")
Log.e("TAG", "onQueryTextChange: ${newText}, cursor count : ${cursor?.count}" )
if(search.suggestionsAdapter == null){
val cursorAdapter = SimpleCursorAdapter(this@MainActivity,
R.layout.search_item_layout, cursor,
listOf<String>("title", "content").toTypedArray(), listOf<Int>(R.id.search_item_tv, R.id.search_item_subTitle).toIntArray()
) as CursorAdapter
search.suggestionsAdapter = cursorAdapter
} else{
search.suggestionsAdapter.changeCursor(cursor)
}
return false
}
})
return true
}
~~~
效果:
![](https://img.kancloud.cn/b6/32/b632a210925431a686c810ccf1b06f26_365x587.png)
但是有个弊端就是需要输入两个字符才会有数据提示。这里通过反射来实现:
~~~
// 通过反射设置只要有一个文字就触发查询
val clazz = search.javaClass
val field =
clazz.getDeclaredField("mSearchSrcTextView")
field.isAccessible = true
val searchAutoComplete = field.get(search) as AutoCompleteTextView
searchAutoComplete.threshold = 1
~~~
效果:
![](https://img.kancloud.cn/62/d9/62d96ee7038050f161b3e04128cc3662_306x257.png)
当然,也可以直接不使用默认的这个结果显示,自己写一个`ListView`,然后进行监听`SearchView`的数据变化,自己来渲染在`xml`中写入的`ListView`即可。这里不采用这种方式,所以这里不再给出案例。
# 3. 搜索提示监听
当然,上面的功能还没完,我们还需要响应点击事件。这里就需要查阅官方文档:[创建搜索界面 | Android 开发者 | Android Developers](https://developer.android.com/guide/topics/search/search-dialog)
## 3.1 配置xml文件
首先需要的是一个名为可搜索配置的 `XML `文件。名为`searchable.xml`,并且必须保存在`res/xml/`项目目录中。
> 注意:系统使用此文件来实例化`[SearchableInfo](https://developer.android.com/reference/android/app/SearchableInfo)`对象,但您无法在运行时自行创建此对象,您必须在 `XML` 中声明可搜索配置。比如:
~~~
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:hint="@string/search"
android:label="@string/title">
</searchable>
~~~
## 3.2 创建可搜索 Activity
因为这里我只是在`MainActivity`中有搜索框`SearchView`,所以这里我也只能配置在`MainActivity`来接受结果。比如:
~~~
<activity android:name=".MainActivity">
<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>
~~~
1. 在[`<intent-filter>`](https://developer.android.com/guide/topics/manifest/intent-filter-element)元素中声明要接受[`ACTION_SEARCH`](https://developer.android.com/reference/android/content/Intent#ACTION_SEARCH)`intent` 的 `Activity`。
2. 在[`<meta-data>`](https://developer.android.com/guide/topics/manifest/meta-data-element)元素中指定要使用的可搜索配置。
## 3.3 配置关联
需要为每个[`SearchView`](https://developer.android.com/reference/android/widget/SearchView)启用辅助搜索。为此,您可以调用[`setSearchableInfo()`](https://developer.android.com/reference/android/widget/SearchView#setSearchableInfo(android.app.SearchableInfo))并向其传递表示可搜索配置的[`SearchableInfo`](https://developer.android.com/reference/android/app/SearchableInfo)对象。
~~~
// 响应搜索列表点击
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
search.apply {
setSearchableInfo(searchManager.getSearchableInfo(componentName))
}
~~~
## 3.4 显示搜索结果
在可搜索 `Activity` 中执行搜索涉及三个步骤:
1. [接收查询](https://developer.android.com/guide/topics/search/search-dialog#ReceivingTheQuery)
2. [搜索数据](https://developer.android.com/guide/topics/search/search-dialog#SearchingYourData)
3. [显示结果](https://developer.android.com/guide/topics/search/search-dialog#PresentingTheResults)
比如下面的代码:
~~~
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.search)
// Verify the action and get the query
if (Intent.ACTION_SEARCH == intent.action) {
intent.getStringExtra(SearchManager.QUERY)?.also { query ->
doMySearch(query)
}
}
}
~~~
更多细节可以查阅官方文档:[设置搜索界面 | Android 开发者 | Android Developers](https://developer.android.com/training/search/setup)
因为这里所有的控件都是在`MainActivity`中,所以这里需要代码设置部分显示和部分不显示。而且我们需要设置这个`Activity`的启动模式:
```
android:launchMode="singleTop"
```
然后,在[`onNewIntent()`](https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent))方法中处理[`ACTION_SEARCH`](https://developer.android.com/reference/android/content/Intent#ACTION_SEARCH)`intent`。
比如:
~~~
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
processSearchData(intent)
}
fun processSearchData(intent: Intent?){
if(intent?.action == Intent.ACTION_SEARCH){
val stringExtra = intent.extras?.get(SearchManager.QUERY)
Log.e("TAG", "processSearchData: ${ stringExtra }" )
}
}
~~~
但是呢,很不幸,结果如下:
![](https://img.kancloud.cn/78/36/783624883f1a5ce439218d3af6794fef_1025x174.png)
这里并没有获取到任何数据,所以这里需要再次查阅其源码:
~~~
private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) {
try {
// use specific action if supplied, or default action if supplied, or fixed default
String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
if (action == null) {
action = mSearchable.getSuggestIntentAction();
}
if (action == null) {
action = Intent.ACTION_SEARCH;
}
// use specific data if supplied, or default data if supplied
String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
if (data == null) {
data = mSearchable.getSuggestIntentData();
}
// then, if an ID was provided, append it.
if (data != null) {
String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
if (id != null) {
data = data + "/" + Uri.encode(id);
}
}
Uri dataUri = (data == null) ? null : Uri.parse(data);
String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
return createIntent(action, dataUri, extraData, query, actionKey, actionMsg);
} catch (RuntimeException e ) {
int rowNum;
try { // be really paranoid now
rowNum = c.getPosition();
} catch (RuntimeException e2 ) {
rowNum = -1;
}
Log.w(LOG_TAG, "Search suggestions cursor at row " + rowNum +
" returned exception.", e);
return null;
}
}
~~~
以及:
~~~
private Intent createIntent(String action, Uri data, String extraData, String query,
int actionKey, String actionMsg) {
// Now build the Intent
Intent intent = new Intent(action);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// We need CLEAR_TOP to avoid reusing an old task that has other activities
// on top of the one we want. We don't want to do this in in-app search though,
// as it can be destructive to the activity stack.
if (data != null) {
intent.setData(data);
}
intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
if (query != null) {
intent.putExtra(SearchManager.QUERY, query);
}
if (extraData != null) {
intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
}
if (mAppSearchData != null) {
intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
}
if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
intent.putExtra(SearchManager.ACTION_KEY, actionKey);
intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
}
intent.setComponent(mSearchable.getSearchActivity());
return intent;
}
~~~
根据上面源码可以很容易知道放置进去的键值对,故而这里可以多测试几组:
~~~
fun processSearchData(intent: Intent?){
if(intent?.action == Intent.ACTION_SEARCH){
Log.e("TAG", "QUERY: ${ intent.extras?.get(SearchManager.QUERY) }" )
Log.e("TAG", "USER_QUERY: ${ intent.extras?.get(SearchManager.USER_QUERY) }" )
Log.e("TAG", "EXTRA_DATA_KEY: ${ intent.extras?.get(SearchManager.EXTRA_DATA_KEY) }" )
Log.e("TAG", "ACTION_KEY: ${ intent.extras?.get(SearchManager.ACTION_KEY) }" )
Log.e("TAG", "ACTION_MSG: ${ intent.extras?.get(SearchManager.ACTION_MSG) }" )
}
}
~~~
输入:
![](https://img.kancloud.cn/6e/f3/6ef30e5361bfefb700418380fba6d7b7_567x359.png)
然后随机点击一个:
![](https://img.kancloud.cn/d3/5b/d35ba9448a3dfef4deb1ab5053605c35_1030x213.png)
可以看见此时有效的只有`USER_QUERY`。故而这里还需要看源码,看看如何设置。可以看见:
~~~
// Cursor c
String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
~~~
而这个方法来自:
~~~
import static android.widget.SuggestionsAdapter.getColumnString;
~~~
其方法细节为:
~~~
public static String getColumnString(Cursor cursor, String columnName) {
int col = cursor.getColumnIndex(columnName);
return getStringOrNull(cursor, col);
}
~~~
也就是从`cursor`中获取指定名字的列的数据。所以我们只需要确保查询到的数据中有`SearchManager.SUGGEST_COLUMN_QUERY`这么一列。而这个值为:
~~~
public final static String SUGGEST_COLUMN_QUERY = "suggest_intent_query";
~~~
这里由于我使用的是`Room`所以这里我直接在`Entity`类中添加一个属性:
~~~
@ColumnInfo(name = "suggest_intent_query")
var query: Int = 0
~~~
然后重新生成数据库即可。然后添加两天数据,再次测试:
![](https://img.kancloud.cn/5a/3a/5a3afd553a5782a88cd21b5973e26002_514x349.png)
再次随机点击一条,查看控制台打印信息:
![](https://img.kancloud.cn/69/d6/69d659a7cf7352a854e65ffa052e07e9_1024x187.png)
可以看到此时对应的`QUERY`字段就有值了。由于我们可能需要通过这个字段来查询数据,进而显示详细的数据,所以这里需要其每个记录的唯一,所以后面可能需要修改为`Long`,来存储毫秒数。
# 3.5 重新修改逻辑
如果均在`MainActivity`显示主要内容,以及显示搜索结果,那么就需要设置很多的显示和隐藏,比如:
~~~
// 进入显示搜索结果的时候
fun enterSearchResultView(search_result_ll: LinearLayout){
search_result_ll.visibility = View.VISIBLE
swiperefreshLayout.visibility = View.GONE
bottom_tab.visibility = View.GONE
floatingactionbutton.visibility = View.GONE
}
~~~
但是,感觉这样太麻烦了,所以这里我预期将搜索结果显示放置在另外一个`Activity`中。首先修改一下清单文件:
~~~
<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>
</activity>
<activity android:name=".activitys.TestActivity">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
~~~
然后,需要关联对应的`TestActivity`文件:
~~~
// 响应搜索列表点击
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
search.apply {
setSearchableInfo(searchManager.getSearchableInfo(componentName))
}
~~~
但是,`componentName`是在当前`Activity`的`this`中获取到的,这里我无法直接获取到`TestActivity`的`componentName`,所以直接手动创建一个对应的对象。
~~~
// 响应搜索列表点击
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
search.apply {
val search_componentName = ComponentName("com.weizu.myapplication.activitys", "com.weizu.myapplication.activitys.TestActivity")
setSearchableInfo(searchManager.getSearchableInfo(search_componentName))
}
~~~
但是这里失败了:
![](https://img.kancloud.cn/fb/09/fb09c29316232c35648c6aeb10736bd7_1649x183.png)
这里也就懒得继续看源码来看如何解决了。这里就不修改了!!!直接修改逻辑为查询到数据,就跳转到一个新的`Activity`:
~~~
fun processSearchData(intent: Intent?){
if(intent?.action == Intent.ACTION_SEARCH){
// 用户点击的数据在数据库表MFNote对应的suggest_intent_query的值
val suggest_intent_query = intent.getStringExtra(SearchManager.QUERY)
Log.e("TAG", "QUERY: ${suggest_intent_query}")
// 直接跳转到显示Activity
startActivity(Intent().setClass(this, TestActivity::class.java))
}
}
~~~
# 3.6 清除搜索框文本
~~~
fun processSearchData(intent: Intent?){
if(intent?.action == Intent.ACTION_SEARCH){
// 用户点击的数据在数据库表MFNote对应的suggest_intent_query的值
val suggest_intent_query = intent.getStringExtra(SearchManager.QUERY)
// 直接跳转到显示Activity
if(!TextUtils.isEmpty(suggest_intent_query)){
// 清除搜索框文本,关闭键盘,关闭搜索框
searchAutoComplete?.setText("")
searchAutoComplete?.clearFocus()
val clazz = search.javaClass
val declaredMethod = clazz.getDeclaredMethod("onCloseClicked")
declaredMethod.setAccessible(true);
declaredMethod.invoke(search)
val searchResultsIntent = Intent()
searchResultsIntent.apply {
setClass(this@MainActivity, TestActivity::class.java)
putExtra("suggest_intent_query", suggest_intent_query)
}
startActivity(searchResultsIntent)
}
}
}
~~~
至于`searchAutoComplete`来自反射:
~~~
// 通过反射设置只要有一个文字就触发查询
val clazz = search.javaClass
val field =
clazz.getDeclaredField("mSearchSrcTextView")
field.isAccessible = true
if(searchAutoComplete == null){
searchAutoComplete = field.get(search) as AutoCompleteTextView
}
searchAutoComplete?.threshold = 1
~~~
结果就可以做到返回后关闭键盘,清空搜索框,关闭搜索框。
- 介绍
- UI
- MaterialButton
- MaterialButtonToggleGroup
- 字体相关设置
- Material Design
- Toolbar
- 下拉刷新
- 可折叠式标题栏
- 悬浮按钮
- 滑动菜单DrawerLayout
- NavigationView
- 可交互提示
- CoordinatorLayout
- 卡片式布局
- 搜索框SearchView
- 自定义View
- 简单封装单选
- RecyclerView
- xml设置点击样式
- adb
- 连接真机
- 小技巧
- 通过字符串ID获取资源
- 自定义View组件
- 使用系统控件重新组合
- 旋转菜单
- 轮播图
- 下拉输入框
- 自定义VIew
- 图片组合的开关按钮
- 自定义ViewPager
- 联系人快速索引案例
- 使用ListView定义侧滑菜单
- 下拉粘黏效果
- 滑动冲突
- 滑动冲突之非同向冲突
- onMeasure
- 绘制字体
- 设置画笔Paint
- 贝赛尔曲线
- Invalidate和PostInvalidate
- super.onTouchEvent(event)?
- setShadowLayer与阴影效果
- Shader
- ImageView的scaleType属性
- 渐变
- LinearGradient
- 图像混合模式
- PorterDuffXfermode
- 橡皮擦效果
- Matrix
- 离屏绘制
- Canvas和图层
- Canvas简介
- Canvas中常用操作总结
- Shape
- 圆角属性
- Android常见动画
- Android动画简介
- View动画
- 自定义View动画
- View动画的特殊使用场景
- LayoutAnimation
- Activity的切换转场效果
- 属性动画
- 帧动画
- 属性动画监听
- 插值器和估值器
- 工具
- dp和px的转换
- 获取屏幕宽高
- JNI
- javah命令
- C和Java相互调用
- WebView
- Android Studio快捷键
- Bitmap和Drawable图像
- Bitmap简要介绍
- 图片缩放和裁剪效果
- 创建指定颜色的Bitmap图像
- Gradle本地仓库
- Gradle小技巧
- RxJava+Okhttp+Retrofit构建网络模块
- 服务器相关配置
- node环境配置
- 3D特效