ScrollView with children view, how to intercept scroll conditionally

11,127

Well after a night of coca cola & debugging I managed to get this to work. I share the solution just in case it is of interest to anyone, because it took me quite a lot of time to get it running.

I didn't manage to get it running with getParent().onRequestDisableInterceptTouch(), I was close, but couldn't find a way for the child widgets to get the MotionEvents they need for scrolling once I intercepted the touch on the parent, so even though the outer scroll was prevented correctly, the inner widgets didn't scroll.

So the solution is to interceptTouchEvents in the children ONLY, and if the children is scrollable (known property), and the touch is ACTION_DOWN, then disable the scrollview two levels above. If the touch is ACTION_UP, we enable the scrollview.

To enable/disable the scrollview I just intercept the touch event and with a flag filter the event or not.

I did three auxiliary classes, one for the ScrollView, one for the Container, One for the widgets:

This class wraps every widget and, if I call setNeedsScroll(true) , then touches will be intercepted, and when it is touched, it will (tell the container to) tell the scrollview to disable itself. When the touch is released, it will re-enable the scrollview.

class WidgetWrapperLayout extends FrameLayout {

    private boolean mNeedsScroll=false;

    public WidgetWrapperLayout(Context context) {
        super(context);
    }

  /** Called anytime, ie, during construction, to indicate that this 
    * widget uses vertical scroll, so we need to disable its container scroll 
    */

    public void setNeedsScroll(boolean needsScroll) { 
        mNeedsScroll=needsScroll; 
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mNeedsScroll)  {
            switch (ev.getAction()) {  
            case MotionEvent.ACTION_DOWN:
                ((SlideLayout)getParent()).setEnableScroll(false);
                break;
            case MotionEvent.ACTION_UP:
                ((SlideLayout)getParent()).setEnableScroll(true);
                break;
            }
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

This is the container, only child of the scrollview, and holds the different widgets. It just provides methods for the children so they can enable/disable the scroll:

public class ContainerLayout extends FrameLayout {

    public ContainerLayout(Context context) {
        super(context);
    }

    public void setEnableScroll(boolean status) {
        if (Conf.LOG_ON) Log.d(TAG, "Request enable scroll: "+status);
        ((StoppableScrollView)getParent()).setScrollEnabled(status);
    }
}

and finally a scrollview capable of deactivation. It disables the scroll 'old-skool', intercepting and blocking events.

public class StoppableScrollView extends ScrollView {

    private String TAG="StoppableScrollView";

    private boolean mDisableScrolling=false;

    public StoppableScrollView(Context context) {
        super(context);
    }

    /** Enables or disables ScrollView scroll */
    public void setScrollEnabled (boolean status) { 
        if (Conf.LOG_ON) Log.d(TAG, "Scroll Enabled "+status);
        mDisableScrolling=!status; 
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mDisableScrolling) return false;
        return super.onInterceptTouchEvent(ev);
    }
}
Share:
11,127
rupps
Author by

rupps

Programming everyday since I was a kid and got my first computer back in 1983, where I enjoyed fantastic games like Xenon 1 for a while. But as this was a niche computer, software was nearly non-existant. And my journey started there ... from BASIC and 6502 Assembler in the funny 80´s, to today´s distributed cloud computing, I have been around digging as deep as possible. During the journey I studied Telecommunications Engineering, worked in Telefonica R&D, Lucent Technologies and Alcatel, mostly in the IPTV field, always taking charge of hardcore programming tasks, and finally created my independent "garage company" Nebular Streams, where research does not stop. LD HL, #C000 LD DE, #C001 LD BC, #3FFF LDIR RET

Updated on June 15, 2022

Comments

  • rupps
    rupps almost 2 years

    I have a container ViewGroup, lets call it screen inside a ScrollView. This container view hosts a number of other Views let's call them widgets, and some of them are interested in preventing the ScrollView from scrolling and using the MotionEvent theirselves (for example a pannable image)

    I can't figure out the proper event intercept strategy to use. ScrollView always processes the event before the children, or the children process the event but scrollview is disabled.

    I read about issuing getParent().requestDisableInterceptTouchEvent() in the child views if this view wants to capture the event, but their onTouchEvent is not called, I suppose because ScrollView has engulfed the event beforehand. I guess the fact that I have 2 levels of layers (container + widgets) prevents this from working, I suppose the container ViewGroup has to play an important part here, but I can't figure out which one...

    Can I know, at the ScrollView's onInterceptTouchEvent level, which widget on the container viewGroup has been touched to decide if I should intercept or not?

    or...

    How can the 'widget' layers in the ViewGroup get the event before ScrollView so I can call getParent().onRequestDisableInterceptTouch() ... or is it getParent().getParent().onRequestDisableInterceptTouch()?

    Thanks in advance

    I've read related questions but no luck ... Handle touch events in ScrollView Android