这篇文章主要介绍“Android Compose如何实现联系人列表”,在日常操作中,相信很多人在Android Compose如何实现联系人列表问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”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)@Composablefun 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}
到此,关于“Android Compose如何实现联系人列表”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!