androidx / constraintlayout

ConstraintLayout is an Android layout component which allows you to position and size widgets in a flexible way
Apache License 2.0
1.07k stars 176 forks source link

Touch event interception during swipe #406

Open MMKrivtsov opened 2 years ago

MMKrivtsov commented 2 years ago

Currently if there is scrolling view (ex. RecyclerView) inside of MotionLayout, this view grabs touch events preventing transitions with swipe over that view. Method onInterceptTouchEvent can handle and intercept touches if touch region id is set, but in that case it uses onTouchEvent and it returns true even if touch is not swipe yet, preventing interaction with child views.

I suppose it would be good to have (at least option) to return result of this.mScene.processTouchEvent(...) instead of true, which in turn would return result of this.mCurrentTransition.mTouchResponse.processTouchEvent(...) and that one would return value of mDragStarted.

Or at least make more getters for accessing such fields: getter for current scene in MotionLayout, make TouchResponse class public and add getter for mDragStarted in TouchResponse. Maybe getter for mCurrentTransition in MotionScene too, but it can be accessed with bestTransitionFor(), so separate getter is not necessary now.

jafu888 commented 2 years ago

I am not sure what you are asking for: MotionLayout should only be grabbing swipe if you have an and you should be able to Disable it with: <OnSwipe ... nestedScrollFlags="disableScroll" />

MMKrivtsov commented 2 years ago

I have a layout with vertical RecyclerView inside of MotionLayout. By default I can swipe both of them simultaneously and that works worse for me than same RecyclerView inside of ViewPager.

jafu888 commented 2 years ago

Two things:

  1. You can use MotionLayout inside a ViewPager if it has the behavior you are looking for. Listen to The view pager and set progress. (https://developer.android.com/training/constraint-layout/motionlayout/examples#viewpager)

  2. Still not sure the behavior you are asking for. In general I do not recommend using touchRegionId its behavior is unpredictable. (But cannot be change because legacy)

MMKrivtsov commented 2 years ago

Link you provided shows how to use MotionView alongside of ViewPager, not inside. of it. Would not swiping of view pager move child MotionLayout out of view?

If you look inside of HorizontalScrollView class method onInterceptTouchEvent - it does return whether it is being swiped or not, not true as MotionLayout. does. I ask for MotionLayout to work similarly by returning current transition's touch response's value of mDragStarted instead of true in 'onInterceptTouchEvent`. This way it works as expected, but i have to use some reflection calls to achieve this.

For backwards compatibility it can be toggled behavior with additional attribute. By default - return true from onInterceptTouchEvent, if attribute is set - return 'mDragStarted'.

jafu888 commented 2 years ago

I think I get it. I will leave this open till I fix it.

blad commented 1 year ago

I'm having an issue that's I believe may be closely related to what is being described in this issue.

Given that I have a Motion Layout with a Horizontal RecyclerView. Horizontal Recycler Views seem to prevent MotionLayout transitions from taking place. However, Vertical Recycler Views seems to correctly trigger the MotionLayout transition when scroll interaction takes place.

I've tried using the flag added in commit 9ec3c08, however there is no change to the behavior.

I've created this example project to showcase the issue: Layout: activity_main.xml Motion Scene: motion_layout_scene.xml Activity: (MainActivity.kt)[https://github.com/blad/motion-layout-recycler-view-issue/blob/main/ap

Capture d’écran 2022-10-28 à 3 29 20 PM
jafu888 commented 1 year ago

RecyclerView's default behavior is to consumes drag events. You can see this by adding a line if (horizontal) rv.suppressLayout(true) to configure. This is probably a know issues with RecyclerView that cannot be fixed with out breaking compatibility. For more information: Bug: RecyclerView should not interfere with opposite direction scroll touch events Example Workaround: Fixing RecyclerView nested scrolling in opposite direction

blad commented 1 year ago

Thank you for your reply John.

Unfortunately the workaround linked does not directly work for my use case.

However it does make it clear that I would possibly need to override MotionLayout's onInterceptTouchEvent method so that I would have any hope of the nested RecyclerView not consuming the event before MotionLayout can react appropriately.

Knowing that, I did find a workaround of wrapping the horizontal RecyclerView in a NestedScrollView with vertical orientation.

I suppose that the NestedScrollView handles the vertical drags and allows the parent to MotionLayout to react correctly.

Leading to something like:

<!-- pseudo-layout xml -->
<MotionLayout>
  <NestedScrollView android:orientation="vertical" android:layout_height="wrap_content"> 
    <RecyclerView /> <!-- Horizontal Orientation -->
  </NestedScrollView>
  <RecyclerView /> <!-- Vertical Orientation -->
</MotionLayout>

activity_mail.xml: Layout with workaround wrapping NestedScrollView

jafu888 commented 1 year ago

Very cool approach. It works well! I will put a version of that in EamplesRecyclerView. I put examples of how to interact with RecyclerView and ConstraintLayout/MotionLayout.

DearZack commented 1 year ago

Thank you for your plan. It has been a big help for me. I have already spent two whole days before that