Как реализовать отслеживание показов элементов LazyColumn
У меня есть ленивый столбец с элементами, и я хочу отправлять событие каждый раз, когда один из элементов появляется на экране. Есть примеры событий, отправляемых в первый раз (например, здесь https://plusmobileapps.com/2022/05/04/lazy-column-view-impressions.html), но этот пример не отправляет события в последующие разы одинаково элемент появляется снова (при прокрутке вверх, например).
Я знаю, что это не должно быть связано с композицией, потому что может быть несколько рекомпозиций, пока элемент остается на экране. Что было бы лучшим подходом для решения чего-то подобного?
2 ответа
Я изменил пример в статье с ключей на индекс, и он работает нормально, вы должны проверить, есть ли что-то не так с сопоставлением ключей.
@Composable
private fun MyRow(key: Int, lazyListState: LazyListState, onItemViewed: () -> Unit){
Text(
"Row $key",
color = Color.White,
modifier = Modifier
.fillMaxWidth()
.background(Color.Red)
.padding(20.dp)
)
ItemImpression(key = key, lazyListState = lazyListState) {
onItemViewed()
}
}
@Composable
fun ItemImpression(key: Int, lazyListState: LazyListState, onItemViewed: () -> Unit) {
val isItemWithKeyInView by remember {
derivedStateOf {
lazyListState.layoutInfo
.visibleItemsInfo
.any { it.index == key }
}
}
if (isItemWithKeyInView) {
LaunchedEffect(Unit) {
onItemViewed()
}
}
}
Затем использовал его как
LazyColumn(
verticalArrangement = Arrangement.spacedBy(14.dp),
state = state
) {
items(100) {
MyRow(key = it, lazyListState = state) {
println(" Item $it is displayed")
if(it == 11){
Toast.makeText(context, "item $it is displayed", Toast.LENGTH_SHORT).show()
}
}
}
}
Результат
Кроме того, вместо отправки LazyListState в каждый компонуемый пользовательский интерфейс вы можете переместить ItemImpression выше списка как Composable, который отслеживает только события с использованием состояния. Я поставил 2, но вы можете отправить список и создать для нескольких из них либо
@Composable
private fun LazyColumnEventsSample() {
val context = LocalContext.current
val state = rememberLazyListState()
ItemImpression(key = 11, lazyListState = state) {
Toast.makeText(context, "item 11 is displayed", Toast.LENGTH_SHORT).show()
}
ItemImpression(key = 13, lazyListState = state) {
Toast.makeText(context, "item 13 is displayed", Toast.LENGTH_SHORT).show()
}
LazyColumn(
verticalArrangement = Arrangement.spacedBy(14.dp),
state = state
) {
items(100) {
Text(
"Row $it",
color = Color.White,
modifier = Modifier
.fillMaxWidth()
.background(Color.Red)
.padding(20.dp)
)
}
}
}
The LazyListState#layoutInfo
содержит всю информацию о видимых элементах. Вы можете использовать его, чтобы узнать, виден ли конкретный элемент в списке.
Что-то вроде:
@Composable
private fun LazyListState.containsItem(index:Int): Boolean {
return remember(this) {
derivedStateOf {
val visibleItemsInfo = layoutInfo.visibleItemsInfo
if (layoutInfo.totalItemsCount == 0) {
false
} else {
visibleItemsInfo.toMutableList().map { it.index }.contains(index)
}
}
}.value
}
Затем просто используйте что-то вроде:
val state = rememberLazyListState()
var isItemVisible = state.containsItem(index = 5)
Затем вы можете наблюдать значение, используя побочный эффект:
LaunchedEffect(isItemVisible){
if (isItemVisible)
//do something
}
Вместо этого, если вам нужны все видимые элементы, вы можете использовать эту функцию, чтобы получить список со всеми видимыми элементами и сохранить его в переменной.
@Composable
private fun LazyListState.visibleItems(): List<Int> {
return remember(this) {
derivedStateOf {
val visibleItemsInfo = layoutInfo.visibleItemsInfo
if (layoutInfo.totalItemsCount == 0) {
emptyList()
} else {
visibleItemsInfo.toMutableList().map { it.index }
}
}
}.value
}