MotionEvent handling in ScrollView in Android
Asked Answered
S

2

5

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.

Sb answered 14/10, 2012 at 16:35 Comment(0)
D
12

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.

Draghound answered 29/11, 2012 at 15:6 Comment(2)
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.Sb
The best explanation I have ever read about onInterceptTouchEvent() and onTouchEvent(). Thanks alot!Mimimimic
L
1

I have views with animations on scroll view. Animations strat and stop equals of motion event (DOWN and UP). I fix the same solution:

public class RootActivity extends Activity implements OnTouchListener {

private View tochedView = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_root); 

    ((View) findViewById(R.id.btnOther)).setOnTouchListener(this);

    ScrollView scroll = (ScrollView) findViewById(R.id.scrollView);
    scroll.setOnTouchListener(new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_UP && tochedView != null) {
                Log.i("Touche", "ScrollView ACTION_UP");
                Animation upAnim = AnimationUtils.loadAnimation(RootActivity.this, R.anim.btn_up_anim);
                tochedView.startAnimation(upAnim);
                tochedView = null;
                return true;
            }
            return false;
        }
    });
}


private void animateView(View v, MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        Log.i("Touche", "ACTION_DOWN");
        Animation downAnim = AnimationUtils.loadAnimation(this, R.anim.btn_down_anim);
        v.startAnimation(downAnim);
        tochedView = v;
    }
    if (event.getAction() == MotionEvent.ACTION_UP && tochedView != null) {
        Log.i("Touche", "ACTION_UP");
        Animation upAnim = AnimationUtils.loadAnimation(this, R.anim.btn_up_anim);
        v.startAnimation(upAnim);
        tochedView = null;
    }

}


@Override
public boolean onTouch(View v, MotionEvent event) {
        animateView(v, event);
}

}

Lustick answered 8/4, 2014 at 16:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.