ListView: TextView with LinkMovementMethod makes list item unclickable?

前端 未结 11 737
野的像风
野的像风 2020-11-29 17:33

What I want to do: A list with messages like this:

and here is the mnessage the user writes, that will wrap nicely to the next line.

相关标签:
11条回答
  • 2020-11-29 18:06

    Here is quick fix that makes ListView items and 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
           }
    
        }
    
    0 讨论(0)
  • 2020-11-29 18:08

    babay's answer is very nice. But if you don't want to subclass TextView and don't care about LinkMovementMethod features other than clicking on links you could use this approach (this is basically copying LinkMovementMethod's onTouch functionality into TextView's 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;
                }
            });
    

    Just assign this listener to your TextView in your list adapter getView() method.

    0 讨论(0)
  • 2020-11-29 18:08

    well,@babay's answer is right,but he seems had forgot something, you should write "TextViewFixTouchConsume" instead of "TextView" in xml, and then it will be work! for example :

    <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" />
    
    0 讨论(0)
  • 2020-11-29 18:18

    There are THREE show-stoppers in this situation. The root reason is that when you call setMovementMethod or setKeyListener, TextView "fixes" it's settings:

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

    The first problem is that when a View is clickable - it always consumes ACTION_UP event (it returns true in onTouchEvent(MotionEvent event)).
    To fix that you should return true in that method only if the user actually clicks the URL.

    But the LinkMovementMethod doesn't tell us, if the user actually clicked a link. It returns "true" in it's onTouch if the user clicks the link, but also in many other cases.

    So, actually I did a trick here:

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

    You should call somewhere

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

    to set this MovementMethod for the textView.

    This MovementMethod raises a flag in TextViewFixTouchConsume if user actually hits link. (only in ACTION_UP and ACTION_DOWN events) and TextViewFixTouchConsume.onTouchEvent returns true only if user actually hit link.

    But that's not all!!!! The third problem is that ListView (AbsListView) calls it's performClick method (that calls onItemClick event handler) ONLY if ListView's item view has no focusables. So, you need to override

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

    in a view that you add to ListView. (in my case that is a layout that contains textView)

    or you can use setOnClickLIstener for that view. The trick is not very good, but it works.

    0 讨论(0)
  • 2020-11-29 18:18

    Why do you use ListView.setOnItemClickListener()? You can provide the same in adapter by messageTextView.setOnClickListener().

    Another method - set second clickable span for message - and provide actions there. If you don't want second part looks like a 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 讨论(0)
  • 2020-11-29 18:19

    For all who interessted to do that with EmojisTextView from what @babay said in the fist answer i make some chages like that:

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

    }

    after that you just do the same here:

    yourTextView.setMovementMethod(EmojiconTextView.LocalLinkMovementMethod.getInstance());
    
    0 讨论(0)
提交回复
热议问题