2

Consider this hypothetical hierarchy...

LinearLayoutA <-- I want to handle the touches here...
  |
  +-SomeViewX
  +-SomeOtherViewY
  +-LinearLayoutB
      |
      +-CustomView1 <-- for these three CustomView objects
      +-CustomView2
      +-CustomView3

What I would like to do is know which (grand)child view was touched and handle it from within LinearLayoutA. Also, I don't control those views so I can't simply make them handle the touch internally and delegate to their parent(s).

Currently I'm manually adding touch listeners to CustomViews 1-3 but that requires a lot of 'boilerplate' work and also means I won't get notified if someone clicks on SomeViewX or SomeOtherViewY, only the specific ones I've attached the listener to.

Now in other languages such as C# with WPF, if you handle the event at the equivalent of LinearLayoutA, part of the event payload is a source, which is the view that initiated the touch, but I'm not aware of any such thing in Android.

All the examples I've seen require looping through the children and hit-testing them, then disambiguating by z-order if there's an overlap, and when you've identified the one child, then you have to go through its children and repeat.

So, is there a built-in, or 'Androidy' way to know which child in a ViewGroup was touched without manually iterating and hit-testing, or manually attaching listeners to all its children?

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • OnTouchListener takes in the `View` as a parameter, so you can use the same listener for all 3 objects and know which one was hit by looking at the argument. This still requires manually attaching the single listener, though. – Elan Hamburger Jul 20 '17 at 14:47
  • Yeah, manually attaching the listener was what I was trying to avoid, especially since some of those views are created outside of my control and/or dynamically. So that means I'm correct then... there's no way to know the source view of the tap event using the default eventing path? – Mark A. Donohoe Jul 20 '17 at 14:55
  • Since you don't have control of the views, I think you'll have to attach a listener somewhere, but reusing a single `OnTouchListener` object should make that relatively painless. – Elan Hamburger Jul 20 '17 at 15:10
  • Thanks. If you put that in an answer and if no one comes up with anything better in a day or so, I'll mark yours as the accepted one. – Mark A. Donohoe Jul 20 '17 at 15:11
  • I'm going to try doing a little more research when I have more time tonight before posting anything as an answer. There really _should_ be a way to do this, but everything I've seen so far manages to miss this exact case. – Elan Hamburger Jul 20 '17 at 15:19
  • Really appreciate the effort! Thanks! :) – Mark A. Donohoe Jul 20 '17 at 15:19
  • Sorry for the delay, things got a little hectic at work and I haven't had much time to code for myself. Answer posted. – Elan Hamburger Jul 27 '17 at 23:15

1 Answers1

0

The OnTouchListener for ViewGroups passes in the ViewGroup as its View argument and not the child within that View that was actually touched. Since you don't have control over the source of the Views in the layout, you'll have to add the OnTouchListener manually from outside of them.

As mentioned in my comments, you can reuse the same listener for all the Views by attaching it to each of the Views you want to listen on. If you're adding these Views dynamically, it should be trivial to also call setOnTouchListener() on them as you create them.

For more on the subject, check out Android's guide to managing touch events in a ViewGroup which provides a way for the parent to intercept touch events on the child, but not vice versa.

Elan Hamburger
  • 2,137
  • 10
  • 14
  • Yeah, that's what I was afraid of. This won't work because it adds listeners to the direct children whereas what I want may be a (great)grandchild. It's a shame Android doesn't bubble up the touched view along with the event like they do on Windows. Again, there you have the sender (which would be the ViewGroup) but you also have the source which would be the actually-touched view, regardless of where it is in the hierarchy (and provided no one above you handled it.) – Mark A. Donohoe Jul 27 '17 at 23:18
  • You don't have to use the immediate parent to apply the listener. Wherever you receive the `View` that you're adding, call `setOnTouchListener()` and pass it an `OnTouchListener` object. You can store that object anywhere in the running activity, so it shouldn't matter where in the hierarchy the `View` goes. – Elan Hamburger Jul 28 '17 at 00:45
  • I'm not following you here. Say I'm loading a layout that itself includes a layout which itself includes a layout. In the deepest layout, say it's using controls called DrumPads. I want to know which DrumPad that's been clicked. If I have to add the handler to it, then I'd have to walk all the trees looking for them to attach to, no? Seems a but much. I know... I'm spoiled. I'm just used to adding one listener that listens for everything under it. – Mark A. Donohoe Jul 28 '17 at 03:28
  • Find your `addView()` where you add the DrumPads to the layout. Right before that line, call `setOnTouchListener()` on each DrumPad and pass it the listener. There's no need to go crawling, because the listener will already be attached. – Elan Hamburger Jul 28 '17 at 16:25
  • I guess I'm not being clear. I'm not controlling that layout or its hierarchy so I'm not adding them to the layout. This could load a layout that has a hierarchy depth of 100 levels. I'd have to spider down through them all. Plus, that means I'd have to know about those layouts. What I was after was like in WPF. The layout doesn't matter. You can always know who triggered the event, no matter how deep in the hierarchy. You just ask for the original source. I guess the answer is 'You can't, automatically'. – Mark A. Donohoe Jul 28 '17 at 16:40
  • I see. I thought you meant you didn't have control over the DrumPads, but you're saying you also don't have control over the layout that holds the DrumPads. Sorry for the confusion. – Elan Hamburger Jul 28 '17 at 16:41
  • 1
    Yeah. And not really drum pads in my example either. No idea why I said that! lol. SO yes, long story short, Android doesn't seem to be able to tell me what I need, which is a shame because the system clearly knows who initiated the touch because it already knows how to route it to them. Since you can intercept, it's a shame they didn't say who they were intercepting. After all, that's who they're sending the TOUCHES_CANCELED to, so again, it is known. Just not to the consumer of the API. – Mark A. Donohoe Jul 28 '17 at 16:44