130

I have a TextView in a layout whos background is a Selector. And the TextView's text is set to Spanned from HTML. Then I set the TextView with the LinkMovementMethod.

Now when I tap on the TextView, the click event is not sent to its parent layout to trigger the selector.

How should this be solved?

Fatima
  • 869
  • 10
  • 35
shiami
  • 7,174
  • 16
  • 53
  • 68

8 Answers8

245

Declare your TextView not clickable / focusable by using android:clickable="false" and android:focusable="false" or v.setClickable(false) and v.setFocusable(false). The click events should be dispatched to the TextView's parent now.

Note:

In order to achieve this, you have to add click to its direct parent. or set android:clickable="false" and android:focusable="false" to its direct parent to pass listener to further parent.

siyb
  • 2,837
  • 2
  • 21
  • 18
  • 1
    Oh man if I could give this +100 I would. Been trying to figure out how to make a click only work in a ViewPager and not in the adapters layout with TouchImageView implementing onTouch! This solution worked like a charm. – JPM Dec 23 '13 at 18:59
  • Works perfectly. Much better than the excepted answer. – Rooster242 Jun 16 '14 at 19:04
  • 1
    but what if I want to handle Focus change event of EditText ? – SweetWisher ツ Jan 28 '15 at 09:15
  • @SweetWisherヅ: please provide some source code and explain your problem in detail. In general, dispatching events from views might also work, but I cannot help you unless you provide more useful information. – siyb Jan 28 '15 at 09:18
  • 2
    The scenario is : I have a layout which contains edittext ,relative layout, spinner and list view. I want to fire touch event of parent layout whenever i touch on anywhere on the screenexcept the edittext and spinner – SweetWisher ツ Jan 28 '15 at 09:35
  • Why wonT this work for a ListView instead of a TextView? anyone? – Orkun Mar 09 '15 at 12:40
  • 16
    Something important to bear in mind is that if you use `TextView#setOnClickListener()` on the `TextView`, it becomes clickable, even if it's declared as `android:clickable="false"`, and even if the the `ClickListener` is set to `null` (`setOnclickListener(null)`) – Christian García May 08 '15 at 10:39
  • But this solution is making the imageView unclickable, and thus only clicking outside the image is triggering the click. – srinivas Oct 07 '15 at 16:34
  • What if I want both the parent and the child to intercepte their OnClick ? – An-droid Aug 02 '16 at 11:31
  • I want to pass the onclick event to the parent but I Don't need to pass this event while Onlong press. what is the best solution. your solution is best for only onclick event – M.ArslanKhan Oct 18 '16 at 14:43
  • @Tarikhelian: I would suggest using RecyclerView now, it allows you to be more flexible in this regard. Basically you would first declare all child views clickable=false and focusable=false and set a OnLongClickListener / OnClickListener on the parent view. Hope that helps. – siyb Oct 18 '16 at 14:50
  • @Nepster: I don't get what you are trying to say with your edit, are you talking about dispatching a click event to a parent view further up the hierarchy (referring to: "add click")? Please clarify. I hope you don't mind me correcting some of the grammatical errors ;). – siyb Nov 03 '16 at 12:49
  • 1
    I have experience that if we set clickable and focusable = false to any View. It retrun false to handle the event. So the event pass to it's parent . And if that parent also don't want to handle the event . they should also set Focusable and clickable = false. – Zar E Ahmer Nov 03 '16 at 13:03
  • 4
    Fun fact: if in TextView there is an `android:inputType` attribute it will silently consume click events even with clickable: false & focusable: false; in my case it was set in a style and it cost me 30 minutes to figure out the cause. – Kirill Karmazin Sep 20 '18 at 15:27
53

I think you need to use one of those methods in order to be able to intercept the event before it gets sent to the appropriate components:

Activity.dispatchTouchEvent(MotionEvent) - This allows your Activity to intercept all touch events before they are dispatched to the window.

ViewGroup.onInterceptTouchEvent(MotionEvent) - This allows a ViewGroup to watch events as they are dispatched to child Views.

ViewParent.requestDisallowInterceptTouchEvent(boolean) - Call this upon a parent View to indicate that it should not intercept touch events with onInterceptTouchEvent(MotionEvent).

More information here.

Hope that helps.

Luis Miguel Serrano
  • 5,029
  • 2
  • 41
  • 41
  • 1
    Is there an easier way to pass the events to trigger the onClick and onLongClick in parent view, or I have to implement them myself? – shiami Dec 12 '10 at 13:59
  • Check this out: http://stackoverflow.com/questions/2136696/pass-event-to-parent-view Possibly you might want to try passing the same click listener to the other instances as suggested, even though I don't know if it will work, but might be worth a try. – Luis Miguel Serrano Dec 12 '10 at 16:58
  • Answer below is much cleaner – JPM Dec 23 '13 at 19:00
  • Should I instantiate the MotionEvent? Please, explain, @LuisMiguelSerrano – olegario Apr 19 '19 at 22:36
  • @olegario, typically you redefine the method for example for dispatchTouchEvent, with @Override, to define a new behavior for it, so you just receive a motion event the system has provided to your activity (or another component of your application), and you treat it and/or forward to any number of children components according to your own rules. Something like: `@Override public boolean dispatchTouchEvent(MotionEvent ev) { ... }` – Luis Miguel Serrano Apr 19 '19 at 23:06
25

Sometime only this helps:

View child = parent.findViewById(R.id.btnMoreText);
    child.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            View parent = (View) v.getParent();
            parent.performClick();
        }
    });

Another variant, works not always:

child.setOnClickListener(null);
Alexander Ukhov
  • 458
  • 6
  • 10
  • This worked for `CardView` as the child view, since `CardView` seems to ignore `clickable="false"` and `focusable="false"`. However, using `setOnTouchListener` and calling `onTouchEvent` on the parent instead allows the parent to display touch feedback (e.g. ripple effect). – methodsignature Oct 22 '18 at 11:56
  • I posted relevant code below: https://stackoverflow.com/a/52928884/5652513. – methodsignature Oct 22 '18 at 12:04
16

Put

android:duplicateParentState="true"

in child then the views get its drawable state (focused, pressed, etc.) from its direct parent rather than from itself. you can set onclick for parent and it call on child clicked

Ahmad Ronagh
  • 790
  • 10
  • 16
7

If your TextView create click issues, than remove android:inputType="" from your xml file.

Satan Pandeya
  • 3,747
  • 4
  • 27
  • 53
5

This answer is similar to Alexander Ukhov's answer, except that it uses touch events rather than click events. Those event allow the parent to display the proper pressed states (e.g., ripple effect). This answer is also in Kotlin instead of Java.

view.setOnTouchListener { view, motionEvent ->
    (view.parent as View).onTouchEvent(motionEvent)
}
methodsignature
  • 4,152
  • 2
  • 20
  • 21
  • Helpful answer, thanks! But I would suggest the following clarifications: 1) use other name for touched view - 'touchedView', for example; 2) call performClick() instead of onTouchEvent() – DmitryKanunnikoff Feb 08 '21 at 11:51
0

If you want to both OnTouch and OnClick listener to parent and child view both, please use below trick:

  1. User ScrollView as a Parent view and inside that placed your child view inside Relative/LinearLayout.

  2. Make Parent ScrollView android:fillViewport="true" so View not be scrolled.

  3. Then set OnTouch listener to parent and OnClick listener to Child views. And enjoy both listener callbacks.

Matheus Lacerda
  • 5,983
  • 11
  • 29
  • 45
0

Then I set the TextView with the LinkMovementMethod.

TextView.setMovementMethod() internally calls a private method fixFocusableAndClickableSettings. It is the root of the problem: calls setFocusable(FOCUSABLE); setClickable(true); setLongClickable(true);. So no matter what clickability you think you've set, it'll be all true.

source

Those 3 flags have to be reset back to false in order for the view to become non-clickable.

Agent_L
  • 4,960
  • 28
  • 30