溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android?Compose如何實(shí)現(xiàn)聯(lián)系人列表

發(fā)布時(shí)間:2023-03-10 11:24:13 來源:億速云 閱讀:114 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“Android Compose如何實(shí)現(xiàn)聯(lián)系人列表”,在日常操作中,相信很多人在Android Compose如何實(shí)現(xiàn)聯(lián)系人列表問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Android Compose如何實(shí)現(xiàn)聯(lián)系人列表”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

效果:

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來觀察滑動(dòng)的一些參數(shù),來控制右側(cè)字母的位置

  • 右側(cè)使用canvas繪制字母 在觸控的時(shí)候使用scrollToItem準(zhǔn)確的定位到左側(cè)應(yīng)該滑動(dòng)的位置,感覺有些不準(zhǔn)確,不知道是計(jì)算的問題還是bug,在谷歌上看到類似的issue提交。

  • 中間顯示滑動(dòng)到的字母,也可以使用貝塞爾曲線來繪制類似于水滴的樣式,懶得去搞了。

  • 利用derivedStateOf來跟蹤變化的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) {
                //通過偏移的倍數(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)畫的形式過度到手指按下的位置
                            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í)候,才通過動(dòng)畫的形式,回到原點(diǎn)的位置
                            val dragUpOrCancelPointerInputChange = awaitPointerEventScope {
                                awaitDragOrCancellation(downPointerInputChange.id)
                            }
                            // 等于空,說明已經(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)系人列表”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI