У меня есть TvLazyRows внутри TvLazyColumn. Когда я перехожу к концу всех списков (позиция [20,20]), перехожу к следующему экрану и возвращаюсь назад, фокус восстанавливается до первой видимой позиции [15,1], а не до позиции, где я был до [20,20 ]. Как я могу восстановить фокус в какой-то конкретной позиции?
class MainActivity : ComponentActivity() {
private val rowItems = (0..20).toList()
private val rows = (0..20).toList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
MyAppNavHost(navController = navController)
}
}
@Composable
fun List(navController: NavController) {
val fr = remember {
FocusRequester()
}
TvLazyColumn( modifier = Modifier
.focusRequester(fr)
.fillMaxSize()
,
verticalArrangement = Arrangement.spacedBy(16.dp),
pivotOffsets = PivotOffsets(parentFraction = 0.05f),
) {
items(rows.size) { rowPos ->
Column() {
Text(text = "Row $rowPos")
TvLazyRow(
modifier = Modifier
.height(70.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
pivotOffsets = PivotOffsets(parentFraction = 0.0f),
) {
items(rowItems.size) { itemPos ->
var color by remember {
mutableStateOf(Color.Green)
}
Box(
Modifier
.width(100.dp)
.height(50.dp)
.onFocusChanged {
color = if (it.hasFocus) {
Color.Red
} else {
Color.Green
}
}
.background(color)
.clickable {
navController.navigate("details")
}
) {
Text(text = "Item ${itemPos.toString()}", Modifier.align(Alignment.Center))
}
}
}
}
}
}
LaunchedEffect(true) {
fr.requestFocus()
}
}
@Composable
fun MyAppNavHost(
navController: NavHostController = rememberNavController(),
startDestination: String = "list"
) {
NavHost(
navController = navController,
startDestination = startDestination
) {
composable("details") {
Details()
}
composable("list") { List(navController) }
}
}
@Composable
fun Details() {
Box(
Modifier
.background(Color.Blue)
.fillMaxSize()) {
Text("Second Screen", Modifier.align(Alignment.Center), fontSize = 48.sp)
}
}
}
версии
dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation 'androidx.activity:activity-compose:1.7.1'
implementation platform('androidx.compose:compose-bom:2022.10.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
// Compose for TV dependencies
def tvCompose = '1.0.0-alpha06'
implementation "androidx.tv:tv-foundation:$tvCompose"
implementation "androidx.tv:tv-material:$tvCompose"
def nav_version = "2.5.3"
implementation "androidx.navigation:navigation-compose:$nav_version"
}
Я попытался передать FocusRequestor каждому фокусируемому элементу внутри списка. В этом случае мне удалось восстановить фокус. Но для большого количества элементов внутри списка он начинает падать с OutOfMemmoryError. Поэтому мне нужно другое решение.
В Jetpack Compose навигация по своей природе не имеет состояния, что означает, что состояние фокуса не сохраняется по умолчанию. Чтобы достичь этого, мы должны сами поддерживать состояние (положение элемента).
Ниже представлено предлагаемое решение, которое вы можете интегрировать в свой код. Обратите внимание, что это решение работает с предположением, что элементы вашего списка не изменяются динамически. Если они это сделают, вам, возможно, придется немного изменить логику:
private var lastFocusedItem by rememberSaveable{ mutableStateOf(Pair(0, 0)) }
lastFocusedItem..onFocusChanged { focusState ->
if (focusState.hasFocus) {
lastFocusedItem = Pair(rowPos, itemPos)
}
...
}
LaunchedEffect(true) {
// Request focus to the last focused item
focusRequesters[lastFocusedItem]?.requestFocus()
}
Чтобы достичь последней точки, нам нужна карта FocusRequesters. Мы должны использовать карту с ключами в качестве позиций элементов (rowPos, itemPos) и значениями в качестве FocusRequesters.
Вот обновленная часть вашего кода, которая поддерживает и восстанавливает фокус последнего перемещенного элемента.
Это двухэтапный процесс:
FocusRequester в качестве значений. Используйте RememberSaveable, чтобы сохранить значение во время навигации по экрану.FocusRequester для каждого предмета и добавьте его на карту focusRequesters.Ваш обновленный составной List может выглядеть примерно так:
@Composable
fun List(navController: NavController) {
val focusRequesters = remember { mutableMapOf<Pair<Int, Int>, FocusRequester>() }
var lastFocusedItem by rememberSaveable{ mutableStateOf(Pair(0, 0)) }
TvLazyColumn(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(16.dp),
pivotOffsets = PivotOffsets(parentFraction = 0.05f),
) {
items(rows.size) { rowPos ->
Column() {
Text(text = "Row $rowPos")
TvLazyRow(
modifier = Modifier
.height(70.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
pivotOffsets = PivotOffsets(parentFraction = 0.0f),
) {
items(rowItems.size) { itemPos ->
var color by remember { mutableStateOf(Color.Green) }
val fr = remember { FocusRequester() }
focusRequesters[Pair(rowPos, itemPos)] = fr
Box(
Modifier
.width(100.dp)
.height(50.dp)
.focusRequester(fr)
.onFocusChanged {
color = if (it.hasFocus) {
lastFocusedItem = Pair(rowPos, itemPos)
Color.Red
} else {
Color.Green
}
}
.background(color)
.clickable {
navController.navigate("details")
}
) {
Text(text = "Item ${itemPos.toString()}", Modifier.align(Alignment.Center))
}
}
}
}
}
}
LaunchedEffect(true) {
focusRequesters[lastFocusedItem]?.requestFocus()
}
}
PS. иметь компонуемые методы - плохая идея. Составные должны быть чистыми функциями без побочных эффектов.
@ Ремко Шур. с requestFocus нужно быть осторожным. Но в этом случае TvLazyRow восстанавливает положение прокрутки. И этот код (LaunchedEffect(true)) будет запущен только после завершения композиции. Итак, все видимые элементы созданы. Кроме того, нулевая проверка поможет избежать сбоев. Мы добавляем FocusRequesters на карту focusRequesters только после добавления элемента.
Хотя трудно дать правильный ответ на этот вопрос, предложенный здесь подход имеет несколько потенциальных проблем. Во-первых, запрос фокуса при добавлении этого компонуемого в композицию может привести к непредсказуемым изменениям фокуса во многих сценариях. Еще одна вещь, на которую следует обратить внимание, это то, что есть большая вероятность, что последний элемент в фокусе еще не составлен при возврате, что приведет к сбою, если вы попытаетесь вызвать requestFocus для соответствующего FocusRequester.