Подтвердить что ты не робот

ListView: TextView с LinkMovementMethod делает элемент списка незаметным?

Что я хочу сделать: список с такими сообщениями:

< имя_пользователь > и вот mnessage, который пользователь пишет, который хорошо переносит на следующую строку. точно так же.

Что у меня:

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

Адаптер, который раздувает вышеприведенную компоновку и делает:

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

Класс 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);
    }
}

В активности у меня есть onCreate(...):

listView.setOnItemClickListener(ProgramChecksActivity.this);

и реализации вышеперечисленного

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

Проблема:

Нажав на элемент, не показывайте тост. Только нажатие на имя пользователя показывает тост.

Я предполагаю, что setMovementMethod(LinkMovementMethod.getInstance()); делает TextView доступным. Таким образом, элементы сами никогда больше не нажимаются.

Как я могу снова сделать элементы повторно? Имея ту же функциональность, что и я.

4b9b3361

Ответ 1

В этой ситуации есть три шоу-стопоры. Основная причина заключается в том, что при вызове setMovementMethod или setKeyListener, TextView "исправляет" его настройки:

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

Первая проблема заключается в том, что когда View является кликабельным - он всегда потребляет событие ACTION_UP (он возвращает true в onTouchEvent(MotionEvent event)).
Чтобы исправить это, вы должны вернуть true в этом методе, только если пользователь на самом деле нажимает на URL.

Но LinkMovementMethod не говорит нам, если пользователь действительно нажал ссылку. Он возвращает "true" в нем onTouch, если пользователь нажимает на ссылку, но также и во многих других случаях.

Итак, на самом деле я сделал трюк здесь:

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

Вы должны позвонить где-нибудь

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

чтобы установить этот MovementMethod для textView.

Этот MovementMethod повышает флаг в TextViewFixTouchConsume, если пользователь на самом деле нажимает ссылку. (только в событиях ACTION_UP и ACTION_DOWN), а TextViewFixTouchConsume.onTouchEvent возвращает true, только если пользователь на самом деле нажимает ссылку.

Но это не все!!!! Третья проблема заключается в том, что ListView (AbsListView) вызывает метод performClick (который вызывает onItemClick обработчик событий) ТОЛЬКО, если ListView item view не имеет фокуса. Итак, вам нужно переопределить

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

в представлении, которое вы добавляете в ListView. (в моем случае это макет, содержащий textView)

или вы можете использовать setOnClickLIstener для этого представления. Трюк не очень хорош, но он работает.

Ответ 2

Ответ на babay очень приятный. Но если вы не хотите подклассифицировать TextView и не заботитесь о функциях LinkMovementMethod, кроме ссылки на ссылки, вы можете использовать этот подход (это в основном копирование функции LinkMovementMethod onTouch в TextView OnTouchListener):

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

Просто назначьте этот прослушиватель вашему TextView в методе getView() адаптера списка.

Ответ 3

Вот быстрое исправление, которое делает элементы ListView и TextView UrlSpans clickable:

   private class YourListadapter extends BaseAdapter {

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

           return convertView
       }

    }

Ответ 4

Проблема заключается в том, что LinkMovementMethod указывает, что будет управлять сенсорным событием, независимо от касания в Spannable или в обычном тексте.

Это должно работать.

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

    ...
}

Ответ 5

Это происходит потому, что, когда мы нажимаем на элемент списка, он отправляет событие пресса всем своим дочерним элементам, поэтому вместо выбирается дочерний setPressed. Следовательно, для того, чтобы щелкнуть элемент списка, вы должны установить для child setPressed значение false. Для этого вам необходимо создать собственный класс TextView и переопределить желаемый метод. Вот пример кода

Открытый класс DontPressWithParentTextView расширяет 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);
}

}

Ответ 6

У меня есть другое решение, использующее макет элемента списка с двумя разными текстами:

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

а код адаптера:

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

Это сработает..: -)

Ответ 7

Почему вы используете ListView.setOnItemClickListener()? Вы можете предоставить то же самое в адаптере с помощью messageTextView.setOnClickListener().

Другой метод - установить второй кликабельный диапазон для сообщения - и предоставить там действия. Если вы не хотите, чтобы вторая часть выглядела как 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.
    }
}

Ответ 8

Для всех, кто заинтересован в том, чтобы сделать это с EmojisTextView из того, что @babay сказал в кулачном ответе, я делаю несколько таких шагов:

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

}

после этого вы просто сделаете то же самое здесь:

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

Ответ 10

Вы должны поместить эту строку в родительский вид элемента адаптера android:descendantFocusability="blocksDescendants"

Ответ 11

ну, ответ на вопрос о бабае прав, но он, кажется, что-то забыл, вы должны написать "TextViewFixTouchConsume" вместо "TextView" в xml, и тогда это будет работа! например:

<com.gongchang.buyer.widget.TextViewFixTouchConsume xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/wx_comment_friendsname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/comment_bg_with_grey_selector"
android:lineSpacingExtra="1dp"
android:paddingBottom="1dp"
android:paddingLeft="@dimen/spacing_third"
android:paddingRight="@dimen/spacing_third"
android:paddingTop="1dp"
android:textColor="@color/text_black_2d"
android:textSize="@dimen/content_second" />