Как возворащать фокус на то же view при клике (remote control) влево?

У меня есть такой экран (Andorid TV)

введите сюда описание изображения

мне нужно сделать так:

  1. Когда экран открывается, фокус на первом вью в левой панели (Left Panel: 0)
  2. Юзер может переключать (на пульте) элементы в левой панели вверх вниз
  3. Если юзер нажимает вправо то фокус перемещается в правую панель на первый вью (Right panel: 0)
  4. Юзер может перемещать фокус как угодно в правой панели между его елементами
  5. Если юзер находясь в правой панели в левом стаке нажимает влево то фокус должен переместиться в левую панель на тот элемент с коротого он уходил в правую панель. То есть если юзер в левой панели был на 3м элементе и нажал вправо, то когда он возвращается с правой панели в левую фокус должен переметисться обратно на 3й элемент где и был до того.

В целом все работает так как и нужно, кроме 5го пункта

Вот такой код

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 leftPanelFocusRequester: FocusRequester = remember { FocusRequester() }
    val rightPanelFocusRequester: FocusRequester = remember { FocusRequester() }

    Row(modifier = Modifier
        .fillMaxSize()
    ) {
        LeftPanel(
            focusRequester = leftPanelFocusRequester,
            onRightDirectionClicked = {
                rightPanelFocusRequester.requestFocus()
            }
        )
        RightPanel(focusRequester = rightPanelFocusRequester)
    }
}

@Composable
fun RowScope.LeftPanel(
    focusRequester: FocusRequester,
    onRightDirectionClicked: () -> Unit
) {
    LaunchedEffect(Unit) {
        this.coroutineContext.job.invokeOnCompletion {
            focusRequester.requestFocus()
        }
    }

    Column(
        modifier = Modifier
            .background(Color.Blue.copy(alpha = 0.1f))
            .fillMaxHeight()
            .weight(1f)
            .onKeyEvent {
                if (it.key == Key.DirectionRight) {
                    onRightDirectionClicked()
                    true
                } else {
                    false
                }
            },
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        repeat(5) {
            Button(
                modifier = Modifier
                    .let { modifier ->
                        if (it == 0) {
                            modifier.focusRequester(focusRequester)
                        } else {
                            modifier
                        }
                    },
                onClick = { }
            ) {
                Text(text = "Left Panel: $it")
            }
        }
    }
}

@Composable
fun RowScope.RightPanel(focusRequester: FocusRequester) {
    Column(
        modifier = Modifier
            .background(Color.Green.copy(alpha = 0.1f))
            .fillMaxHeight()
            .weight(1f),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        val buttons by rememberSaveable { mutableStateOf(List(10) { "Button ${it + 1}" }) }

        LazyVerticalGrid(
            columns = GridCells.Fixed(2),
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            itemsIndexed(
                items = buttons,
                key = { idx, _ -> idx }
            ) { idx, _ ->
                Button(
                    modifier = Modifier
                        .padding(8.dp)
                        .let {
                            if (idx == 0) {
                                it.focusRequester(focusRequester)
                            } else {
                                it
                            }
                        }
                    ,
                    onClick = { }
                ) {
                    Text(text = "Right Panel: $idx")
                }
            }
        }
    }
}

Насколько я могу себе представить, то нужно запомнать индекс с коротого юзер ушел в правую панель (допустим кнопка 3) и трекать нажатие юзером влево, когда юзер нажимает влево то запрашивать фокус на сохраненый индекс (в нашем примере 3). Но тот 2 вопроса

  1. Как различать клик вправо между элементами правой панели (допустим Right Panel 3 -> Right Panel 2 клик) от нужного клика (допустим Righ Panel: 6 -> Left Panel 3)?
  2. Мне кажется, что такое решение выглядит слишком громоздко и как то это все должно работать проще.

Любые идеи приветствуются:)

UPD

У меня тут еще был смежный вопрос, если кто будет интересоваться велком - https://ru.stackoverflow.com/a/1586671/195957


Ответы (1 шт):

Автор решения: Sirop4ik

В итоге я использовал focusRestorer функциональность, таким способом она сама запоминает последнюю позицию фокуса.

Но нужно сказать, что это помогло мне найти решение проблемы которую я описал в вопросе, но появилась еще одна которую я описал в этом вопросе -> Как присвоить фокус на ту же позицию когда возвращаешся на экран? (Дайте знать если у кого будут какие то идеи)

Вот код который у меня получился как решение для вопроса который был задан в этом треде

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")
                }
            }
        }
    }
}
→ Ссылка