武义企业网站建设,wordpress免费主题删除尾巴,域名注册流程和费用,网站手机版下悬浮条怎么做效果如上图。
Main Ideas
左右两个列表左列表展示人员数据#xff0c;含有姓氏首字母的 header item右列表是一个全由姓氏首字母组成的索引列表#xff0c;点击某个item#xff0c;展示一个气泡组件(它会自动延时关闭)#xff0c; 左列表滚动并显示与点击的索引列表item … 效果如上图。
Main Ideas
左右两个列表左列表展示人员数据含有姓氏首字母的 header item右列表是一个全由姓氏首字母组成的索引列表点击某个item展示一个气泡组件(它会自动延时关闭) 左列表滚动并显示与点击的索引列表item 相同的 header搜索动作后匹配人员名称中是否包含搜索字符串或搜索字符串为单一字符时是否能匹配到某个首字母而且滚动后左右列表都能滚动至对应 Header 或索引处。 Steps
S1. 汉字拼音转换
先找到了 Pinyin4J 这个库后来发现没有对多音字姓氏 的处理之后找到 TinyPinyin 它可以自建字典指明多音汉字(作为姓氏时)的指定读音。
fun initChinaNamesDictMap() {// 增加 多音字 姓氏拼音词典Pinyin.init(Pinyin.newConfig().with(object : PinyinMapDict() {override fun mapping(): MutableMapString, ArrayString {val map hashMapOfString, ArrayString()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: ListDriverInfo): ListDriverInfo {list.forEachIndexed { index, driverInfo -if (driverInfo.Name.isNullOrEmpty()) returnforEachIndexed// Pinyin.toPinyin(char) 方法不使用自定义字典val header Pinyin.toPinyin(driverInfo.Name!!.first().toString(), ,).first()driverInfo.headerPinyinText header.toString()}// 以拼音首字母排序(list as MutableList).sortBy { it.headerPinyinText }val newer mutableListOfDriverInfo()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。
左侧的点击事件不会触发右侧的联动
override fun onItemClick(position: Int, data: DriverInfo) {if (data.isHeader) return// 如果是点击 item 做自己的业务
}右侧点击事件会触发左侧的滚动还可以触发气泡 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:idid/bubbleViewandroid:layout_width100dpandroid:layout_height100dpandroid:visibilityinvisibleapp:layout_constraintStart_toStartOfparentapp:layout_constraintTop_toTopOfid/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 ?: returnlaunchmBind.bubbleView.showAtLocation(itemView)delay(1000)mBind.bubbleView.visibility View.GONE}
}自定义 气泡 view
/*** desc: 指定view左侧显示的气泡view* author: stone* email: aa86799163.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: ListDriverInfo?, text: String): ListDriverInfo? {// 如果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 mutableListOfDriverInfo()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: ListDriverInfo? adapter.filterTextToNewHeaderList(driverList, text)adapter.updateData(beanList)val indexList beanList.filter { it.isHeader }.map { it.headerPinyinText ?: }indexAdapter.updateData(indexList)
}整体核心实现都贴出来了如果有什么bug欢迎回复