Simultaneous touch events handling on multiple views in Android
Asked Answered
E

2

8

Considering the example from the below image,

enter image description here

I'm looking for a solution to pass the touch events from the canvas View to the Viewpager. This is necessary for applying the same amount of zoom on both views at the same time.

Here's the technical description of what's going on,

(Lets consider the view as A, B & C)

A. Parent View

B. View

C. View Pager

Before the dispatchTouchEvent() is called on the children (B & C), the A.dispatchTouchEvent() will first calls A.onInterceptTouchEvent() to see if the view group is interested in intercepting the event.

So if A.onTouchEvent() returns false, then it goes back up to B.onTouchEvent(). If that returns false then it goes back up to C.onTouchEvent() and the dispatching continues as usual.

And upon returning true,

  1. ACTION_CANCEL will be dispatched to all the children.

  2. All the subsequent gesture events (till ACTION_UP/ACTION_CANCEL) will be consumed by the event listeners (OnTouchListener.onTouch()) if defined, else the event handler A.onTouchEvent() at A’s level.

At this point I can pass the events from the parent to the child views but can't let the 2 child view B. & C handle the events together (applying same amount of zoom on both views).

Is there any way to pass the touch events from the parent view to a child one so that they can process the events simultaneously?

Here's my layout setup,

<RelativeLayout
    android:id="@+id/layoutParent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFFFF">

            <com.androidapp.NonSwipeableViewPager
                android:id="@+id/pager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_gravity="center"
                android:background="@color/transparent" />

            <com.androidapp.DrawingView
                android:id="@+id/drawing"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/transparent" />

</RelativeLayout>
Electroencephalograph answered 6/12, 2016 at 10:39 Comment(4)
How the canvas is positioned in viewpager control? Does it appearing as a pop-up or some other way?Infinite
The canvas and the viewpager is inside 2 separate views. Check my layout code for a closer look. @InfiniteElectroencephalograph
So your DrawingView intercepts NonSwipeableViewPager(according to your layout code), isn't it?Infinite
Yes it does! @InfiniteElectroencephalograph
S
6

If I'm not wrong, you want to process the same touch event both on the top and bottom view. According to Android touch architecture, you can't handle touch events from multiple views at the same time. Either you have to process it inside the onTouchEvent() method or pass to the next view. You also need to wait till the top view finish processing the MotionEvent() and let the child view to handle.

Here's a possible solution using RxAndroid approach,

Inside your canvas view's MotionEvent(),

@Override
    public boolean onTouchEvent(MotionEvent event) {

    // process all touch events (UP/DOWN/MOVE)

    //send events as observable
    RxBus.getInstance().sendMotionEvent(event);

    return true;
}

This will observe the motion events and notify all observers.

Now, inside your viewpager's onResume() subscribe for the motion events so that whenever a change is made from the canvas It'll immediately pass the events.

Subscription RxBus _rxBus = RxBus.getInstance();
        subscription = _rxBus.getMotionEvent()
                .subscribe(new Action1<MotionEvent>() {
                    @Override
                    public void call(MotionEvent event) {
                       // call the onTouchEvent of your widget e.g. ImageView and pass the received event
                       // this will apply the same touch event that was applied in the canvas 

                        // .onTouchEvent(event) is important to override the onTouchEvent() of your widget that will use the touch event
                        imageView.onTouchEvent(event);
                    }
            });

The RxBus class is given below,

public class RxBus {

    private static RxBus instance;

    private final PublishSubject<MotionEvent> motion_event = PublishSubject.create();

    public static RxBus getInstance() {
        if (instance == null) {
            instance = new RxBus();
        }
        return instance;
    }

    public void sendMotionEvent(MotionEvent motionEvent) {
        motion_event.onNext(motionEvent);
    }

    public Observable<MotionEvent> getMotionEvent() {
        return motion_event;
    }
}
Soelch answered 13/12, 2016 at 5:46 Comment(2)
what about deliver the touch to more than one view?Shainashaine
There is a mistake in the function sendMotionEvent i.e. it should be: motion_event.onNext(motionEvent) rather than motion_event.onNext(motion_event)Carbonyl
R
2

It is pretty easy to implement by using call back. Just create an Interface with whatever methods, you need and then implement in your Viewpager. Call will be invoked in your View class based on your touch events and responds will come to Viewpager. I have created an example with an ImageView in my MainActivity and called to changed its size from a different fragment and it works. Here is my code below:

public interface ICallBackForResize {
   void resizeme();
   void actionUp();
   void actionDown();

 }

MainActivity:

public class MainActivity extends AppCompatActivity implements ICallBackForResize {

ImageView img;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    img= (ImageView) findViewById(R.id.image);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}

@Override
public void resizeme() {
    Toast.makeText(MainActivity.this,"called",Toast.LENGTH_LONG).show();
    img.getLayoutParams().height += 20;
    img.getLayoutParams().width += 20;
    img.requestLayout();
}

@Override
public void actionUp() {
    //do whatever
}

@Override
public void actionDown() {
    //do whatever

}
 }

My Fragment class with a simple button:

public class MyFragmentButton extends Fragment {
View view;
ICallBackForResize callBackForResize;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    callBackForResize= (ICallBackForResize) getActivity();
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    view=inflater.inflate(R.layout.mybutton,container,false);
    Button btn= (Button) view.findViewById(R.id.button);
    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            callBackForResize.resizeme();
        }
    });
    return view;
}
}

I believe you dont need xml file. I just shared if somebody needs:

activity_main:

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.AppBarLayout
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:theme="@style/AppTheme.AppBarOverlay">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_main"/>


 </android.support.design.widget.CoordinatorLayout>

content_main:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/activity_main"
tools:context="nanofaroque.com.addlistener.MainActivity">

<ImageView
    android:id="@+id/image"
    android:text="Hello World!"
    android:layout_width="100dp"
    android:layout_gravity="center"
    android:src="@mipmap/ic_launcher"
    android:layout_height="100dp" />


<fragment
    android:name="nanofaroque.com.addlistener.MyFragmentButton"
    android:id="@+id/headlines_fragment"
    android:layout_width="100dp"
    android:layout_height="100dp" />

 </FrameLayout>

mybutton:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

 </LinearLayout>
Rameau answered 8/12, 2016 at 4:28 Comment(2)
Thanks for a brief answer, however I'm interested in handling the touch events on both of the views at the same time (right now if I pass the touch event from one view to another it simply ignore the event on top level view). Please check the updated question.Electroencephalograph
In that case, ChildView should be in separate layout i believe. Relative layout wont work in this scenario because one views position relative to others. Thanks and best of luck.Rameau

© 2022 - 2024 — McMap. All rights reserved.