Status Update
Comments
ni...@google.com <ni...@google.com> #2
This is a particularly hard device to come by - do you happen to have access to the device? If so could you provide us with the output of: adb shell dumpsys media.camera > info.txt
Thanks!
ra...@google.com <ra...@google.com> #3
Stacktrace:
Caused by: java.lang.IllegalArgumentException: Can not get supported output size under supported maximum for the format: 34
at androidx.camera.camera2.internal.SupportedSurfaceCombination.getSupportedOutputSizes(SupportedSurfaceCombination.java:355)
at androidx.camera.camera2.internal.SupportedSurfaceCombination.getSuggestedResolutions(SupportedSurfaceCombination.java:197)
at androidx.camera.camera2.internal.Camera2DeviceSurfaceManager.getSuggestedResolutions(Camera2DeviceSurfaceManager.java:198)
at androidx.camera.core.CameraX.calculateSuggestedResolutions(CameraX.java:943)
at androidx.camera.core.CameraX.bindToLifecycle(CameraX.java:293)
at androidx.camera.lifecycle.ProcessCameraProvider.bindToLifecycle(ProcessCameraProvider.java:227)
Below are some findings based on our debugging
When Dex is connected
previewConfig.getMaxResolution() is returning "731x411" as maxSize.
Inside Preview.Builder.build() -> Default_MAX_resolution is set to "CameraX.getSurfaceManager().getPreviewSize()" which is 731x411
this is being picked as maxSize.
While rendering maxSize is 731x411 and minSize is 640x480 and below are available outputSizes
0 = {Size@11860} "4032x3024"
1 = {Size@11861} "3984x2988"
2 = {Size@11862} "4032x2268"
3 = {Size@11863} "3024x3024"
4 = {Size@11864} "2976x2976"
5 = {Size@11865} "3840x2160"
6 = {Size@11866} "3264x2448"
7 = {Size@11867} "4032x1960"
8 = {Size@11868} "2880x2160"
9 = {Size@11869} "3264x1836"
10 = {Size@11870} "2160x2160"
11 = {Size@11871} "2560x1440"
12 = {Size@11872} "2224x1080"
13 = {Size@11873} "2048x1152"
14 = {Size@11874} "1920x1080"
15 = {Size@11875} "1440x1080"
16 = {Size@11876} "1088x1088"
17 = {Size@11877} "1280x720"
18 = {Size@11878} "1024x768"
19 = {Size@11879} "1056x704"
20 = {Size@11880} "960x720"
21 = {Size@11881} "960x540"
22 = {Size@11882} "720x720"
23 = {Size@11883} "800x450"
24 = {Size@11884} "720x480"
25 = {Size@11885} "640x480"
26 = {Size@11886} "352x288"
27 = {Size@11887} "320x240"
28 = {Size@11888} "256x144"
29 = {Size@11889} "176x144"
and couldn't find any size in this range.
When Dex not connected
minsize = 640x480
maxsize = 1920x1080
0 = {Size@11836} "4032x3024"
1 = {Size@11837} "3984x2988"
2 = {Size@11838} "4032x2268"
3 = {Size@11839} "3024x3024"
4 = {Size@11840} "2976x2976"
5 = {Size@11841} "3840x2160"
6 = {Size@11842} "3264x2448"
7 = {Size@11843} "4032x1960"
8 = {Size@11844} "2880x2160"
9 = {Size@11845} "3264x1836"
10 = {Size@11846} "2160x2160"
11 = {Size@11847} "2560x1440"
12 = {Size@11848} "2224x1080"
13 = {Size@11849} "2048x1152"
14 = {Size@11850} "1920x1080"
15 = {Size@11851} "1440x1080"
16 = {Size@11852} "1088x1088"
17 = {Size@11853} "1280x720"
18 = {Size@11854} "1024x768"
19 = {Size@11855} "1056x704"
20 = {Size@11856} "960x720"
21 = {Size@11857} "960x540"
22 = {Size@11858} "720x720"
23 = {Size@11859} "800x450"
24 = {Size@11860} "720x480"
25 = {Size@11861} "640x480"
26 = {Size@11862} "352x288"
27 = {Size@11863} "320x240"
28 = {Size@11864} "256x144"
29 = {Size@11865} "176x144"
and we have 12 available sizes in this range
Camera2DeviceSurfaceManager.java:: getPreviewSize()
mCameraSupportedSurfaceCombinationMap.get(cameraId).getSurfaceDefinition().getPreviewSize() = "1920x1080"
cameraId=0
ma...@gmail.com <ma...@gmail.com> #4
The issue root cause is that CameraX will default filter out sizes smaller than 640x480. For Preview, the max size will be limited to under display size. I checked the HW spec info for the issue related devices. Display size of FUJITSU F-04J/F-05J is 360x640. That will result int that no size exists in the conditions that is larger or equal to 640x480 and smaller or equal to 360x640.
A temporary workaround for this situation is to use Preview.Builder#setTargetResolution() to set a size smaller than 640x480 to bypass the problem.
For device FUJITSU arrowsM04, I checked its HW spec info and its display size I found is 1280x720. It seems that the problem should not exist in the device.
Could you confirm that the problem exist on arrowsM04 device? What will be the returned value when using Display#getRealSize to obtain the display size?
wa...@reaktor.fi <wa...@reaktor.fi> #5
> A temporary workaround for this situation is to use Preview.Builder#setTargetResolution() to set a size smaller than 640x480 to bypass the problem.
OK. I will try it.
> Could you confirm that the problem exist on arrowsM04 device?
We receive the crash report (Crashlytics) that this crash has occurred on arrowsM04.
We don't have this device so we can't confirm that the problem really exist on arrowsM04.
> What will be the returned value when using Display#getRealSize to obtain the display size?
We can't investigate it for the same reason.
Thank you.
hv...@gmail.com <hv...@gmail.com> #6
This issue happened on devices that the display size is smaller than 640x480. In original auto-resolution mechanism, supported sizes smaller than 640x480 will be default filter out.
The auto-resolution mechanism encodes the guaranteed configurations tables in CameraDevice#createCaptureSession(SessionConfiguration). It defines that the PREVIEW size is the small one of the device display size and 1080p. The PREVIEW size will be the maximal size limitation for Preview use case. The reason it limits the size to display size and 1080p is the stream output in display size or 1080p has been able to provide good enough preview quality. Therefore, auto-resolution mechanism will limit the selected size to be smaller than the small one of the device display size and 1080p.
With above two conditions, in this issue, all sizes smaller than 640x480 have been filter out, therefore, there is no size smaller than the display size 320x240 can be selected to use. And cause the exception.
Solution:
When the display size is smaller than 640x480, auto-resolution mechanism won't filter out those small sizes smaller than 640x480. This makes those small size be left and can be selected for the Preview use case on small display devices.
The solution has been merged and will be included in next CameraX release.
na...@vitruvian.me <na...@vitruvian.me> #7
Hello.
This crash still occurs.
- CAMERAX VERSION: 1.0.0-beta4
- ANDROID OS BUILD NUMBER: Android 7.1.1
- DEVICE NAME: FUJITSU F-02H
We receive following crash report from FUJITSU F-02H. So far We have received this crash report only from F-02H.
java.lang.IllegalArgumentException
Can not get supported output size under supported maximum for the format: 34
androidx.camera.camera2.internal.SupportedSurfaceCombination.getSupportedOutputSizes (SupportedSurfaceCombination.java:349)
androidx.camera.camera2.internal.SupportedSurfaceCombination.getSuggestedResolutions (SupportedSurfaceCombination.java:197)
androidx.camera.camera2.internal.Camera2DeviceSurfaceManager.getSuggestedResolutions (Camera2DeviceSurfaceManager.java:198)
androidx.camera.core.CameraX.calculateSuggestedResolutions (CameraX.java:949)
androidx.camera.core.CameraX.bindToLifecycle (CameraX.java:351)
androidx.camera.lifecycle.ProcessCameraProvider.bindToLifecycle (ProcessCameraProvider.java:230)
(our application's package name).CameraFragment.bindCameraUseCases (CameraFragment.java:174)
ra...@google.com <ra...@google.com>
sz...@gmail.com <sz...@gmail.com> #8
Could you help to provide the following information to clarify the issue?
1. Is the full name of the device Fujitsu Arrows NX F-02H that has a 1440x2560 display?
2. Please help to provide the supported output sizes of ImageFormat.PRIVATE that is obtained by StreamConfigurationMap#getOutputSizes(int).
ap...@google.com <ap...@google.com> #9
- Is the full name of the device Fujitsu Arrows NX F-02H that has a 1440x2560 display?
Yes
- Please help to provide the supported output sizes of ImageFormat.PRIVATE that is obtained by StreamConfigurationMap#getOutputSizes(int).
Since we don't have this device, we'll try to collect this information in the next version of our app. The next version will be released later this month.
ap...@google.com <ap...@google.com> #10
Hello.
- Please help to provide the supported output sizes of ImageFormat.PRIVATE that is obtained by StreamConfigurationMap#getOutputSizes(int).
We have collected the output of the device where the crash occurs.
Device1
- Model : arrows Be F-05J
- Android Version : 7.1.1
- Supported output sizes of ImageFormat.PRIVATE
CameraId 0: 480x480
CameraId 1: 2048x1536 ,1920x1080 ,1280x720 ,960x720 ,640x480 ,320x240 ,176x144
Device2
- Model : Fujitsu arrows M04
- Android Version : 7.1.1
- Supported output sizes of ImageFormat.PRIVATE
CameraId 0: 480x480
CameraId 1: 2048x1536 ,1920x1080 ,1280x720 ,960x720 ,640x480 ,320x240 ,176x144
Additional Information
CameraX version : 1.0.0-beta04
We collect the supported output sizes by following code.
val errorString = buildString {
append("The supported output sizes of ImageFormat.PRIVATE: ")
(requireContext().getSystemService(Context.CAMERA_SERVICE) as CameraManager).apply {
cameraIdList.forEachIndexed { index, cameraId ->
val msg = if (VERSION.SDK_INT >= VERSION_CODES.M) {
val configurationMap =
getCameraCharacteristics(cameraId).get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
val sizes = configurationMap?.getOutputSizes(ImageFormat.PRIVATE)
"CameraId $index: ${sizes?.joinToString(" ,")}"
} else {
"CameraId $index: This device version is under M."
}
append(msg)
}
}
}
ap...@google.com <ap...@google.com> #11
ap...@google.com <ap...@google.com> #12
I tried to find the device specs and both 720x1280
size display. For the camera id 0 device, it is a different case that the display size is larger than 640x480
but the device only supports a 480x480
size. The case also caused the same IllegalArgumentException and was also fixed by 1.0.0-beta04
release. Before 480x480
size would be filtered out and then caused the IllegalArgumentException. After it was merged, the 640x480
size threshold was removed and then the 480x480
size would be kept and selected to use.
It looks like 1.0.0-beta04
release had been used to collect the supported sizes information. But the issue should have been fixed by 1.0.0-beta04
release. Did you only check the device model name to collect the supported sizes information or collect the information when the IllegalArgumentException issue happens again?
CameraX's 1.0.0-beta04
version. Maybe you can also consider to upgrade to the latest 1.0.0-rc01
version for your application. Thanks.
ap...@google.com <ap...@google.com> #13
Did you only check the device model name to collect the supported sizes information or collect the information when the IllegalArgumentException issue happens again?
We collect informations only from the device on which IllegalArgumentException happened.
Our latest app uses CameraX version 1.0.0-beta10
and this issue still occurres.
However we don't receive crash report from Fujitsu arrows Be F-05J
or Fujitsu arrows M04
so far. (This doesn't mean this issue is fixed on these devices because our app is heavily rely on camera so these device's user wouldn't use our app anymore.)
Instead, we receive crash report from
- Model : Fujitsu F-03K
- Android Version : 7.1.2
- Supported output sizes of ImageFormat.PRIVATE
CameraId 0 : 480x480
CameraId 1 : 2048x1536 ,1920x1080 ,1280x720 ,960x720 ,640x480 ,320x240 ,176x144
ap...@google.com <ap...@google.com> #14
I missed some settings when I simulated the issue by robolectric test so that I was not able to reproduce it. Now, I can reproduce the issue if the device only supports one 480x480 resolution. I'm working on the solution and target to make it included in next release.
ni...@google.com <ni...@google.com> #15
Branch: androidx-main
commit 69d15dff7bb857ee33a0f643ff42a0f8bc475ab2
Author: charcoalchen <charcoalchen@google.com>
Date: Fri Jan 08 18:30:03 2021
Fixed IllegalArgumentException issue happened when all preview supported sizes are smaller than 640x480 and display size is larger than 640x480.
Do not filter out sizes smaller than 640x480 when all preview supported sizes are smaller than 640x480 and display size is larger than 640x480.
Relnote:"Fixed IllegalArgumentException issue happened when all preview supported sizes are smaller than 640x480 and display size is larger than 640x480."
Bug: 150506192
Test: SupportedSurfaceCombinationTest
Change-Id: I2a63ce8e2ad42a9cc060c8635ac3603bf440b1ec
M camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
M camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java
ap...@google.com <ap...@google.com> #16
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.