ListView:与LinkMovementMethod的TextView使列表项不可点击?

我想要做什么:带有如下消息的列表:

<用户名>,这里是用户写的mness,很好地包装到下一行。 完全像这样。

我拥有的:

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可点击。 所以项目本身不会再被点击。

我怎样才能使物品再次点击? 具有相同的function,我想要的。

在这种情况下有三个显示屏。 根本原因是,当你调用setMovementMethodsetKeyListenerTextView “修复”它的设置:

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

第一个问题是,当一个视图是可点击的 – 它总是消耗ACTION_UP事件(它在onTouchEvent(MotionEvent event)返回true)。
要解决这个问题,只有在用户真正点击了URL的情况下,你才应该在该方法中返回true。

LinkMovementMethod并没有告诉我们,如果用户实际上点击了一个链接。 如果用户单击链接,它会在onTouch返回“true”,但在其他许多情况下也是如此。

所以,其实我在这里做了一个小把戏:

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

为textView设置MovementMethod。

如果用户实际点击链接,则此MovementMethod在TextViewFixTouchConsume引发一个标志。 (仅在ACTION_UPACTION_DOWN事件中), TextViewFixTouchConsume.onTouchEvent仅在用户实际点击链接时返回true。

但是,这不是全部! 第三个问题是ListViewAbsListView )调用它的performClick方法(即调用onItemClick事件处理程序),如果ListView的项目视图没有可聚焦的。 所以,你需要重写

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

在一个视图中添加到ListView 。 (在我的情况下,这是一个包含textView的布局)

或者你可以使用setOnClickLIstener作为该视图。 诀窍不是很好,但它的工作原理。

巴贝的回答非常好。 但是如果你不想在子类TextView和不关心LinkMovementMethodfunction,而不是点击链接,你可以使用这种方法(这基本上是将LinkMovementMethod的onTouchfunction复制到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; } }); 

只需将此侦听器分配给您的列表适配器getView()方法中的TextView即可。

这是快速修复,使ListView项目和TextView UrlSpans点击:

  private class YourListadapter extends BaseAdapter { @Override public View getView(int position, View convertView, ViewGroup parent) { ((ViewGroup)convertView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); return convertView } } 

问题在于LinkMovementMethod指示将要pipe理触摸事件,独立地,触摸处于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; } ... } 

发生这种情况是因为当我们按下列表项目时,它将button事件发送给它的所有孩子,所以孩子的setPressed调用而不是列表项目。 因此,要点击列表项目,你必须将孩子的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); } 

}

我有另一个解决scheme使用两个不同的文本列表项目布局:

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

这将工作.. 🙂

为什么使用ListView.setOnItemClickListener() ? 您可以通过messageTextView.setOnClickListener()在适配器中提供相同的内容。

另一种方法 – 为消息设置第二个可点击的范围 – 并在那里提供操作。 如果你不想第二部分看起来像一个linke创build

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

对于所有使用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()); 

您必须将此行放在适配器项目父视图中android:descendantFocusability="blocksDescendants"

好吧,@ babay的答案是正确的,但他似乎已经忘记了一些东西,你应该在xml中写“TextViewFixTouchConsume”,而不是“TextView”,然后它会工作! 例如 :

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