1

Given an arbitrary ViewGroup G with an arbitrary collection of child views, how can I detect when the user clicks on any of the child views? In this case, I want to draw a highlight for G.

I could add an onClick listener for each child, but I'm trying to avoid that so that the code doesn't have to be changed when the layouts change.

Alternatively, I could add onTouch handlers to G and set the highlight during ACTION_DOWN. However, this would trigger for actions that don't actually result in clicks, such as a swipe (the swipe could be handled by ViewPager, for example, and ultimately be irrelevant to G).

My layout for G has the focusable attributes:

android:focusable="true"
android:focusableInTouchMode="true"

Thanks.

Peri Hartman
  • 19,314
  • 18
  • 55
  • 101
  • First: OnClicklistener is a implementation of touchlistener, so if onClick doesn't meet your requirements then you should create your own. I would suggest taking a look at the source of View, there you will find onTouchEvent method which will invoke the ClickListener (performClick()). Second: Theoretically it should be possible to determine if the touch event should be dispatched to the view or to the viewpager (onTouchIntercept). Prepare to get your hands dirty, it will not be easy ;) – Tobrun Mar 10 '14 at 19:59
  • Yes, onTouchEvent() is the basis for all clicks, etc. I think the right place to look is AbsListView.onTouchUp(). There is a riddle of code which probably does exactly what I'm looking for. – Peri Hartman Mar 10 '14 at 21:39

3 Answers3

1

Here is how I do it:

//in onTouch method of parent, I get the coordinates of click
int x = ((int) motionEvent.getX());
int y = ((int) motionEvent.getY());

//obtain the clickable arrea of the child as HitRect
Rect clickRect = new Rect();
Rect rect = new Rect();
imageView.getHitRect(rect);

//ask if the area contains the coordinates of the click
if(rect.contains(x, y)){
    //do some work like if onClickListener on the child was called.
    return false; //you clicked here, don't need to handle other Childs
}

//ask for other childs like before...

Now, you can target the parent as the delegate of all clicks done inside it, even if it is done in a child.


EDIT:

To ignore other touch event that are not click, you can ask for how much user moved the finger:

case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_CANCEL:
     if (Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
              return true; // user mover finger too much, ignore touch
     }
     return false; // finger still there waiting for click

I give a square of 10 pixels to permit a confortable click, and if you exit it, I ignore it.


EXTRA:

Here is the complete code for click and long click with onTouchListener.

IgniteCoders
  • 4,834
  • 3
  • 44
  • 62
  • This sort of works, but ignores the requirement in my OP (see 3rd paragraph). – Peri Hartman May 16 '18 at 14:49
  • I'll give you an upvote for the effort but I think there will be all kinds of special cases which fail. One can continue to fine tune to handle them, of course. And it's been 4 years since I posted the question so it's not very fresh in my mind. Thanks, again. – Peri Hartman May 16 '18 at 20:09
  • I know, I just posted the answer because I was looking for it and after some search and nothing clear, I wrote it and post it here for future searchers. – IgniteCoders May 16 '18 at 22:16
  • Some of the things that is not handled, is if the user use two fingers, but could be solved storing the touch id. – IgniteCoders May 16 '18 at 22:19
0

You could use the View.getChildCount() to loop through all child views and see if the touch intersects with the child view. This involves getting x and y positions and calculating if it fits within the child view, use View.getChildAt(position) to get the reference to the child view .

So it would be something like this:

int childNr = theView.getChildCount();
for (int i = 0; i < childNr; i++){
     YourView tmp = (YourView) theView.getChildAt(i);
     if(tmp.intersects(x, y)){
          do some work
     }
}

here you would have to put your view variable instead of theView and the class name which handles the views instead of (YourView) and x, y are the coordinates of the pressed spot.

ojs
  • 492
  • 4
  • 13
  • Maybe I'm not understanding something, but how does this help determining when a click happens in a child? First I need to know the click happened. Then, using your code, I could determine the child it occurred. But my problem is detecting the click. – Peri Hartman Mar 10 '14 at 20:14
  • Ahh, yes, you always need an onClickListener for that. But it would be a listener for the main View, not the child Views. – ojs Mar 10 '14 at 20:15
  • That doesn't seem to work. I believe it's because the child views are consuming the touch events. – Peri Hartman Mar 10 '14 at 20:25
  • Hmm, that should not happen I think, try emitting a logcat message in the onClickListener to see if it gets called at all. – ojs Mar 10 '14 at 20:35
0

In your XML, you could add point all the children to the same onClick method. Inside that method you could draw the highlight to G and then do something (or nothing) for the individual child view.

scottt
  • 8,301
  • 1
  • 31
  • 41
  • This might work, assuming a view can have more than one onClick handler. I'm hoping there's a better solution :) – Peri Hartman Mar 10 '14 at 20:22
  • No, any one view would only have the one handler. But multiple views can share the same handler. Inside the handler, you can determine which view was clicked if your care. – scottt Mar 10 '14 at 20:35
  • Right. I just tested. Only one handler per view. So this solution doesn't help. It would be a horrible mess to collect all clicks in one handler. Thanks for the thoughts. – Peri Hartman Mar 10 '14 at 20:56