Jetpack Compose LazyRow焦点项报错:LayoutCoordinate操作仅在isAttached为true时有效
问题描述
我计划使用Jetpack Compose开发一款Android TV应用,页面中包含一个带有多个条目的LazyRow组件,需求是当条目获得焦点时改变其背景色与边框样式。但快速切换条目焦点时,出现了错误提示:LayoutCoordinate operations are only valid when isAttached is true。
相关代码:
@OptIn(ExperimentalTvMaterial3Api::class) @Composable fun MainScreen( mainViewModel: MainViewModel = viewModel() ) { val isLoading by mainViewModel.isLoading.collectAsState() if (isLoading){ Box(modifier = Modifier .fillMaxWidth() .padding(top = 16.dp), contentAlignment = Alignment.Center ){ Box(modifier = Modifier .size(40.dp) .background( color = Color.White, shape = CircleShape ) .padding(all = 8.dp) ){ CircularProgressIndicator( color = MaterialTheme.colorScheme.primary ) } } } else { LazyColumn( modifier = Modifier.padding(top = 16.dp) ) { items(mainViewModel.homeSections.size) { Section(homeSection = mainViewModel.homeSections[it]) } } } } @Composable fun Section(homeSection: HomeSection) { var selectedItem by remember { mutableIntStateOf(-1) } Column( modifier = Modifier .padding(bottom = 16.dp) ) { Row( horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .width(8.dp) .height(32.dp) .background( color = MaterialTheme.colorScheme.primary, shape = RoundedCornerShape( topEnd = 80.dp, bottomEnd = 80.dp ) ) ) Spacer(modifier = Modifier.width(16.dp)) Text( text = homeSection.name, style = TextStyle( fontWeight = FontWeight.W700 ) ) } Spacer(modifier = Modifier.height(16.dp)) LazyRow { items(homeSection.posts.size) { Column( modifier = Modifier .width(120.dp) .height(200.dp) .background(if (selectedItem == it) Color.Gray.copy(alpha = 0.3f) else Color.Transparent) .border( width = if (selectedItem == it) 2.dp else 0.dp, color = if (selectedItem == it) MaterialTheme.colorScheme.primary else Color.Transparent ) .onFocusChanged { f -> if (f.isFocused) selectedItem = it else if (selectedItem == it) selectedItem = -1 } .focusable() .clickable {} .padding( top = 8.dp, start = 8.dp, end = 8.dp ) ) { AsyncImage( model = homeSection.posts[it].thumbnail, contentDescription = "", contentScale = ContentScale.FillBounds, modifier = Modifier .fillMaxWidth() .height(140.dp) ) Spacer(modifier = Modifier.height(8.dp)) Text( text = homeSection.posts[it].title, style = TextStyle( fontSize = TextUnit( 12f, TextUnitType.Sp ) ), textAlign = TextAlign.Center, maxLines = 2, overflow = TextOverflow.Ellipsis ) } } } } }
解决方案
问题根源
快速切换焦点时,LazyRow的部分条目可能已被Compose的回收机制从布局树中移除(isAttached为false),但onFocusChanged回调仍在触发,此时修改状态会导致布局操作在未附着的组件上执行,引发错误。
具体修复步骤
1. 替换焦点回调并增加附着检查
用Modifier.onFocusEvent()替代onFocusChanged,在处理焦点事件前先判断组件是否已附着,避免无效的布局操作:
2. 优化后的Section组件代码
@Composable fun Section(homeSection: HomeSection) { var selectedItem by remember { mutableIntStateOf(-1) } Column( modifier = Modifier .padding(bottom = 16.dp) ) { // 标题部分保持不变 Row( horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .width(8.dp) .height(32.dp) .background( color = MaterialTheme.colorScheme.primary, shape = RoundedCornerShape( topEnd = 80.dp, bottomEnd = 80.dp ) ) ) Spacer(modifier = Modifier.width(16.dp)) Text( text = homeSection.name, style = TextStyle( fontWeight = FontWeight.W700 ) ) } Spacer(modifier = Modifier.height(16.dp)) LazyRow { items(homeSection.posts.size) { index -> Column( modifier = Modifier .width(120.dp) .height(200.dp) .background(if (selectedItem == index) Color.Gray.copy(alpha = 0.3f) else Color.Transparent) .border( width = if (selectedItem == index) 2.dp else 0.dp, color = if (selectedItem == index) MaterialTheme.colorScheme.primary else Color.Transparent ) .onFocusEvent { event -> // 先检查组件是否已附着在布局树中 if (event.isAttached) { when (event.type) { FocusEventType.Focus -> selectedItem = index FocusEventType.Unfocus -> { if (selectedItem == index) selectedItem = -1 } else -> {} } } } .focusable() .clickable {} .padding( top = 8.dp, start = 8.dp, end = 8.dp ) ) { AsyncImage( model = homeSection.posts[index].thumbnail, contentDescription = "", contentScale = ContentScale.FillBounds, modifier = Modifier .fillMaxWidth() .height(140.dp) ) Spacer(modifier = Modifier.height(8.dp)) Text( text = homeSection.posts[index].title, style = TextStyle(fontSize = 12.sp), textAlign = TextAlign.Center, maxLines = 2, overflow = TextOverflow.Ellipsis ) } } } } }
3. 额外优化建议
- 改用
TvLazyRow(来自androidx.tv.foundation.lazy.TvLazyRow)替代普通LazyRow,它针对TV的焦点导航和条目回收逻辑做了专门优化。 - 避免在Lazy组件的item中直接使用
it作为索引,改用命名参数(如index)提升代码可读性。
内容的提问来源于stack exchange,提问作者علیرضا شجاعی
相关产品推荐
相关产品推荐

