最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

android - How to maintain column during focus traversal LazyVerticalGrid on TV - Stack Overflow

programmeradmin0浏览0评论

I've noticed an issue that consistently happens when combining LazyVerticalGrid default focus handling with smooth scroll animations. If I hold down the up button on the remote the grid will start up focusing to the item directly above the previous item in the same column then as the scroll speeds up it will jump over to the last column.

I have a guess as to why this is. The system does not see the target item in the layout so it defaults to the "closest" 1-dimensional item index the target row relative to the previous item which is always in the last (far right) column.

Any idea how to workaround this limitation and maintain column during focus traversal? I've tried setting up custom focus traversal with .focusProperties {} modifier, defining the up and down directions, but once the target item is no longer in the layout it throws an exception because it's trying to traverse to a FocusRequester the isn't installed.

Update: Goggle knows about this on their issue tracker. Appears it has to do with LazyGrid using the same "BeyondBoundsLayout modifier" as LazyList which looks only at the first offscreen item to move focus to, not the entire row.

Minimal Reproducible Example (Trouble Building? Paste into New Project -> Television -> Empty Activity template)

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val colors = arrayOf(
            Color.Red,
            Color(255, 165, 0),
            Color.Yellow,
            Color.Green
        )

        setContent {
            LazyVerticalGrid(
                state = rememberLazyGridState(),
                columns = GridCells.Fixed(4)
            ) {
                for (i in 0 until 100) {
                    item {
                        ClassicCard(
                            modifier = Modifier.padding(10.dp),
                            image = {
                                Box(modifier = Modifier
                                    .fillMaxWidth()
                                    .aspectRatio(16/9f)
                                    .background(colors[i % 4]))
                            },
                            title = {
                                Text("Item ${i + 1}")
                            },
                            onClick = {}
                        )
                    }
                }
            }
        }
    }
}

Custom Focus Traversal Example (Throws IllegalStateException if up or down button is held)

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val numItems = 100
        val cols = 4

        val colors = arrayOf(
            Color.Red,
            Color(255, 165, 0),
            Color.Yellow,
            Color.Green
        )

        val focusRequesters = Array(numItems) { FocusRequester() }

        setContent {
            LazyVerticalGrid(
                state = rememberLazyGridState(),
                columns = GridCells.Fixed(cols)
            ) {
                for (i in 0 until numItems) {
                    val addUpFocus = i - cols in focusRequesters.indices
                    val addDownFocus = i + cols in focusRequesters.indices

                    val focusPropertiesMod = when {
                        addUpFocus && addDownFocus -> {
                            Modifier.focusProperties {
                                up = focusRequesters[i - cols]
                                down = focusRequesters[i + cols]
                            }
                        }
                        addUpFocus -> {
                            Modifier.focusProperties {
                                up = focusRequesters[i - cols]
                            }
                        }
                        addDownFocus -> {
                            Modifier.focusProperties {
                                down = focusRequesters[i + cols]
                            }
                        }
                        else -> {
                            Modifier
                        }
                    }

                    item {
                        ClassicCard(
                            modifier = Modifier
                                .padding(10.dp)
                                .focusRequester(focusRequesters[i])
                                .then(focusPropertiesMod),
                            image = {
                                Box(modifier = Modifier
                                    .fillMaxWidth()
                                    .aspectRatio(16/9f)
                                    .background(colors[i % 4]))
                            },
                            title = {
                                Text("Item ${i + 1}")
                            },
                            onClick = {}
                        )
                    }
                }
            }
        }
    }
}

Dependencies

[versions]
agp = "8.6.0-alpha06"
kotlin = "1.9.0"
coreKtx = "1.15.0"
appcompat = "1.7.0"
composeBom = "2025.01.01"
tvFoundation = "1.0.0-alpha12"
tvMaterial = "1.0.0"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-compose-bom = { group = "androidxpose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui-tooling-preview = { group = "androidxpose.ui", name = "ui-tooling-preview" }
androidx-ui-tooling = { group = "androidxpose.ui", name = "ui-tooling" }
androidx-ui-test-manifest = { group = "androidxpose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidxpose.ui", name = "ui-test-junit4" }
androidx-tv-foundation = { group = "androidx.tv", name = "tv-foundation", version.ref = "tvFoundation" }
androidx-tv-material = { group = "androidx.tv", name = "tv-material", version.ref = "tvMaterial" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = ".jetbrains.kotlin.android", version.ref = "kotlin" }
发布评论

评论列表(0)

  1. 暂无评论