Android 简单实现联系人列表+字母索引联动效果

请添加图片描述
效果如上图。

Main Ideas

  1. 左右两个列表
  2. 左列表展示人员数据,含有姓氏首字母的 header item
  3. 右列表是一个全由姓氏首字母组成的索引列表,点击某个item,展示一个气泡组件(它会自动延时关闭), 左列表滚动并显示与点击的索引列表item 相同的 header
  4. 搜索动作后,匹配人员名称中是否包含搜索字符串,或搜索字符串为单一字符时,是否能匹配到某个首字母;而且滚动后,左右列表都能滚动至对应 Header 或索引处。

Steps

S1. 汉字拼音转换

先找到了 Pinyin4J 这个库;后来发现没有对多音字姓氏 的处理;之后找到 TinyPinyin ,它可以自建字典,指明多音汉字(作为姓氏时)的指定读音。

fun initChinaNamesDictMap() {// 增加 多音字 姓氏拼音词典Pinyin.init(Pinyin.newConfig().with(object : PinyinMapDict() {override fun mapping(): MutableMap<String, Array<String>> {val map = hashMapOf<String, Array<String>>()map["解"] = arrayOf("XIE")map["单"] = arrayOf("SHAN")map["仇"] = arrayOf("QIU")map["区"] = arrayOf("OU")map["查"] = arrayOf("ZHA")map["曾"] = arrayOf("ZENG")map["尉"] = arrayOf("YU")map["折"] = arrayOf("SHE")map["盖"] = arrayOf("GE")map["乐"] = arrayOf("YUE")map["种"] = arrayOf("CHONG")map["员"] = arrayOf("YUN")map["繁"] = arrayOf("PO")map["句"] = arrayOf("GOU")map["牟"] = arrayOf("MU") // mù、móu、mūmap["覃"] = arrayOf("QIN")map["翟"] = arrayOf("ZHAI")return map}}))
}

// Pinyin.toPinyin(char) 方法不使用自定义字典
而使用 Pinyin.toPinyin(nameText.first().toString(), ",").first() // 将 nameText 的首字符,转为拼音,并取拼音首字母


S2. 数据bean 和 item view

对原有数据bean 增加 属性:

data class DriverInfo(var Name: String?, // 人名var isHeader: Boolean, // 是否是 header itemvar headerPinyinText: String? // header item view 的拼音首字母
)

左列表的 item view,当数据是 header时,仅显示 header textView (下图红色的文字),否则仅显示 item textView (下图黑色的文字):
在这里插入图片描述
右列表的 item view,更简单了,就只含一个 TextView 。


S3. 处理数据源

这一步要做的是:转拼音;拼音排序;设置 isHeader、headerPinyinText 属性;构建新的数据源集合 …

// 返回新的数据源
fun getPinyinHeaderList(list: List<DriverInfo>): List<DriverInfo> {list.forEachIndexed { index, driverInfo ->if (driverInfo.Name.isNullOrEmpty()) return@forEachIndexed// Pinyin.toPinyin(char) 方法不使用自定义字典val header = Pinyin.toPinyin(driverInfo.Name!!.first().toString(), ",").first()driverInfo.headerPinyinText = header.toString()}// 以拼音首字母排序(list as MutableList).sortBy { it.headerPinyinText }val newer = mutableListOf<DriverInfo>()list.forEachIndexed { index, driverInfo ->val newHeader = index == 0 || driverInfo.headerPinyinText != list[index - 1].headerPinyinTextif (newHeader) {newer.add(DriverInfo(null, true, driverInfo.headerPinyinText))}newer.add(driverInfo)}return newer
}

当左侧列表有了数据源之后,那右侧的也就可以有了:将所有 header item 的 headerPinyinText 取出,并转为 新的 集合。

val indexList = driverList?.filter { it.isHeader }?.map { it.headerPinyinText ?: ""} ?: arrayListOf()
indexAdapter.updateData(indexList)

S4. Adapter 的点击事件

这里省略设置 adapter 、LinearLayoutManager 等 样板代码 …

设定:左侧的适配器名为 adapter, 右侧字母索引名为 indexAdapter;左侧 RV 名为 recyclerView,右侧的名为了 rvIndex。

  1. 左侧的点击事件,不会触发右侧的联动
override fun onItemClick(position: Int, data: DriverInfo) {if (data.isHeader) return// 如果是点击 item, 做自己的业务
}
  1. 右侧点击事件,会触发左侧的滚动;还可以触发气泡 view 的显示,甚至自身的滚动
override fun onItemClick(position: Int, data: DriverInfo) {val item = adapter.dataset.first { it.isHeader && it.headerPinyinText == data }val index = adapter.dataset.indexOf(item)
//  mBind.recyclerView.scrollToPosition(index)(mBind.recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(index, 0)//  mBind.rvIndex.scrollToPosition(position)val rightIndex = indexAdapter.dataset.indexOf(item.headerPinyinText)(mBind.rvIndex.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(rightIndex, 0)showBubbleView(position, data)
}

一开始用 rv#scrollToPosition(),发现也能滚动。但是呢,指定 position 之后还有其它内容时,且该位置之前也有很多的内容;点击后,仅将 该位置 item ,显示在页面可见项的最后一个位置。 改成 LinearLayoutManager#scrollToPositionWithOffset()后,更符合预期。


S5. 气泡 view

<widget.BubbleViewandroid:id="@+id/bubbleView"android:layout_width="100dp"android:layout_height="100dp"android:visibility="invisible"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="@id/rv_index" />

设置 文本;获取点击的 item view;根据 item view 的位置 进行显示设置;延迟1秒 隐藏气泡:

private fun showBubbleView(position: Int, data: String) {lifecycleScope.launch {mBind.bubbleView.setText(data)val itemView = mBind.rvIndex.findViewHolderForAdapterPosition(position)?.itemView ?: return@launchmBind.bubbleView.showAtLocation(itemView)delay(1000)mBind.bubbleView.visibility = View.GONE}
}

自定义 气泡 view:

/*** desc:    指定view左侧显示的气泡view* author:  stone* email:   aa86799@163.com* time:    2024/9/27 18:22*/
class BubbleView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) {  private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {  color = resources.getColor(R.color.syscolor)style = Paint.Style.FILL  }  private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {  color = Color.WHITE  textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics)textAlign = Paint.Align.CENTER  }  private val path = Path()  private var text: String = ""  fun setText(text: String) {  this.text = text  invalidate()  }  override fun onDraw(canvas: Canvas) {  super.onDraw(canvas)  // 绘制贝塞尔曲线气泡  path.reset()  path.moveTo(width / 2f, height.toFloat())  path.quadTo(width.toFloat(), height.toFloat(), width.toFloat(), height / 2f)  path.quadTo(width.toFloat(), 0f, width / 2f, 0f)  path.quadTo(0f, 0f, 0f, height / 2f)  path.quadTo(0f, height.toFloat(), width / 2f, height.toFloat())  path.close()  canvas.drawPath(path, paint)  // 绘制文本  canvas.drawText(text, width / 2f, height / 2f + textPaint.textSize / 3, textPaint)}fun showAtLocation(view: View) {view.let {val location = IntArray(2)it.getLocationOnScreen(location)// 设置气泡的位置x = location[0] - width.toFloat() - 10y = location[1] - abs(height - it.height) / 2f - getStatusBarHeight()visibility = View.VISIBLE}}private fun getStatusBarHeight(): Int {var result = 0val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")if (resourceId > 0) {result = resources.getDimensionPixelSize(resourceId)}return result}
}

S6. 搜索实现

  • 空白输入字符时,左侧返回全数据源;右侧列表跟随左侧变化。
  • 有输入时,根据全数据源,获取 匹配的子数据源;右侧列表跟随左侧变化。
fun filterTextToNewHeaderList(list: List<DriverInfo>?, text: String): List<DriverInfo>? {// 如果item 的拼音和 查询字符 相同;或者,非 header 时,名称包含查询字符val filterList = list?.filter { it.headerPinyinText?.equals(text, true) == true|| !it.isHeader && it.Name?.contains(text, ignoreCase = true) == true }if (filterList.isNullOrEmpty()) {return null}val newer = mutableListOf<DriverInfo>()filterList.forEachIndexed { index, driverInfo ->val newHeader = (index == 0 || driverInfo.headerPinyinText != filterList[index - 1].headerPinyinText) && !driverInfo.isHeaderif (newHeader) {newer.add(DriverInfo(null, true, driverInfo.headerPinyinText))}newer.add(driverInfo)}return newer
}// 搜索点击
mBind.tvSearch.setOnClickListener {val beanList: List<DriverInfo>? = adapter.filterTextToNewHeaderList(driverList, text)adapter.updateData(beanList)val indexList = beanList.filter { it.isHeader }.map { it.headerPinyinText ?: ""}indexAdapter.updateData(indexList)
}

整体核心实现都贴出来了,如果有什么bug,欢迎回复

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Elasticsearch 8.16 和 JDK 23 中的语言环境变化

作者&#xff1a;来自 Elastic Simon Cooper 随着 JDK 23 即将发布&#xff0c;语言环境信息中有一些重大变化&#xff0c;这将影响 Elasticsearch 以及你提取和格式化日期时间数据的方式。首先&#xff0c;介绍一些背景知识。 什么是语言环境&#xff1f; 每次 Java 程序需要…

理解Matplotlib构图组成

介绍 Matplotlib 是 Python 中最流行的数据可视化库之一。它提供了一系列丰富的工具&#xff0c;可以绘制高度自定义且适用于各种应用场景的图表。无论你是数据科学家、工程师&#xff0c;还是需要处理数据图形表示的任何人&#xff0c;理解如何操作和定制 Matplotlib 中的图表…

三、数据链路层(上)

目录 3.1数据链路层概述 3.1.1术语 3.1.2功能 3.2封装成帧和透明传输 3.2.1封装成帧 ①字符计数法 ②字符&#xff08;节&#xff09;填充法 ③零比特填充法 ④违规编码法 3.2.2透明传输 3.2.3差错控制 差错原因 检错编码 奇偶校验 ☆循环冗余码CRC 例题 纠错…

骨架屏 (懒加载优化)

骨架屏 &#xff08;懒加载优化&#xff09; 即便通过 Webpack 的按需加载、CDN 静态资源缓存 和 代码分割 等技术来减少首屏的代码体积&#xff0c;首屏加载时的白屏时间&#xff08;也称为首屏等待时间&#xff09;仍然可能存在&#xff0c;尤其在网络条件较差或页面内容复杂…

MongoDB 快速入门+单机部署(附带脚本)

目录 介绍 体系结构 数据模型 BSON BSON 数据类型 特点 高性能 高可用 高扩展 丰富的查询支持 其他特点 部署 单机部署 普通安装 脚本安装 Docker Compose 安装 卸载 停止 MongoDB 删除包 删除数据目录 参考&#xff1a; https://docs.mongoing.com/ 介绍…

python全栈学习记录(二十一)类的继承、派生、组合

类的继承、派生、组合 文章目录 类的继承、派生、组合一、类的继承二、派生三、组合 一、类的继承 继承是一种新建类的方式&#xff0c;新建的类称为子类&#xff0c;被继承的类称为父类。 继承的特性是&#xff1a;子类会遗传父类的属性&#xff08;继承是类与类之间的关系&a…

2024年研究生数学建模“华为杯”E题——肘部法则、k-means聚类、目标检测(python)、ARIMA、逻辑回归、混淆矩阵(附:目标检测代码)

文章目录 一、情况介绍二、思路情况二、代码展示三、感受 一、情况介绍 前几天也是参加了研究生数学建模竞赛&#xff08;也就是华为杯&#xff09;&#xff0c;也是和本校的两个数学学院的朋友在网上组的队伍。昨天&#xff08;9.25&#xff09;通宵干完论文&#xff08;一条…

Prompt 初级版:构建高效对话的基础指南

Prompt 初级版&#xff1a;构建高效对话的基础指南 文章目录 Prompt 初级版&#xff1a;构建高效对话的基础指南一 “标准”提示二 角色提示三 多范例提示四 组合提示五 规范化提示 本文介绍了提示词的基础概念与不同类型&#xff0c;帮助用户更好地理解如何在对话中构建有效的…

Pytorch实现玉米基因表达量预测模型

一、实验要求 通过搭建残差卷积网络&#xff0c;实现对玉米基因表达量的预测 二、实验目的 理解基因表达量预测问题&#xff1a;基因表达预测是生物信息学和基因组学领域中的重要任务之一&#xff0c;促进学科交叉融合。熟悉深度学习框架PyTorch&#xff1a;通过实现基因表达量…

css 数字比汉字要靠上

这个问题通常是由于数字字体的下排的问题造成的&#xff0c;也就是数字的底部边缘位置比汉字的顶部边缘位置更靠下。为了解决这个问题&#xff0c;可以尝试以下几种方法&#xff1a; 使用CSS的vertical-align属性来调整对齐方式。例如&#xff0c;可以将数字的对齐方式设置为to…

Linux高级编程_27_系统调用

文章目录 系统调用函数分类系统编程概述系统调用概述**类UNIX系统的软件层次** 用户态和内核态系统调用与库函数的关系文件操作符概述文件磁盘权限 系统调用之文件操作open:打开文件close:关闭文件write:写入read:读取 文件状态fcntl 函数stat 函数 st_mode的值示例 1&#xff…

【优选算法之队列+宽搜/优先级队列】No.14--- 经典队列+宽搜/优先级队列算法

文章目录 前言一、队列宽搜示例&#xff1a;1.1 N 叉树的层序遍历1.2 ⼆叉树的锯⻮形层序遍历1.3 ⼆叉树最⼤宽度1.4 在每个树⾏中找最⼤值 二、优先级队列&#xff08;堆&#xff09;示例&#xff1a;2.1 最后⼀块⽯头的重量2.2 数据流中的第 K ⼤元素2.3 前 K 个⾼频单词2.4 …

数造科技入选中国信通院《高质量数字化转型产品及服务全景图》三大板块

9月24日&#xff0c;2024大模型数字生态发展大会暨“铸基计划”年中会议在北京召开。会上&#xff0c;中国信通院发布了2024年《高质量数字化转型产品及服务全景图&#xff08;上半年度&#xff09;》和《高质量数字化转型技术解决方案&#xff08;上半年度&#xff09;》等多项…

网络编程篇:UDP协议

一 UDP协议格式 16位源端口号&#xff1a;表示数据从哪里来。16位目的端口号&#xff1a;表示数据要到哪里去。16位UDP长度&#xff1a;表示整个数据报&#xff08;UDP首部UDP数据&#xff09;的长度。16位UDP检验和&#xff1a;如果UDP报文的检验和出错&#xff0c;就会直接将…

【Kubernetes】常见面试题汇总(五十三)

目录 118. pod 状态为 ErrlmagePull &#xff1f; 119.探测存活 pod 状态为 CrashLoopBackOff &#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。…

MongoDB聚合操作及索引底层原理

目录 链接:https://note.youdao.com/ynoteshare/index.html?id=50fdb657a9b06950fa255a82555b44a6&type=note&_time=1727951783296 本节课的内容: 聚合操作: 聚合管道操作: ​编辑 $match 进行文档筛选 ​编辑 将筛选和投影结合使用: ​编辑 多条件匹配: …

Springboot + netty + rabbitmq + myBatis

目录 0.为什么用消息队列1.代码文件创建结构2.pom.xml文件3.三个配置文件开发和生产环境4.Rabbitmq 基础配置类 TtlQueueConfig5.建立netty服务器 rabbitmq消息生产者6.建立常规队列的消费者 Consumer7.建立死信队列的消费者 DeadLetterConsumer8.建立mapper.xml文件9.建立map…

King3399 SDK(ubuntu文件系统)编译简明教程

该文章仅供参考&#xff0c;编写人不对任务实验设备、人员及测量结果负责&#xff01;&#xff01;&#xff01; 0 引言 文章主要介绍King3399&#xff08;瑞芯微rk3399开发板&#xff0c;荣品&#xff09;官方SDK&#xff08;Ubuntu文件系统&#xff09;编译过程&#xff0c…

【本地免费】SimpleTex 图像识别latex公式

文章目录 相关教程相关文献安装教程 由于mathpix开始收费了&#xff0c;于是本文将介绍一款目前本地免费的SimpleTex工具 相关教程 【超详细安装教程】LaTeX-OCR 图像识别latex公式&#xff08;开源免费&#xff09;_latex图片识别-CSDN博客 相关文献 SimpleTex主页——致力…

Elasticsearch使用Easy-Es + RestHighLevelClient实现深度分页跳页

注意&#xff01;&#xff01;&#xff01;博主只在测试环境试了一下&#xff0c;没有发到生产环境跑。因为代码还没写完客户说不用弄了( •̩̩̩̩&#xff3f;•̩̩̩̩ ) 也好&#xff0c;少个功能少点BUG 使用from size的时候发现存在max_result_window10000的限制&…