it-swarm-pt.tech

ListView: TextView com LinkMovementMethod torna o item da lista desmarcável?

O que eu quero fazer: uma lista com mensagens como esta:

<UserName> e aqui está o mnessage que o usuário escreve, que será bem ajustado à próxima linha. exatamente assim.

O que eu tenho:

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)" />

Adaptador que infla o layout acima e faz:

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);

A classe InternalURLSpan

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

Na atividade que tenho em onCreate(...):

listView.setOnItemClickListener(ProgramChecksActivity.this);

e a implementação do acima

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

O problema:

Clicando no item, não mostra o brinde. Apenas clicar no nome de usuário mostra o brinde.

Eu estou supondo, que setMovementMethod(LinkMovementMethod.getInstance()); faz o TextView clicável. Assim, os itens em si nunca são mais clicados.

Como posso tornar os itens clicáveis ​​novamente? Tendo a mesma funcionalidade que eu quero.

35
Patrick Boos

Há três show-stoppers nesta situação. A razão raiz é que quando você chama setMovementMethod ou setKeyListener, TextView "corrige" suas configurações:

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

O primeiro problema é que quando um View é clicável - sempre consome o evento ACTION_UP (ele retorna true em onTouchEvent(MotionEvent event)).
Para corrigir isso, você deve retornar true nesse método somente se o usuário realmente clicar no URL.

Mas a LinkMovementMethod não nos diz se o usuário realmente clicou em um link. Ele retorna "true" em sua onTouch se o usuário clicar no link, mas também em muitos outros casos.

Então, na verdade eu fiz um truque aqui:

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

Você deveria ligar para algum lugar

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

para definir este MovementMethod para o textView.

Esse MovementMethod gera um sinalizador em TextViewFixTouchConsume se o usuário realmente atingir o link. (Somente em ACTION_UP e ACTION_DOWN events) e TextViewFixTouchConsume.onTouchEvent retornará true somente se o usuário realmente clicar em link.

Mas isso não é tudo !!!! O terceiro problema é que ListView (AbsListView) chama o método performClick (que chama o manipulador de eventos onItemClick) SOMENTE se a visão de item de ListView não tem focusables. Então, você precisa substituir 

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

em uma exibição que você adiciona a ListView. (no meu caso, é um layout que contém textView)

ou você pode usar setOnClickLIstener para essa visão. O truque não é muito bom, mas funciona.

57
babay

a resposta do babay é muito boa. Mas se você não quiser subclassificar o TextView e não se importar com os recursos do LinkMovementMethod além de clicar nos links, você pode usar essa abordagem (basicamente, copiar a funcionalidade onTouch do LinkMovementMethod no OnTouchListener do 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;
            }
        });

Basta atribuir esse ouvinte ao seu TextView no método getView () do adaptador de lista.

44
Dennis K

Aqui está uma correção rápida que torna os itens ListView e TextViewUrlSpans clicáveis:

   private class YourListadapter extends BaseAdapter {

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

           return convertView
       }

    }
5
user1888162

O problema está em que LinkMovementMethod indica que vai gerenciar o evento de toque, independentemente do toque estar em um texto Spannable ou normal.

Isso deve funcionar.

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;
    }

    ...
}
3
Narkha

Isso acontece porque, quando pressionamos o item da lista, ele envia o evento de imprensa para todos os seus filhos, portanto as chamadas setPressed da criança, e não o item da lista. Portanto, para clicar no item da lista, você precisa definir o setPressed para o filho da criança. Para isso, você precisa criar TextView classe personalizada e substituir o método desejado. Aqui está o código de exemplo

classe pública DontPressWithParentTextView estende 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);
}

}

2
Neeraj Nama

Eu tenho outra solução use o layout do item de lista com dois textos diferentes:

<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="wrap_content"
Android:layout_height="fill_parent" >

<com.me.test.DontPressWithParentTextView
    Android:id="@+id/text_user"
    Android:layout_width="wrap_content"
    Android:layout_height="fill_parent"
    Android:text="(Message Text)" />

<TextView
    Android:id="@+id/text_message"
    Android:layout_width="wrap_content"
    Android:layout_height="fill_parent"
    Android:text="(Message Text)" />

e o código do adaptador como:

DontPressWithParentTextView text1 =  (DontPressWithParentTextView) convertView.findViewById(R.id.text_user);

                TextView text2 = (TextView) convertView.findViewById(R.id.text_message);
                text2.setText(message);

                SpannableStringBuilder f = new SpannableStringBuilder();
                CharSequence username = names1[position];
                f.append(username );
                f.setSpan(new InternalURLSpan(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(getBaseContext(), "Clicked User", Toast.LENGTH_SHORT).show();
                    }
                }), f.length() - username.length(), f.length(),
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

                f.append(" ");

                text1.setText(f);
                text1.setMovementMethod(LinkMovementMethod.getInstance());
                text1.setFocusable(false);

Isso vai funcionar .. :-)

1
Neeraj Nama

Para todos que se interessaram em fazer isso com o EmojisTextView pelo que o @babay disse na primeira resposta eu faço alguns chages assim:

public class EmojiconTextView extends TextView {
private int mEmojiconSize;
private int mTextStart = 0;
private int mTextLength = -1;

boolean dontConsumeNonUrlClicks = true;
boolean linkHit;

public EmojiconTextView(Context context) {
    super(context);
    init(null);
}

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

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

private void init(AttributeSet attrs) {
    if (attrs == null) {
        mEmojiconSize = (int) getTextSize();
    } else {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Emojicon);
        mEmojiconSize = (int) a.getDimension(R.styleable.Emojicon_emojiconSize, getTextSize());
        mTextStart = a.getInteger(R.styleable.Emojicon_emojiconTextStart, 0);
        mTextLength = a.getInteger(R.styleable.Emojicon_emojiconTextLength, -1);
        a.recycle();
    }
    setText(getText());
}

@Override
public void setText(CharSequence text, BufferType type) {
    SpannableStringBuilder builder = new SpannableStringBuilder(text);
    EmojiconHandler.addEmojis(getContext(), builder, mEmojiconSize, mTextStart, mTextLength);
    super.setText(builder, type);
}

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

    if (dontConsumeNonUrlClicks)
        return linkHit;
    return res;

}






/**
 * Set the size of emojicon in pixels.
 */
public void setEmojiconSize(int pixels) {
    mEmojiconSize = pixels;
}


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 EmojiconTextView){
                    ((EmojiconTextView) widget).linkHit = true;
                }
                return true;
            } else {
                Selection.removeSelection(buffer);
                Touch.onTouchEvent(widget, buffer, event);
                return false;
            }
        }
        return Touch.onTouchEvent(widget, buffer, event);
    }
}

}

depois disso, você faz o mesmo aqui:

yourTextView.setMovementMethod(EmojiconTextView.LocalLinkMovementMethod.getInstance());
0
Context

aplique esta visão de texto no layout. https://Gist.github.com/amilcar-andrade/e4b76840da1dc92febfc

0
Arslan

Você precisa colocar esta linha na exibição pai do item do adaptador Android:descendantFocusability="blocksDescendants"

0
Deepak Gupta

Por que você usa ListView.setOnItemClickListener()? Você pode fornecer o mesmo no adaptador por messageTextView.setOnClickListener().

Outro método - defina o segundo período clicável para mensagem - e forneça ações lá. Se você não quer que a segunda parte se pareça com um linke create 

public class InternalClickableSpan extends ClickableSpan {
    OnClickListener mListener;

    public InternalClickableSpan(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);
        ds.setColor(Color.WHITE);// Here you can provide any color - take it from resources or from theme.
    }
}
0
Jin35