강윤식*_*강윤식 24 events android textview intercept
我在布局中有一个TextView.这很简单.我在布局中放了一个OnClickListener,TextView的某些部分设置为ClickableSpan.我希望ClickableSpan在点击时在onClick函数中执行某些操作,当单击TextView的其他部分时,它必须在布局的OnClickListener的onClick函数中执行某些操作.这是我的代码.
RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayout);
l.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "whole layout", Toast.LENGTH_SHORT).show();
}
});
TextView textView = (TextView)findViewById(R.id.t1);
textView.setMovementMethod(LinkMovementMethod.getInstance());
SpannableString spannableString = new SpannableString(textView.getText().toString());
ClickableSpan span = new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(MainActivity.this, "just word", Toast.LENGTH_SHORT).show();
}
};
spannableString.setSpan(span, 0, 5, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(spannableString);
Run Code Online (Sandbox Code Playgroud)
Hai*_*ang 20
我也遇到了这个问题,并且由于提到的源代码@KMDev,我想出了一个更清洁的方法.
首先,因为我只有一部分TextView可以部分点击,实际上我不需要大多数功能LinkMovementMethod(及其超类ScrollingMovementMethod),这增加了处理按键,滚动等功能.
而是创建一个MovementMethod使用以下OnTouch()代码的自定义LinkMovementMethod:
ClickableMovementMethod.java
package com.example.yourapplication;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.method.BaseMovementMethod;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.MotionEvent;
import android.widget.TextView;
/**
* A movement method that traverses links in the text buffer and fires clicks. Unlike
* {@link LinkMovementMethod}, this will not consume touch events outside {@link ClickableSpan}s.
*/
public class ClickableMovementMethod extends BaseMovementMethod {
private static ClickableMovementMethod sInstance;
public static ClickableMovementMethod getInstance() {
if (sInstance == null) {
sInstance = new ClickableMovementMethod();
}
return sInstance;
}
@Override
public boolean canSelectArbitrarily() {
return false;
}
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getActionMasked();
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 {
Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
} else {
Selection.removeSelection(buffer);
}
}
return false;
}
@Override
public void initialize(TextView widget, Spannable text) {
Selection.removeSelection(text);
}
}
Run Code Online (Sandbox Code Playgroud)
然后使用它ClickableMovementMethod,移动方法将不再消耗触摸事件.但是,TextView.setMovementMethod()哪些调用TextView.fixFocusableAndClickableSettings()会将可点击,长按和可聚焦设置为true,这将View.onTouchEvent()消耗触摸事件.要解决此问题,只需重置三个属性即可.
所以最后的实用方法,伴随着ClickableMovementMethod,在这里:
public static void setTextViewLinkClickable(TextView textView) {
textView.setMovementMethod(ClickableMovementMethod.getInstance());
// Reset for TextView.fixFocusableAndClickableSettings(). We don't want View.onTouchEvent()
// to consume touch events.
textView.setClickable(false);
textView.setLongClickable(false);
}
Run Code Online (Sandbox Code Playgroud)
这对我来说就像一个魅力.
ClickableSpan触发s 上的Click事件,并在它们外部单击将传递给父布局侦听器.
请注意,如果您正在进行TextView选择,我还没有对此案进行测试,也许您需要自己深入研究来源:P
KMD*_*Dev 17
您的问题的第一个答案是您没有在TextView上设置点击监听器,因为用户2558882指出这是点击事件.在TextView上设置单击侦听器后,您将看到ClickableSpans触摸区域外的区域将按预期工作.但是,您将发现当您单击其中一个ClickableSpans时,TextView的onClick回调也将被触发.如果让火灾成为你的问题,那将导致我们陷入困境.user2558882的回复无法保证您的ClickableSpan的onClick回调将在TextView之前被触发.以下是来自类似线程的一些解决方案,这些解决方案更好地实现并从源代码解释.线程应该适用于大多数设备的公认答案,但对该答案的评论提到某些设备存在问题.看起来一些具有自定义运营商/制造商用户界面的设备应该受到指责,但这是猜测.
那么为什么你不能保证onClick回调顺序呢?如果您查看TextView(Android 4.3)的源代码,您会注意到在onTouchEvent方法中, boolean superResult = super.onTouchEvent(event);(super是View)被调用,之后handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);调用您的移动方法,然后调用ClickableSpan的onClick.看一下super的(View)onTouchEvent(..),您会注意到:
// Use a Runnable and post this rather than
// performClick directly. This lets other visual
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) { // <---- In the case that this won't post,
performClick(); // it'll fallback to calling it directly
}
Run Code Online (Sandbox Code Playgroud)
performClick()调用click侦听器集,在本例中是我们的TextView的单击侦听器.这意味着,您将无法知道onClick回调将以什么顺序触发.你知道的是,你的ClickableSpan和TextView点击听众会被调用.我之前提到的线程上的解决方案有助于确保订单,因此您可以使用标志.
如果确保与许多设备兼容是首要任务,那么最好再次查看布局,看看是否可以避免遇到这种情况.通常有很多布局选项来像这样的裙子.
编辑评论答案:
当TextView执行onTouchEvent时,它会调用LinkMovementMethod的onTouchEvent,以便它可以处理对各种ClickableSpan的onClick方法的调用.您的LinkMovementMethod在其onTouchEvent中执行以下操作:
@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]));
}
return true;
} else {
Selection.removeSelection(buffer);
}
}
return super.onTouchEvent(widget, buffer, event);
}
Run Code Online (Sandbox Code Playgroud)
您会注意到它需要MotionEvent,获取动作(ACTION_UP:提升手指,ACTION_DOWN:按下手指),触摸起源位置的x和y坐标,然后找到哪个行号和偏移量(文本中的位置)触摸得很厉害.最后,如果存在包含该点的ClickableSpans,则会检索它们并调用它们的onClick方法.由于我们希望将任何触摸传递给您的父布局,如果您希望它在触摸时执行它所做的一切,您可以调用onTouchEvent上的布局,或者如果它实现了您所需的功能,您可以调用它的单击侦听器.这是在哪里做到的:
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]));
}
return true;
} else {
Selection.removeSelection(buffer);
// Your call to your layout's onTouchEvent or it's
//onClick listener depending on your needs
}
}
Run Code Online (Sandbox Code Playgroud)
所以要查看,你将创建一个扩展LinkMovementMethod的新类,覆盖它的onTouchEvent方法,将你的调用复制并粘贴到我评论的正确位置,确保你将TextView的移动方法设置为这个新的子类,你应该被设定.
再次编辑可能的副作用避免
看看ScrollingMovementMethod的源代码(LinkMovementMethod的父级),你会发现它是一个调用静态方法的委托方法return Touch.onTouchEvent(widget, buffer, event); 这意味着你可以将它添加为方法中的最后一行并避免调用超级(LinkMovementMethod)的onTouchEvent实现,它将复制您正在粘贴的内容,并且其他事件可以按预期落实.
这是一个简单的解决方案,它对我有用
您可以使用 Textview 类的 getSelectionStart() 和 getSelectionEnd() 函数中的变通方法来实现这一点,
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ClassroomLog.log(TAG, "Textview Click listener ");
if (tv.getSelectionStart() == -1 && tv.getSelectionEnd() == -1) {
//This condition will satisfy only when it is not an autolinked text
//Fired only when you touch the part of the text that is not hyperlinked
}
}
});
Run Code Online (Sandbox Code Playgroud)
声明一个全局boolean变量:
boolean wordClicked = false;
Run Code Online (Sandbox Code Playgroud)
声明并初始化l为final:
final RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayout);
Run Code Online (Sandbox Code Playgroud)
添加一个OnClickListener到textView:
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!wordClicked) {
// Let the click be handled by `l's` OnClickListener
l.performClick();
}
}
});
Run Code Online (Sandbox Code Playgroud)
改成span:
ClickableSpan span = new ClickableSpan() {
@Override
public void onClick(View widget) {
wordClicked = true;
Toast.makeText(Trial.this, "just word", Toast.LENGTH_SHORT).show();
// A 100 millisecond delay to let the click event propagate to `textView's`
// OnClickListener and to let the check `if (!wordClicked)` fail
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
wordClicked = false;
}
}, 100L);
}
};
Run Code Online (Sandbox Code Playgroud)
编辑:
考虑到用户 KMDev 的答案,以下代码将满足您的规范。我们创建两个跨度:一个具有指定的长度:spannableString.setSpan(.., 0, 5, ..);,另一个具有余数:spannableString.setSpan(.., 6, spannableString.legth(), ..);。第二个ClickableSpan(span2) 对 执行单击RelativeLayout。此外,通过重写updateDrawState(TextPaint),我们能够为第二个跨度提供无特色(无样式)的外观。而第一个跨度具有链接颜色并带有下划线。
final RelativeLayout l = (RelativeLayout)findViewById(R.id.contentLayout);
l.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Toast.makeText(Trial.this, "whole layout", Toast.LENGTH_SHORT).show();
}
});
TextView textView = (TextView)findViewById(R.id.t1);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setHighlightColor(Color.TRANSPARENT);
SpannableString spannableString = new SpannableString(textView.getText().toString());
ClickableSpan span = new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(Trial.this, "just word", Toast.LENGTH_SHORT).show();
}
};
spannableString.setSpan(span, 0, 5, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
ClickableSpan span2 = new ClickableSpan() {
@Override
public void onClick(View widget) {
l.performClick();
}
@Override
public void updateDrawState(TextPaint tp) {
tp.bgColor = getResources().getColor(android.R.color.transparent);
tp.setUnderlineText(false);
}
};
spannableString.setSpan(span2, 6, spannableString.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(spannableString);
Run Code Online (Sandbox Code Playgroud)
特别感谢用户 KMDev 注意到我原来的答案中的问题。不需要使用布尔变量执行(错误)检查,并且OnclickListener不需要为 TextView 设置。
| 归档时间: |
|
| 查看次数: |
12677 次 |
| 最近记录: |