软件编程
位置:首页>> 软件编程>> Android编程>> Android Compose实现联系人列表流程

Android Compose实现联系人列表流程

作者:会编程的猩猩  发布时间:2023-09-25 00:50:04 

标签: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)
@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

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com