إنشاء تصاميم قابلة للتمرير على التلفزيون

بالنسبة إلى تطبيقات التلفزيون، تعتمد تجربة التصفّح على التنقّل الفعّال المستند إلى التركيز. باستخدام التصاميم الكسولة العادية في Compose Foundation، يمكنك إنشاء قوائم عمودية وأفقية عالية الأداء تتعامل تلقائيًا مع الانتقال المستند إلى التركيز لإبقاء العناصر النشطة في العرض.

تحسين السلوك التلقائي للتمرير على شاشات التلفزيون

بدءًا من الإصدار 1.7.0 من Compose Foundation، تتضمّن التصاميم العادية للتحميل البطيء (مثل LazyRow وLazyColumn) ميزة مدمجة لتحديد موضع التركيز. هذه هي الطريقة المقترَحة لإنشاء كتالوجات لتطبيقات التلفزيون، لأنّها تساعد في إبقاء العناصر المركّز عليها مرئية وموضّعة بشكل بديهي للمستخدم.

لتنفيذ قائمة أساسية قابلة للتمرير، استخدِم المكوّنات العادية التي يتم تحميلها عند الحاجة. تتعامل هذه المكوّنات تلقائيًا مع التنقّل باستخدام لوحة التحكّم الاتجاهية وتعرض العنصر الذي تم التركيز عليه.

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- إيقاف التضمين لتنسيقات متداخلة معيّنة

إذا كان لديك تنسيق متداخل معيّن يجب أن يستخدم سلوك التمرير العادي بدلاً من التمحور المخصّص، عليك تقديم 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 من المكوّنات باتّباع الخطوات التالية:

مكوّن التلفزيون المتوقّف نهائيًا استبدال Compose Foundation
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec (عبر LocalBringIntoViewSpec)