Is it possible to pass an onTouchEvent to multiple views in Android?
Asked Answered
S

2

5

I have read a few questions regarding this topic on SO but haven't really found a solid answer to it.

I have a framelayout that I stack multiple custom views on, however the onTouch event only works with the top view. (the custom views are all the same view with the same onTouch event, just multiple of them)

FrameLayout

  • customView[2] <--- this is the last view added and the only one that receives the event
  • customView[1]
  • customView[0]

I'm testing it on Android 2.2 and am wondering if there is any way for the other views below to know where the touch happened?


EDIT (Adding some code)

I'm adding some code to hopefully help explain where I'm running into issues. At first I just automatically had the onTouchEvent return true. This made it so that the last view (in my case customerView[2]) would be the only one generating a value.

However, once I added the method to set the onTouchEvent to return true or false, now the only view returning a generated value is customView[0].

I hope this clears up what I am asking. I'm rather new to this and I appreciate you taking the time to explain it (and of course I appreciate your patience).

Also, I realize that my TextView's don't update with the value on each touchEvent, I'm working on fixing that.

My Activity:

public class MyActivity extend Activity {

    CustomView[] customView;
    TextView[] textView;
    int numViews 3;
    //FrameLayout and Params created

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        for(int i = 0; i < numViews; i++) {
            customView[i] = new CustomView(this, i);


            //Allows the onTouch to be handled by all Views - View[0] is the bottom view
            if(i == 0) {
                customView[i].setTouchBool(true);        //set view's onTouch to return true
            } else {
                customView[i].setTouchBool(false);       //set view's onTouch to return false
            }

            //Set TextView to display the number generated by the CustomView
            textView[i].setText(Double.toString(customView[i].getGeneratedNumber()));

            //Add views to main layout
            frame.addView(textView[i]);
            frame.addView(customView[i]);
        }
    }
}

My View:

public class CustomView extends View {

    boolean onTouchHandler = true;
    int xVal = 0, yVal = 0;
    int index;
    double generatedNum = 0;

    public CustomView(Context context) {
        this(context, 0);
        this.index = 0;
    }

    public CustomView(Context context, int index) {
        super(context);
        this.index = index;
    }


    @Override 
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();

        switch(action) {
            case MotionEvent.ACTION_DOWN: {
                //do logic 
            }

            case MotionEvent.ACTION_MOVE: {
                //do logic
            }

            case MotionEvent.ACTION_UP: {
                xVal = (int) ev.getX();
                yVal = (int) ev.getY();

                generateNumber(xVal, yVal, index);

                break; 
            }
        }
         return onTouchHandler;    
    }  


    private void generateNumber(int x, int y, int index) {
        if(index == 0) {
            generatedNum = (x / 2) * (y / 2) + 64;
        } else {
            generatedNum = (x / 2) * (y / 2) + (index * 128);
        }
    }

    public double getGeneratedNumber() {
        return generatedNum;
    }

    public boolean setTouchBool(boolean b) {
        this.onTouchHandler = b;
    }
}
Softpedal answered 18/11, 2011 at 22:52 Comment(0)
E
16

Android will cascade down the views calling onTouchEvent on each one until it receives a true from one of them. If you want a touch event to be handled by all of them, then return false until it reaches the last one.

EDIT:

Ok. If I understand correctly, you have a single top view containing a bunch of child views one layer deep. My original answer was assuming that you had three custom views that were on top of each other in the ViewGroup's hierarchy (View3 is a child of View2. View2 is a child of View1. View1 is a child of ParentView). You want the user's touch event on the parent view to get sent to all of it's children.

If that's the case, AFAIK, there is no view in Android's API that allows that. So, you'll have to make a custom view that does it.

OK, I haven't tested this, so please tell me if it works and if it's what you're trying. Create a custom class that extends whatever object frame is, then override the onTouch method like so.

@Override 
public boolean onTouchEvent(MotionEvent ev) {
   for(int i = 0; i < this.getChildCount(); i++){
      this.getChildAt(i).dispatchTouchEvent(ev);
   } 
   return true;
} 

Now, keep the same logic that your custom views have, except they should all return false because your parent view will not receive the onTouch event unless they do as stated in my previous answer

note: with this implementation, the child view that the user actually touches will fire twice because the logic will go fire child touch event -> return false -> fire parent touch event -> fire child touch event again

Economically answered 18/11, 2011 at 22:55 Comment(6)
The only way to tell which views returned true is through the code logic that you make up. Each view will have to decide if the touch has been handled. If it has, return true. If it hasn't, then you can called super.onTouchEvent() which will return true or false based on what the parent view returned. As for gathering values for each view, that's entirely dependent on how your class structure is set up.Economically
I realize that I gave you little to work with (no code). I went ahead and typed up some code that is similar to my View and Activity. Perhaps you can show me where I need to go from there. I'm wanting each view to generate a number rather than just one view doing so. Ultimately I would like the TextViews to display them, but I'm attempting to go one step at a time. I appreciate the explanations you've given me thus far.Softpedal
Oh, I think I understand now. I thought each view was a child of the other view and you wanted the onTouch event to traverse upward through the hierarchy which is usually what people ask. From what it looks like, they're all children to a single view. You want the single touch event to be sent to all children views, correct? I'll update my answer with a possible solution.Economically
I apologize for the late response (it's been the holidays). If I understand you correctly, I should do the following: frame is a LinearLayout so I should extend it and override the onTouchEvent. Then the only thing I should edit after that is making my CustomView's onTouchEvent to always return false? If this is correct, I'll go ahead and implement it and report back!Softpedal
Now, I only say they should return false because they are all on top of the LinearLayout which means the user will be touching the children rather than the parent so you'll need a way to cascade the event to the parent. It's the only way I've been able to do it, but then again I've never needed to do it another way. Now that I think about it, this method may constantly do a recursive loop. You may be able to fix that by calling onTouch() on the children directly rather than dispatchTouchEvent(), or there's a way to disable touch events through the focus methods. I'll research it a bit.Economically
I ended up doing it a different way. But I am still very much interested to know if it's still possible :) Also thank you for going through all the extra trouble you have alreadySoftpedal
W
0

I know this question is very old, but I had the same problem and solved it by creating my own Layout to determine which child is actually touched.

I therefore iterate over the children of my custom layout and check if the user actually clicked on the view. The collision detection is handled in the custom view's onTouch() method. (Collision detection is done by intersecting a Region() with the event's x,y coordinates. For me this was convennient because I drew the custom view with a Path())

Here is a kotlin code snippet from my custom layout for better understanding:

class CustomLayout(context: Context, attrs: AttributeSet) : 
RelativeLayout(context, attrs){

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        if(ev.action != MotionEvent.ACTION_UP){
           return true
        }
        //Iterate over child view and search for the right child that should handle this touch event
        for (i in childCount - 1 downTo 0) {
            val child = getChildAt(i)
            if (!viewTouched(child, ev)) {
                continue
        }
        //Do something
        Timber.d("Touched view: ${child.id}")
        }
        return true
    }

    private fun viewTouched(child: View, ev: MotionEvent) : Boolean {
        child as OnTouchListener
        //onTouch() does the collision detection
        return child.onTouch(child, ev)
    }
Wallet answered 4/11, 2019 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.