使用 ClickableSpan 将 TextView 上的触摸事件重定向到父视图
Redirect touch event on TextView with ClickableSpan to parent view
我有一个带有 TextView 的 RecyclerView,它可以包含应该可点击的自定义主题标签。所以我创建了 TextView 的子类,在其中使用 Pattern 我创建了 ClickableSpan。为了激活 ClickableSpan,我添加了
setMovementMethod(LinkMovementMethod.getInstance())
此方法更改 TextView 的属性:
setFocusable(true);
setClickable(true);
setLongClickable(true);
点击链接有效,但它阻止了 ripple drawable 显示在列表项上,点击标签外的 TextView 将被忽略。
所以我感兴趣的是如何将 TextView 上的触摸(除了标签上的所有触摸)重定向到它的父级?
所以在网上一直没找到解决方法所以自己实现了。
因此,我没有将简单的 TextView 与 MovementMethod 结合使用,而是创建了一个新的 class 通过 onTouchEvent 扩展文本视图。
public class MyTextView extends TextView {
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (linksActive) {
Layout layout = this.getLayout();
if (layout != null) {
int line = layout.getLineForVertical((int) event.getY());
int offset = layout.getOffsetForHorizontal(line, event.getX());
if (getText() != null && getText() instanceof Spanned) {
Spanned spanned = (Spanned) getText();
ClickableSpan[] links = spanned.getSpans(offset, offset, ClickableSpan.class);
if (links.length > 0) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
links[0].onClick(this);
} else {
return super.onTouchEvent(event);
}
}
}
}
}
return super.onTouchEvent(event);
}
}
我还创建了一个 class 来扩展处理点击的 ClickableSpan:
public class URLSpan extends ClickableSpan {
private String url;
private int spanType;
public URLSpan(String url, int spanType) {
this.url = url;
this.spanType = spanType;
}
public int getType() {
return spanType;
}
@Override
public void onClick(@NonNull View widget) {
if (widget instanceof OnSpanClickListener) {
((OnSpanClickListener) widget).onSpanClicked(url, type);
} else {
// ignored
}
}
}
在这种情况下,我必须自己管理跨度(在 setText()
方法中)
public static void formatPhoneNumbers(@NonNull Spannable spannable, @ColorInt int color) {
Matcher matcher = MY_Patterns.PHONE_NO.matcher(spannable);
while (matcher.find()) {
if (!Linkify.sPhoneNumberMatchFilter.acceptMatch(spannable, matcher.start(), matcher.end())) {
continue;
}
removeSpans(spannable, SpanType.PHONE_NO, matcher.start(), matcher.end());
if (hasClickableSpans(spannable, matcher.start(), matcher.end())) {
// ignore different clickable spans overlap
continue;
}
final ForegroundColorSpan span = new ForegroundColorSpan(color);
final ClickableSpan urlSpan = new URLSpan(matcher.group(), SpanType.PHONE_NO);
spannable.setSpan(urlSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(span, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private static void removeSpans(@NonNull Spannable spannable, int type, int start, int end) {
URLSpan[] arr = spannable.getSpans(start, end, URLSpan.class);
for (URLSpan span : arr) {
if (span.getType() == type) {
spannable.removeSpan(span);
}
}
}
private static boolean hasClickableSpans(@NonNull Spannable spannable, int start, int end) {
ClickableSpan[] arr = spannable.getSpans(start, end, ClickableSpan.class);
return arr.length > 0;
}
感谢 Viktor --
我用它来允许触摸事件 not 在可点击范围内传递给父级
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
if (hasOnClickListeners() == false && linksClickable == false) {
// Avoid all touches
return false
}
// Handle avoiding touch events, if not on a clickable span
if (event != null && hasOnClickListeners() == false) {
if (layout != null) {
val line = layout.getLineForVertical(event.y.toInt())
val offset = layout.getOffsetForHorizontal(line, event.x)
if (text != null && text is Spanned) {
val spanned = text as Spanned
val spansAtPoint = spanned.getSpans(offset, offset, ClickableSpan::class.java)
if (spansAtPoint.size == 0) {
// No clickable span at event -- abort touch
return false
}
}
}
}
return super.dispatchTouchEvent(event)
}
我有一个带有 TextView 的 RecyclerView,它可以包含应该可点击的自定义主题标签。所以我创建了 TextView 的子类,在其中使用 Pattern 我创建了 ClickableSpan。为了激活 ClickableSpan,我添加了
setMovementMethod(LinkMovementMethod.getInstance())
此方法更改 TextView 的属性:
setFocusable(true);
setClickable(true);
setLongClickable(true);
点击链接有效,但它阻止了 ripple drawable 显示在列表项上,点击标签外的 TextView 将被忽略。
所以我感兴趣的是如何将 TextView 上的触摸(除了标签上的所有触摸)重定向到它的父级?
所以在网上一直没找到解决方法所以自己实现了。
因此,我没有将简单的 TextView 与 MovementMethod 结合使用,而是创建了一个新的 class 通过 onTouchEvent 扩展文本视图。
public class MyTextView extends TextView {
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (linksActive) {
Layout layout = this.getLayout();
if (layout != null) {
int line = layout.getLineForVertical((int) event.getY());
int offset = layout.getOffsetForHorizontal(line, event.getX());
if (getText() != null && getText() instanceof Spanned) {
Spanned spanned = (Spanned) getText();
ClickableSpan[] links = spanned.getSpans(offset, offset, ClickableSpan.class);
if (links.length > 0) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
links[0].onClick(this);
} else {
return super.onTouchEvent(event);
}
}
}
}
}
return super.onTouchEvent(event);
}
}
我还创建了一个 class 来扩展处理点击的 ClickableSpan:
public class URLSpan extends ClickableSpan {
private String url;
private int spanType;
public URLSpan(String url, int spanType) {
this.url = url;
this.spanType = spanType;
}
public int getType() {
return spanType;
}
@Override
public void onClick(@NonNull View widget) {
if (widget instanceof OnSpanClickListener) {
((OnSpanClickListener) widget).onSpanClicked(url, type);
} else {
// ignored
}
}
}
在这种情况下,我必须自己管理跨度(在 setText()
方法中)
public static void formatPhoneNumbers(@NonNull Spannable spannable, @ColorInt int color) {
Matcher matcher = MY_Patterns.PHONE_NO.matcher(spannable);
while (matcher.find()) {
if (!Linkify.sPhoneNumberMatchFilter.acceptMatch(spannable, matcher.start(), matcher.end())) {
continue;
}
removeSpans(spannable, SpanType.PHONE_NO, matcher.start(), matcher.end());
if (hasClickableSpans(spannable, matcher.start(), matcher.end())) {
// ignore different clickable spans overlap
continue;
}
final ForegroundColorSpan span = new ForegroundColorSpan(color);
final ClickableSpan urlSpan = new URLSpan(matcher.group(), SpanType.PHONE_NO);
spannable.setSpan(urlSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(span, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private static void removeSpans(@NonNull Spannable spannable, int type, int start, int end) {
URLSpan[] arr = spannable.getSpans(start, end, URLSpan.class);
for (URLSpan span : arr) {
if (span.getType() == type) {
spannable.removeSpan(span);
}
}
}
private static boolean hasClickableSpans(@NonNull Spannable spannable, int start, int end) {
ClickableSpan[] arr = spannable.getSpans(start, end, ClickableSpan.class);
return arr.length > 0;
}
感谢 Viktor --
我用它来允许触摸事件 not 在可点击范围内传递给父级
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
if (hasOnClickListeners() == false && linksClickable == false) {
// Avoid all touches
return false
}
// Handle avoiding touch events, if not on a clickable span
if (event != null && hasOnClickListeners() == false) {
if (layout != null) {
val line = layout.getLineForVertical(event.y.toInt())
val offset = layout.getOffsetForHorizontal(line, event.x)
if (text != null && text is Spanned) {
val spanned = text as Spanned
val spansAtPoint = spanned.getSpans(offset, offset, ClickableSpan::class.java)
if (spansAtPoint.size == 0) {
// No clickable span at event -- abort touch
return false
}
}
}
}
return super.dispatchTouchEvent(event)
}