MotionEvent handling in ScrollView in Android

13,924

Maybe you already figured out why the behaviour above, but just in case you don't, here goes the reason.

Overview

The onInterceptTouchEvent() are called top-down (from parent to child) enabling one view to intercept the motion event before being handled by a child.

The onTouchEvent() are called down-top (from child to parent) until one of them consum it and the cycle finishs.

A ScrollView intercepts MotionEvent to check if it should scroll the view before passing them to the child. If the scroll should to be perfomed, the event is consumed and child view sees nothing.

In the case of LinearLayout, there is no reason why the event should be consumed during onInterceptTouchEvent(), and is always passed to the child view.

What's happening in your code

Because MyInnerLayout is empty the ScrollView is always consuming the MotionEvent.

If, for example, you set the inner layout backgound like this:

    MyInnerLayout inner = new MyInnerLayout(getApplicationContext());
    inner.setBackground(getResources().getDrawable(R.drawable.ic_launcher));
    MyLayout layout = new MyLayout(getApplicationContext());

you will see that if you touch over the background image the event will reach the child. If you touch outside the background image, the event will be consumed by the ScrollView.

Hope this helps.

Regards.

Share:
13,924
Rotem
Author by

Rotem

Updated on June 12, 2022

Comments

  • Rotem
    Rotem almost 2 years

    I've been trying to figure out the behavior of MotionEvents in ScrollViews in Android and there's something i can't figure out.

    As an example I made an Activity that has a ScrollView inside of it and the ScrollView has a LinearLayout inside of it. I implemented my own classes to have control over the touch-related functions:

        public class MainActivity extends Activity {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            MyInnerLayout inner = new MyInnerLayout(getApplicationContext());
            MyLayout layout = new MyLayout(getApplicationContext());
    
            layout.addView(inner,new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
            setContentView(layout);
    
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            Log.i("scrollview","activity dispatchTouchEvent "+ev.getAction());
            return super.dispatchTouchEvent(ev);
        };
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            Log.i("scrollview","activity on touch "+ev.getAction());
            return super.onTouchEvent(ev);
        }
    
    
    
    
        public class MyLayout extends ScrollView {
    
            public MyLayout(Context context) {
                super(context);
            }
    
            @Override
            public boolean dispatchKeyEvent(KeyEvent ev) {
                Log.i("scrollview","layout dispatchKeyEvent "+ev.getAction());
                return super.dispatchKeyEvent(ev);
            }
    
            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                Log.i("scrollview","layout onInterceptTouchEvent "+ev.getAction());
                return false;
            }
    
            @Override
            public boolean onTouchEvent(MotionEvent ev) {
                Log.i("scrollview","layout on touch "+ev.getAction());
                return false;
            }
    
        }
    
        public class MyInnerLayout extends LinearLayout{
    
            public MyInnerLayout(Context context) {
                super(context);
            }
    
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                Log.i("scrollview","inner layout dispatchTouchEvent "+ev.getAction());
                return true;
            }
    
            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                Log.i("scrollview","inner layout onInterceptTouchEvent "+ev.getAction());
                return true;
            }
    
            @Override
            public boolean onTouchEvent(MotionEvent ev) {
                Log.i("scrollview","inner layout on touch "+ev.getAction());
                return true;
            }
    
        }
    
    }
    

    When I click anywhere on the screen I get this log:

    10-14 18:11:48.631: I/scrollview(14906): activity dispatchTouchEvent 0
    10-14 18:11:48.631: I/scrollview(14906): layout onInterceptTouchEvent 0
    10-14 18:11:48.631: I/scrollview(14906): layout on touch 0
    10-14 18:11:48.631: I/scrollview(14906): activity on touch 0
    10-14 18:11:48.647: I/scrollview(14906): activity dispatchTouchEvent 1
    10-14 18:11:48.647: I/scrollview(14906): activity on touch 1
    

    that means that the touch event didn't make the way down to the inner layout inside the scrollview. however when I change the ScrollView to a LinearLayout (simply just change it in the extends), the event goes down to the inner layout:

    10-14 18:24:08.975: I/scrollview(15115): activity dispatchTouchEvent 0
    10-14 18:24:08.975: I/scrollview(15115): layout onInterceptTouchEvent 0
    10-14 18:24:08.975: I/scrollview(15115): inner layout dispatchTouchEvent 0
    10-14 18:24:09.045: I/scrollview(15115): activity dispatchTouchEvent 1
    10-14 18:24:09.045: I/scrollview(15115): layout onInterceptTouchEvent 1
    10-14 18:24:09.045: I/scrollview(15115): inner layout dispatchTouchEvent 1
    

    I looked in the source code of the ScrollView class and the only touch-related methods that it overrides are the ones I overrided myself. So I don't understand what makes the difference between the behavior of the LinearLayout and the ScrollView.

  • Rotem
    Rotem over 11 years
    the problem was not related to touch event, but to the difference in the LayoutParams of ScrollView and LinearLayout: FILL_PARENT has no meaning in ScrollView, instead I had to set SetFillViewport to true :) thanks anyways.
  • Hrishikesh Kadam
    Hrishikesh Kadam almost 6 years
    The best explanation I have ever read about onInterceptTouchEvent() and onTouchEvent(). Thanks alot!