Создание прокручиваемых макетов для телевизоров

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

Поведение прокрутки по умолчанию оптимизировано для телевизоров.

Начиная с Compose Foundation 1.7.0, стандартные ленивые макеты (такие как LazyRow и LazyColumn ) включают встроенную поддержку функций позиционирования фокуса. Это рекомендуемый способ создания каталогов для приложений на ТВ, поскольку он помогает удерживать сфокусированные элементы видимыми и интуитивно понятным для пользователя расположением.

Для реализации простого прокручиваемого списка используйте стандартные ленивые компоненты. Эти компоненты автоматически обрабатывают навигацию с помощью D-pad и выводят на экран сфокусированный элемент.

import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items

@Composable
fun MovieCatalog(movies: List<Movie>) {
    LazyRow {
        items(movies) { movie ->
            MovieCard(
                movie = movie,
                onClick = { /* Handle click */ }
            )
        }
    }
}

Настройте поведение прокрутки с помощью BringIntoViewSpec

Если для вашего дизайна требуется определенная «точка опоры» (например, чтобы сфокусированный элемент находился ровно на 30% от левого края), вы можете настроить поведение прокрутки с помощью BringIntoViewSpec . Это заменяет старую функциональность pivotOffsets , позволяя точно определить, как область просмотра должна прокручиваться для размещения сфокусированного элемента.

1. Определите пользовательский параметр BringIntoViewSpec

Следующий вспомогательный компонент позволяет определить «точку опоры» на основе долей родительского и дочернего элементов. parentFraction определяет, где в контейнере должен находиться элемент, а childFraction определяет, какая часть элемента будет совпадать с этой точкой.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PositionFocusedItemInLazyLayout(
    parentFraction: Float = 0.3f,
    childFraction: Float = 0f,
    content: @Composable () -> Unit,
) {
    val bringIntoViewSpec = remember(parentFraction, childFraction) {
        object : BringIntoViewSpec {
            override fun calculateScrollDistance(
                offset: Float,       // Item's initial position
                size: Float,         // Item's size
                containerSize: Float // Container's size
            ): Float {
                // Calculate the offset position of the item's leading edge.
                val initialTargetForLeadingEdge =
                    parentFraction * containerSize - (childFraction * size)
                // If the item fits in the container, and scrolling would cause
                // its trailing edge to be clipped, adjust targetForLeadingEdge
                // to prevent over-scrolling near the end of list.
                val targetForLeadingEdge = if (size <= containerSize &&
                    (containerSize - initialTargetForLeadingEdge) < size) {
                    // If clipped, align the item's trailing edge with the
                    // container's trailing edge.
                    containerSize - size
                } else {
                    initialTargetForLeadingEdge
                }
                // Return scroll distance relative to initial item position.
                return offset - targetForLeadingEdge
            }
        }
    }

    // Apply the spec to all scrollables in the hierarchy
    CompositionLocalProvider(
        LocalBringIntoViewSpec provides bringIntoViewSpec,
        content = content,
    )
}

2. Примените пользовательскую спецификацию.

Для применения позиционирования оберните ваши макеты в вспомогательный элемент. Это полезно для создания "единой линии фокусировки" на разных строках вашего каталога.

PositionFocusedItemInLazyLayout(
    parentFraction = 0.3f, // Pivot 30% from the edge
    childFraction = 0.5f   // Center of the item aligns with the pivot
) {
    LazyColumn {
        items(sectionList) { section ->
            // This row and its items will respect the 30% pivot
            LazyRow { ... }
        }
    }
}

3. Отказ от использования определенных вложенных макетов.

Если у вас есть определённый вложенный макет, который должен использовать стандартное поведение прокрутки вместо вашего пользовательского элемента Pivot, укажите DefaultBringIntoViewSpec :

private val DefaultBringIntoViewSpec = object : BringIntoViewSpec {}

PositionFocusedItemInLazyLayout {
    LazyColumn {
        item {
            // This row will ignore the custom pivot and use default behavior
            CompositionLocalProvider(LocalBringIntoViewSpec provides DefaultBringIntoViewSpec) {
                LazyRow { ... }
            }
        }
    }
}

По сути, передача пустого значения BringIntoViewSpec позволяет использовать поведение фреймворка по умолчанию.

Переход из фонда TV Foundation в фонд Compose Foundation

Специализированные для телевизоров отложенные макеты в androidx.tv.foundation устарели и заменены стандартными макетами Compose Foundation.

Обновления зависимостей

Убедитесь, что в вашем build.gradle используется версия 1.7.0 или выше для следующих параметров:

  • androidx.compose.foundation
  • androidx.compose.runtime

Сопоставление компонентов

Для миграции обновите импорт и удалите префикс Tv из ваших компонентов:

Устаревший телевизионный компонент Замена фундамента
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (через LocalBringIntoViewSpec)