Android Compose實(shí)現(xiàn)聯(lián)系人列表流程
準(zhǔn)備數(shù)據(jù)
data class ContactEntity( val letter: Char, val name: String, val color: Color ) /** * 獲取聯(lián)系人數(shù)據(jù) */ 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 = "聯(lián)系人 $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布局包裹, 左側(cè)是LazyColumn 右側(cè)放置自定義布局
- 左側(cè)LazyColumn用state來(lái)觀察滑動(dòng)的一些參數(shù),來(lái)控制右側(cè)字母的位置
- 右側(cè)使用canvas繪制字母 在觸控的時(shí)候使用scrollToItem準(zhǔn)確的定位到左側(cè)應(yīng)該滑動(dòng)的位置,感覺有些不準(zhǔn)確,不知道是計(jì)算的問(wèn)題還是bug,在谷歌上看到類似的issue提交。
- 中間顯示滑動(dòng)到的字母,也可以使用貝塞爾曲線來(lái)繪制類似于水滴的樣式,懶得去搞了。
- 利用derivedStateOf來(lái)跟蹤變化的remember數(shù)據(jù),這樣可以減少性能的損耗,但是感覺有個(gè)地方?jīng)]處理好,就是觸摸的時(shí)候需要改變,滑動(dòng)的時(shí)候也需要改變,而derivedStateOf是val類型的,不能直接賦值,所以又設(shè)置了一個(gè)remember變量。有可以優(yōu)化的地方請(qǐng)指出。
- 數(shù)據(jù)和樣式都可以自定義,非常方便
代碼實(shí)現(xiàn)
@OptIn(ExperimentalTextApi::class) @Composable fun ContactPage(navCtrl: NavHostController, title: String) { //聯(lián)系人數(shù)據(jù) val contactList = getContactData() //字母數(shù)據(jù) 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) } //觸摸到的上一次的字母下標(biāo) var touchIndexLast by remember { mutableStateOf(-1) } //觸摸的字母的下標(biāo) val touchIndex = remember(touchOffset.y) { derivedStateOf { if (touchOffset.y > 0f) { //通過(guò)偏移的倍數(shù)計(jì)算滑動(dòng)到哪個(gè)字母的位置了 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("") } //滑動(dòng)到的letter val scrollLetter = remember { derivedStateOf { if (touchIndex.value > 0) { val letter = (65 + touchIndex.value).toChar() coroutineScope.launch { //找到相應(yīng)的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}聯(lián)系人", fontSize = 12.sp, color = Color(0xFF333333), modifier = Modifier .fillMaxWidth() .padding(vertical = 20.dp), textAlign = TextAlign.Center ) } }) //繪制右側(cè)的26個(gè)字母 Canvas(modifier = Modifier .padding(top = 30.dp) .width(50.dp) .fillMaxHeight() .align(Alignment.TopEnd) .pointerInput(Unit) { coroutineScope { while (true) { // down事件 val downPointerInputChange = awaitPointerEventScope { awaitFirstDown() } // 如果位置不在手指按下的位置,先動(dòng)畫的形式過(guò)度到手指按下的位置 if (touchOffset.x != downPointerInputChange.position.x && touchOffset.y != downPointerInputChange.position.y) { launch { touchOffset = downPointerInputChange.position } } // touch Move事件 // 滑動(dòng)的時(shí)候,box隨著手指的移動(dòng)去移動(dòng) awaitPointerEventScope { drag(downPointerInputChange.id, onDrag = { touchOffset = it.position }) } // 在手指彈起的時(shí)候,才通過(guò)動(dòng)畫的形式,回到原點(diǎn)的位置 val dragUpOrCancelPointerInputChange = awaitPointerEventScope { awaitDragOrCancellation(downPointerInputChange.id) } // 等于空,說(shuō)明已經(jīng)抬起 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 }
到此這篇關(guān)于Android Compose實(shí)現(xiàn)聯(lián)系人列表流程的文章就介紹到這了,更多相關(guān)Android Compose內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android學(xué)習(xí)之AppWidget高級(jí)效果
這篇文章主要為大家詳細(xì)介紹了Android學(xué)習(xí)之AppWidget高級(jí)效果的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-08-08Android 實(shí)現(xiàn)仿網(wǎng)絡(luò)直播彈幕功能詳解及實(shí)例
這篇文章主要介紹了Android 實(shí)現(xiàn)仿網(wǎng)絡(luò)直播彈幕功能詳解的相關(guān)資料,并附實(shí)例代碼及實(shí)現(xiàn)效果圖,需要的朋友可以參考下2016-11-11Android編程開發(fā)之EditText中不輸入特定字符會(huì)顯示相關(guān)提示信息的方法
這篇文章主要介紹了Android編程開發(fā)之EditText中不輸入特定字符會(huì)顯示相關(guān)提示信息的方法,涉及Android針對(duì)EditText的布局操作及內(nèi)容判定相關(guān)技巧,需要的朋友可以參考下2015-12-12Android自定義viewgroup快速滑動(dòng)(4)
這篇文章主要為大家詳細(xì)介紹了Android自定義viewgroup快速滑動(dòng)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12android USB如何修改VID具體實(shí)現(xiàn)
在android 設(shè)備的Linux 內(nèi)核中把 USB 驅(qū)動(dòng)的 PID VID 修改以后,也許之前的adb工具就不能識(shí)別設(shè)備了,會(huì)打印出"device not found"的提示2013-06-06詳解flutter之網(wǎng)絡(luò)請(qǐng)求dio,請(qǐng)求,攔截器簡(jiǎn)單示例
這篇文章主要介紹了詳解flutter之網(wǎng)絡(luò)請(qǐng)求dio,請(qǐng)求,攔截器簡(jiǎn)單示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06淺談Android硬件加速原理與實(shí)現(xiàn)簡(jiǎn)介
這篇文章主要介紹了淺談Android硬件加速原理與實(shí)現(xiàn)簡(jiǎn)介,本文嘗試從底層硬件原理,一直到上層代碼實(shí)現(xiàn),對(duì)硬件加速技術(shù)進(jìn)行簡(jiǎn)單介紹,感興趣的小伙伴們可以參考一下2018-07-07Android studio利用gradle打jar包并混淆的方法詳解
昨天準(zhǔn)備把寫好的代碼使用gradle打jar包出來(lái),并打算加混淆。打jar包容易,結(jié)果在混淆上走了彎路。所以這篇文章主要介紹了關(guān)于Android studio利用gradle打jar包并混淆的方法,需要的朋友可以參考下。2017-03-03Android?O對(duì)后臺(tái)Service限制詳解
這篇文章主要為大家介紹了Android?O對(duì)后臺(tái)Service限制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11