我正在开发一个关于 AndroidTV 的项目,该平台的独特之处在于用户使用遥控器将焦点移动到屏幕上。
这是屏幕:
当您专注工作时,有两个主要任务:
当您从左向右移动时,反之亦然,焦点不应混淆位置。例如,用户单击鼠标右键
Left Panel 0 -> Right Panel 0
,在右侧面板中我们移动到Right Panel 2
左侧单击,焦点移动到,Left Panel 0
因为用户将焦点移动到右侧面板。此功能已使用focusRestorer
并已在运行。当右侧面板中的用户单击(例如:) 时
RigthPanel 1
,它会打开Second Screen
,然后用户单击后退并返回到First Screen
,此处预计用于打开屏幕的按钮将成为焦点,即Right Panel 1
,但对于某些这是因为它在 30% 的情况下有效,并且焦点不是集中在预期的按钮上,而是另一个按钮上。
视频 -> https://drive.google.com/file/d/1NCal4kxx0op74-Yj5v00wOBcnCRSSlUb/view
以下是您可以复制并运行的代码:
private const val FIRST_SCREEN_ROUTE = "first_screen"
private const val SECOND_SCREEN_ROUTE = "second_screen"
private const val DEFAULT_FOCUS_POSITION = -1
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Test_delete_itTheme {
Surface(
modifier = Modifier.fillMaxSize(),
shape = RectangleShape
) {
Greeting()
}
}
}
}
}
@Composable
fun Greeting() {
val navigator: NavHostController = rememberNavController()
NavHost(
navController = navigator,
startDestination = FIRST_SCREEN_ROUTE
) {
composable(FIRST_SCREEN_ROUTE) {
DisposableEffect(Unit) {
Log.e("HERE", "1 CREATED first_screen_route")
onDispose {
Log.e("HERE", "DISPOSED first_screen_route")
}
}
FirstScreen(onClick = {
Log.e("HERE", "NAVIGATION TO SECOND SCREEN")
navigator.navigate(SECOND_SCREEN_ROUTE)
})
}
composable(SECOND_SCREEN_ROUTE) {
DisposableEffect(Unit) {
Log.e("HERE", "CREATED second_screen_route")
onDispose {
Log.e("HERE", "DISPOSED second_screen_route")
}
}
SecondScreen()
}
}
}
@Composable
fun SecondScreen() {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Red.copy(alpha = 0.1f)),
contentAlignment = Alignment.Center
) {
Text(text = "SECOND SCREEN")
}
}
@Composable
fun FirstScreen(
onClick: () -> Unit
) {
var focusBtnIdx by rememberSaveable { mutableIntStateOf(DEFAULT_FOCUS_POSITION) }
Row(modifier = Modifier
.fillMaxSize()
) {
LeftPanel()
RightPanel(onClick = onClick, focusBtnIdx = focusBtnIdx, setFocusBtnIdx = { focusBtnIdx = it })
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun RowScope.LeftPanel() {
val firstItemFr = remember { FocusRequester() }
val buttons by rememberSaveable { mutableStateOf(List(5) { "Button ${it + 1}" }) }
LaunchedEffect(Unit) {
this.coroutineContext.job.invokeOnCompletion {
try { firstItemFr.requestFocus() }
catch (e: Exception) {/* do nothing */ }
}
}
TvLazyColumn(
modifier = Modifier
.focusRestorer { firstItemFr }
.background(Color.Blue.copy(alpha = 0.1f))
.fillMaxHeight()
.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
itemsIndexed(
items = buttons,
key = { idx, _ -> idx }
) { idx, _ ->
Button(
modifier = Modifier
.let { modifier ->
if (idx == 0) {
modifier.focusRequester(firstItemFr)
} else {
modifier
}
},
onClick = {}
) {
Text(text = "Left Panel: $idx")
}
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun RowScope.RightPanel(
onClick: () -> Unit,
focusBtnIdx: Int,
setFocusBtnIdx: (Int) -> Unit
) {
val firstItemFr = remember { FocusRequester() }
LaunchedEffect(Unit) {
this.coroutineContext.job.invokeOnCompletion {
try {
Log.e("HERE", ">>> REQUEST FOCUS")
if (focusBtnIdx != DEFAULT_FOCUS_POSITION) {
firstItemFr.requestFocus()
Log.e("HERE", "<<< REQUEST FOCUS")
}
}
catch (e: Exception) {
/* do nothing */
Log.e("HERE", "FOCUS ERROR: $e")
}
}
}
Column(
modifier = Modifier
.background(Color.Green.copy(alpha = 0.1f))
.fillMaxHeight()
.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val buttons: List<String> by rememberSaveable { mutableStateOf(List(4) { "Button ${it + 1}" }) }
TvLazyVerticalGrid(
modifier = Modifier
.focusRestorer { firstItemFr }
.padding(16.dp),
columns = TvGridCells.Fixed(2),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
itemsIndexed(
items = buttons,
key = { idx, _ -> idx }
) { idx, _ ->
Button(
modifier = Modifier
.padding(8.dp)
.let {
Log.e("HERE", "1 RightPanel: $idx")
if (idx == focusBtnIdx || (focusBtnIdx == DEFAULT_FOCUS_POSITION && idx == 0)) {
Log.e("HERE", "2 RightPanel: $idx")
it.focusRequester(firstItemFr)
} else {
it
}
},
onClick = {
setFocusBtnIdx(idx)
onClick()
}
) {
Text(text = "Right Panel: $idx")
}
}
}
}
}
日志显示焦点已分配并在所需的按钮上调用,但由于某种原因,屏幕上的焦点位于另一个按钮上。
假设实现本身存在错误focusRequester
我缺少什么?
最后我找到了这个解决方案 -
这样我就能够配置焦点,使其按预期工作