如何在Android中实现搜索视图

168 阅读7分钟

在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

它们应该看起来像这样。

packages-image

这有助于组织项目,使相关文件属于同一类别。这样一来,就可以迅速建立健全的、具有生产质量的应用程序。

说到这里,把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 ,分别显示一个人的姓名和年龄。实际的数据将在运行时被获取和绑定。

预览。

row-item-image

为了预览运行时在RecyclerView中的显示情况,在RecyclerView标签中添加以下属性。

<androidx.recyclerview.widget.RecyclerView
    ...

    tools:listitem="@layout/row_item" />

预览。

recyclerview-preview-image

F).创建数据模型

模型是一个独立的组件,负责处理一个应用程序的数据。在我们的案例中,我们需要使用一个数据列表,其类型为PersonPerson 数据类持有两个不可置空的变量,姓名和年龄。

继续,在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)

    ...
}

这声明了我们以后要使用的私有全局变量。peoplematchedPeople 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。有点花哨的是,emptynull 并不是同义词!

长度为0的被认为是空的而不是空的。这意味着,当查询文本的长度从1变为0时,搜索函数将被调用。

问题是,没有任何东西会被过滤掉,这将导致100%的匹配,因此将返回一个原始列表的克隆。

这并不总是理想的,因为它可能导致极端的内存消耗。

这可以通过以下两种方法来避免。

  • 限制搜索功能,只在查询长度大于0时运行。
  • 通过保持其主体为空来禁用onQueryTextChange() 方法。

最后,这个应用程序应该是这样的。

results-image

结论

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