效果图
!
00001.jpg?auth_key=4818491540-0-0-c8bbe27fc97f582aa0dd1b29a371a81f)(title-实现宜家 双联列表)]
代码
@Composable
fun PartList(
leftList : List<String>,
scrollToItem : (Int) -> Int,
itemToScroll : (Int) -> Int,
content: LazyListScope.() -> Unit
)
{
val leftScrollState = rememberLazyListState()
val rightScrollState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
var onClickItem by remember {
mutableStateOf(-1)
}
val getFirstVisibleItemIndex by remember {
derivedStateOf {
rightScrollState.firstVisibleItemIndex
}
}
LaunchedEffect(Unit) {
snapshotFlow { getFirstVisibleItemIndex }
.collect {
onClickItem = scrollToItem(it)
}
}
Row(modifier = Modifier.fillMaxSize())
{
LazyColumn(
state = leftScrollState,
modifier = Modifier
.fillMaxHeight()
.background(IKEA_GRAY_LIGHT)
.weight(2f)
)
{
items(leftList.size)
{
Row(
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
.background(if (onClickItem == it) white else IKEA_GRAY_LIGHT)
.clickable {
onClickItem = it
coroutineScope.launch {
rightScrollState.animateScrollToItem(itemToScroll(it))
}
},
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Text(
text = leftList[it],
modifier = Modifier,
color = if(onClickItem == it) Color.Blue else Color.Black,
fontSize = 11.sp,
)
}
}
}
Spacer(modifier = Modifier.weight(0.5f))
LazyColumn(
state = rightScrollState,
modifier = Modifier
.fillMaxHeight()
.weight(8f),
content = content
)
}
}
解析
主要矛盾
联动采用将两边滑动状态上提然后根据规则变动:
- 状态上提方法:
通过rememberLazyListState 来获取滑动状态
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF)
@Composable
fun PartList()
{
val leftScrollState = rememberLazyListState()
val rightScrollState = rememberLazyListState()
Row {
LazyColumn(
state = leftScrollState,
。。。
)
{
。。。。
}
Spacer(modifier = Modifier.weight(0.5f))
LazyColumn(
state = rightScrollState,
。。。
) {
。。。。
}
}
}
- 点击 左侧,右侧滑动到相应位置:
a. 点击右侧:左侧跳转到相应标头 b. 滑动右侧到新的标头:左侧跟着跳转
a) 左侧只要监听点击事件,然后对右侧跳转即可: 我们以 左侧条目 的 Modifier 来进行点击事件监听:
.clickable {
onClickItem = it
coroutineScope.launch {
rightScrollState.animateScrollToItem(it * 3)
}
}
b) 右侧滑动是实时监听的,所以只需要在函数内最上层写相关变化即可:
run {
onClickItem = (rightScrollState.firstVisibleItemIndex / 3)
coroutineScope.launch {
leftScrollState.animateScrollToItem(onClickItem)
}
}
性能问题:
上文中,直接访问rememberLazyListState 的firstVisibleItemIndex 不是一个好选择,他会造成性能问题。
在使用 LazyColumn 或者 LazyRow 时,应该避免在 LazyListScope 中访问 LazyListState,这可能会造成隐藏的性能问题
因此,我们需要改进:将判断 list 滚动的逻辑抽象为一个 getFirstVisibleItemIndex 状态,然后通过 snapshotFlow 单独定义其变化,这样避免 LazyColumn 的 content 的重组。
val getFirstVisibleItemIndex by remember {
derivedStateOf {
rightScrollState.firstVisibleItemIndex
}
}
LaunchedEffect(Unit) {
snapshotFlow { getFirstVisibleItemIndex }
.collect {
onClickItem = scrollToItem(it)
}
}
次要矛盾
也就是尽可能符合宜家设计标准:
- 点击左侧,左侧跳转到指定位置,这里就引入
onClickItem 和其他外观区别。 - 其他讨论脱离文章范畴,不赘述。
|