在Android中实现搜索视图
在处理大量的数据时,要找到你想要的那块数据有时会很吃力。这个问题可以通过在提供的keyword(s) ,过滤出必要的数据来解决。这就是SearchView 的用处。
A).简介
SearchView是一个Android widget,它简化了在一堆数据中寻找匹配的过程。这通过节省时间改善了用户的体验。
B).目标
在本教程中,我们将学习如何使用RecyclerView来实现Android中的SearchView。本项目中使用的样本数据将被明确生成,用于演示目的。
C).前提条件
要完成本教程,你需要。
- 在你的机器上安装了[Android Studio]IDE。
- 在创建Android应用程序时要灵巧地使用。
- [Kotlin]。
- [限制性布局]。
- [数据绑定]。
D).开始工作
首先,启动Android Studio并创建一个Empty Activity 项目。如果你熟悉创建项目和设置RecyclerView,你可以跳到step (H) 。
i).项目结构
当构建过程完成后,展开app/java/"package-name" 下的包,并创建三个包,分别命名为:adapter,model, 和view 。
它们应该看起来像这样。

这有助于组织项目,使相关文件属于同一类别。这样一来,就可以迅速建立健全的、具有生产质量的应用程序。
说到这里,把MainActivity.kt 文件拖到view 包里。
ii).设置插件
由于我们要使用dataBinding,打开build.gradle(Module) 文件,在plugins范围内添加以下插件。
plugins {
id 'kotlin-kapt'
}
这个插件带有负责dataBinding的实用程序,需要启用它,这样才能使用。在Android块内添加以下内容(在同一个文件中)。
android {
...
buildFeatures{
dataBinding true
}
}
同步项目并等待构建完成。
E).创建用户界面
我们在用户界面中只需要两个视图,一个SearchView和一个RecyclerView。将下面的代码粘贴到activity_main.xml 文件中来创建它们。
<SearchView
android:id="@+id/searchView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#55F5F5F5"
android:elevation="1dp"
android:queryHint="Search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginVertical="10dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/searchView"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@id/searchView"
app:layout_constraintTop_toBottomOf="@+id/searchView" />
记住要用<layout> </layout> 标签包围根视图组,以便为这个XML文件生成一个绑定类。
接下来,我们需要创建一个项目,将其填充到RecyclerView中。创建一个名为row_item.xml 的xmllayout 文件,并在其中粘贴以下代码。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:background="#90F5F5F5"
android:paddingVertical="10dp">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.15"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Eric gacoki" />
<TextView
android:id="@+id/age"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="@+id/name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.75"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/name"
app:layout_constraintVertical_bias="0.0"
tools:text="19" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
这将创建两个TextViews ,分别显示一个人的姓名和年龄。实际的数据将在运行时被获取和绑定。
预览。

为了预览运行时在RecyclerView中的显示情况,在RecyclerView标签中添加以下属性。
<androidx.recyclerview.widget.RecyclerView
...
tools:listitem="@layout/row_item" />
预览。

F).创建数据模型
模型是一个独立的组件,负责处理一个应用程序的数据。在我们的案例中,我们需要使用一个数据列表,其类型为Person 。Person 数据类持有两个不可置空的变量,姓名和年龄。
继续,在model包内创建一个数据类,并在包名下面粘贴以下代码。
data class Person(
val name: String,
val age: Int
)
G).设置RecyclerView适配器
RecyclerView是一个灵活的widget,可以根据其类型绑定各种数据。然而,没有特定类型的数据应该提供给它。一个适配器可以帮助我们准备好RecyclerView来处理给定的数据。
在适配器包内(在步骤D(i)创建),创建一个名为PersonAdapter 的Kotlin类。这个类将扩展RecyclerView的适配器,并接受一个Person (上一步创建的数据类)类型的ArrayList 。
与List不同,ArrayList支持在运行时添加和删除元素,这就是我们使用它的原因。
复制并粘贴下面的代码到PersonAdapter.kt 文件中。
class PersonAdapter(
var list: ArrayList<Person>
) : RecyclerView.Adapter<PersonAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
RowItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(
list[position]
)
}
override fun getItemCount(): Int = list.size
inner class ViewHolder(private var item: RowItemBinding) : RecyclerView.ViewHolder(item.root) {
fun bind(person: Person) {
item.name.text = person.name.trim()
item.age.text = person.age.toString().trim()
}
}
}
按Alt+Enter ,以修复丢失的导入。上面的代码使RecyclerView能够绑定视图,并按照ArrayList中出现的顺序向其填充数据。
H).线性搜索算法如何工作
就像任何其他任务一样,搜索是一个需要执行一系列步骤的过程。在本教程中,我们将利用Linear search algorithm 。
首先设置两个列表,一个包含所考虑的所有数据,而另一个则留空。因此,我们将循环浏览第一个列表中的元素,将每个内容与给定的关键词进行比较。
在这种情况下,关键词是用户在SearchView中输入的text 。如果找到了匹配的内容,包含匹配内容的元素就会被克隆到第二个列表中,RecyclerView也会被更新为新的列表。
当处理一个相对较大的列表或一个未知大小的列表时,建议每次发现匹配时都更新RecyclerView。
否则,由于内存消耗过大,应用程序可能会陷入ANR (App Not Responding)的情况。另外,出于同样的原因,新创建的列表在不需要的时候应该被清除。
I).实现搜索算法
继续,打开MainActivity.kt 文件,如图所示依次粘贴以下代码。
i).就在onCreate函数的前面
class MainActivity : AppCompatActivity() {
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
private var people: ArrayList<Person> = arrayListOf()
private var matchedPeople: ArrayList<Person> = arrayListOf()
private var personAdapter: PersonAdapter = PersonAdapter(people)
...
}
这声明了我们以后要使用的私有全局变量。people 和matchedPeople ArrayLists 将分别保存所有的数据和匹配的数据,如step (H) 所解释。
ii).在onCreate函数内部
这是我们绑定用户界面和应用程序的逻辑的地方。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
}
iii).初始化阶段
在这里,我们需要初始化RecyclerView并准备SearchView。要做到这一点,请将以下代码粘贴在onCreate() 。
private fun initRecyclerView() {
people = arrayListOf(
Person("Eric G", 19),
Person("Reen", 19),
Person("Jeff", 21),
Person("Geoffrey", 19),
Person("Lorem ipsum", 35),
Person("Paul N", 23),
Person("Diana", 20),
Person("Peter", 24),
Person("Amos", 41),
Person("Steve", 17),
)
personAdapter = PersonAdapter(people).also {
binding.recyclerView.adapter = it
binding.recyclerView.adapter!!.notifyDataSetChanged()
}
binding.searchView.isSubmitButtonEnabled = true
}
在这个函数中,我们已经用10个类型的实体初始化了people 列表,Person 。这个列表的大小可以根据需要而定。
private fun performSearch() {
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
search(query)
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
search(newText)
return true
}
})
}
记住要在onCreate() 方法中调用上述两个函数,如下面的片段所示。
override fun onCreate(savedInstanceState: Bundle?) {
...
initRecyclerView()
performSearch()
}
iv).了解 performSearch 函数背后的逻辑
你是否一直想知道SearchView是如何知道用户输入了什么?好吧,SearchView有一个内置的函数,setOnQueryTextListener() ,接受一个类型为OnQueryTextListener 的对象作为参数。
这个对象是一个interface ,是SearchView 类的成员,包含两个成员函数,onQueryTextSubmit() 和onQueryTextChange() 。
这两个接受一个类型为String的可空参数,在使用接口时必须使用override 关键字来实现。
顾名思义,onQueryTextSubmit() 在用户点击SearchView的提交按钮时被调用。另一方面,每当SearchView中的文本发生变化时,就会调用onQueryTextChange() 。
这种变化可能是由于增加或删除了一些字符。总而言之,这两个函数会调用另一个函数,即下面讨论的search() 。
private fun search(text: String?) {
matchedPeople = arrayListOf()
text?.let {
people.forEach { person ->
if (person.name.contains(text, true) ||
person.age.toString().contains(text, true)
) {
matchedPeople.add(person)
}
}
updateRecyclerView()
if (matchedPeople.isEmpty()) {
Toast.makeText(this, "No match found!", Toast.LENGTH_SHORT).show()
}
updateRecyclerView()
}
}
首先,matchedPeople 列表被清空或设置为空的arrayList,以避免累积之前的搜索结果。如果参数text 的参数不为空,则对people 列表中的每个元素进行循环,检查该人的姓名或年龄是否包含text (查询)。
默认情况下,contains() 函数对查询中字符的大小写和顺序是敏感的。当发现匹配时,当前的人被添加到一个新的列表中,如上面step (H) 所述。如果没有找到匹配的人,就会显示一个吐司,表明这一点。
在我们的方案中,列表相对较小,因此适合在循环之后更新RecyclerView。否则,我们需要在每次发现匹配时调用updateRecyclerView() 函数。
v). updateRecyclerView()解释
private fun updateRecyclerView() {
binding.recyclerView.apply {
personAdapter.list = matchedPeople
personAdapter.notifyDataSetChanged()
}
}
这个函数负责在检查完成后更新RecyclerView。有点花哨的是,empty 和null 并不是同义词!
长度为0的被认为是空的而不是空的。这意味着,当查询文本的长度从1变为0时,搜索函数将被调用。
问题是,没有任何东西会被过滤掉,这将导致100%的匹配,因此将返回一个原始列表的克隆。
这并不总是理想的,因为它可能导致极端的内存消耗。
这可以通过以下两种方法来避免。
- 限制搜索功能,只在查询长度大于0时运行。
- 通过保持其主体为空来禁用
onQueryTextChange()方法。
最后,这个应用程序应该是这样的。

结论
在本教程中,我们已经学会了如何在Android中创建和使用SearchView来过滤RecyclerView中的数据。这是一个提高应用程序整体性能的好方法。