2

I have parent view v extends linear layout. And child one is LinearLayout, child two is ScrollView.

I want to make MyParent View intercept vertical MotionEvent.ACTION_MOVE (only down direction, child scrollview.getScrollY()==0) and processing it in parent, and other MotionEvent is processed by children

Here is MyView.xml

  <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android" >
    <LinearLayout
        android:id="@+id/child_linear>
        android:height="50dp"
        android:width="50dp">
            ...something...
    </LinearLayout>
    <ScrollView
       android:id="@+id/child_scrollview>
      <LinearLayout/>
      </LinearLayout>
    </ScrollView>
    </merge> 

And this is my code below

public class MyCustomView extends LinearLayout{
    public MyCustomView(Context context) {
        super(context);
        init();
    }
    private void init(){
        setOrientation(LinearLayout.VERTICAL);
        LayoutInflater inflater = 
(LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflater.inflate(R.layout.MyView, this, true);


    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        Log.d(log,"onInterceptTouchEvent : "+action);
        if (action == MotionEvent.ACTION_CANCEL || action == 
MotionEvent.ACTION_UP) {
            mIsBeingDragged = false;
            return false;
        }
        if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
            return true;
        }

        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                if (isReadyForPull()) {
                    final float y = event.getY(), x = 
event.getX();
                    final float diff, oppositeDiff, absDiff;
                    diff = y - mLastMotionY;
                    oppositeDiff = x - mLastMotionX;
                    absDiff = Math.abs(diff);
                    ViewConfiguration config = 
ViewConfiguration.get(getContext());

                    if (absDiff > config.getScaledTouchSlop() &&  
absDiff > Math.abs(oppositeDiff) && diff >= 1f) {
                            mLastMotionY = y;
                            mLastMotionX = x;
                            mIsBeingDragged = true;
                            Log.d(log,"Flag setting");
                    }
                }
                break;
            }
            case MotionEvent.ACTION_DOWN: {
                if (isReadyForPull()) {
                    mLastMotionY = mInitialMotionY = event.getY();
                    mLastMotionX = event.getX();
                    mIsBeingDragged = false;
                }
                break;
            }
        }
        return mIsBeingDragged;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action=event.getAction();
        Log.d(log,"onTouchEvent : "+action);
        if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
            return false;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE: {
                Log.d(log,"ACTION MOVE RECEIVE");
                if (mIsBeingDragged) {
                    mLastMotionY = event.getY();
                    mLastMotionX = event.getX();
                    pullEvent();
                    return true;
                }
                break;
            }

            case MotionEvent.ACTION_DOWN: {
                if (isReadyForPull()) {
                    mLastMotionY = mInitialMotionY = event.getY();
                    return true;
                }
                break;
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                if (mIsBeingDragged) {
                    mIsBeingDragged = false;

                    if (mState == State.RELEASE_TO_REFRESH ) {
                        setState(State.REFRESHING);
                        return true;
                    }
                    if (isRefreshing()) {
                        smoothScrollTo(-mHeaderHeight , null);
                        return true;
                    }

                    setState(State.RESET);
                    return true;
                }
                break;
            }
        }
        return false;
    }

isReadyForPull() function check only this

    private boolean isReadyForPull(){
        return mScrollView.getScrollY()==0;
    }

My code works well.

If I move down my child scrollview, onInterceptTouchEvent first get MotionEvent.ACTION_DOWN and initialize values, and get sequentially MotionEvent.ACTION_MOVE so set mIsBeingDragged flag to true and return true. So I can process event in my ParentView's onTouchEvent.

Otherwise, if I move up child scrollview, then onInterceptTouchEvent return false and my child scrollview get that MotionEvent and scrolling down.

This is expected work. Good.

But, when I touch my child linearlayout, it doesn't not work!

If I touch and drag my child linearlayout, my CustomView's onInterceptTouchEvent get MotionEvent.ACTION_DOWN, but couldn't get second MotionEvent.ACTION_MOVE.

Why this not work on only child linearlayout?

Note: I tested several times and know that (default)touchable view or viewgroup (like button, scrollview etc.) do work with my code, while (default)untouchable view or widget(like imageview, framelayout) don't work with my code.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Abyss
  • 91
  • 1
  • 1
  • 7

2 Answers2

7

The solution relates to this answer, as it is a result of the standard MotionEvent handling.

In my code, when I touch the child LinearLayout, the parent CustomViewGroup doesn't intercept the MotionEvent as it returns false, but my child LinearLayout doesn't consume the MotionEvent either, so the MotionEvent is returned to the parent's onTouchEvent, not onInterceptTouchEvent.

On the other hand, when I touch my child ScrollView, it consumes the MotionEvent whether scrolling is enabled or disabled.

==> I think because Android doesn't generate a MotionEvent again until the original one is either consumed or finished, so the parent CustomViewGroup doesn't get the ACTION_MOVE MotionEvent via onInterceptTouchEvent, instaed it is piped to onTouchEvent when a child doesn't consume the MotionEvent.

I found two solutions

Solution one

Forcibly make my LinearLayout consume the MotionEvent. This solution is available only when the child LinearLayout has no touchable View, ViewGroup or Widget. Like this:

LinearLayout mLinearLayout = (LinearLayout)findViewById(R.id.child_linear);
mLinearLayout.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return true;
    }
});

Solution two

Process the MotionEvent returning from a child in my CustomViewGroup's onTouchEvent. Like this: (Just add else if case in onTouchEvent).

case MotionEvent.ACTION_MOVE: {
    if (mIsBeingDragged) {
        mLastMotionY = event.getY();
        mLastMotionX = event.getX();
        pullEvent();
        return true;
    }
    else if (isReadyForPull()) {
        final float y = event.getY(), x = event.getX();
        final float diff, oppositeDiff, absDiff;
        diff = y - mLastMotionY;
        oppositeDiff = x - mLastMotionX;
        absDiff = Math.abs(diff);
        ViewConfiguration config = ViewConfiguration.get(getContext());

        if (absDiff > config.getScaledTouchSlop() &&  absDiff > 
        Math.abs(oppositeDiff) && diff >= 1f) {
             mLastMotionY = y;
             mLastMotionX = x;
             mIsBeingDragged = true;
        }
     }
     break;
 }

While solution 1 is a quick fix for certain situations, solution two is the most flexible and reliable.

Community
  • 1
  • 1
Abyss
  • 91
  • 1
  • 1
  • 7
0

There is very good answer

onInterceptTouchEvent only gets ACTION_DOWN

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
MyLog.d(MyLog.DEBUG, "dispatchTouchEvent(): "+event.getAction());
if (isEnabled()) {
    MyLog.d(MyLog.DEBUG, "dispatchTouchEvent()2: "+event.getAction());

    processEvent(event);//here you get all events include move & up

    super.dispatchTouchEvent(event);

    return true; //to keep receive event that follow down event
}
return super.dispatchTouchEvent(event);
}
Sirop4ik
  • 4,543
  • 2
  • 54
  • 121