יצירת פריסות שניתן לגלול בהן בטלוויזיה

באפליקציות לטלוויזיה, חוויית הגלישה מסתמכת על ניווט יעיל שמבוסס על מיקוד. בעזרת פריסות עם טעינה מדורגת (lazy) רגילות של Compose Foundation, אתם יכולים ליצור רשימות אנכיות ואופקיות עם ביצועים גבוהים, שמטפלות באופן אוטומטי בגלילה מבוססת-מיקוד כדי להשאיר את הפריטים הפעילים בתצוגה.

התנהגות ברירת המחדל של הגלילה מותאמת לטלוויזיה

החל מגרסה 1.7.0 של Compose Foundation, פריסות רגילות עם טעינה מדורגת (כמו 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. החלת המפרט בהתאמה אישית

כדי להחיל את המיקום, צריך לעטוף את הפריסות בעזרת ה-helper. האפשרות הזו שימושית ליצירת "קו מיקוד עקבי" בשורות שונות בקטלוג.

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. ביטול ההסכמה לפריסות ספציפיות בתצוגת עץ

אם יש לכם פריסה ספציפית בתצוגת עץ שצריכה להשתמש בהתנהגות גלילה רגילה במקום בציר המותאם אישית, צריך לספק את 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

הפריסות בטעינה מדורגת (lazy) הספציפיות לטלוויזיה ב-androidx.tv.foundation הוצאו משימוש, ובמקומן משתמשים בפריסות הרגילות של Compose Foundation.

עדכוני תלויות

מוודאים שב-build.gradle מותקנת גרסה 1.7.0 ואילך של:

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

מיפוי רכיבים

כדי להעביר, צריך לעדכן את הייבוא ולהסיר את הקידומת Tv מהרכיבים:

רכיב טלוויזיה שהוצא משימוש החלפת Compose Foundation
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (באמצעות LocalBringIntoViewSpec)