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,我想要的。
在这种情况下有三个显示屏。 根本原因是,当你调用setMovementMethod
或setKeyListener
, TextView
“修复”它的设置:
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_UP
和ACTION_DOWN
事件中), TextViewFixTouchConsume.onTouchEvent
仅在用户实际点击链接时返回true。
但是,这不是全部! 第三个问题是ListView
( AbsListView
)调用它的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());
在布局中应用这个textview。 https://gist.github.com/amilcar-andrade/e4b76840da1dc92febfc
您必须将此行放在适配器项目父视图中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" />