saket / Better-Link-Movement-Method

Attempts to improve how clickable links are detected, highlighted and handled in TextView
Apache License 2.0
780 stars 78 forks source link

Linkified TextView consumes touch events #14

Closed rubengees closed 5 years ago

rubengees commented 6 years ago

One of my apps requires a click listener on the TextView and/or parent views (e.g. in a RecyclerView) and also has linkified text in it.

After the MovementMethod is applied, the click listeners do not work anymore. This is an age-old bug, but there are workarounds.

A similar library solves this by having a custom TextView that intercepts onTouch events and handles them differently when a touch on a link is in progress or not.

Maybe a similar approach can be introduced to this library? Currently i am using this gross hack:

// BetterLinkTextView.kt

override fun onTouchEvent(event: MotionEvent?): Boolean {
    super.onTouchEvent(event)

    if (movementMethod is BetterLinkMovementMethod) {
        val span = getTag(R.id.bettermovementmethod_highlight_background_span)

        return span != null && (text as? Spannable)?.getSpanStart(span) ?: -1 >= 0
    }

    return false
}

This could be improved by having a first class api if a click is in progress and/or by having a TextView doing this in the library.

Thanks for looking into this!

saket commented 6 years ago

Oh yea this is really annoying. I'm doing something less gross in my app:

class SomeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

  private val bodyTextView: TextView

  @SuppressLint("ClickableViewAccessibility")
  fun forwardTouchEventsToBackground(linkMovementMethod: BetterLinkMovementMethod) {
    // Bug workaround: TextView with clickable spans consume all touch events. Manually
    // transfer them to the parent so that the background touch indicator shows up +
    // click listener works.
    bodyTextView!!.setOnTouchListener { o, event ->
      val handledByMovementMethod = linkMovementMethod.onTouchEvent(bodyTextView, bodyTextView.text as Spannable, event)
      handledByMovementMethod || itemView.onTouchEvent(event)
    }
  }
}

Maybe we can generalize this solution without explicitly knowing about the existence of BetterLinkMovementMethod?

noties commented 6 years ago

Hey @saket !

Maybe BetterLinkMovementMethod can capture click event and call TextView#performClick?

saket commented 6 years ago

Oh hey @noties. Sorry i just saw your message. I was busy attending droidcon Berlin and roaming around Europe. Capturing TextView's click events sounds like an interesting idea. I will explore this, thanks!

rayliverified commented 6 years ago

Thank you so much! The problem of how to pass up textview clicks to the parent has been troubling people for years. @saket your solution works great :)

saket commented 5 years ago

Closing this issue. Please feel free to re-open it for more questions.

chankruse commented 5 years ago

Was there a solution to passing touch events to parent views when the user taps somewhere on the textview that isn't a link?

saket commented 5 years ago

@chankruse does the solution in this comment not work? https://github.com/saket/Better-Link-Movement-Method/issues/14#issuecomment-388698953

JunbinDeng commented 5 years ago

@saket solution works great. It would be better to write in a TextView.

public class LinkifyTextView extends TextView {

    public LinkifyTextView(Context context) {
        super(context);
    }

    public LinkifyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public LinkifyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public LinkifyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final MovementMethod movementMethod = getMovementMethod();
        final CharSequence text = getText();
        if (movementMethod instanceof BetterLinkMovementMethod && text instanceof Spannable) {
            return movementMethod.onTouchEvent(this, (Spannable) text, event) || super.onTouchEvent(event);
        }
        return super.dispatchTouchEvent(event);
}