Android: clickableSpan dans TextView cliquable

J'ai une vision de texte qui peut contenir des liens cliquables. Lorsque l'un de ces liens est cliqué, je souhaite démarrer une activité. Cela fonctionne bien, mais il devrait également être possible de cliquer sur toute la vision de texte et de lancer une autre activité.

Voilà ma solution actuelle:

  • Est-ce que quelqu'un a des repères (code et résultats) comparant les performances des applications Android écrites dans Xamarin C # et Java?
  • Déplacer des icônes sur un MapView Android
  • Replace () ne fonctionne pas correctement avec des multi-fragments
  • Android @NonNull utilité
  • Java - la meilleure façon d'implémenter un tableau d'objets de taille dynamique
  • Création de Build.FINGERPRINT sur Android
  • TextView tv = (TextView)findViewById(R.id.textview01); Spannable span = Spannable.Factory.getInstance().newSpannable("test link span"); span.setSpan(new ClickableSpan() { @Override public void onClick(View v) { Log.d("main", "link clicked"); Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show(); } }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); tv.setText(span); tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d("main", "textview clicked"); Toast.makeText(Main.this, "textview clicked", Toast.LENGTH_SHORT).show(); } }); tv.setMovementMethod(LinkMovementMethod.getInstance()); 

    Le problème est que lorsque j'ai configuré OnClickListener, chaque fois que je clique sur un lien, d' abord, l'auditeur pour la totalité de la vision de texte , puis celui de ClickableSpan est appelé.

    Existe-t-il un moyen d'empêcher Android d'appeler l'auditeur pour l'ensemble de la vision de texte, lorsqu'un lien est cliqué? Ou pour décider dans l'auditeur pour toute la vue, si un lien a été cliqué ou non?

    6 Solutions collect form web for “Android: clickableSpan dans TextView cliquable”

    Trouvé une solution de contournement qui est tout à fait simple. Définissez ClickableSpan sur toutes les zones de texte qui ne font pas partie des liens et manipulez le clic sur eux comme si la vue de texte avait été cliquée:

     TextView tv = (TextView)findViewById(R.id.textview01); Spannable span = Spannable.Factory.getInstance().newSpannable("test link span"); span.setSpan(new ClickableSpan() { @Override public void onClick(View v) { Log.d("main", "link clicked"); Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show(); } }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // All the rest will have the same spannable. ClickableSpan cs = new ClickableSpan() { @Override public void onClick(View v) { Log.d("main", "textview clicked"); Toast.makeText(Main.this, "textview clicked", Toast.LENGTH_SHORT).show(); } }; // set the "test " spannable. span.setSpan(cs, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // set the " span" spannable span.setSpan(cs, 6, span.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); tv.setText(span); tv.setMovementMethod(LinkMovementMethod.getInstance()); 

    J'espère que cela aide (je sais que ce fil est vieux, mais dans le cas où quelqu'un le voit maintenant …).

    Matthew a suggéré de sous-classer TextView et, avec cet indice, une solution de rechange plutôt moche. Mais cela fonctionne:

    J'ai créé un "ClickPreventableTextView" que j'utilise quand j'ai clickablespans dans un TextView qui devrait être cliquable dans son ensemble.

    Dans sa méthode onTouchEvent, cette classe appelle la méthode OnTouchEvent de MovementMethod avant d'appeler onTouchEvent sur sa classe TextView de base. Donc, il est garanti, que l'écouteur de clickablespan sera invoqué en premier. Et je peux empêcher d'invoquer OnClickListener pour l'ensemble TextView

     /** * TextView that allows to insert clickablespans while whole textview is still clickable<br> * If a click an a clickablespan occurs, click handler of whole textview will <b>not</b> be invoked * In your span onclick handler you first have to check whether {@link ignoreSpannableClick} returns true, if so just return from click handler * otherwise call {@link preventNextClick} and handle the click event * @author Lukas * */ public class ClickPreventableTextView extends TextView implements OnClickListener { private boolean preventClick; private OnClickListener clickListener; private boolean ignoreSpannableClick; public ClickPreventableTextView(Context context) { super(context); } public ClickPreventableTextView(Context context, AttributeSet attrs) { super(context, attrs); } public ClickPreventableTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public boolean onTouchEvent(MotionEvent event) { if (getMovementMethod() != null) getMovementMethod().onTouchEvent(this, (Spannable)getText(), event); this.ignoreSpannableClick = true; boolean ret = super.onTouchEvent(event); this.ignoreSpannableClick = false; return ret; } /** * Returns true if click event for a clickable span should be ignored * @return true if click event should be ignored */ public boolean ignoreSpannableClick() { return ignoreSpannableClick; } /** * Call after handling click event for clickable span */ public void preventNextClick() { preventClick = true; } @Override public void setOnClickListener(OnClickListener listener) { this.clickListener = listener; super.setOnClickListener(this); } @Override public void onClick(View v) { if (preventClick) { preventClick = false; } else if (clickListener != null) clickListener.onClick(v); } } 

    L'auditeur pour la portée cliquable ressemble maintenant

      span.setSpan(new ClickableSpan() { @Override public void onClick(View v) { Log.d("main", "link clicked"); if (widget instanceof ClickPreventableTextView) { if (((ClickPreventableTextView)widget).ignoreSpannableClick()) return; ((ClickPreventableTextView)widget).preventNextClick(); } Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show(); } }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 

    Pour moi, le principal inconvénient est que maintenant getMovementMethod (). OnTouchEvent sera appelé deux fois (les appels TextView cette méthode dans la méthode onTouchEvent). Je ne sais pas si cela a des effets secondaires, cela fonctionne comme prévu.

    C'est une solution assez simple … Cela a fonctionné pour moi

     textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ClassroomLog.log(TAG, "Textview Click listener "); if (textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) { // do your code here this will only call if its not a hyperlink } } }); 

    Résolu quelque chose de très similaire d'une manière très agréable. Je voulais avoir un texte qui possède un lien cliquable! Et je voulais pouvoir appuyer sur le texte où il n'y a pas de lien et avoir un écouteur sur le clic. J'ai pris le LinkMovementMethod du code grep et l'ai changé un peu Copier et passer cette classe et copier le fond et cela fonctionnera:

     import android.text.Layout; import android.text.NoCopySpan; import android.text.Selection; import android.text.Spannable; import android.text.method.MovementMethod; import android.text.method.ScrollingMovementMethod; import android.text.style.ClickableSpan; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; public class CustomLinkMovementMethod extends ScrollingMovementMethod { private static final int CLICK = 1; private static final int UP = 2; private static final int DOWN = 3; public abstract interface TextClickedListener { public abstract void onTextClicked(); } TextClickedListener listener = null; public void setOnTextClickListener(TextClickedListener listen){ listener = listen; } @Override public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: if (event.getRepeatCount() == 0) { if (action(CLICK, widget, buffer)) { return true; } } } return super.onKeyDown(widget, buffer, keyCode, event); } @Override protected boolean up(TextView widget, Spannable buffer) { if (action(UP, widget, buffer)) { return true; } return super.up(widget, buffer); } @Override protected boolean down(TextView widget, Spannable buffer) { if (action(DOWN, widget, buffer)) { return true; } return super.down(widget, buffer); } @Override protected boolean left(TextView widget, Spannable buffer) { if (action(UP, widget, buffer)) { return true; } return super.left(widget, buffer); } @Override protected boolean right(TextView widget, Spannable buffer) { if (action(DOWN, widget, buffer)) { return true; } return super.right(widget, buffer); } private boolean action(int what, TextView widget, Spannable buffer) { boolean handled = false; Layout layout = widget.getLayout(); int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); int areatop = widget.getScrollY(); int areabot = areatop + widget.getHeight() - padding; int linetop = layout.getLineForVertical(areatop); int linebot = layout.getLineForVertical(areabot); int first = layout.getLineStart(linetop); int last = layout.getLineEnd(linebot); ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class); int a = Selection.getSelectionStart(buffer); int b = Selection.getSelectionEnd(buffer); int selStart = Math.min(a, b); int selEnd = Math.max(a, b); if (selStart < 0) { if (buffer.getSpanStart(FROM_BELOW) >= 0) { selStart = selEnd = buffer.length(); } } if (selStart > last) selStart = selEnd = Integer.MAX_VALUE; if (selEnd < first) selStart = selEnd = -1; switch (what) { case CLICK: if (selStart == selEnd) { return false; } ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class); if (link.length != 1) return false; link[0].onClick(widget); break; case UP: int beststart, bestend; beststart = -1; bestend = -1; for (int i = 0; i < candidates.length; i++) { int end = buffer.getSpanEnd(candidates[i]); if (end < selEnd || selStart == selEnd) { if (end > bestend) { beststart = buffer.getSpanStart(candidates[i]); bestend = end; } } } if (beststart >= 0) { Selection.setSelection(buffer, bestend, beststart); return true; } break; case DOWN: beststart = Integer.MAX_VALUE; bestend = Integer.MAX_VALUE; for (int i = 0; i < candidates.length; i++) { int start = buffer.getSpanStart(candidates[i]); if (start > selStart || selStart == selEnd) { if (start < beststart) { beststart = start; bestend = buffer.getSpanEnd(candidates[i]); } } } if (bestend < Integer.MAX_VALUE) { Selection.setSelection(buffer, beststart, bestend); return true; } break; } return false; } public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { return false; } @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { int x = (int) event.getX(); int y = (int) event.getY(); x -= widget.getTotalPaddingLeft(); y -= widget.getTotalPaddingTop(); x += widget.getScrollX(); y += widget.getScrollY(); Layout layout = widget.getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); if (link.length != 0) { if (action == MotionEvent.ACTION_UP) { link[0].onClick(widget); } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0])); } return true; } else { Selection.removeSelection(buffer); if (action == MotionEvent.ACTION_UP) { if(listener != null) listener.onTextClicked(); } } } return super.onTouchEvent(widget, buffer, event); } public void initialize(TextView widget, Spannable text) { Selection.removeSelection(text); text.removeSpan(FROM_BELOW); } public void onTakeFocus(TextView view, Spannable text, int dir) { Selection.removeSelection(text); if ((dir & View.FOCUS_BACKWARD) != 0) { text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT); } else { text.removeSpan(FROM_BELOW); } } public static MovementMethod getInstance() { if (sInstance == null) sInstance = new CustomLinkMovementMethod(); return sInstance; } private static CustomLinkMovementMethod sInstance; private static Object FROM_BELOW = new NoCopySpan.Concrete(); 

    }

    Ensuite, dans votre code où la vue de texte est ajoutée:

      CustomLinkMovementMethod link = (CustomLinkMovementMethod)CustomLinkMovementMethod.getInstance(); link.setOnTextClickListener(new CustomLinkMovementMethod.TextClickedListener() { @Override public void onTextClicked() { Toast.makeText(UserProfileActivity.this, "text Pressed", Toast.LENGTH_LONG).show(); } }); YOUR_TEXTVIEW.setMovementMethod(link); 

    Je pense que cela implique de sous-classer TextView et de modifier son comportement, malheureusement. Avez-vous pensé à essayer de mettre un arrière-plan derrière TextView et de l'associer à un OnClickListener?

    Le code fonctionne pour moi et c'est à partir du code source de LinkMovementMethod

     tv.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { TextView tv = (TextView) v; if (action == MotionEvent.ACTION_UP) { int x = (int) event.getX(); int y = (int) event.getY(); Layout layout = tv.getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); ClickableSpan[] link = h.diary.contentSpan.getSpans(off, off, ClickableSpan.class); if (link.length != 0) { link[0].onClick(tv); } else { //do other click } } return true; } }); 
    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.