Status Update
Comments
ni...@google.com <ni...@google.com> #2
First of all thanks for this detailed issue.
This issue had been investigated thoroughly when it was first reported internally. The surprising detail in this report is that the issue is not reproducible before 1.7
. I will look into this.
The main problem with POBox is the fact that it is deprecated. Since 2021 Sony has been shipping new Xperia devices with Gboard pre-installed. Although we are aware that there is still a considerable amount of users still using POBox, the described behavior is caused by POBox's noncompliant behavior with InputConnection
and InputMethodManager
documentation. However, this is understandable since TextView
implementation was also not respecting the behavior that is expected from Editors.
Ultimately we have decided to enforce the documented behavior with specifically regards to when editors should call InputMethodManager.updateSelection
. Also, although unconfirmed, there were traces of possible custom code being included in Sony OEM images that changed how InputMethodManager was notified from TextView. If POBox also depended on something like this, it would be impossible for Compose code to replicate the same unknown behavior.
ra...@google.com <ra...@google.com> #3
Or is that option not available?
Even if the root cause is POBox, from the perspective of the app's customers, it looks like an app bug, so this issue is a blocker against updating Jetpack Compose.
ma...@gmail.com <ma...@gmail.com> #4
Just to be sure, it is dangerous to replace Compose TextField with Android View EditText as a workaround for this issue.
Compose 1.7 has a bug that causes ANR when the focus is on EditText.
Another View-related bug in Compose 1.7 is that an Android View is focused by calling FocusManager.clearFocus().
Perhaps there is a lack of testing of Compose 1.7 in combination with Android View. There is also a possibility that there are other fatal bugs related to View.
In other words, the only options for apps targeting the Japanese market that require POBox support are to continue using Compose 1.6 or to use EditText in combination with various workarounds.
wa...@reaktor.fi <wa...@reaktor.fi> #5
Project: platform/frameworks/support
Branch: androidx-main
Author: Halil Ozercan <
Link:
Fix POBox keyboard issue
Expand for full commit details
Fix POBox keyboard issue
Fix: 373743376
Fix: 329209241
Test: NullableInputConnectionWrapperTest
Change-Id: I94e0e598274fb88b255f977f9fbd50dfbbb1ecb1
Files:
- M
compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/NullableInputConnectionWrapperTest.kt
- M
compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/NullableInputConnectionWrapper.android.kt
Hash: 57f58c4b80d5d8470b2aca325dfdcd55f235231e
Date: Thu Oct 24 01:25:20 2024
hv...@gmail.com <hv...@gmail.com> #6
Many thanks again for this report. Especially for giving us a huge clue in terms of what could be going wrong. The fix is now merged and I will ask for a cherry-pick into a stable release.
na...@vitruvian.me <na...@vitruvian.me> #7
Do you have any concrete plan to cherry-pick the fix into current stable version (1.7.x)? We are currently waiting it.
ra...@google.com <ra...@google.com>
sz...@gmail.com <sz...@gmail.com> #8
Yes, this fix is planned to be included in a future 1.7.x
release.
ap...@google.com <ap...@google.com> #9
Thanks for the fix. Sorry to follow up on this. is it possible for you to share specific release version/date for the stable version? We are waiting on this to decide on our direction.
ap...@google.com <ap...@google.com> #10
Branch: androidx-main
commit 3b4ce572c2fa394843ff0082cc985c8d02228560
Author: Ralston Da Silva <ralu@google.com>
Date: Tue Feb 15 16:57:08 2022
BeyondBoundsLayout ModifierLocal
This CL adds a BeyondBoundsLayout modifier local.
Bug: 184670295
Test: ./gradlew compose:ui:ui:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.ui.layout.BeyondBoundsLayoutTest
Relnote: Added a BeyondBoundsLayout modifier local
Change-Id: If8b51c6e08a375d1c733588e53c9b07474c0855c
A compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/BeyondBoundsLayoutTest.kt
M compose/ui/ui/api/restricted_current.txt
M compose/ui/ui/api/current.txt
A compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/BeyondBoundsLayout.kt
M compose/ui/ui/api/public_plus_experimental_current.txt
ap...@google.com <ap...@google.com> #11
Branch: androidx-main
commit e5cb882c220a13f87054aa5ddcf6410b59c12d9a
Author: Ralston Da Silva <ralu@google.com>
Date: Fri Feb 11 14:11:09 2022
Adding Focus Group API
Add an API to specify groups of composables that should be treated
as a focus group. ie, we give priority to the items within the group
before we move focus to items outside the group.
Bug: 213508274
Bug: 184670295
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.focus.FocusGroupTest
Relnote: Added FocusGroup modifier
Change-Id: I64bc0b945bf172ad37b64d011d7055f4a99bfeca
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
M compose/foundation/foundation/api/public_plus_experimental_current.txt
A compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/focus/FocusDemos.kt
A compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/FocusGroupTest.kt
M compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/FocusableSample.kt
M compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/FocusableTest.kt
ap...@google.com <ap...@google.com> #12
Branch: androidx-main
commit fec4977a16aec28d1f56b80a941c4890189e35bc
Author: Ralston Da Silva <ralu@google.com>
Date: Wed Mar 16 01:15:29 2022
Change BeyondBoundsLayout API
The current API accepts two lambdas. This simplifies
it so that we use only one lambda parameter.
Bug: 184670295
Test: ./gradlew compose:ui:ui:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.ui.layout.BeyondBoundsLayoutTest
Relnote: N/A
Change-Id: Ic41edde9b83cd5f3a602f332e5f441cd00b0be47
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/BeyondBoundsLayoutTest.kt
M compose/ui/ui/api/restricted_current.txt
M compose/ui/ui/api/current.txt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/BeyondBoundsLayout.kt
M compose/ui/ui/api/public_plus_experimental_current.txt
ap...@google.com <ap...@google.com> #13
Branch: androidx-main
commit 137c8716674d40eaf9411819e8e5632a7653cb08
Author: Ralston Da Silva <ralu@google.com>
Date: Mon Mar 14 13:55:18 2022
Make Scrollable a focusGroup
This CL makes scrollable a focus group, which ensures that we visit
all the items in the scrollable before moving to the next focus group.
Textfield has a scrollable that is added before the focus modifier.
This causes issues with FocusRequester - The focusRequester is
associated with the focusGroup instead of the focusModifier. This
also causes issues with onFocusChanged observers that would see the
state of the deactivated focus modifier within the focusGroup instead
of the focus state of the focus modifier. I solved these issues by
swapping the order of the scrollable and focusModifier in CoreTextField.
Bug: 184670295
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.ScrollableTest
Change-Id: I149acc64301086f0fd69a99f1e3c816259637367
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
ap...@google.com <ap...@google.com> #14
Branch: androidx-main
commit e062417461d8b52a759fab284fdf771c1046f87b
Author: Ralston Da Silva <ralu@google.com>
Date: Thu Mar 17 15:02:33 2022
Adding a PinnableParent ModifierLocal
LazyLists provide a PinnableParent that can be used by components like
focusable to prevent the currently composed children from being disposed.
This CL introduces the PinnableParent interface that will later be
implemented by LazyList, LazyGrid etc.
Bug: 184670295
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.FocusableTest
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.lazy.layout.PinRequesterTest
Relnote: Added a PinnableParent API that allows children of lazy layouts to prevent the currently composed items from being disposed
Change-Id: Ibbdd02b0d25db2e0de343d5d2278287ab1991831
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
A compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/PinnableParent.kt
M compose/foundation/foundation/api/public_plus_experimental_current.txt
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/restricted_current.txt
A compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/PinnableParentTest.kt
M compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/FocusableSample.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/FocusableTest.kt
ni...@google.com <ni...@google.com> #15
Compose status update: Please update the Status
and Status Summary
fields of this bug! (or Public Status
/Public Status Summary
)
ap...@google.com <ap...@google.com> #16
Branch: androidx-main
commit 68c019d14d3ab4d1e38dbe927bdcd879c4f1d47b
Author: Ralston Da Silva <ralu@google.com>
Date: Fri Apr 08 11:58:17 2022
Updated BeyondBoundsLayout API
Added a BeyondBoundsLayoutScope that provides access to
a hasMoreItems property.
Bug: 184670295
Test: ./gradlew compose:ui:ui:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.ui.layout.BeyondBoundsLayoutTest
Relnote: N/A
Change-Id: I96f1bd707a48d3195bc4ef35948b6de1b02fca7b
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/BeyondBoundsLayoutTest.kt
M compose/ui/ui/api/restricted_current.txt
M compose/ui/ui/api/current.txt
M compose/ui/ui/api/public_plus_experimental_current.txt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/BeyondBoundsLayout.kt
ap...@google.com <ap...@google.com> #17
Branch: androidx-main
commit a1625c770a6b7a8c22860e7347ebd91626c8e4e0
Author: Ralston Da Silva <ralu@google.com>
Date: Thu Apr 21 01:43:21 2022
Focus in LazyList Demo
Add a demo to demonstrate how a user can move focus
within a lazyList by using the DPad.
Bug: 184670295
Test: N/A
Change-Id: I4c83e043d2042b951ec6e852a97bbcef1183f223
M compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
A compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ScrollableLazyRowFocusDemo.kt
ap...@google.com <ap...@google.com> #18
Branch: androidx-main
commit 7979439ccc7bed82fffa252aa4794f37dc8e565d
Author: Ralston Da Silva <ralu@google.com>
Date: Thu Apr 14 17:50:03 2022
Focus search should only consider placed items
Focus search should ignore items that are not placed.
Sometimes, we compose and measure items but don't place
them. These should be excluded from focus search. A
good example of this is when LazyLists reuse layout
nodes. The cached layout nodes are attached to the
hierarchy but should be ignored by focus search if
they are not currently in use.
Bug: 184670295
Test: ./gradlew compose:ui:ui:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.ui.focus.FocusSearchNonPlacedItemsTest
Change-Id: I995990df2a171bb08cc1be2ed1d1db35cd65027a
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
A compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusSearchNonPlacedItemsTest.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
ap...@google.com <ap...@google.com> #19
Branch: androidx-main
commit 86f7296f0e6b037823ab57ca32e271bee40c9fd5
Author: Ralston Da Silva <ralu@google.com>
Date: Thu Apr 14 13:53:13 2022
BeyondBoundsLayout modifier local for LazyList
LazyList now provides a BeyondBoundsLayout modifier local
that adds items in response to a request from its children.
LazyList has an optimization where it only places items
that are within visible bounds. This CL removes that
optimization so that we can place items beyond visible
bounds. This is needed for focus search anyway, since it
needs to search through the entire item to find the
focusable modifier.
Bug: 184670295
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.lazy.list.LazyListBeyondBoundsTest
Relnote: N/A
Change-Id: I01c19b6e92a9eac39bea74a6cf97c50b5f6f1a0c
M compose/foundation/foundation/api/public_plus_experimental_current.txt
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/restricted_current.txt
A compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
A compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListBeyondBoundsInfo.kt
A compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyBeyondBoundsModifier.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt
ap...@google.com <ap...@google.com> #20
Branch: androidx-main
commit 009c801a23d65b7a9d4d4cfc860386debf05db95
Author: Ralston Da Silva <ralu@google.com>
Date: Thu Apr 21 01:23:39 2022
Adding LazyList pinning modifier
Add a modifier that provides a PinnableParent modifier local
implementation for LazyList.
Bug: 184670295
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.lazy.list.LazyListPinningTest
Relnote: "Added experimental BeyondBoundsInterval that can be used
by custom implementations of LazyList when they layout items beyond visible bounds"
Change-Id: Ifabfbd95ba53bad23ce73bdb74f816c7854222bf
M compose/foundation/foundation/api/public_plus_experimental_current.txt
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/restricted_current.txt
A compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPinningTest.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyBeyondBoundsModifier.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
A compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListPinningModifier.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
ap...@google.com <ap...@google.com> #21
Branch: androidx-main
commit 43d92d01dd8deac0e51faed7c656adf4196155f1
Author: Ralston Da Silva <ralu@google.com>
Date: Tue Apr 26 16:09:07 2022
2D Focus Search in a LazyList
This CL adds support for 2D focus search in a lazylist
Bug: 184670295
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.lazy.list.LazyListFocusMoveTest
Relnote: N/A
Change-Id: Id672e565d3c7ea456ada76fe8bded0636c23e166
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
M compose/ui/ui/api/restricted_current.txt
M compose/ui/ui/api/current.txt
A compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
A compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/BeyondBoundsLayout.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
M compose/ui/ui/api/public_plus_experimental_current.txt
an...@nrk.no <an...@nrk.no> #22
We just updated to Compose 1.2.0-beta01 in our project and it works much better - but we still experience a few issues witht the focus jumping around.
In our setup we have a list of LazyRow
in a LazyColumn
. Simplified our setup can be stated like this (the issue is also with this example):
LazyColumn {
items(20) { verticalIndex ->
LazyRow {
items(20) { horizontalIndex ->
var color by remember { mutableStateOf(Color.White) }
Text(
text = "$verticalIndex,$horizontalIndex",
fontSize = 50.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.size(100.dp)
.border(2.dp, Color.Gray)
.onFocusChanged { color = if (it.isFocused) Color.Red else Color.White }
.background(color)
.focusable()
)
}
}
}
}
When moving within a row by holding down the d-pad (left or right) it might sometimes lag a bit, but in the end it will end up giving focus to an expected item. However, if we navigate between the rows by holding down the d-pad (up or down) it just circles the visible views. Same goes if you just tap the d-pad quickly, but it's more consistent when holding it down.
Are we using the combination of LazyLists wrong or is there still a bug with this custom case?
ap...@google.com <ap...@google.com> #24
Branch: androidx-main
commit ed9efae72e56ea2e3750ece34f914fa8d0bacd0d
Author: Ralston Da Silva <ralu@google.com>
Date: Tue May 24 12:26:35 2022
Fix focus search in nested lazylists
We weren't sending a pin request to grandparents, which is needed
to support focus search through nested lazylists. This CL fixes
that so that we can move focus among nested lazylists.
Bug: 184670295
Bug: 232033100
Fixes: 232033100
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.lazy.list.LazyListFocusMoveTest
Change-Id: Ia7603f3844160010ed75aaebd80a7ad83a9c1a73
M compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListPinningModifier.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
A compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/NestedLazyListFocusSearchDemo.kt
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
ar...@gmail.com <ar...@gmail.com> #25
Good day!
What if I need to provide concrete focus order on the page (focusProperties: next, previous) on the device with only two nav buttons left direction and right direction, how does should it look like?
I've tried to do example with fixes regarding focus in LazyColumn but it doesn't work for this case. Focus is going crazy.
Compose version: 1.2.0-beta02
Example 1 - Without setting Focus options for each element of list (will fail if go to the 'After btn 1' by pressing left btn direction 2 times and then one more time left)
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.*
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalComposeUiApi::class)
@Preview
@Composable
fun FocusTestPage() {
val focusManager = LocalFocusManager.current
val beforeFocus1 = remember { FocusRequester() }
val beforeFocus2 = remember { FocusRequester() }
val afterFocus1 = remember { FocusRequester() }
val afterFocus2 = remember { FocusRequester() }
val firstItemFocus = remember { FocusRequester() }
val lastItemFocus = remember { FocusRequester() }
Column(
modifier = Modifier.onKeyEvent {
if (it.type != KeyEventType.KeyDown)
return@onKeyEvent false
return@onKeyEvent when (it.key) {
Key.DirectionDown, Key.DirectionRight -> {
focusManager.moveFocus(FocusDirection.Next)
true
}
Key.DirectionUp, Key.DirectionLeft -> {
focusManager.moveFocus(FocusDirection.Previous)
true
}
else -> false
}
}
) {
Row {
FocusableBtn(
text = "Before btn 1",
modifier = Modifier.focusProperties {
previous = afterFocus2
next = beforeFocus2
},
focusRequester = beforeFocus1
)
FocusableBtn(
text = "Before btn 2",
modifier = Modifier.focusProperties {
previous = beforeFocus1
next = firstItemFocus
},
focusRequester = beforeFocus2
)
}
LazyColumn(
modifier = Modifier.height(300.dp)
) {
items(20) { i ->
when (i) {
0 -> {
FocusableBtn(
text = "First list btn $i",
modifier = Modifier.focusProperties {
previous = beforeFocus2
},
focusRequester = firstItemFocus
)
}
19 -> {
FocusableBtn(
text = "Last list btn $i",
modifier = Modifier.focusProperties {
next = afterFocus1
},
focusRequester = lastItemFocus
)
}
else -> {
FocusableBtn(
text = "List btn $i"
)
}
}
}
}
Row {
FocusableBtn(
text = "After btn 1",
modifier = Modifier.focusProperties {
previous = lastItemFocus
next = afterFocus2
},
focusRequester = afterFocus1
)
FocusableBtn(
text = "After btn 2",
modifier = Modifier.focusProperties {
previous = afterFocus1
next = beforeFocus1
},
focusRequester = afterFocus2
)
}
}
}
@Composable
fun FocusableBtn(
text: String,
modifier: Modifier = Modifier,
focusRequester: FocusRequester = FocusRequester(),
) {
var color by remember { mutableStateOf(Color.White) }
OutlinedButton(
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged {
color = if (it.isFocused) Color.Red else Color.White
}
.then(modifier)
.focusable(),
onClick = { /*TODO*/ },
border = BorderStroke(2.dp, color)
) {
Text(text = text)
}
}
Example 2 - Setting focus options for each LazyList item (will fail with java.lang.IllegalStateException: FocusRequester is not initialized.
)
package com.augvantis.careviewjetpack.pages
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.*
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalComposeUiApi::class)
@Preview
@Composable
fun FocusTestPage() {
val focusManager = LocalFocusManager.current
val beforeFocus1 = remember { FocusRequester() }
val beforeFocus2 = remember { FocusRequester() }
val afterFocus1 = remember { FocusRequester() }
val afterFocus2 = remember { FocusRequester() }
val focusList = remember { List(20) { FocusRequester() } }
Column(
modifier = Modifier.onKeyEvent {
if (it.type != KeyEventType.KeyDown)
return@onKeyEvent false
return@onKeyEvent when (it.key) {
Key.DirectionDown, Key.DirectionRight -> {
focusManager.moveFocus(FocusDirection.Next)
true
}
Key.DirectionUp, Key.DirectionLeft -> {
focusManager.moveFocus(FocusDirection.Previous)
true
}
else -> false
}
}
) {
Row {
FocusableBtn(
text = "Before btn 1",
modifier = Modifier.focusProperties {
previous = afterFocus2
next = beforeFocus2
},
focusRequester = beforeFocus1
)
FocusableBtn(
text = "Before btn 2",
modifier = Modifier.focusProperties {
previous = beforeFocus1
next = focusList[0]
},
focusRequester = beforeFocus2
)
}
LazyColumn(
modifier = Modifier.height(300.dp)
) {
items(20) { i ->
when (i) {
0 -> {
FocusableBtn(
text = "First list btn $i",
modifier = Modifier.focusProperties {
previous = beforeFocus2
next = focusList[1]
},
focusRequester = focusList[0]
)
}
19 -> {
FocusableBtn(
text = "Last list btn $i",
modifier = Modifier.focusProperties {
previous = focusList[18]
next = afterFocus1
},
focusRequester = focusList[19]
)
}
else -> {
FocusableBtn(
text = "List btn $i",
modifier = Modifier.focusProperties {
previous = focusList[i-1]
next = focusList[i+1]
},
focusRequester = focusList[i]
)
}
}
}
}
Row {
FocusableBtn(
text = "After btn 1",
modifier = Modifier.focusProperties {
previous = focusList[19]
next = afterFocus2
},
focusRequester = afterFocus1
)
FocusableBtn(
text = "After btn 2",
modifier = Modifier.focusProperties {
previous = afterFocus1
next = beforeFocus1
},
focusRequester = afterFocus2
)
}
}
}
@Composable
fun FocusableBtn(
text: String,
modifier: Modifier = Modifier,
focusRequester: FocusRequester = FocusRequester(),
) {
var color by remember { mutableStateOf(Color.White) }
OutlinedButton(
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged {
color = if (it.isFocused) Color.Red else Color.White
}
.then(modifier)
.focusable(),
onClick = { /*TODO*/ },
border = BorderStroke(2.dp, color)
) {
Text(text = text)
}
}
Example of focus ordering in image attachment
Thanks, Vadim
ap...@google.com <ap...@google.com> #26
Branch: androidx-main
commit b1fbc771c5675eed692fd69fda1653f5147f9594
Author: Ralston Da Silva <ralu@google.com>
Date: Thu Jun 02 16:10:27 2022
Add 1D Focus Search support for Lazylists
1D focus search currently traverses items in the order they
were composed. However this doesn't work for lazylists which
reuses composed items. So we use placement order instead.
Bug: 184670295
Fixes: 184670295
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.lazy.list.LazyListFocusMoveTest
Change-Id: I77bfa2a3017934726d3c4149ccfd30d8f682a5ae
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
M compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ScrollableLazyRowFocusDemo.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListFocusMoveTest.kt
M compose/ui/ui/lint-baseline.xml
ra...@google.com <ra...@google.com> #27
Hi Vadim,
In your example above, the issue seems to be that you are adding a focusable to OutlinedButton. Buttons use a focusable internally, so you don't need to add another one. I tried out your example, and just commenting out the line with focusable()
from FocusableBtn
seems to fix the issue.
However we can simplify your sample a little, after you switch to a build that includes
@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
@Preview
@Composable
fun FocusTestPage2() {
val focusManager = LocalFocusManager.current
fun Modifier.only1DFocusSearch() = onKeyEvent {
if (it.type != KeyDown) return@onKeyEvent false
when (it.key) {
DirectionDown, DirectionRight -> { focusManager.moveFocus(Next); true }
DirectionUp, DirectionLeft -> { focusManager.moveFocus(Previous); true }
else -> false
}
}
Column(Modifier.only1DFocusSearch().focusGroup()) {
Row(Modifier.focusGroup()) {
FocusableBtn("Before btn 1")
FocusableBtn("Before btn 2")
}
LazyColumn(Modifier.height(300.dp)) {
items(20) {
FocusableBtn("List btn $it")
}
}
Row(Modifier.focusGroup()) {
FocusableBtn("After btn 1")
FocusableBtn("After btn 2")
}
}
}
@Composable
fun FocusableBtn(text: String, modifier: Modifier = Modifier) {
var color by remember { mutableStateOf(White) }
OutlinedButton(
modifier = modifier.onFocusChanged { color = if (it.isFocused) Red else White },
onClick = { /*TODO*/ },
border = BorderStroke(2.dp, color)
) {
Text(text = text)
}
}
Note: An additional unrelated suggestion: Outlined Button already provides focused indication so you don't have to add the border around the box when it is focused, (unless of course, you have some use-case that needs it).
na...@vitruvian.me <na...@vitruvian.me> #28
Hi Ralston,
Apologies for pinging this issue. Not sure if its the right place but I just wanted to follow up what control we have on the scroll behaviour / if I have missed something. This is based on the Android TV DPAD use case.
We have a layout similar to below (i've also attached a sample at the end + a screenshot)
LazyColumn(state = lazyColumnListState) {
items(COLUMN_ITEM_COUNT) { columnIndex ->
Column(
modifier = Modifier
) {
Text(
"I am section $columnIndex"
)
LazyRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
items(ROW_ITEM_COUNT) { rowIndex ->
FocusableItem()
..........................
If we rely on the bringinto view provided by the lazy list then when scrolling up only the row is brought into view (without the title).
We then naturally end up trying to use animateScrollToItem which then runs into the issues of cancelled scrolls caused by bring into view.
This / workarounds are talked about here
Is there better way to control the behaviour of lazy list scrolling that I have missed or is the best workaround to copy focusable and clickable (clickable is harder as it references package internal items) into source and remove the bringintoview call?
Examples of all the stuff mentioned above with comments available here
Thanks! Nathan
ra...@google.com <ra...@google.com> #29
Hi Nathan,
Thanks for raising this issue. Instead of trying to cancel the default bringIntoView behavior and start a new animation, you can intercept the bringIntoView request instead:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BringTitleIntoView(){
LazyColumn {
items(10) { columnIndex ->
val bringIntoViewRequester = remember { BringIntoViewRequester() }
val bringIntoViewResponder = remember { CustomBringIntoViewResponder(bringIntoViewRequester) }
Column(
modifier = Modifier
.bringIntoViewRequester(bringIntoViewRequester)
.bringIntoViewResponder(bringIntoViewResponder)
) {
Text("I am section $columnIndex")
LazyRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
items(10) { rowIndex ->
Text(text = "$columnIndex $rowIndex", Modifier.focusable())
}
}
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
private class CustomBringIntoViewResponder(private val parent: BringIntoViewRequester) : BringIntoViewResponder {
@ExperimentalFoundationApi
override fun calculateRectForParent(localRect: Rect): Rect {
return localRect
}
@ExperimentalFoundationApi
override suspend fun bringChildIntoView(localRect: Rect) {
parent.bringIntoView()
}
}
an...@nrk.no <an...@nrk.no> #30
Hi Ralston.
Thanks for your replies here and in the other thread. In our case we want to center the focused row (so whatever is focused is in the middle of the screen), but struggle to do so. Seems like CustomBringIntoViewResponder
is quite relevant in this case, but we experience that we "overshoot" when using the localrect
provided by bringChildIntoView
- at least for the views that aren't visible when you move focus.
We have tried to dig a bit into the implementation and think it might be related to that the underlying scrollable starts the scroll and thus localRect
isn't up to date when we receive it (or when we cancel the scroll by start scrolling). In that case we might need to implement our own LazyColumn/LazyRow (as you also suggest here:
We've tried to return various rects for calculateRectForParent
without any luck. We'll be happy to provide more info if helps.
Best regards Anders
an...@nrk.no <an...@nrk.no> #31
Hi again.
Just tried to make a simple example that shows our issue. It's really a simple version of it, but it still happens in this case. Here we're only using a lazy row, but happens for columns. If you uncomment the to log-statmenst (and print them to log) it's clear that when you navigate to item with index 4 (that's not fully visible) we're basing out calculations on an outdated localRect
.
Hope this shows what our issue is.
Best regards Anders
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun BringIntoViewCentered() {
val rowListState = rememberLazyListState()
val bringIntoViewResponder = remember(rowListState) { BringIntoViewCenterResponder(rowListState) }
LazyRow(
state = rowListState,
modifier = Modifier.bringIntoViewResponder(bringIntoViewResponder)
) {
listOf(1.until(10), 8.downTo(1)).flatten().forEachIndexed { index, itemFraction ->
item {
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
Text(
text = "#$index",
textAlign = TextAlign.Center,
modifier = Modifier
.fillParentMaxWidth(fraction = itemFraction.toFloat() / 10)
.focusable(interactionSource = interactionSource)
.background(if (isFocused) Color.Green else if (itemFraction % 2 == 0) Color.Red else Color.Blue)
.padding(vertical = 16.dp)
)
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
class BringIntoViewCenterResponder(private val listState: LazyListState) : BringIntoViewResponder {
@ExperimentalFoundationApi
override fun calculateRectForParent(localRect: Rect): Rect {
return localRect
}
@ExperimentalFoundationApi
override suspend fun bringChildIntoView(localRect: Rect) {
val parentWidth = listState.layoutInfo.viewportSize.width
val destinationLeft = (parentWidth / 2) - (localRect.width / 2)
val scrollDelta = localRect.left - destinationLeft
listState.stopScroll() // Just for debug purposes, animateScrollBy would normally do this
// When scrolling to item #4 (zero-indexed) this clearly shows that the list has been scrolled after localRect was calculated
// LOG - expected left offset: "Local rect left: ${localRect.left}"
// LOG - actual left offset: "Visible items offset after scroll cancel: ${listState.layoutInfo.visibleItemsInfo.joinToString("; ") { "#${it.index}: ${it.offset}" }}"
listState.animateScrollBy(scrollDelta)
}
}
fa...@gmail.com <fa...@gmail.com> #32
With 1.2.0-rc03, we are also running into the same D-Pad focus issues with nested LazyLists as described in #22. I'm guessing that the fix described in #23 hasn't landed in a release yet?
is...@fubo.tv <is...@fubo.tv> #33
ap...@google.com <ap...@google.com> #34
Branch: androidx-main
commit ec0b0c6c04008d708ffa52cd6413a48ff50ff34a
Author: Ralston Da Silva <ralu@google.com>
Date: Mon Aug 01 16:30:34 2022
Fix Incorrect Traversal Order in 1D Focus Search
1D Focus search visits children in composition order. However,
if items are re-used (Eg. LazyList), we use placement order.
When we added a fix for LazyLists, we ended up introducing a
bug where we have an incorrect traversal order if focusable
siblings have different layoutnode parents. This CL fixes this
issue by using the placement order relative to the nearest
common ancestor instead of using the placement order of the
layout node the focus modifier is connected to.
Bug: 238210250
Bug: 184670295
Fixes: 238210250
Test: ./gradlew compose:f:f:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.foundation.lazy.list.LazyListFocusMoveTest
Test: ./gradlew compose:ui:ui:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.ui.focus.OneDimensionalFocusSearchNextTest
Test: ./gradlew compose:ui:ui:cC -P android.testInstrumentationRunnerArguments.class=androidx.compose.ui.focus.OneDimensionalFocusSearchPreviousTest
Change-Id: I400efc722521516aef9449db6bd3ef403fa710e0
M compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearch.kt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchPreviousTest.kt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/OneDimensionalFocusSearchNextTest.kt
vr...@gmail.com <vr...@gmail.com> #35
Hi there,
I'm trying to use LazyVerticalGrid
with Dpad and noticing that the items do not scroll past the visible items. I assume as per LazyVerticalGrid
as well.
Using compose version: 1.3.0-alpha03
Here is an example to test the focus behavior:
@Composable
fun GridExample() {
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
items(100) { index ->
FocusableBox() {
Text("$index")
}
}
}
}
@Composable
private fun FocusableBox(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit = {}
) {
var borderColor by remember { mutableStateOf(Color.Black) }
Box(
modifier = modifier
.size(100.dp)
.padding(2.dp)
.onFocusChanged { borderColor = if (it.isFocused) Color.Red else Color.Black }
.border(2.dp, borderColor)
.focusable(),
content = content
)
}
ra...@google.com <ra...@google.com> #36
@vrkovvuru: The code changed over time, and now LazyGrid uses a separate implementation. We need to repeat this implementation for LazyGrid. Filed
do...@gmail.com <do...@gmail.com> #37
Hi there,
I'm trying to use LazyRow in LazyColumn with Dpad and I saw some delay with move focus to down. It seems to be due to the recomposition of all items in the LazyRow from which the focus is moved. Then more elements in LazyRaw, the greater the delay when moving focus
Using compose version: 1.2.0
Here is an example to test the focus behavior:
LazyColumn {
items(20) { verticalIndex ->
LazyRow{
items(500) { horizontalIndex ->
println("There is recomposition item with index=$verticalIndex,$horizontalIndex")
var color by remember { mutableStateOf(Color.White) }
Text(
text = "$verticalIndex,$horizontalIndex",
fontSize = 50.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.size(100.dp)
.border(2.dp, Color.Gray)
.onFocusChanged { color = if (it.isFocused) Color.Red else Color.White }
.background(color)
.focusable()
)
}
}
}
}
sa...@gmail.com <sa...@gmail.com> #38
Hello, I'm trying to replicate Leanback focus behavior and failed. What I'm came up with is that I totally abandoned LazyColumn DPad handling and overrided keyEvent. I store focus requesters for each item in the hashmap and track selected position in a state variable. Then I animate scroll gallery row to the center of the window and then request focus.
.onKeyEvent {
// completeley custom focus handling
// the relevant issue https://issuetracker.google.com/issues/184670295
// it may be better in the future
val code = it.nativeKeyEvent.keyCode
var pos = focusedItemPosition.value
var move = false
var isUp = false
var direction = FocusDirection.Next
if (it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) {
when (code) {
KeyEvent.KEYCODE_DPAD_LEFT -> {
direction = FocusDirection.Left
move = true
pos--
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
direction = FocusDirection.Right
move = true
pos++
}
KeyEvent.KEYCODE_DPAD_UP -> {
direction = FocusDirection.Up
move = true
pos -= columnsCount
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
direction = FocusDirection.Down
move = true
pos += columnsCount
}
else -> Unit
}
isUp = pos < 0
pos = pos.coerceIn(0, collectionItems.lastIndex)
focusedItemPosition.value = pos
}
if (move) {
scope.launchSafe {
if (focusedItemPosition.value != pos) return@launchSafe
if (isUp) {
lazyGridState.animateScrollToItem(0, 0)
focusManager.moveFocus(FocusDirection.Up)
} else {
val animateTo = (pos / columnsCount) + 1
lazyGridState.animateScrollToItem(animateTo, -150)
val fr = focusRequesterByPosition[pos]?.invoke()
if (fr == null) {
focusManager.moveFocus(direction) // possible fix crash
} else {
fr.requestFocus()
}
}
}
}
return@onKeyEvent move
}
Sometimes, however, focus jumps to unknown location and causing a crash :(
pi...@gmail.com <pi...@gmail.com> #39
Compose 1.3.2.
na...@google.com <na...@google.com> #41
The following release(s) address this bug:
androidx.compose.foundation:foundation:1.3.0
androidx.compose.ui:ui:1.3.0
pi...@gmail.com <pi...@gmail.com> #42
It still looks broken/slow in 1.4.0-alpha01
.
[Deleted User] <[Deleted User]> #43
Very slow in 1.4.0-alpha02
. The issue seems to be when BringIntoViewRequester is used
ni...@svt.se <ni...@svt.se> #44
Problem seems to be happening directly when using FocusGroup and FocusProperties on both LazyRow an TvLazyRow.
el...@gmail.com <el...@gmail.com> #45
I investigated the issue mentioned in #37, #39, #42, #43 and #44 because it also critically impacts me and created a new bug:
wa...@gmail.com <wa...@gmail.com> #46
I am using androidx.compose.ui:ui:1.5.0. I found focus will jump to unexpected item when using LazyVerticalGrid.
@Composable
fun GridView() {
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 128.dp)) {
items(100) { index ->
FocusableBox(index = index) {
Text("$index")
}
}
}
}
@Composable
private fun FocusableBox(
modifier: Modifier = Modifier,
index: Int,
content: @Composable BoxScope.() -> Unit = {}
) {
var borderColor by remember { mutableStateOf(Color.Black) }
Box(
modifier = modifier
.size(100.dp)
.padding(2.dp)
.onFocusChanged {
Log.d("Test", "onFocusChanged index: $index $it")
borderColor = if (it.isFocused) Color.Red else Color.Black
}
.border(2.dp, borderColor)
.focusable(),
content = content
)
}
Each row has 7 items. Current focus item is 0. After press Dpad key down, the expected focus item should be 7. But it moved to 8.
Output log:
2023-08-29 14:52:01.493 23514-23514 onFocusChanged index: 0 Inactive
2023-08-29 14:52:01.504 23514-23514 onFocusChanged index: 7 Active
2023-08-29 14:52:01.550 23514-23514 onFocusChanged index: 7 Inactive
2023-08-29 14:52:01.551 23514-23514 onFocusChanged index: 8 Active
Description
Add support for moving focus in a LazyList. When we reach the end of the list, the list should automatically scroll.