Status Update
Comments
si...@gmail.com <si...@gmail.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!
ma...@google.com <ma...@google.com>
vi...@dowjones.com <vi...@dowjones.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...@google.com <ma...@google.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?
vi...@dowjones.com <vi...@dowjones.com> #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.
jr...@gmail.com <jr...@gmail.com> #6
[Deleted User] <[Deleted User]> #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)
ma...@google.com <ma...@google.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).
ch...@gmail.com <ch...@gmail.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.
vi...@dowjones.com <vi...@dowjones.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)
}
}
}
v....@gmail.com <v....@gmail.com> #11
[Deleted User] <[Deleted User]> #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.
ub...@gmail.com <ub...@gmail.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
ch...@gmail.com <ch...@gmail.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.
ub...@gmail.com <ub...@gmail.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
es...@gmail.com <es...@gmail.com> #16
di...@zillowgroup.com <di...@zillowgroup.com> #17
ya...@gmail.com <ya...@gmail.com> #18
ya...@gmail.com <ya...@gmail.com> #19
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/exploreDynamic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:context="com.goldengekko.o2pm.presentation.landing.LandingFragment">
<com.goldengekko.o2pm.feature.views.TallCampaignView
android:id="@+id/tallCampaignView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Just for you module -->
<LinearLayout
android:id="@+id/just_for_you_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<com.goldengekko.o2pm.presentation.landing.justforyoumodule.JustForYouModuleView
android:id="@+id/just_for_you_module"
android:layout_width="match_parent"
android:layout_height="244dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/thank_you_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.goldengekko.o2pm.presentation.landing.thankyourewardsmodule.ThankYouModuleView
android:id="@+id/thank_you_module"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/landing_page_list_divider" />
</LinearLayout>
<LinearLayout
android:id="@+id/nearby_vertical_offer_cell"
android:nestedScrollingEnabled = "true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.goldengekko.o2pm.presentation.landing.composeviews.VerticalComposeOffersModule
android:id="@+id/nearby_vertical_offer_module"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/landing_page_list_divider" />
</LinearLayout>
<LinearLayout
android:id="@+id/prizedraws_horizontal_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.goldengekko.o2pm.presentation.util.TextView
android:id="@+id/prizedrawtitle"
style="@style/H3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="24dp"
android:paddingTop="48dp"
android:paddingEnd="24dp" />
<com.goldengekko.o2pm.composecard.carousal.CarousalCardView
android:id="@+id/prizedraws_horizontal_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/banner_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/banner_cell"
layout="@layout/layout_landing_banner_cell" />
</LinearLayout>
<LinearLayout
android:id="@+id/ourpicks_horizontal_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.goldengekko.o2pm.presentation.util.TextView
android:id="@+id/ourtitle"
style="@style/H3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="24dp"
android:paddingTop="48dp"
android:paddingEnd="24dp" />
<com.goldengekko.o2pm.composecard.carousal.CarousalCardView
android:id="@+id/ourpicks_horizontal_module"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/landing_page_list_divider" />
</LinearLayout>
<LinearLayout
android:id="@+id/categories_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.goldengekko.o2pm.presentation.landing.categoriesmodule.CategoriesModuleView
android:id="@+id/categories_module"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/landing_page_list_divider" />
</LinearLayout>
<LinearLayout
android:id="@+id/tickets_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.goldengekko.o2pm.presentation.landing.verticalmodule.VerticalModuleView
android:id="@+id/tickets_module"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/landing_page_list_divider" />
</LinearLayout>
<LinearLayout
android:id="@+id/o2deals_horizontal_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.goldengekko.o2pm.presentation.util.TextView
android:id="@+id/o2title"
style="@style/H3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="24dp"
android:paddingTop="48dp"
android:paddingEnd="24dp" />
<com.goldengekko.o2pm.composecard.carousal.CarousalCardView
android:id="@+id/o2deals_horizontal_module"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/landing_page_list_divider" />
</LinearLayout>
<LinearLayout
android:id="@+id/newest_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.goldengekko.o2pm.presentation.landing.verticalmodule.VerticalModuleView
android:id="@+id/newest_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/landing_fragment_snow_flakes_tint"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/ic_tint_overlay_snow_flake" />
<com.goldengekko.o2pm.presentation.landing.SnowView
android:id="@+id/landing_fragment_snow_flakes"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</androidx.core.widget.NestedScrollView>
ya...@gmail.com <ya...@gmail.com> #20
ya...@gmail.com <ya...@gmail.com> #21
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {
private var selectedSortTab = mutableStateOf(Tab.NEARBY)
var list by mutableStateOf<List<ListModel>>(emptyList())
var onClick by mutableStateOf({ index: Int -> })
@Composable
override fun Content() {
CardsMaterialTheme(content = { VerticalOffersList() })
}
@ExperimentalCoilApi
@Composable
private fun VerticalOffersList() {
val listState = rememberLazyListState()
Column(
modifier = Modifier
.background(colorResource(id = R.color.light_grey))
.fillMaxWidth()
.fillMaxHeight()
.nestedScroll(rememberViewInteropNestedScrollConnection())
) {
LazyColumn(
modifier = Modifier
.background(colorResource(id = com.goldengekko.o2pm.common.R.color.light_grey))
.fillMaxWidth(),
state = listState
) {
item {
TextTab(mapSelectedTabIndex(selectedSortTab.value)) {
tabClicked()
}
}
items(0, itemContent = { index ->
})
}
}
}
me...@gmail.com <me...@gmail.com> #22
[Deleted User] <[Deleted User]> #23
I have a simple hierarchy:
Coordinator
- AppBarLayout with height > ?actionBarSize (scrollFlags = scroll)
- ComposeView
- LazyColumn(rememberViewInteropNestedScrollConnection)
The regular scroll works fine, but when I do fling, coordinator intercepts only part of a velocity, doesn't matter how "hard" I swipe.
Don't you guys have this issue?
Are there any workaround for fling behavior?
ma...@google.com <ma...@google.com> #24
Is there any planned release for this fix? Should we hope it for 1.2.0 alpha 3?
No updates yet in this regard, we will let you know when there's a known release this will be provided in.
ni...@google.com <ni...@google.com>
ma...@google.com <ma...@google.com>
di...@swiggy.in <di...@swiggy.in> #25
NestedScrollView
ComposeView
LazyColumn
RecyclerView
The following error is thrown:
java.lang.IllegalStateException: Nesting scrollable in the same direction layouts like LazyColumn and Column(Modifier.verticalScroll()) is not allowed. If you want to add a header before the list of items please take a look on LazyColumn component which has a DSL api which allows to first add a header via item() function and then the list of items via items().
ma...@google.com <ma...@google.com> #26
This is an unrelated problem to the nested scroll in this ticket. In your case error message indicates that you are on the old compose version, newer error message explain how to fix it. Right now it says
Vertically scrollable component was measured with an infinity maximum height " +
"constraints, which is disallowed. One of the common reasons is nesting layouts " +
"like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a " +
"header before the list of items please add a header as a separate item() before " +
"the main items() inside the LazyColumn scope. There are could be other reasons " +
"for this to happen: your ComposeView was added into a LinearLayout with some " +
"weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a " +
"custom layout. Please try to remove the source of infinite constraints in the " +
"hierarchy above the scrolling container.
If you observe any bug in the toolkit - please file another issue, otherwise there's stackoverflow and kotlinlang slack where community can help with the code and error.
ap...@google.com <ap...@google.com> #27
Branch: androidx-main
commit fc31a066f4f02a0b9e7a22de99a4255602200a06
Author: Levi Albuquerque <levima@google.com>
Date: Tue Mar 01 09:50:06 2022
Nested Scrolling Interop Phase 1: Scrolling compose in cooperating view
In this first implementation of the nested scrolling interop we're adding support
for the case where a Compose scrolling container is placed within a cooperating
view. This means the View can receive scroll deltas from a scrolling child (it already
implements NestedScrollingParent3). This is the case of a CoordinatorLayout.
Test: Added sample and tests to check the behavior.
Relnote: "Enable Nested Scroll interop between View and Compose for
cooperating View classes. This means compose is now able to dispatch scroll deltas to a (cooperating) View parent."
Fixes: 174348612
Change-Id: I5d1ac5fdeced612ac07f0c26ce14284a43369673
A compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/NestedScrollInteropSamples.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt
M compose/ui/ui/api/current.txt
A compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/NestedScrollInteropAdapter.kt
M compose/ui/ui/build.gradle
M compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
M compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/PointerInputInteropComposeInAndroid.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
M compose/ui/ui/api/restricted_current.txt
A compose/ui/ui/src/androidAndroidTest/res/layout/test_nested_scroll_coordinator_layout.xml
A compose/ui/ui/integration-tests/ui-demos/src/main/res/layout/compose_in_android_coordinator_layout.xml
M compose/ui/ui/integration-tests/ui-demos/build.gradle
A compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/NestedScrollInteropTest.kt
M compose/ui/ui/integration-tests/ui-demos/src/main/AndroidManifest.xml
M compose/ui/ui/api/public_plus_experimental_current.txt
le...@google.com <le...@google.com>
ap...@google.com <ap...@google.com> #28
Branch: androidx-main
commit 697235331591da8efbc20a8f4a9896b2e17b0c1e
Author: Levi Albuquerque <levima@google.com>
Date: Tue Mar 22 16:31:44 2022
Nested Scrolling Interop Phase 2: A scrolling view child inside a compose parent
In this phase I made AndroidViewHolder to implement NestedScrollingParent3.
That way it will act as a nested scroll parent for a containing view.
At the same time, it uses an internal NestedScrollDispatcher to forward
deltas to a Compose Parent, so it also can act as a nested scroll child
for a compose parent.
Test: Added sample and tests to check the behavior.
Relnote: "Enable Nested Scroll interop between Compose and View in the
direction Compose > View. This means that a compose parent will be able to
receive nested scroll deltas from a nested scroll view."
Fixes: 174348612
Change-Id: If7949ccb50ba7897bc740b489fd3f4d615a5369b
M compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/NestedScrollInteropSamples.kt
M compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/NestedScrollInteropConnectionTest.kt
A compose/ui/ui/src/androidAndroidTest/res/layout/android_in_compose_nested_scroll_interop_list_item.xml
A compose/ui/ui/src/androidAndroidTest/res/layout/test_nested_scroll_coordinator_layout_without_toolbar.xml
M compose/ui/ui/api/current.txt
M compose/ui/ui/samples/build.gradle
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
M compose/ui/ui/build.gradle
M compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/PointerInputInteropAndroidInCompose.kt
A compose/ui/ui/integration-tests/ui-demos/src/main/res/layout/android_in_compose_nested_scroll_interop_list_item.xml
M compose/ui/ui/api/restricted_current.txt
A compose/ui/ui/integration-tests/ui-demos/src/main/res/layout/android_in_compose_nested_scroll_interop.xml
A compose/ui/ui/src/androidAndroidTest/res/layout/android_in_compose_nested_scroll_interop.xml
M compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/NestedScrollInteropConnection.kt
A compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/NestedScrollInteropViewHolderTest.kt
M compose/ui/ui/api/public_plus_experimental_current.txt
ub...@gmail.com <ub...@gmail.com> #29
The change from requestDisallowInterceptTouchEvent()
setup from setNestedScrollingEnabled()
on top of the MapView
and a scrollable()
Modifier on a parent Box
. Am I missing something, or does this not work with MapView for some reason? My MapView is inside a horizontal accompanist pager; I tried both vertical and horizontal orientations for scrollable()
, but either one just gets swallowed, and I cannot drag the map in the specified orientation, or drag the pager element.
le...@google.com <le...@google.com> #30
Hi, thanks for your comment. The Modifier.scrollable in the example is just an illustration so one can see the scroll deltas being propagated up the nested scrolling chain across view and compose. If I understand you example correctly, you have a compose scrollable parent (HorizontalPager) + a View (MapView) child. In this case, just setNestedScrollingEnabled() should suffice.
ub...@gmail.com <ub...@gmail.com> #31
Gotcha, thanks for the quick response. Alas, it did not fix my problem. What I want is to have my MapView be scrollable horizontally inside a horizontal accompanist pager. (There is more screen real estate besides the MapView that the user can drag the pager on.) With or without setNestedScrollingEnabled()
, only the pager ever scrolls.
Here's the rough Compose tree:
HorizontalPager {
Column {
AndroidView({
MapView().also { ViewCompat.setNestedScrollingEnabled(it, true) }
})
Text("Drag me to flip the page")
}
}
le...@google.com <le...@google.com> #32
Hey, so from the sample you added above, if you swipe on top of the text "Drag me to flip the page" the pager goes to the next page, but if you do the same on top of the map, you want the map to scroll instead of the pager? I can see how this would be the desired behavior, but ff this is the case we'll need to re-check. MapView is a "special" type of View and I don't think it dispatches NestedScrolling the same way other scrollable containers would (using the nested scrolling system).
ub...@gmail.com <ub...@gmail.com> #33
Yes, that is the desired behavior. Short of Compose support, I am able to make it happen by wrapping the MapView inside this custom view; it's a pattern from androidx ViewPager2:
class TouchWrappingLayout(context: Context) : FrameLayout(context) {
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
parent.requestDisallowInterceptTouchEvent(true)
return super.onInterceptTouchEvent(e)
}
}
mr...@gmail.com <mr...@gmail.com> #34
On non-compose version a fling event propagates to top appbar, so it collapses smothly
But compose behaviour is different - appbar wont collapse (or expand) when I do a fling gesture
Am I doing something wrong?
n....@gmail.com <n....@gmail.com> #35
le...@google.com <le...@google.com> #36
Hi, we are aware that nestedScroll has some issues with fling movements (see:
wo...@ubique.ch <wo...@ubique.ch> #37
I'm having trouble using Compose nested scrolling inside a BottomSheet. Scrolling upwards (so further down in a list) works fine, meaning it first expands the BottomSheet before scrolling in the Compose LazyColumn. However scrolling downwards (so further up in a list) immediately starts collapsing the BottomSheet instead of first scrolling to the top of the LazyColumn and then collapsing the BottomSheet. Is there a fix or workaround for this issue?
Here's an example:
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<LinearLayout
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#e9e9e9"
android:orientation="vertical"
android:padding="5dp"
app:layout_behavior="@string/bottom_sheet_behavior">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/composeContent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Activity:
class ComposeActivity : AppCompatActivity() {
@OptIn(ExperimentalComposeUiApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityComposeBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.composeContent.setContent {
LazyColumn(
Modifier
.nestedScroll(rememberNestedScrollInteropConnection(binding.composeContent))
.fillMaxSize()
) {
items(100) {
Text("Item $it", Modifier.fillMaxWidth(), Color.Black)
}
}
}
}
}
[Deleted User] <[Deleted User]> #38
wo...@ubique.ch <wo...@ubique.ch> #39
Neither the official Compose rememberNestedScrollInteropConnection()
nor the ViewInteropNestedScrollConnection
from #9 fixes the problem I described in #37. I've attached a video showing the issue. When the bottom sheet is fully expanded and the nested composable is not scrolled to the top, I would expect to first scroll the nested composable and not start dragging the bottom sheet down until its collapsed state is reached.
le...@google.com <le...@google.com> #40
Hi, I have tested #37 locally using rememberNestedScrollInteropConnection() and I see the correct behavior. Are you using the latest version of compose? I noticed your rememberNestedScrollInteropConnection still has binding.composeContent, whilst in the latest version we don't need to pass the compose view anymore.
le...@gmail.com <le...@gmail.com> #41
This rememberNestedScrollInteropConnection() is not working for me. Can you please explain what is going wrong here?
My situation is that I'm building a bottom sheet with using BottomSheetDialogFragment. Here is my layout
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<androidx.compose.ui.platform.ComposeView
android:id="@+id/composeView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Here is how I render my ComposeView, note that the top node of the ComposeView is not LazyColumn:
composeView.setContent {
Column // Set nestedScroll(rememberNestedScrollInteropConnection()) here
- Composable 1 (Top bar)
- Columm(modifier = Modifier.weight(1f))
- LazyColumn // Doesn't work with this LazyColumn here
- Composable 2 (Bottom bar)
}
What I'm doing wrong here? Note that using the workaround from #9 works for me. I use #9 directly to the LazyColumn as it does not require binding with composeView.
wo...@ubique.ch <wo...@ubique.ch> #42
#40: I am using Compose 1.2.0-alpha08, which at the time of writing is the latest version in the Google Maven. When checking the 1.2.0-alpha08 sources jar from rememberNestedScrollInteropConnection
function in NestedScrollInteropConnection.kt
still requires passing a ComposeView as parameter. I can see
I have attached a sample project which shows the behavior I reported in #37 using Compose 1.2.0-alpha08 and using either the official NestedScrollInteropConnection
or the ViewInteropNestedScrollConnection
from #9.
le...@google.com <le...@google.com> #43
Hi #41, could you provide a full code sample so I can better understand and test it? From your sample alone I don't see anything apart the ComposeView inside your xml so I'm not sure why you're using the interop connection here. Thank you!
le...@google.com <le...@google.com> #44
Hi #42, thanks for the sample. Yes, I'm looking into the schedule and it seems that it hasn't been released yet. You can copy and paste it to test it out, but I'm attaching a video I made with the latest version and this seems to be working. Once you're able to test it (either by copying and pasting or waiting for the next release) let me know if the issue is really solved. I'll keep an eye out for this, thanks for reporting!
fl...@polarsteps.com <fl...@polarsteps.com> #45
wo...@ubique.ch <wo...@ubique.ch> #46
Hi #44: I've updated to the new Compose 1.2.0-beta01 where the new parameter-less rememberNestedScrollInteropConnection()
function is available. However, this update made it worse in my sample project, as the bottom sheet now no longer expands/shrinks at all when scrolling in the compose content. Only grabbing the bottom sheet itself changes the expanded state. I've attached a video of the new behavior.
le...@google.com <le...@google.com> #47
Hi #46, this is the version of your sample I have used to generate the video above. I only made minimal changes to update the version and remove unnecessary code. I have just tried it again and it seems to be working.
If the sample you're using is different than this one, could you file a bug in another ticket so we can follow up from there?
Thank you.
wo...@ubique.ch <wo...@ubique.ch> #48
Hi #47: You're right, it is working with the new 1.2.0-beta01. I still had a reference to the rememberViewInteropNestedScrollConnection(binding.composeContent)
from #9 in the code, which interfered with the NestedScrollingChildHelper of the official rememberNestedScrollInteropConnection()
. Thanks a lot for the support and the fix!
kr...@codequest.com <kr...@codequest.com> #49
#47, I modified your BottomSheetCompose2.zip to provide a minimal reproducible example. My main issue is that some places of bottom sheet don't react at all to drag&dismiss gesture (apart from `LazyColumn`). Please see `BottomSheetFragment` class for reference.
ch...@gmail.com <ch...@gmail.com> #50
One of the issues here is that BottomSheetBehavior
calls view.canScrollVertically()
on the nested scrolling view. ComposeView
doesn't implement that, so it's a bit broken atm. I'm surprised this wasn't picked up in testing?
We previously had a workaround of manually creating a 'proxy' nested scrolling view, attaching it via AndroidView
and then instrumenting its canScrollVertically
function based on the LazyListState
. That no longer works in Compose 1.2.0 as rememberNestedScrollInteropConnection()
uses the LocalView
internally rather than allowing us to pass that view through.
qu...@gmail.com <qu...@gmail.com> #51
le...@google.com <le...@google.com> #52
Hi #49, thank you for reporting this. I looked into your sample and I think some clarifications are needed, we're working on adding more documentation around the nested scroll interop in order to avoid confusions in the future, but for now here's the explanation:
- rememberNestedScrollInteropConnection will install a NestedScrollConnection in the element you attached it to, in this case the Box element. The NestedScrollConnection is responsible for transmitting the deltas from the Compose level to the View level. Note that it enables the element to participate in nested scrolling, but it doesn't scroll elements automatically.
- Box is not scrollable automatically, so when you touch the non-scrollable area it, Compose won't propagate deltas in the nested scroll system and the deltas won't reach the NestedScrollConnection provided by rememberNestedScrollInteropConnection, therefore those deltas won't reach the view world.
- When you touch the scrollable portion of compose, the LazyList, the deltas are propagated in the nested scroll system and that's why it works when you scroll it.
- To have the scrolling deltas reach the view world you'd need to either include your text in the Lazy list as a one-off element (using the item{} DSL or to make your outter box scrollable using the .scrollable modifier.
Hopefully the explanation makes sense, let us know what you think. Thank you!
le...@google.com <le...@google.com> #53
Hi #51, the issues with fling is on our roadmap for upcoming releases, stay tuned to these two tickets (
fj...@gmail.com <fj...@gmail.com> #54
I want to scroll pager when i have scrolled to the bottom of lazy column, but its not working.
I tried using rememberNestedScrollInteropConnection, but it did not work.
I Have ViewPager with vertical orientation, Which has fragment with structure like this
<NestedScrollableHost>
<ComposeView>
</NestedScrollHost>
ComposeView has lazy column with vertical orientation.
Seems thats because ComposeView return canScrollVertically as false. I can't use requestDisallowInterceptTouchEvent() to decide to whom give scroll permission. Is there any workaround that i could use to make it work?
Here is the code for NestedScrollableHost:
class NestedScrollableHost : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private var touchSlop = 0
private var initialX = 0f
private var initialY = 0f
private val parentViewPager: ViewPager2?
get() {
var v: View? = parent as? View
while (v != null && !(v is ViewPager2 && (v as? ViewPager2)?.orientation == ORIENTATION_VERTICAL)) {
v = v.parent as? View
}
return v as? ViewPager2
}
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
init {
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
}
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
val direction = -delta.sign.toInt()
return when (orientation) {
0 -> child?.canScrollHorizontally(direction) ?: false
1 -> child?.canScrollVertically(direction) ?: false
else -> throw IllegalArgumentException()
}
}
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
handleInterceptTouchEvent(e)
return super.onInterceptTouchEvent(e)
}
private fun handleInterceptTouchEvent(e: MotionEvent) {
val orientation = parentViewPager?.orientation ?: return
// Early return if child can't scroll in same direction as parent, except if child is also viewpager
if (child !is ViewPager2
&& !canChildScroll(orientation, -1f)
&& !canChildScroll(orientation, 1f)
) {
return
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
parent.requestDisallowInterceptTouchEvent(true)
} else if (e.action == MotionEvent.ACTION_MOVE) {
val dx = e.x - initialX
val dy = e.y - initialY
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of child
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
if (child is ViewPager2) {
// In case child and parent are both ViewPagers - calculate on which angle,
// which widget will be swiped
val angle = getAngle(initialX, initialY, e.x, e.y)
// Right
if (inRange(angle, 0f, 55f) || inRange(angle, 305f, 360f)) {
parent.requestDisallowInterceptTouchEvent(true)
// Left
} else if (inRange(angle, 125f, 235f)) {
parent.requestDisallowInterceptTouchEvent(true)
} else {
parent.requestDisallowInterceptTouchEvent(false)
}
} else {
// Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
}
} else {
// Gesture is parallel, query child if movement in that direction is possible
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
// Child can scroll, disallow all parents to intercept
parent.requestDisallowInterceptTouchEvent(true)
} else {
// Child cannot scroll, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
}
private fun getAngle(x1: Float, y1: Float, x2: Float, y2: Float): Double {
val rad = atan2((y1 - y2).toDouble(), (x2 - x1).toDouble()) + Math.PI
return (rad * 180 / Math.PI + 180) % 360
}
private fun inRange(angle: Double, init: Float, end: Float): Boolean {
return angle >= init && angle < end
}
}
[Deleted User] <[Deleted User]> #55
lu...@bertelsmann.de <lu...@bertelsmann.de> #57
The header can be dragged as expected while the list is scrolled fully to the top, however as soon as this is not the case dragging on the header does no longer work as expected, I can only drag the header a few pixels, if at all, and then it instantly dismisses the BottomSheetDialogFragment.
This is what my code looks like:
Box(
Modifier
.nestedScroll(rememberNestedScrollInteropConnection())
) {
Column({
Box(Modifier
.height(50.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
contentAlignment = Alignment.Center
) {
// Header
}
Column(
Modifier
.fillMaxWidth()
.wrapContentHeight()
.verticalScroll(rememberScrollState()), content = content
)
}
}
Can someone maybe tell me what I am doing wrong, or how to fix this?
li...@gmail.com <li...@gmail.com> #58
Somehow ViewCompat.setNestedScrollingEnabled
doesn't work for me. The code below is oversimplified, but correctly represents the view heierachy, which I have.
HorizontalPager {
AndroidView({
FrameLayout()
.also { ViewCompat.setNestedScrollingEnabled(it, true) }
.also { frame ->
val recycler = RecyclerView()
ViewCompat.setNestedScrollingEnabled(recycler, true)
frame.addView(recycler)
}
}
})
}
The RecyclerView
simply does not scroll.
Description
when nested scrolling in compose is done, we need to consider supporting cases for view interop when compose is either a parent or a child.