Status Update
Comments
ni...@google.com <ni...@google.com> #2
see
ra...@google.com <ra...@google.com> #3
ma...@gmail.com <ma...@gmail.com> #4
wa...@reaktor.fi <wa...@reaktor.fi> #5
Nevermind. Sorry about my last comment. Thanks for making me realize the bug is public. I'll share it with partners to gauge their interest through their +1s.
hv...@gmail.com <hv...@gmail.com> #6
Is there any progress on this issue?
na...@vitruvian.me <na...@vitruvian.me> #7
According to [¡a conversation from the Compose slack channel on May 2021(see
Now that the support for downloadable fonts in Compose is fixed (April 2022), what is the state of the Emoji compatibility in Compose?
ra...@google.com <ra...@google.com>
sz...@gmail.com <sz...@gmail.com> #8
Yep, static text integration is unblocked.
Editable text will take more work.
ap...@google.com <ap...@google.com> #9
Doing planning now, will take comment as a vote for priority :)
ap...@google.com <ap...@google.com> #10
bugjuggler:
ap...@google.com <ap...@google.com> #11
ap...@google.com <ap...@google.com> #12
+1 🙏
ap...@google.com <ap...@google.com> #13
ap...@google.com <ap...@google.com> #14
Branch: androidx-main
commit 358ce0cf2083f6d0e2e2a0897c860a6c1b71b514
Author: Sean McQuillan <seanmcq@google.com>
Date: Mon Nov 07 15:37:22 2022
Support for EmojiCompat in Compose
Add support for EmojiCompat for all Text and TextField in compose
This adds support for EmojiCompat to all Compose based applications.
EmojiCompat configuration must be done outside of Compose, and the global EmojiCompat config will be used.
This change enables automatic configuration of EmojiCompat to compose :ui:ui-text, similar to the configuration in appcompat.
Apps that wish to configure emojicompat monually should continue to disable [EmojiCompatIniitalizer] in their manifest.
Changes:
* Text, BasicText, *TextField, BasicTextField will all now call EmojiCompat.process as needed
* If EmojiCompat loads the font after a Paragraph or MultiParagraph is create, [hasStaleReslovedFonts] will become true
Bug:
Test: ./gradlew :com:ui:ui-text:cAT
Relnote: "Added EmojiCompat to Compose"
Change-Id: Ibf6f9f9d37c6280fe1b051269b127f0dfb1d6b6a
A compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/EmojiCompatDemo.kt
M compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
M compose/ui/ui-text/api/current.txt
M compose/ui/ui-text/api/public_plus_experimental_current.txt
M compose/ui/ui-text/api/restricted_current.txt
M compose/ui/ui-text/build.gradle
A compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsicsTest.kt
A compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/EmojiCompatStatusTest.kt
M compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphHelper.android.kt
M compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
A compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/EmojiCompatStatus.kt
ni...@google.com <ni...@google.com> #15
Branch: androidx-main
commit e5c660680805acb637b7a8b7ee26ba168562ef1f
Author: zzeng <zzeng@google.com>
Date: Mon Nov 21 16:08:50 2022
Let EmojiCompat supports returning emoji boundaries.
This is part of the effort to make Compose Apps use EmojiCompat to determine cursor movement or deletion.
Bug:
Test: ./gradlew :emoji2:emoji2:cAT
Relnote: "Added EmojiCompat to Compose"
Change-Id: I96f37bd992fc8cf305e4cb5e3bbe9ea3be19e52b
M emoji2/emoji2/api/current.txt
M emoji2/emoji2/api/public_plus_experimental_current.txt
M emoji2/emoji2/api/restricted_current.txt
M emoji2/emoji2/src/androidTest/java/androidx/emoji2/text/EmojiProcessorTest.java
M emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
M emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiProcessor.java
ap...@google.com <ap...@google.com> #16
Branch: androidx-main
commit 42039d57d8357094bdacb901d28689ba55b99d92
Author: zzeng <zzeng@google.com>
Date: Fri Dec 02 15:34:12 2022
Support for EmojiCompat in Compose
Add support for cursor movement and deletion by EmojiCompat in Compose.
Bug: 139326806
Test: NA
Change-Id: I34773512ffbfaddbd796e8b9c33447e114b9a651
M compose/foundation/foundation/build.gradle
M compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/StringHelpers.android.kt
M settings.gradle
ap...@google.com <ap...@google.com> #17
Branch: androidx-main
commit 5f032489693eed2771265c47da815a908b4a06fb
Author: Sean McQuillan <seanmcq@google.com>
Date: Thu Dec 08 14:54:39 2022
Add EmojiCompat editorInfo when configured
Bug:
Test: ./gradlew :com:ui:ui:cAT
Change-Id: Ic121f989d549309058c36fd54fd3caa50f23f172
M compose/ui/ui/build.gradle
A compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/TextInputServiceAndroidEmojiTest.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
ap...@google.com <ap...@google.com> #18
Branch: androidx-main
commit 5b20374ecd983e5078f8ea6852ae60888d2007e4
Author: Sean McQuillan <seanmcq@google.com>
Date: Thu Dec 15 15:56:42 2022
PlatformParagraphStyle to disable emoji processing for a Paragraph.
Fixes:
Test: ./gradlew :com:ui:ui-text:cAT
Relnote: "Add PlatformTextStyle(emojiSupportMatch) to optionally disable emoji support processing for a single Paragraph."
Change-Id: Ia710096395ecfe4bbd986326882331e0a7e6a74d
M compose/ui/ui-text/api/current.txt
M compose/ui/ui-text/api/public_plus_experimental_current.txt
M compose/ui/ui-text/api/restricted_current.txt
M compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/PlatformParagraphStyleTest.kt
M compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/PlatformTextStyleTest.kt
M compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsicsTest.kt
M compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidTextStyle.android.kt
A compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/EmojiSupportMatch.kt
M compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidParagraphIntrinsics.android.kt
ap...@google.com <ap...@google.com> #19
The following release(s) address this bug.It is possible this bug has only been partially addressed:
androidx.compose.foundation:foundation:1.4.0-alpha04
androidx.compose.ui:ui:1.4.0-alpha04
androidx.compose.ui:ui-text:1.4.0-alpha04
androidx.emoji2:emoji2:1.3.0-alpha01
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.