← Назад к вопросам

Что делать если LazyRow лагает

2.8 Senior🔥 62 комментариев
#UI и вёрстка#Производительность и оптимизация

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проблема лагов в LazyRow и её решение

LazyRow — это мощный инструмент Compose для горизонтальной ленивой компоновки, но при неправильном использовании он действительно может вызывать лаги, особенно при работе с большими наборами данных или сложными элементами. Давайте разберем основные причины и практические решения.

🔍 Диагностика проблемы

Первым делом нужно определить источник лагов. Используйте Layout Inspector и Compose Metrics:

// Включите отслеживание рекомпозиций в разработке
@Composable
fun MyLazyRowContent() {
    LazyRow {
        items(items) { item ->
            KeyedComposition(item.id) { // Используйте ключи для стабильности
                MyRowItem(item)
            }
        }
    }
}

🎯 Основные причины и решения

1. Чрезмерная рекомпозиция элементов

// ❌ ПЛОХО: весь элемент перекомпозируется при любых изменениях
@Composable
fun BadListItem(item: Item) {
    var localState by remember { mutableStateOf(false) }
    // Сложная логика рекомпозиции
}

// ✅ ХОРОШО: разделяйте стабильные и изменяемые части
@Composable
fun GoodListItem(item: Item) {
    Row {
        // Статичная часть - используйте remember
        Image(
            painter = rememberAsyncImagePainter(item.imageUrl),
            contentDescription = null,
            modifier = Modifier.fillMaxHeight()
        )
        
        // Динамическая часть - изолируйте состояние
        ItemDetails(item) // Выносите в отдельную composable функцию
    }
}

2. Тяжелые операции в Composable функциях

// ❌ ПЛОХО: вычисления внутри composable
@Composable
fun HeavyComputationItem(data: Data) {
    val computedValue = performHeavyCalculation(data) // Блокирует UI поток
    
    Text(text = "Result: $computedValue")
}

// ✅ ХОРОШО: выносите вычисления в background
@Composable
fun OptimizedItem(data: Data) {
    val computedValue by remember(data) {
        derivedStateOf {
            // Используйте Dispatchers.Default для тяжелых операций
            withContext(Dispatchers.Default) {
                performHeavyCalculation(data)
            }
        }
    }
    
    Text(text = "Result: $computedValue")
}

📊 Оптимизационные стратегии

Размеры и кэширование

LazyRow(
    state = rememberLazyListState(),
    modifier = Modifier.fillMaxWidth(),
    contentPadding = PaddingValues(horizontal = 16.dp),
    flingBehavior = ScrollableDefaults.flingBehavior(),
    userScrollEnabled = true
) {
    items(
        items = items,
        key = { it.id }, // Ключи обязательны для правильной переиспользуемости
        contentType = { it.type } // Группировка по типу для оптимизации
    ) { item ->
        MyListItem(
            item = item,
            modifier = Modifier
                .width(IntrinsicSize.Max) // Используйте intrinsic размеры
                .animateItemPlacement() // Анимация переупорядочивания
        )
    }
}

Пагинация и предзагрузка

val lazyListState = rememberLazyListState()

LaunchedEffect(lazyListState) {
    snapshotFlow { lazyListState.layoutInfo }
        .map { layoutInfo ->
            val lastVisible = layoutInfo.visibleItemsInfo.lastOrNull()
            lastVisible?.index == layoutInfo.totalItemsCount - 5
        }
        .distinctUntilChanged()
        .collect { needLoadMore ->
            if (needLoadMore) viewModel.loadMore()
        }
}

🛠️ Дополнительные техники оптимизации

  1. Использование derivedStateOf для минимизации рекомпозиций:
val scrollState = rememberLazyListState()
val isScrolling by remember {
    derivedStateOf {
        scrollState.isScrollInProgress
    }
}
  1. Оптимизация изображений с помощью Coil или Glide:
AsyncImage(
    model = ImageRequest.Builder(context)
        .data(item.url)
        .diskCachePolicy(CachePolicy.ENABLED)
        .memoryCachePolicy(CachePolicy.ENABLED)
        .size(Size.ORIGINAL)
        .build(),
    contentDescription = null,
    modifier = Modifier.fillMaxSize(),
    contentScale = ContentScale.Crop
)
  1. Виртуализация содержимого — рендерите только видимые элементы:
LazyRow(
    state = rememberLazyListState(),
    beyondBoundsItemCount = 2, // Рендерить +2 элемента за пределами видимости
    // ...
)

🚀 Профилирование производительности

  • Включите Show Layout Bounds в настройках разработчика
  • Используйте Compose Compiler Metrics для анализа стабильности
  • Проверяйте Skipped frames в Logcat с тегом Choreographer
  • Замеряйте время рекомпозиций с помощью BenchmarkRule в тестах

💡 Практические советы

  • Избегайте вложенных ленивых layout'ов — это убийца производительности
  • Используйте Modifier с умом — некоторые модификаторы очень дорогие
  • Минимизируйте количество состояний в каждом элементе
  • Профилируйте на реальных устройствах, особенно среднего и низкого класса
  • Рассмотрите альтернативы — для простых списков иногда лучше подойдет Row с горизонтальным скроллом

Помните, что оптимальная производительность достигается балансом между функциональностью и оптимизацией. Начинайте с простой реализации, добавляйте оптимизации только после выявления конкретных проблем через профилирование.