Android 如何实现搜索功能:本地搜索?数据模型如何设计?数据如何展示和保存?

news2025/1/22 19:12:35

目录

  1. 效果图
  2. 为什么需要搜索功能
  3. 如何设计搜索本地的功能,如何维护呢?
  4. 总结

一、效果图

在这里插入图片描述在这里插入图片描述

二、为什么需要搜索功能

找一个选项,需要花非常多的时间,并且每次都需要指导客户在哪里,现在只要让他们搜索一下就可以。这也是模仿手机里面的设置功能来进行开发的。这些选项我是存储在本地的。参数太多,暂时还没考虑做到后台,当然,即使后面做到后台,也只是替换数据而已。

在这里插入图片描述

三、如何设计搜索本地的功能,如何维护呢?

我们可以看到效果图:

  1. 有开关类的、有输入类的。
  2. 有分类:系统设置、串口设置、功能开启等等。
  3. 有默认值,如:123456,默认是关的等等。

so,我们需要设计数据模型。

3.1 设计数据模型

大家可以思考一下为什么数据模型要这样设计?有默认值,有key,有nameId

/**
 * nameId:显示名称,这里存储的是rid,方便后续国际化
 * nameS:名称
 * category:分类
 * drfault:默认值
 * type:类型 1 多选项值 2 开关 3 输入类
 * mmkvName:保存key
 */
enum class OtherEnum(var nameId:Int,var nameS:String,var category:String,var drfault:Any,var type:Int,var mmkvName: String) {
    MDB(R.string.base_two_code,"通讯协议","串口设置","MDB", 2,MMKVName.MDB),
    CHANGE(R.string.base_nayax,"找零功能","找零设置",false, 1,MMKVName.CHANGE),
    CONTACT_NUMBER(R.string.leak_canary_test_class_name,"联系方式","系统设置","123456", 2,MMKVName.CONTACT_NUMBER),
}

3.2 Repo类

这里只是增加了一个要展示的数据,后面如果把数据放在了后端,也就只是替换这一部分就可以。

class OtherFragmentRepo @Inject constructor() : BaseRepository() {

    var arrayList: MutableList<OtherEnum> = Arrays.asList(//用于列表展示
        OtherEnum.MDB,
        OtherEnum.CHANGE,
        OtherEnum.CONTACT_NUMBER,
    )

}

如果有新增的数据,只需要在这里增加就可以了。这样也非常好维护,所以需要数据模型设置好来。

3.3 VM:主要是提供搜索的功能

  1. search方法其实就是遍历所有的集合元素,找到匹配的内容,存放到一个list里面进行展示,通过_readAllDataSuccess进行数据通知界面刷新。因为数据量较小,不到100个左右,所以这里使用的for循环遍历。
class OtherFragmentVM @Inject constructor(private val mRepo: OtherFragmentRepo) : BaseViewModel() {


    //搜索功能【通信设置、系统设置、功能开启等等】
    //1. 首先我们需要先添加我们的所有设置。【通过一个bean来存储,一个list来存储所有的】
    //bean:名称,分类,类型【开关类、输入类、多值类、音量调节】,value
    //举例:优惠券、功能开启、开关、false
    //2. 首先把所有的功能项拿到,进行遍历
    //3. 具体的数据展示:如何展示呢?navigation+fragment??【搜索的时候展示另外一个fragment,而不搜索的时候展示其中一个。】


    //1.拿到所有的通讯设置
    var list :ArrayList<OtherEnum> = ArrayList()

    private var _readAllDataSuccess = MutableLiveData<Int>()//是否读取所有数据成功
    val readAllDataSuccess: LiveData<Int> get() = _readAllDataSuccess

	//2.搜索功能
    fun search(searchText: CharSequence) {
        list.clear()
        val arrayList = mRepo.arrayList
        for (otherEnum in arrayList) {
            val stringRes = UiUtil.getStringRes(otherEnum.nameId).uppercase()
            if (stringRes.contains(searchText.toString().uppercase())) {
                list.add(otherEnum)
            }
        }
        _readAllDataSuccess.value = list.size
    }

}

3.4 Fragment

  1. 搜索功能:先判断是否输入内容为空,如果为空就提示用户。符合条件就调用search进行模糊匹配搜索。搜索到以后,就将默认显示的navigation进行GONE,将搜索结果进行VISIBLE
class OtherFragment : BaseFragment<BackstageFragmentOtherBinding, OtherFragmentVM>() {
    private  val TAG = "OtherFragment"

    override val mViewModel: OtherFragmentVM by viewModels()
    override fun createVB() = BackstageFragmentOtherBinding.inflate(layoutInflater)
    private var otherAdapter: OtherAdapter? = null


    override fun BackstageFragmentOtherBinding.initView() {
        Log.d(TAG, "OtherFragment initView: ")
        ivSearch.setOnClickListener {
            val searchText = etSearch.text.trim()
            Log.d(TAG, "searchText: "+searchText)
            if(""==searchText){
                ToastUtil.switchToastStyleToWarn("请输入内容后搜索")
                return@setOnClickListener
            }
            //进行数据的搜索
            mViewModel.search(searchText)
        }

        with(rvOtherSearch){
            //设置布局排列方式,默认垂直排列
            val gridLayoutManager: GridLayoutManager =
                GridLayoutManager(this@OtherFragment.context, 2, androidx.recyclerview.widget.GridLayoutManager.VERTICAL, false)
            layoutManager = gridLayoutManager

            mViewModel.list.add(OtherEnum.CHANGE)
            otherAdapter = OtherAdapter(mViewModel.list)
            otherAdapter!!.setItemListener(object : AdapterClickListener {
                override fun onClickListener(view: View?, position: Int, data: String?) {
                }
            })
            adapter = otherAdapter
        }
    }

    override fun initObserve() {
        observeLiveData(mViewModel.readAllDataSuccess,::searchResult)
    }

    fun searchResult(i: Int) {
        if (i==0) {
            ToastUtil.switchToastStyleToWarn("搜索结果为空")
            mBinding.rvOtherSearch.visibility = View.GONE
            mBinding.fcvOther.visibility = View.VISIBLE
            return
        }
        mBinding.rvOtherSearch.visibility = View.VISIBLE
        mBinding.fcvOther.visibility = View.GONE
        Log.d(TAG, "searchResult: "+mViewModel.list)
        otherAdapter?.setData(mViewModel.list)
        otherAdapter?.notifyDataSetChanged()

    }

    override fun initRequestData() {
    }

}

3.5 xml:UI应该如何编写呢?

UI方面:
(1)最简单的:一个输入框;一个搜索按钮;一个recycleview展示搜索结果;一个navigation+FragmentContainerView展示默认的内容,也就是不搜索的时候全部展示。
(2)扩展:1. 搜索历史,热门搜索,输入的时候补全提示。后面可以增加。我们先实现最简单的。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:background="@drawable/no_nav_bg"
    android:layout_height="match_parent">



    <net.lucode.hackware.magicindicator.MagicIndicator
        android:id="@+id/magic_other"
        android:layout_width="match_parent"
        android:layout_height="88dp"
        android:background="@drawable/backstage_shape_product_nav"
        android:paddingLeft="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/et_search"
        android:layout_width="1000dp"
        android:layout_height="80dp"
        android:background="@drawable/home_other_rectangle_background"
        android:paddingLeft="20dp"
        android:maxLines="1"
        android:inputType="text"
        android:layout_marginVertical="10dp"
        android:hint="@string/backstage_search_hint"
        android:textColor="#2E80DD"
        android:textSize="32sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.51"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/magic_other" />

    <ImageView
        android:id="@+id/iv_search"
        android:layout_width="90dp"
        android:layout_height="90dp"
        android:padding="10dp"
        android:layout_marginLeft="10dp"
        android:src="@drawable/search"
        app:layout_constraintBottom_toBottomOf="@+id/et_search"
        app:layout_constraintStart_toEndOf="@+id/et_search"
        app:layout_constraintTop_toTopOf="@+id/et_search" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fcv_other"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_search"
        app:navGraph="@navigation/backstage_other_nav" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_other_search"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_search" />

</androidx.constraintlayout.widget.ConstraintLayout>

3.6 Adapter:这里我们需要思考子项不一样应该如何设计,保存也数据逻辑也不一样!

  1. 子项不一样,我们应该如何处理呢?我们可以看到otherEnum里面有一个type属性,就是用于定义不同的布局的,如下:
  2. 那么开关类的保存,输入类的保存有应该如何呢?只需要使用不同的布局进行不同的逻辑进行处理就可以。如下:

class OtherAdapter(var productList: MutableList<OtherEnum>) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private val TAG = "HomeProductAdapter"

    var SWITCH_TYPE = 1
    var INPUT_TYPE = 2


    fun setItemListener(itemListener: AdapterClickListener?) {
        this.itemListener = itemListener
    }

    private var itemListener: AdapterClickListener? = null


    private var generalParamData: MutableList<String>? = null
	//开关类布局
    inner class MyViewHolder(binding: BackstageItemSystemSettingsBinding) :
        ViewHolder(binding.root) {
        private val mBinding = binding
        fun bind(otherEnum: OtherEnum) {
            mBinding.run {

                Log.d(TAG, "MyViewHolder bind: " + otherEnum)

                tvName.text = UiUtil.getStringRes(productList[layoutPosition].nameId)
                //开关类的数据保存
                rgSwitch.setOnCheckedChangeListener { group, checkedId ->
                    when (checkedId) {
                        R.id.rb_close -> {
                            SpUtils.putBoolean(otherEnum.mmkvName,false)
                        }
                        R.id.rb_open -> {
                            SpUtils.putBoolean(otherEnum.mmkvName,true)
                        }

                        else -> {}
                    }
                }
                var flag = SpUtils.getBoolean(otherEnum.mmkvName,otherEnum.drfault as Boolean)
                if(flag == true){
                    rbOpen.isChecked = true
                }else{
                    rbClose.isChecked = true
                }

            }
        }
    }

	//输入类的布局
    inner class InputViewHolder(binding: BackstageItemInputBinding) : ViewHolder(binding.root) {
        private val mBinding = binding
        fun bind(otherEnum: OtherEnum) {
            mBinding.run {

                Log.d(TAG, "InputViewHolder bind: " +otherEnum.mmkvName+":"+ SpUtils.contains(otherEnum.mmkvName))

                tvName.text = UiUtil.getStringRes(productList[layoutPosition].nameId)

                var defaultValue = SpUtils.getString(otherEnum.mmkvName,otherEnum.drfault as String)
                etValue.setText(defaultValue)
                //输入类的数据保存
                btnUpdate.setOnClickListener {
                    val value = etValue.text.trim().toString()
                    if(value==""){
                        ToastUtil.switchToastStyleToWarn("输入为空")
                        return@setOnClickListener
                    }
                    SpUtils.putString(otherEnum.mmkvName,value)
                    ToastUtil.switchToastStyleToSuccess("更新成功:"+otherEnum.mmkvName)
                }

            }
        }
    }

    override fun getItemViewType(position: Int): Int {
    	//不同的类型,使用不同的布局
        return productList[position].type
    }




    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        when (viewType) {
            SWITCH_TYPE -> {
                return MyViewHolder(
                    BackstageItemSystemSettingsBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false
                    )
                )
            }
            INPUT_TYPE -> {
                Log.d(TAG, "onCreateViewHolder: InputViewHolder:"+viewType)

                return InputViewHolder(
                    BackstageItemInputBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false
                    )
                )
            }

            else -> {
            }
        }
        return MyViewHolder(
            BackstageItemSystemSettingsBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun getItemCount(): Int {
        return productList.size
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is MyViewHolder) {
            holder.bind(productList.get(position))

        } else if (holder is InputViewHolder) {
            holder.bind(productList.get(position))
        }

        itemListener?.onClickListener(holder.itemView, position, null)


    }

    fun setData(productList: MutableList<OtherEnum>) {
        this.productList = productList
    }
}

四、总结:

其实搜索功能的重点在于数据模型的设计,还有apdater布局的设置。以前都是一个一个控件的增加,数据也可以直接就增加,所以维护很模仿,现在换成了recycleview,所以我们需要思考每一个item,他的数据应该如何展示,默认值是如何,在哪个分类,如何保存数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2148559.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

react hooks--useMemo

概述 相当于计算属性!!! useMemo实际的目的也是为了进行性能的优化。 ◼ 如何进行性能的优化呢&#xff1f;  useMemo返回的也是一个 memoized&#xff08;记忆的&#xff09; 值&#xff1b;  在依赖不变的情况下&#xff0c;多次定义的时候&#xff0c;返回的值是相同…

HarmonyOS Next开发----使用XComponent自定义绘制

XComponent组件作为一种绘制组件&#xff0c;通常用于满足用户复杂的自定义绘制需求&#xff0c;其主要有两种类型"surface和component。对于surface类型可以将相关数据传入XComponent单独拥有的NativeWindow来渲染画面。 由于上层UI是采用arkTS开发&#xff0c;那么想要…

Nexpose 6.6.269 发布下载,新增功能概览

Nexpose 6.6.269 for Linux & Windows - 漏洞扫描 Rapid7 Vulnerability Management, release Sep 11, 2024 请访问原文链接&#xff1a;https://sysin.org/blog/nexpose-6/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.or…

【Python爬虫系列】_024.MySQL数据库

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈

六、JSON

文章目录 1. 什么是JSON1.1 JSON 在 JavaScript 中的使用1.1.1 json 的定义1.1.2 json 的访问1.1.3 json 的两个常用方法 1.2、JSON 在 java 中的使用1.2.1、javaBean 和 json 的互转1.2.2、List 和 json 的互转1.2.3、map 和 json 的互转 1. 什么是JSON 1.1 JSON 在 JavaScrip…

8.1差分边缘检测

基本概念 差分边缘检测是一种图像处理技术&#xff0c;用于检测图像中的边缘。边缘是指图像中灰度值发生显著变化的区域。差分边缘检测通常通过计算图像的梯度来实现&#xff0c;梯度反映了灰度值的变化率。在OpenCV中&#xff0c;可以使用不同的算子来检测不同方向的边缘&…

PDP 和 ICE 图的终极指南

部分依赖图和单独条件期望图背后的直觉、数学和代码(R 和 Python) PDP 和 ICE 图都可以帮助我们了解我们的模型如何做出预测。 使用个人显示面板我们可以将模型特征和目标变量之间的关系可视化。它们可以告诉我们某种关系是线性的、非线性的还是没有关系。 同样,当特征之间…

006.MySQL_查询数据

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

FPGA随记-二进制转格雷码

反射二进制码&#xff08;RBC&#xff09;&#xff0c;也称为反射二进制&#xff08;RB&#xff09;或格雷码&#xff08;Gray code&#xff09;&#xff0c;得名于Frank Gray&#xff0c;是二进制数制的一种排列方式&#xff0c;使得连续两个值之间仅有一个比特&#xff08;二…

EmguCV学习笔记 VB.Net 12.1 二维码解析

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

算法.图论-建图/拓扑排序及其拓展

文章目录 建图的三种方式邻接矩阵邻接表链式前向星 拓扑排序拓扑排序基础原理介绍拓扑排序步骤解析拓扑排序模板leetcode-课程表 拓扑排序拓展食物链计数喧闹与富有并行课程 建图的三种方式 我们建图的三种方式分别是邻接矩阵, 邻接矩阵, 链式前向星 邻接矩阵 假设我们的点的…

自定义复杂AntV/G6案例

一、效果图 二、源码 /** * * Author: me * CreatDate: 2024-08-22 * * Description: 复杂G6案例 * */ <template><div class"moreG6-wapper"><div id"graphContainer" ref"graphRef" class"graph-content"></d…

计算机毕业设计污染物文献共享数据库管理系统网站开发与实现

计算机毕业设计&#xff1a;污染物文献共享数据库管理系统网站开发与实现 1. 项目背景 随着环境问题日益严峻&#xff0c;对污染物的研究变得尤为重要。然而&#xff0c;在学术界和工业界之间存在着信息孤岛现象&#xff0c;即大量的研究成果被分散在不同的数据…

[Redis][String][上]详细讲解

目录 0.前言1.常见命令1.SET2.GET3.MSET && MGET4.SETNX && SETXX 2.计数命令1.INCR2.INCRBY3.DECR4.DECYBY5.INCRBYFLOAT6.注意 0.前言 字符串类型是Redis最基础的数据类型&#xff0c;关于字符串需要特别注意&#xff1a; Redis中所有的键的类型都是字符串类…

【Elasticsearch系列十四】Elasticsearch

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

大数据概念与价值

文章目录 引言大数据的概念高德纳咨询公司的定义麦肯锡全球研究所的定义什么是大数据&#xff1f; 大数据的特征Volume&#xff08;体积&#xff09;Variety&#xff08;种类&#xff09;Velocity&#xff08;速度&#xff09;Value&#xff08;价值&#xff09;Veracity&#…

Apache Hudi现代数据湖核心技术概论

1. 什么是 Apache Hudi 1.1 简介 Apache Hudi (Hadoop Upserts Deletes and Incrementals) 是一个开源的数据湖框架&#xff0c;旨在提供高效的数据管理和数据更新功能。它允许在大数据平台上执行诸如数据插入、更新和删除操作&#xff0c;同时支持增量式数据处理。Hudi 最初…

React18入门教程

React介绍 React由Meta公司开发&#xff0c;是一个用于 构建Web和原生交互界面的库 React的优势 相较于传统基于DOM开发的优势 组件化的开发方式 不错的性能 相较于其它前端框架的优势 丰富的生态 跨平台支持 React的市场情况 全球最流行&#xff0c;大厂必备 开发环境…

EmguCV学习笔记 C# 12.2 WeChatQRCode

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

Vue.js的前端框架有哪些?

Vue.js 是一款流行的前端 JavaScript 框架&#xff0c;用于构建单页面应用&#xff08;SPA&#xff09;。除了 Vue.js 本身&#xff0c;还有许多基于 Vue.js 的前端框架和 UI 库&#xff0c;它们提供了更多的功能和组件&#xff0c;以便开发者能够快速构建应用程序。以下是一些…