Android compose 实现类似通讯录列表

Android compose 实现类似通讯录列表

前言

本篇文章是能够应用的最小API是24,如果要使用更小版本需要自行对某些逻辑进行修改。这篇文章是今天突然心血来潮写的。自己捣鼓了一些时间,完成的,可以说还是很粗糙的一个版本。具体实现了顶部的吸附式标签和右侧的字母点击索引。来先上成品再说其他。
在这里插入图片描述

实现步骤

  • 对于通讯录列表这个可以看成两个部分一部分是列表一部分是右侧的索引表。那么久可以确定要使用的控件了最外层Row,里面放一个LazyColumn和Column来实现。
Row{
LazyColumn{}
Column{}
}
  • 然后对列表进行设计,我们去看compose的开发文档中可以知道在列表中有一个粘性标题(注:改控件属于实验性的将来可能会被删除,因此使用的时候要介意),这边为了方便就先用了因此列表的部分的控件就可以写成
 val listState = rememberLazyListState()
LazyColumn(
            modifier = Modifier
                .fillMaxHeight()
                .weight(1f),
            state = listState,
        ) {
            data.forEach { initial, listData ->
                stickyHeader(contentType = initial) {
                //将头部UI回调出去让用户自定义
                    contentTitle(initial)
                }
                items(listData) {
                //将内容UI回调出去让用户自定义
                    contentBody(it)
                }
            }
        }

这边的 stickyHeader()加上contentType属性是为了滑动时能够判断索引的位置并且在字母项里面显示出来。

  • 然后就是字母列表的实现啦
 Column(
            modifier = Modifier
                .width(15.dp)
                .fillMaxHeight(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            for (list in charList) {
                Box(
                    modifier = Modifier
                        .size(15.dp)
                        .background(color)
                ) {
                    Text(
                        text = list.toString(),
                        color = Color.Black,
                        fontSize = 10.sp,
                        modifier = Modifier.align(
                            Alignment.Center
                        )
                    )
                }

            }
        }

到这步可以说通讯列表已经基本实现了,

  • 接下来就是将列表和字母索引进行连接啦,思路是列表现在展示的第一个名字的字母是啥,然后在将这个字母与索引列里面进行比对,相同的进行标注。
    按照这个思路我们从listState.layoutInfo的源码可以知道在最后一次布局过程中会去计算的LazyListLayoutInfo的对象。例如,您可以使用它来计算当前可见的项目。
    这样不就简单了嘛,写一个来观察当前第一个的项目的字母是啥(isSelectType ),然后在滑动的时候对其进行赋值。为什么要在其滑动的时候赋值而不是一进入就去赋值,这是因为刚创建的时候layoutInfo.visibleItemsInfo.first()这个是为空的,会报错的呢。代码如下
val layoutInfo by remember { derivedStateOf { listState.layoutInfo } }
var isSelectType by remember {
        mutableStateOf(35.toChar())
    }
if (listState.isScrollInProgress) {
        isSelectType = layoutInfo.visibleItemsInfo.first().contentType as Char
    }
  • 到这步后就要考虑点击索引的时候列表要滑动到相应的位置在这里插入图片描述
    看上图,当我点击#的时候列表要移动到#的位置,又因为#位于第一个所以要滑动到0的位置,当我带你A的时候它要移动到A的位置,我们可以看出A此时的索引为4,那么A的索引计算就变成了#的大小3加1,然后在滑动到相应的位置,因此点击滑动的代码逻辑就可以这样写
if (char == 35.toChar()) {
                                coroutineScope.launch {
                                    listState.animateScrollToItem(0)
                                }
                            } else {
                                var index = 0
                                for (typeList in charList) {
                                    if (typeList == char) {
                                        coroutineScope.launch {
                                            listState.animateScrollToItem(index)
                                        }
                                        break
                                    } else {
                                        val size = data[typeList]?.size ?: 0
                                        index += size + 1
                                    }
                                }
                            }
  • 至此基本上的逻辑都完成了,现在将完整代码如下:
@RequiresApi(Build.VERSION_CODES.N)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun <T> AddressBookView(
    data: Map<Char, List<T>>,
    modifier: Modifier = Modifier,
    contentBody: @Composable (item: T) -> Unit,
    contentTitle: @Composable (item: Char) -> Unit,
) {
    val charList = getCharList()
    val listState = rememberLazyListState()
    val coroutineScope = rememberCoroutineScope()
    val layoutInfo by remember { derivedStateOf { listState.layoutInfo } }
    var isSelectType by remember {
        mutableStateOf(35.toChar())
    }
    Row(modifier = modifier.fillMaxSize()) {
        LazyColumn(
            modifier = Modifier
                .fillMaxHeight()
                .weight(1f),
            state = listState,
        ) {
            data.forEach { initial, listData ->
                stickyHeader(contentType = initial) {
                    contentTitle(initial)
                }
                items(listData) {
                    contentBody(it)
                }
            }
        }
        Column(
            modifier = Modifier
                .width(15.dp)
                .fillMaxHeight(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            for (char in charList) {
                val color = if (isSelectType == char) Color.Green else Color.White
                Box(
                    modifier = Modifier
                        .size(15.dp)
                        .background(color)
                        .clickable {
                            if (char == 35.toChar()) {
                                coroutineScope.launch {
                                    listState.animateScrollToItem(0)
                                }
                            } else {
                                var index = 0
                                for (typeList in charList) {
                                    if (typeList == char) {
                                        coroutineScope.launch {
                                            listState.animateScrollToItem(index)
                                        }
                                        break
                                    } else {
                                        val size = data[typeList]?.size ?: 0
                                        index += size + 1
                                    }
                                }
                            }

                        }
                ) {
                    Text(
                        text = char.toString(),
                        color = Color.Black,
                        fontSize = 10.sp,
                        modifier = Modifier.align(
                            Alignment.Center
                        )
                    )
                }

            }
        }
    }

    if (listState.isScrollInProgress) {
        isSelectType = layoutInfo.visibleItemsInfo.first().contentType as Char
    }
}
//获取字母列表
private fun getCharList(): List<Char> {
    val charList = mutableListOf<Char>()
    val char = 35
    charList.add(char.toChar())
    (65..90).forEach { letter ->
        charList.add(letter.toChar())
    }
    return charList
}

具体应用

在应用的话直接上代码吧


@RequiresApi(Build.VERSION_CODES.N)
@Preview
@Composable
private fun ShowAddressBookView() {
    val data = getData()
    AddressBookView(
        data = data,
        modifier = Modifier
            .fillMaxSize()
            .background(color = Color.White)
            .padding(horizontal = 10.dp),
        contentBody = {

            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(30.dp)
            ) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .weight(1f)
                        .clickable {
                            Log.e("test", "ShowAddressBookView:${it.name} ")
                        },
                    horizontalArrangement = Arrangement.Center
                ) {
                    Text(
                        text = it.name, color = Color.Black, fontSize = 25.sp
                    )
                }
                Divider(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(horizontal = 17.5.dp),
                    color = Color.Black
                )
            }
        },
        contentTitle = {
            Text(
                text = it.toString(),
                color = Color.Black,
                fontSize = 15.sp,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(20.dp)
            )
        })
}
//自定义
data class AddressBookData(
    val nameChar: Char,
    val name: String,
)

//获取数据
private fun getData(): Map<Char, List<AddressBookData>> {
    val data = mutableListOf<AddressBookData>()
    for (i in 0..100) {
        val a = (65..91).random()
        val nameChar = if (a == 91) {
            val char = 35
            char.toChar()
        } else {
            a.toChar()
        }
        data.add(AddressBookData(nameChar = nameChar, name = "name:$i"))
    }
    return data.sortedBy { it.nameChar }.groupBy { it.nameChar }
}

数据的类型可以自己去自定义,要展示的样式也可以自定义,不过字母索引的选中状态需要自己去修改,只要传入的类型没错的话那么就没啥大问题。

结语

好了今天就写到这里啦,有问题的话欢迎大家写出来,这个还存在点击字母索引的时候滑动动画不够自然的问题,当然如果还有其他问题欢迎指出来,感谢各位看官的观看。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

华丽转场

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值