Как возворащать фокус на то же view при клике (remote control) влево?
У меня есть такой экран (Andorid TV)
мне нужно сделать так:
- Когда экран открывается, фокус на первом вью в левой панели (Left Panel: 0)
- Юзер может переключать (на пульте) элементы в левой панели вверх вниз
- Если юзер нажимает вправо то фокус перемещается в правую панель на первый вью (Right panel: 0)
- Юзер может перемещать фокус как угодно в правой панели между его елементами
- Если юзер находясь в правой панели в левом стаке нажимает влево то фокус должен переместиться в левую панель на тот элемент с коротого он уходил в правую панель. То есть если юзер в левой панели был на 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 вопроса
- Как различать клик вправо между элементами правой панели (допустим Right Panel 3 -> Right Panel 2 клик) от нужного клика (допустим Righ Panel: 6 -> Left Panel 3)?
- Мне кажется, что такое решение выглядит слишком громоздко и как то это все должно работать проще.
Любые идеи приветствуются:)
UPD
У меня тут еще был смежный вопрос, если кто будет интересоваться велком - https://ru.stackoverflow.com/a/1586671/195957
Ответы (1 шт):
В итоге я использовал 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")
}
}
}
}
}