Android Compose实现联系人列表流程
作者:会编程的猩猩 发布时间:2023-09-25 00:50:04
标签:Android,Compose,联系人
准备数据
data class ContactEntity(
val letter: Char,
val name: String,
val color: Color
)
/**
* 获取联系人数据
*/
fun getContactData(): MutableList<ContactEntity> {
val contactList = mutableListOf<ContactEntity>()
(65..90).forEach { letter ->
// val random = (5..20).random()
val random = 5
repeat(random) { index ->
contactList.add(
ContactEntity(
letter = letter.toChar(), name = "联系人 $index",
color = Color(
red = (0..255).random(),
blue = (0..255).random(),
green = (0..255).random()
)
)
)
}
}
return contactList
}
/**
* 获取首字母列表
*/
fun getCharList(): MutableList<Char> {
val charList = mutableListOf<Char>()
(65..90).forEach { letter ->
charList.add(letter.toChar())
}
return charList
}
思路
整体是由Box布局包裹, 左侧是LazyColumn 右侧放置自定义布局
左侧LazyColumn用state来观察滑动的一些参数,来控制右侧字母的位置
右侧使用canvas绘制字母 在触控的时候使用scrollToItem准确的定位到左侧应该滑动的位置,感觉有些不准确,不知道是计算的问题还是bug,在谷歌上看到类似的issue提交。
中间显示滑动到的字母,也可以使用贝塞尔曲线来绘制类似于水滴的样式,懒得去搞了。
利用derivedStateOf来跟踪变化的remember数据,这样可以减少性能的损耗,但是感觉有个地方没处理好,就是触摸的时候需要改变,滑动的时候也需要改变,而derivedStateOf是val类型的,不能直接赋值,所以又设置了一个remember变量。有可以优化的地方请指出。
数据和样式都可以自定义,非常方便
代码实现
@OptIn(ExperimentalTextApi::class)
@Composable
fun ContactPage(navCtrl: NavHostController, title: String) {
//联系人数据
val contactList = getContactData()
//字母数据
val charList = getCharList()
val offsetY = with(LocalDensity.current) {
20.dp.toPx()
}
val offsetX = with(LocalDensity.current) {
25.dp.toPx()
}
val coroutineScope = rememberCoroutineScope()
val textMeasure = rememberTextMeasurer()
val state = rememberLazyListState()
//触摸的位置
var touchOffset by remember() {
mutableStateOf(Offset.Zero)
}
//触摸到的上一次的字母下标
var touchIndexLast by remember {
mutableStateOf(-1)
}
//触摸的字母的下标
val touchIndex = remember(touchOffset.y) {
derivedStateOf {
if (touchOffset.y > 0f) {
//通过偏移的倍数计算滑动到哪个字母的位置了
val y = (touchOffset.y / offsetY).roundToInt()
if (y in 0..25) {
touchIndexLast = y
y
} else {
touchIndexLast
}
} else touchIndexLast
}
}
//触摸到的字符的index
val touchLetterIndex = remember {
derivedStateOf {
if (state.isScrollInProgress && state.layoutInfo.visibleItemsInfo.isNotEmpty()) {
val key = state.layoutInfo.visibleItemsInfo[0].key
if (key is Int && key < contactList.size) {
val letter = contactList[key].letter
val findIndex = charList.indexOfFirst {
it == letter
}
findIndex
} else {
touchIndex.value
}
} else {
touchIndex.value
}
}
}
//上一次选择的letter
var lastLetter by remember {
mutableStateOf("")
}
//滑动到的letter
val scrollLetter = remember {
derivedStateOf {
if (touchIndex.value > 0) {
val letter = (65 + touchIndex.value).toChar()
coroutineScope.launch {
//找到相应的item
val index = getIndex(contactList, letter)
state.scrollToItem(index, scrollOffset = 20)
}
val str = letter.toString()
lastLetter = str
str
} else {
lastLetter
}
}
}
CommonToolbar(navCtrl, title) {
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(modifier = Modifier.fillMaxWidth(), state = state, content = {
contactList.forEachIndexed { index, contactEntity ->
item(key = index) {
Column(
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(50.dp)
.clip(CircleShape)
.background(
color = contactEntity.color,
), contentAlignment = Alignment.Center
) {
Text(
text = "${contactEntity.letter}",
fontSize = 16.sp,
color = Color.White,
fontWeight = FontWeight.SemiBold,
)
}
Text(
text = contactEntity.name,
modifier = Modifier
.padding(20.dp)
.weight(1f)
.padding(horizontal = 10.dp, vertical = 16.dp)
)
}
Divider()
}
}
}
item {
Text(
text = "共${contactList.size}联系人",
fontSize = 12.sp,
color = Color(0xFF333333),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
textAlign = TextAlign.Center
)
}
})
//绘制右侧的26个字母
Canvas(modifier = Modifier
.padding(top = 30.dp)
.width(50.dp)
.fillMaxHeight()
.align(Alignment.TopEnd)
.pointerInput(Unit) {
coroutineScope {
while (true) {
// down事件
val downPointerInputChange = awaitPointerEventScope {
awaitFirstDown()
}
// 如果位置不在手指按下的位置,先动画的形式过度到手指按下的位置
if (touchOffset.x != downPointerInputChange.position.x && touchOffset.y != downPointerInputChange.position.y) {
launch {
touchOffset = downPointerInputChange.position
}
}
// touch Move事件
// 滑动的时候,box随着手指的移动去移动
awaitPointerEventScope {
drag(downPointerInputChange.id, onDrag = {
touchOffset = it.position
})
}
// 在手指弹起的时候,才通过动画的形式,回到原点的位置
val dragUpOrCancelPointerInputChange = awaitPointerEventScope {
awaitDragOrCancellation(downPointerInputChange.id)
}
// 等于空,说明已经抬起
if (dragUpOrCancelPointerInputChange == null) {
launch {
touchOffset = Offset.Zero
}
}
}
}
}, onDraw = {
charList.forEachIndexed { index, char ->
drawText(
size = Size(width = offsetY, offsetY),
textMeasurer = textMeasure, text = "$char",
style = TextStyle(
fontWeight = if (touchLetterIndex.value == index) FontWeight.SemiBold else FontWeight.Medium,
color = if (touchLetterIndex.value == index) Color.Blue else Color(
0xFF333333
),
textAlign = TextAlign.Center,
fontSize = if (touchLetterIndex.value == index) 16.sp else 14.sp
),
topLeft = Offset(offsetX, offsetY * index),
)
}
})
//中间显示的大写字母
val textMeasurer1 = rememberTextMeasurer()
if (touchOffset.x != 0f && touchOffset.y != 0f && scrollLetter.value.isNotEmpty()) {
val annotatedString = AnnotatedString(
scrollLetter.value, spanStyle = SpanStyle(
fontWeight = FontWeight.Medium,
color = Color(0xFF333333),
fontSize = 20.sp,
)
)
val textLayoutResult = textMeasurer1.measure(text = annotatedString)
val textSize = textLayoutResult.size
Canvas(modifier = Modifier
.align(Alignment.Center)
.requiredSize(width = 50.dp, height = 50.dp), onDraw = {
//底部颜色
drawRoundRect(
color = Color(0xA403A9F4), cornerRadius = CornerRadius(10f, 10f)
)
//绘制字母
drawText(
textMeasurer = textMeasurer1,
text = annotatedString,
topLeft = Offset(
(size.width - textSize.width) / 2f,
(size.height - textSize.height) / 2f
)
)
})
}
}
}
}
private fun getIndex(list: MutableList<ContactEntity>, letter: Char): Int {
val findIndex = list.indexOfFirst { contactEntity ->
contactEntity.letter == letter
}
return findIndex
}
代码位置点我
来源:https://blog.csdn.net/u010436867/article/details/129021056


猜你喜欢
- 在实战中学习Spring,本系列的最终目的是完成一个实现用户注册登录功能的项目。预想的基本流程如下:1、用户网站注册,填写用户名、密码、em
- 前言Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案,Nacos 作为其核心组件之一,可以作为注册中心和配置中
- 本文实例讲述了C#使用IComparer自定义List类实现排序的方法。分享给大家供大家参考。具体如下:List类中不带参数的Sort函数可
- 在Java中对集合进行操作时,有时候需要对类中的equals() 和 hashCode()进行方法重写.IDEA中实现了利用快捷键即可对上述
- 前言fragment 可认为是一个轻量级的Activity,但不同与Activity,它是要嵌到Activity中来使用的,它用来解决设备屏
- https://www.jb51.net/article/152879.htm上节,我们明白了proc文件系统的作用,接下来我们在已经写好的
- 这篇文章主要介绍了Java内存模型可见性问题相关解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友
- choose标签用法choose 标签是按顺序判断其内部 when 标签中的 test 条件出否成立,如果有一个成立,则 choose 结束
- 引言♀ 小AD:明哥,我终于出了这口恶气了。♂ 明世隐:打爽了是吧。♀ 小AD:那必须的,打十盘我赢九盘,我随意。♂ 明世隐:那小朋友不是搞
- 前提:微信公众平台:注册微信认证的公众号也就是服务号 ,拥有跟高级权限的微信接口。(注册服务号需要一些企业信息,需自己或者公司解决)注: 2
- P代表(Profiles配置文件)在<profiles>指定的<id>中,可以通过-P进行传递或者赋值。假如pom.
- 本文实例为大家分享了Android画画板展示的具体代码,供大家参考,具体内容如下main.xml布局<RelativeLayout x
- 读取本地的xml文件,通过DOM进行解析,DOM解析的特点就是把整个xml文件装载入内存中,形成一颗DOM树形结构,树结构是方便遍历和和操纵
- SwipeRefresh基于原生的SwipeRefreshLayout 做了封装处理此项目中包括种:1.原生SwipeRefreshLayo
- 黑白棋介绍黑白棋,又叫苹果棋,最早流行于西方国家。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。黑白棋非常易于上手,但精通则
- 前言.NET中的委托是一个类,它定义了方法的类型,是一个方法容器。委托把方法当作参数,可以避免在程序中大量使用条件判断语句的情况。项目名为T
- 今天看了看Java并发程序,写一写入门程序,并设置了线程的优先级。class Elem implements Runnable{  
- 在工作中要求将图片上传至本地,如下代码将介绍如何将图片上传至本地准备工作:环境:eclipse4.5-x64,jdk1.7-x64,mave
- 一、准备工作1、pom依赖在pom.xml中加入POI的依赖<dependency> <groupId>org.ap
- @Validated和BindingResult 使用遇到的坑@Validated 与BindingResult 需要相邻,否则 变量res