41 votes

ListView: TextView avec LinkMovementMethod rend l'élément de liste non cliquable?

Ce que je veux faire: Une liste avec des messages comme ceci:

<Nom d'utilisateur> et voici le mnessage l'utilisateur écrit, qui va envelopper joliment à la ligne suivante. exactement comme cela.

Ce que j'ai:

ListView R. layout.list_item:

<TextView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/text_message"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:text="(Message Text)" />

Adaptateur qui gonfle la disposition ci-dessus et n':

SpannableStringBuilder f = new SpannableStringBuilder(check.getContent());
f.append(username);
f.setSpan(new InternalURLSpan(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(context, "Clicked User", Toast.LENGTH_SHORT).show();
    }
}), f.length() - username.length(), f.length(),
        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

f.append(" " + message);

messageTextView.setText(f);
messageTextView.setMovementMethod(LinkMovementMethod.getInstance());
meesageTextView.setFocusable(false);

Le InternalURLSpan classe

public class InternalURLSpan extends ClickableSpan {
    OnClickListener mListener;

    public InternalURLSpan(OnClickListener listener) {
        mListener = listener;
    }

    @Override
    public void onClick(View widget) {
        mListener.onClick(widget);
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        super.updateDrawState(ds);
        ds.setUnderlineText(false);
    }
}

Dans l'activité que j'ai en onCreate(...):

listView.setOnItemClickListener(ProgramChecksActivity.this);

et la mise en œuvre de ce qui précède

@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
    Toast.makeText(context, "Clicked Item", Toast.LENGTH_SHORT).show();
}

Le problème:

En cliquant sur l'élément, ne montre pas le toast. Seulement en cliquant sur le nom d'utilisateur n'montrer le toast.

Je devine, que setMovementMethod(LinkMovementMethod.getInstance()); rend le TextView cliquable. Donc, les éléments eux-mêmes ne jamais obtenir cliqué plus.

Comment puis-je faire des éléments cliquables à nouveau? Avoir les mêmes fonctionnalités que je veux.

60voto

babay Points 1329

Il y a TROIS show-bouchons, dans ce cas. la raison profonde est que lorsque vous appelez setMovementMethod ou setKeyListener, TextView "fixe" c'est les paramètres:

    setFocusable(true);
    setClickable(true);
    setLongClickable(true);

Donc, la première raison est que lorsqu'une Vue est cliquable - il consomme toujours ACTION_UP événement (par renvoi de vrai dans onTouchEvent(MotionEvent event) ). Pour corriger cela, il faut retourner vrai dans cette méthode uniquement si l'utilisateur clique sur l'URL. Mais le LinkMovementMethod ne nous dit pas, si l'utilisateur clique sur un lien. Il retourne "true" dans onTouch si l'utilisateur clique sur le lien, mais aussi dans de nombreux autres cas.

Donc, en fait, j'ai fait un hack ici:

public class TextViewFixTouchConsume extends TextView {
boolean dontConsumeNonUrlClicks = true;
boolean linkHit;

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

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

public TextViewFixTouchConsume(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    linkHit = false;
    boolean res = super.onTouchEvent(event);

    if (dontConsumeNonUrlClicks)
        return linkHit;
    return res;

}

public void setTextViewHTML(String html)
{
    CharSequence sequence = Html.fromHtml(html);
    SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
    setText(strBuilder);
}

public static class LocalLinkMovementMethod extends LinkMovementMethod{
    static LocalLinkMovementMethod sInstance;


    public static LocalLinkMovementMethod getInstance() {
        if (sInstance == null)
            sInstance = new LocalLinkMovementMethod();

        return sInstance;
    }

    @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]));
                }

                if (widget instanceof TextViewFixTouchConsume){
                    ((TextViewFixTouchConsume) widget).linkHit = true;
                }
                return true;
            } else {
                Selection.removeSelection(buffer);
                Touch.onTouchEvent(widget, buffer, event);
                return false;
            }
        }
        return Touch.onTouchEvent(widget, buffer, event);
    }
}
}

Vous devriez appeler quelque part

textView.setMovementMethod(TextViewFixTouchConsume.LocalLinkMovementMethod.getInstance());

pour définir cette MovementMethod pour le textView.

Cette MovementMethod soulève un drapeau dans TextViewFixTouchConsume si l'utilisateur frappe réellement de lien. (seulement dans ACTION_UP et ACTION_DOWN événements) et TextViewFixTouchConsume.onTouchEvent renvoie true uniquement si l'utilisateur a effectivement frappé lien.

Mais ce n'est pas tout!!!! Le troisième problème est que ListView (AbsListView) appelle c'est performClick méthode (qui appelle onItemClick gestionnaire d'événement) SEULEMENT si ListView est le point de vue n'a pas de focusables. Donc, vous avez besoin de remplacer

@Override
public boolean hasFocusable() {
    return false;
}

dans un point de vue que vous l'ajouter à la liste. (dans mon cas, c'est une mise en page qui contient textView)

ou vous pouvez utiliser setOnClickLIstener de ce point de vue. donc, ces sont des hacks, mais ils travaillent.

46voto

Dennis K Points 204

La réponse de Babay est très gentille. Toutefois, si vous ne souhaitez pas sous-classer TextView et que vous ne vous souciez pas des fonctionnalités de LinkMovementMethod autres que de cliquer sur les liens, vous pouvez utiliser cette approche (il s'agit essentiellement de copier la fonctionnalité onTouch de LinkMovementMethod dans OnTouchListener de TextView):

         myTextView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean ret = false;
                CharSequence text = ((TextView) v).getText();
                Spannable stext = Spannable.Factory.getInstance().newSpannable(text);
                TextView widget = (TextView) v;
                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 = stext.getSpans(off, off, ClickableSpan.class);

                    if (link.length != 0) {
                        if (action == MotionEvent.ACTION_UP) {
                            link[0].onClick(widget);
                        }
                        ret = true;
                    }
                }
                return ret;
            }
        });
 

Attribuez simplement cet écouteur à votre TextView dans la méthode getView () de votre adaptateur de liste.

5voto

user1888162 Points 914

Voici une solution rapide qui rend ListView articles et TextView UrlSpans cliquables:

    private class YourListadapter extends BaseAdapter {

       @Override
       public View getView(int position, View convertView, ViewGroup parent) {
           ((ViewGroup)convertView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);

           return convertView
       }

    }
 

3voto

Narkha Points 658

Le problème réside dans le fait que LinkMovementMethod indique qu’il va gérer l’événement tactile, indépendamment du toucher, qu’il s’agisse de Spannable ou de texte normal.

Cela devrait marcher.

 public class HtmlTextView extends TextView {

    ...

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (getMovementMethod() == null ) {
            boolean result = super.onTouchEvent(event); 
            return result;
        }

        MovementMethod m = getMovementMethod();     
        setMovementMethod(null);

        boolean mt = m.onTouchEvent(this, (Spannable) getText(), event);
        if (mt && event.getAction() == MotionEvent.ACTION_DOWN) {
            event.setAction(MotionEvent.ACTION_UP);
            mt = m.onTouchEvent(this, (Spannable) getText(), event);
            event.setAction(MotionEvent.ACTION_DOWN);
        }

        boolean st = super.onTouchEvent(event);

        setMovementMethod(m);
        setFocusable(false);

        return mt || st;
    }

    ...
}
 

2voto

Neeraj Nama Points 1146

C'est arrivé parce que lorsque l'on appuie sur un élément de liste, il envoie l'événement de presse à tous ses enfants, l'enfant setPressed appels plutôt que l'élément de la liste. Donc en cliquant sur l'élément de la liste, vous devez définir l'enfant setPressed à false. Pour cela, vous devez faire de la coutume TextView classe et substituer la méthode souhaitée. Voici un exemple de code

public class DontPressWithParentTextView s'étend TextView {

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

@Override
public void setPressed(boolean pressed) {
    // If the parent is pressed, do not set to pressed.
    if (pressed && ((View) getParent()).isPressed()) {
        return;
    }
    super.setPressed(pressed);
}

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X