当前位置: 首页 > news >正文

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,欢迎回复

相关文章:

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

效果如上图。 Main Ideas 左右两个列表左列表展示人员数据&#xff0c;含有姓氏首字母的 header item右列表是一个全由姓氏首字母组成的索引列表&#xff0c;点击某个item&#xff0c;展示一个气泡组件(它会自动延时关闭)&#xff0c; 左列表滚动并显示与点击的索引列表item …...

自动驾驶-问题笔记-待解决

参考线的平滑方法 参考线平滑算法主要有三种&#xff1a; 离散点平滑&#xff1b;螺旋曲线平滑&#xff1b;多项式平滑&#xff1b; 参考链接&#xff1a;参考线平滑 对于平滑方法&#xff0c;一直不太理解平滑、拟合以及滤波三者的作用与区别&#xff1b; 规划的起点&#x…...

在掌控板中加载人教版信息科技教学指南中的educore库

掌控板中加载educore库 人教信息科技数字资源平台&#xff08;https://ebook.mypep.cn/free&#xff09;中的《信息科技教学指南硬件编程代码说明》文件中提到“本程序说明主要供教学参考。需要可编程主控板须支持运行MicroPython 脚本程序。希望有更多的主控板在固件中支持ed…...

关于CSS Grid布局

关于CSS Grid布局 实际效果参考 参考代码 <template><view class"baseInfo"><up-image class"cover" height"160rpx" width"120rpx" :src"bookInfo.cover"><template #error><view style"…...

初始爬虫12(反爬与反反爬)

学到这里&#xff0c;已经可以开始实战项目了&#xff0c;多去爬虫&#xff0c;了解熟悉反爬&#xff0c;然后自己总结出一套方法怎么做。 1.服务器反爬的原因 服务器反爬的原因 总结&#xff1a; 1.爬虫占总PV较高&#xff0c;浪费资源 2.资源被批量抓走&#xff0c;丧失竞争力…...

成像基础 -- 最大对焦清晰的物距计算

最大对焦清晰的物距计算 1. 基本概念 最大对焦清晰的物距通常与景深&#xff08;Depth of Field, DOF&#xff09;相关&#xff0c;尤其是无穷远处的物体可以被清晰对焦到的距离&#xff0c;称为超焦距&#xff08;Hyperfocal Distance&#xff09;。通过计算超焦距&#xff…...

win10服务器启动且未登录时自动启动程序

场景&#xff1a;公司服务器安装了几个程序&#xff0c;当服务器断电重启之后希望程序能自动打开&#xff0c;而不需要手动登录服务器打开。 因为软件是自己开发的所以安全方面这里没有考虑。 1.打开服务器管理器&#xff0c;点击工具&#xff0c;选择任务计划程序 2.在任务计…...

算法专题四: 前缀和

目录 1. 前缀和2. 二维前缀和3. 寻找数组的中心下标4. 除自身以外数组的乘积5. 和为k的子数组6. 和可被K整除的子数组7. 连续数组8. 矩阵区域和 博客主页:酷酷学!!! 感谢关注~ 1. 前缀和 算法思路: 根据题意, 创建一个前缀和数组, dp[i] dp[i -1] arr[i], 再使用前缀和数组,…...

【Linux】基础IO(文件描述符、缓冲区、重定向)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/qinjh_/category_12625432.html 目录 前言 C文件IO相关操作 系统文件I/O open open函数返回值 文件描述符fd re…...

一篇文章快速学会docker容器技术

目录 一、Docker简介及部署方法 1.1Docker简介 1.1.1什么是docker 1.1.2 docker在企业中的应用场景 1.1.3 docker与虚拟化的对比 1.1.4 docker的优势 二 、部署docker 2.1 容器工作方法 2.2 部署第一个容器 2.2.1 配置软件仓库 2.2.2 安装docker-ce并启动服务 2.2.…...

【MySQL】使用 JDBC 连接数据库

文章目录 前言1. 认识 JDBC1.1 概念1.2 好处 2. 使用 JDBC2.1 安装数据驱动包2.2 把 jar 包导入到项目中2.3 代码编写2.4 测试结果 3. 代码优化4. 源码展示结语 前言 在 MySQL 系列中&#xff0c;我们介绍了很多内容&#xff0c;包括但不限于建库建表&#xff0c;增删查改等等…...

数据结构与算法笔记:概念与leetcode练习题

1、数组Array 时间复杂度 数组访问&#xff1a;O(1) 数组搜索&#xff1a;O(N) 数组插入&#xff1a;O(N) 数组删除&#xff1a;O(N) 特点 适合读&#xff0c;不适合写 数组常用操作 # 1、创建数组 a [] # 2、尾部添加元素 a.append(1) a.append(2) a.append(3) # 3、…...

十大时间序列预测模型

目录 1. 自回归模型 原理 核心公式 推导过程: 完整案例 2. 移动平均模型 原理 核心公式 推导过程: 完整案例 3. 自回归移动平均模型 原理 核心公式 推导过程: 完整案例 4. 自回归积分移动平均模型 原理 核心公式 推导过程 完整案例 5. 季节性自回归积分…...

G2O 通过工厂函数类 OptimizationAlgorithmFactory 来生成固定搭配的优化算法

OptimizationAlgorithmFactory 类位于 optimization_algorithm_factory.h //***g2o源码 g2o/g2o/core/optimization_algorithm_factory.h ***// /*** \brief create solvers based on their short name** Factory to allocate solvers based on their short name.* The Factor…...

手机USB连接不显示内部设备,设备管理器显示“MTP”感叹号,解决方案

进入小米驱动下载界面&#xff0c;等小米驱动下载完成后&#xff0c;解压此驱动文件压缩包。 5、小米USB驱动安装方法&#xff1a;右击“计算机”&#xff0c;从弹出的右键菜单中选择“管理”项进入。 6、在打开的“计算机管理”界面中&#xff0c;展开“设备管理器”项&…...

SpringBootWeb快速入门!详解如何创建一个简单的SpringBoot项目?

在现代Web开发中&#xff0c;SpringBoot以其简化的配置和快速的开发效率而受到广大开发者的青睐。本篇文章将带领你从零开始&#xff0c;搭建一个基于SpringBoot的简单Web应用~ 一、前提准备 想要创建一个SpringBoot项目&#xff0c;需要做如下准备&#xff1a; idea集成开发…...

RabbitMQ 入门到精通指南

RabbitMQ 是一种开源消息代理软件&#xff0c;基于 AMQP&#xff08;高级消息队列协议&#xff09;构建&#xff0c;用于异步传输数据&#xff0c;帮助我们解耦系统、削峰流量、处理高并发。本指南将详细介绍 RabbitMQ 的架构设计、使用场景、安装步骤以及一些高级应用&#xf…...

ARM base instruction -- movz

Move wide with zero moves an optionally-shifted 16-bit immediate value to a register. 用零移动宽值将可选移位的16位即时值移动到寄存器。即把立即数移动寄存器前先把寄存器清零。 32-bit variant MOVZ <Wd>, #<imm>{, LSL #<shift>} 64-bit var…...

安装jdk安装开发环境与maven

1.下载maven 链接: https://pan.baidu.com/s/1gTmIWBFBdIQob0cqGG3E_Q 提取码: 42ck&#xff0c;apache-maven-3.8.4-bin.zip 2.安装java jdk yum install -y java-1.8.0-openjdk-devel 3.在/opt目录下新建目录 mkdir /opt/maven 4.将apache-maven-3.8.4-bin.zip上传到/opt/ma…...

openpnp - 图像传送方向要在高级校正之前设置好

文章目录 openpnp - 图像传送方向要在高级校正之前设置好笔记图像传送方向的确定END openpnp - 图像传送方向要在高级校正之前设置好 笔记 图像传送方向和JOG面板的移动控制和实际设备的顶部摄像头/底部摄像头要一致&#xff0c;这样才能和贴板子时的实际操作方向对应起来。 …...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

工程地质软件市场:发展现状、趋势与策略建议

一、引言 在工程建设领域&#xff0c;准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具&#xff0c;正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...