Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I cannot initiate a scroll from a clickable view inside SmoothAppBarLayout #105

Open
pablo-oses opened this issue May 15, 2016 · 37 comments

Comments

@pablo-oses
Copy link

pablo-oses commented May 15, 2016

I understand this was already reported as issue #54. I'm creating a new issue as that one was closed.

I need to have a clickable ImageView inside a CollapsingToolbarLayout inside the SmoothAppBarLayout.
And as long as the ImageView has a ClickListener the scrolling doesn't work starting the scroll on that view.

It's critical in my case as the ImageView is as big as the entire CollapsingToolbarLayout and contains the first item image and you can tap on it to transition to the all-images dedicated screen.

I reproduced this by adding a clickable view inside the CollapsingToolbarLayout of your samples project screens GsdScrollExitUntilCollapsedActivity and SmoothScrollExitUntilCollapsedActivity to confirm the problem is in SmoothAppBarLayout. This is not a from-scratch implenentation; I'm coming from a:

RelativeLayout
    CoordinatorLayout
        AppBarLayout
            CollapsingToolbarLayout
            RelativeLayout
                ImageView (clickable)
                ~other stuff floating around
            Toolbar
        NestedScrollView
            LinearLayout
                ~item details

I took a look and compared the onTouch events of the clickable view on each of those examples while starting a scroll and noticed that in the GsdScrollExitUntilCollapsedActivity case it receives the ACTION_DOWN... then a few ACTION_MOVE... then an ACTION_CANCEL... and then the further ACTION_MOVE events start to get detected on the parent views.

In the SmoothScrollExitUntilCollapsedActivity case the ACTION_CANCEL is never received by the clickable view and the ACTION_MOVE events never get received by it's parents.

On the other issue both you and the reporting guy mentioned something about "dispatching the touch events on my own" but I need a bit more help about how to do that.... should I be detecting the ACTION_MOVE onTouch events (I already have this) and dispatching them (using the dispatchTouchEvent function ?) but... to who ? to the CoordinatorLayout ?

@henrytao-me
Copy link
Owner

Let me try it again. What SmoothAppBar version are you using?

@henrytao-me
Copy link
Owner

Hi @pablo-oses

Can you change the order in your layout, then see how it works?

RelativeLayout
    CoordinatorLayout
        NestedScrollView
            LinearLayout
                ~item details
        AppBarLayout
            CollapsingToolbarLayout
            RelativeLayout
                ImageView (clickable)
                ~other stuff floating around
            Toolbar

@pablo-oses
Copy link
Author

In my app I'm using 23.1.0 for both the appcompat and smooth-app-bar-layout
The layout structure that I mentioned was before implementing SmoothAppBarLayout.
In the current one I have NestedScrollView first (with a top padding) and SmoothAppBarLayout later.

Instead of trying to fix this in my app with all it's complexities I'm trying to confirm that a possible fix is doable in the samples app as an in-vitro experiment.

I'm using the master branch as of last week (the last thing mentioned in the CHANGES.MD is "Version 23.2.1.1 - March 29th 2016"

@pablo-oses
Copy link
Author

In the sample GsdScrollExitUntilCollapsedActivity the clickable view inside CollapsingToolbarLayout inside AppBarLayout receives the onTouch(ACTION_CANCEL) event after a few onTouch(ACTION_MOVE) events. And in the sample SmoothScrollExitUntilCollapsedActivity it doesn't.

So to try to understand why it's not working I started to research exactly how is it working fine on the GsdScrollExitUntilCollapsedActivity case.

So I started to read about the Android's touch system event propagation and understood that such event (the ACTION_CANCEL) is received because some parent view answered true to a ViewGroup.onInterceptTouchEvent event. So I started to sniff the onInterceptTouchEvent activity of each of the parent views and noticed that CoordinatorLayout is the one detecting a true against ViewGroup.onInterceptTouchEvent and stops sending the ACTION_MOVE events to the clickable view and starts to process them on his own.
The one telling CoordinatorLayout that true is the AppBarLayout.Behavior.
The detection logic is in it's base class HeaderBehavior and is clear in the case of ACTION_MOVE if it detects yDiff > mTouchSlop

And when I analysed all that same logic again for the SmoothScrollExitUntilCollapsedActivity case I noticed that I'm in SmoothAppBarLayout.Behaviour which also extends the same HeaderBehavior so the detection logic should work in the same way but when action == ACTION_MOVE it fails to work because mActivePointerId is -1 (which should have been set in the ACTION_DOWN event.

When the HeaderBehavior of SmoothAppBarLayout received the ACTION_DOWN event it didn't saved the mActivePointerId because canDragView(child) is false (a function that in the case of AppBarLayout.Behavior answers true)

And the reason for that difference is that SmoothAppBarLayout.Behavior extends BaseBehavior before extending AppBarLayout.Behavior and in BaseBehavior's init function it set's a DragCallback that always return false.

I commented out that part and noticed that the ACTION_MOVE events are now properly transferred from the clickable view to the CoordinatorLayout as the case in which it works. But now while I initiate a scroll from SmoothAppBarLayout the other members of the CoordinatorLayout (in your sample a RecyclerView) doesn't scroll :(

Do you have any idea to have a clickable view inside SmoothAppBarLayout ?

@henrytao-me
Copy link
Owner

Hi @pablo-oses

What AppCompat version are you using? The latest version of SmoothAppBarLayout is 23.3.0. Please check if it works for you.

Cheers,
Henry

@pablo-oses
Copy link
Author

I upgraded to 23.4.0 on the sample/build.gradle file and it's still not scrolling from a clickable view inside the SmoothAppBarLayout. (the touch events never get intercepted and captured by it's parent views)

@henrytao-me
Copy link
Owner

Just want to let you know that I am looking into this issue. It's a common problem with custom view, ViewGroup and View... Somehow, original AppBarLayout works. Investigating...

@pablo-oses
Copy link
Author

Thanks !... I hope all the debugging findings that I wrote helps.
Let me know if you can't reproduce in the samples app anything that I described or if I can help trying out possible ideas

@henrytao-me
Copy link
Owner

I am just back from I/O and will ping you when I have some updates. Thanks for your attention.

@henrytao-me
Copy link
Owner

I still haven't had perfect solution for this issue yet. This is tricky issue. I need to spend more time look into event dispatcher things...

@hrsalehi
Copy link

Same issue here. I Have a button inside my CollapsingToolbarLayout but scrolling is disabled.

@gitanshu
Copy link

I am facing a similar issue too. I have a ViewPager inside the SmoothCollapsingToolbarLayout and ViewPager items are clickable.

@Benson-Zheng
Copy link

I also encountered the same problem

@nikhilreprime
Copy link

I am also facing this issue, tested with latest lib

With android AppBarLayout they have fixed this issue
Does this issue has something to do with onTouchListener of clickable view

@henrytao-me
Copy link
Owner

Right, because of the approach of SmoothAppBarLayout, I still can't figure out the way to make touch and scroll work at the same time. :(

@nikhilreprime
Copy link

nikhilreprime commented Sep 10, 2016

I am still new to Application development so i dont know the right approch , but is is possible to detect touch event inside SmoothAppBarLayout or any parent view and check for action type
and separate down action from move action
if it is move then initiate SmoothAppBarLayout scroll event , else do nothing

https://developer.android.com/training/gestures/detector.html
http://stackoverflow.com/questions/14776271/android-ontouch-motionevent-action-move-is-not-recognized

please let me know is this possibly right approach or not
can you try this on your side

@anilbisht
Copy link

I also encountered the same problem for:
SmoothAppBarLayout---->CollapsingToolbarLayout----->ViewPager.

@henrytao-me
Copy link
Owner

Thanks @nikhilreprime for your suggestion. It can be a right approach but it's still not an easy fix.
Thanks @anilbisht for reporting this issue too. I will try my best to support scrolling on clickable elements.

@Pachouri-Sikandar
Copy link

@henrytao-me Hey buddy, Any update on this ? I am also facing the same issue. Please help me out.

@yushilong
Copy link

@henrytao-me Hey buddy, Any update on this ? I am also facing the same issue. Please help me out too ,thank you!.

@florianPOLARSTEPS
Copy link

florianPOLARSTEPS commented Feb 28, 2017

@henrytao-me I found a workaround for the issue, which is nowhere near optimum, but for my requirements it works fine and it could be a start for a solution.

First one would need to return false for all dispatchTouchEvents in SmoothAppBarLayout, so the behaviour can take over the touches.

@CoordinatorLayout.DefaultBehavior(SmoothAppBarLayout.Behavior.class)
public class SmoothAppBarLayout extends AppBarLayout {
  // ...
  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    super.dispatchTouchEvent(ev);
    return false;
  }
// ...
}

Secondly, we will need to pass down the touch events, so all clicklisteners in the header etc. will receive their events, which unfortunately requires to override CoordinatorLayout

public class SmoothCoordinatorLayout extends CoordinatorLayout {
  private SmoothAppBarLayout vAppBarLayout;
 // ...

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = super.dispatchTouchEvent(ev);
    if (handled && vAppBarLayout != null) {
      vAppBarLayout.dispatchTouchEvent(ev);
    }
    return handled;
  }

  @Override
  public void onLayoutChild(View child, int layoutDirection) {
    super.onLayoutChild(child, layoutDirection);
    if (child instanceof SmoothAppBarLayout) {
      vAppBarLayout = (SmoothAppBarLayout) child;
    }
  }
  // ...
}

This is far from optimum, but in my case it works fine.

@jimitpatel
Copy link

@florianPOLARSTEPS that part didn't improved anything in my code. In fact, it has even disabled my touch events

@henrytao-me
Copy link
Owner

Thanks for sharing. I will look into it shortly

@florianPOLARSTEPS
Copy link

florianPOLARSTEPS commented Mar 1, 2017

@henrytao-me Due to the nature of the workaround ( basically duplicating touch events ) there are a few side effects. It can happen that 2 touches at the same time are registered.

I am also looking for a proper solution, but I am still trying to wrap my head around the whole touch event propagation system in a coordinator layout.

@florianPOLARSTEPS
Copy link

@henrytao-me I found another possible workaround which is a bit nicer since it does not require a custom CoordinatorLayout and will work for clicks in the header only.

@CoordinatorLayout.DefaultBehavior(SmoothAppBarLayout.Behavior.class)
public class SmoothAppBarLayout extends AppBarLayout {

  private static final int CUSTOM_EDGE_FLAG = 2023477;

 // ...

@Override
  public boolean dispatchTouchEvent(MotionEvent ev) {

    // We need to check wether we have arrived from our own triggered motion dispatch,
    // There are no really appropriate fields on MotionEvents to store custom data, so we abuse edgeflags
    if (ev.getEdgeFlags() == CUSTOM_EDGE_FLAG) {
      return false;
    }

    boolean dispatched = super.dispatchTouchEvent(ev);
    if (dispatched && ev.getAction() == MotionEvent.ACTION_MOVE) {
      // After we know some view in our hierarchy would want to receive the move touch event, we don't want it to have though,
      // we create a new motion event which will cancel our current motion event stream and will be disregarded by appbarlayout,
      // so CoordinatorLayout.Behaviour can receive the new motion event stream
      MotionEvent motionEvent = MotionEvent.obtain(ev);
      motionEvent.offsetLocation(getLeft(), getTop());
      motionEvent.setAction(MotionEvent.ACTION_DOWN);
      motionEvent.setEdgeFlags(CUSTOM_EDGE_FLAG);
      
      // getParent() cannot return null, since well - who would have called this method
      ((ViewGroup) getParent()).dispatchTouchEvent(motionEvent);
      return false;
    }
    return dispatched;
  }
  // ...
}

@henrytao-me
Copy link
Owner

Hi @florianPOLARSTEPS, it really works. Awesome. Let's me add that to next release and mark you as contribution. Thanks a lot.

@nikhilreprime
Copy link

@florianPOLARSTEPS thanks for providing this workaround
@henrytao-me when are you planning to deploy next release

@nikhilreprime
Copy link

The work around works fine in case of non scrolling elements in header
In my case horizontal scroll view is present in header view
So i added few extra line , please check if they are OK
Thank you

private float downXValue,downYValue;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    if (ev.getEdgeFlags() == CUSTOM_EDGE_FLAG) {
        return false;
    }

    boolean dispatched = super.dispatchTouchEvent(ev);
    if(ev.getAction() == MotionEvent.ACTION_DOWN) {
        downXValue = ev.getX();
        downYValue = ev.getY();
    }else{
        float currentX = ev.getX();
        float currentY = ev.getY();
        if (dispatched && ev.getAction() == MotionEvent.ACTION_MOVE) {
            if (Math.abs(downXValue - currentX) > Math.abs(downYValue - currentY)) {
                Log.d("Motion Type :","Horizantal");
            }else{
                Log.d("Motion Type :","Vertical");

                MotionEvent motionEvent = MotionEvent.obtain(ev);
                motionEvent.offsetLocation(getLeft(), getTop());
                motionEvent.setAction(MotionEvent.ACTION_DOWN);
                motionEvent.setEdgeFlags(CUSTOM_EDGE_FLAG);

                // getParent() cannot return null, since well - who would have called this method
                ((ViewGroup) getParent()).dispatchTouchEvent(motionEvent);
                return false;
            }
        }
    }
    return dispatched;
}

@henrytao-me
Copy link
Owner

Thanks @nikhilreprime for the workaround. I think I will include it to next release by early next month when I have more spare time and add you guys to contributors list.

@henrytao-me
Copy link
Owner

I heard that the fix for AppBarLayout issue is done. It will be included in 26.0.0. Stay tuned.

@prat-z
Copy link

prat-z commented Dec 29, 2017

@florianPOLARSTEPS tried your fix, works great, but another issue arises because of this. The view on which touch has initiated, remains on a clicked state, until the AppbarLayout is touched again.

@pm48
Copy link

pm48 commented Jan 23, 2018

@henrytao-me
I need to implement a pulldown for a viewpager inside collapsible toolbar which is inside appbarlayout which is inside a co-ordinator layout. I tried the callback to touch listener on viewpager and on appbar but the move actions are returning false from coordinator layout methods similar to what is pointed above.Do you think having my own appbar which overrides the above method will help in my case?

@henrytao-me
Copy link
Owner

Hi @pm48

I have marked this library as deprecated. If you are using SupportLibrary 26 and above, I would prefer to user original AppBarLayout.

Thanks,
Henry

@pm48
Copy link

pm48 commented Jan 24, 2018

@henrytao-me
I had used the original AppbarLayout from 26.0.2 but I still see the issue.
Is there an example of how I can implement a pulldown i.e. touch and drag event handlers for a viewpager inside collapsible toolbar which is inside appbarlayout which is inside a co-ordinator layout?

@pm48
Copy link

pm48 commented Jan 24, 2018

@nikhilreprime
Any suggestions for implementing the pulldown.

@nikhilreprime
Copy link

nikhilreprime commented Jan 24, 2018

@pm48
I have not tried it but if you are looking for something similar to SwipeRefresh
i got this link explaining how you can use SwipeRefreshLayout with AppBarLayout

hope this will help you a little

@pm48
Copy link

pm48 commented Jan 25, 2018

@nikhilreprime
Thanks but I am not looking for swiperefresh, but more of trying to pullldown an image inside viewpager which is inside collapsible toolbar which is inside appbarlayout which is inside a co-ordinator layout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests