我在布局中有一个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);
5条答案
按热度按时间k2arahey1#
我也遇到过这个问题,多亏了上面提到的源代码@KMDev,我想出了一个更干净的方法。
首先,由于我只有一个
TextView
,它可以部分点击,实际上我不需要LinkMovementMethod
(及其超类ScrollingMovementMethod
)的大部分功能,LinkMovementMethod
增加了处理按键、滚动等的能力。相反,创建一个使用
LinkMovementMethod
中的OnTouch()
代码的自定义MovementMethod
:ClickableMovementMethod.java
然后使用这个
ClickableMovementMethod
,触摸事件将不再被移动方法消耗。但是,调用TextView.fixFocusableAndClickableSettings()
的TextView.setMovementMethod()
将把可点击、可长时间点击和可聚焦设置为true,这将使View.onTouchEvent()
消耗触摸事件。要解决这个问题,只需重置这三个属性。因此,
ClickableMovementMethod
附带的最后一个实用程序方法如下所示:这对我来说就像一个护身符。
ClickableSpan
上的单击事件被激发,并且它们外部的单击被传递到父布局侦听器。请注意,如果您要使
TextView
可选,我还没有针对这种情况进行测试,也许您需要自己深入了解源代码:Pehxuflar2#
第一个答案是,你没有在TextView上设置一个点击监听器,正如user 2558882指出的那样,TextView正在消耗点击事件。在你的TextView上设置一个点击监听器之后,你会看到ClickableSpans触摸区域之外的区域将按预期工作。然而,你会发现,当你点击你的一个ClickableSpans时,TextView的onClick回调也将被激发。如果同时激发两个回调对您来说是个问题,则这会导致我们遇到一个难题。user 2558882的答复不能保证ClickableSpan的onClick回调将在TextView的onClick回调之前激发。这是a similar thread中一些实现得更好的解决方案,以及来自源代码的解释。线程应该在大多数设备上工作这一公认的答案,但该答案的评论提到某些设备存在问题。看起来一些具有自定义运营商/制造商UI的设备应该受到指责,但那只是猜测。
那么,为什么不能保证onClick回调顺序呢?如果你看一下TextView(Android 4.3)的源代码,你会注意到在onTouchEvent方法中,
boolean superResult = super.onTouchEvent(event);
(super是View)在handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
之前被调用,handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
是对移动方法的调用,然后调用ClickableSpan的onClick。看一下super的(View)onTouchEvent(..),你会注意到:performClick()调用click侦听器集,在本例中是我们TextView的click侦听器。这意味着,您不知道onClick回调将以什么顺序触发。您所知道的是,ClickableSpan和TextView click侦听器将被调用。我前面提到的线程上的解决方案有助于确保顺序,以便您可以使用标志。
如果确保与许多设备的兼容性是一个优先事项,那么最好再看看你的布局,看看你是否可以避免陷入这种情况。通常有很多布局选项来回避这种情况。
编辑备注答案:
当TextView执行onTouchEvent时,它会调用LinkMovementMethod的onTouchEvent,以便处理对各种ClickableSpan的onClick方法的调用。LinkMovementMethod在其onTouchEvent中执行以下操作:
您会注意到,它接受MotionEvent,获取动作(ACTION_UP:提升指,动作向下:按下手指),触摸起始位置的x和y坐标,然后查找触摸击中的行号和偏移(文本中的位置)。最后,如果有包含该点的ClickableSpan,则检索它们并调用它们的onClick方法。由于我们希望将任何触摸传递到父布局,你可以调用布局onTouchEvent,如果你想让它在被触摸时执行所有操作,或者你可以调用它的click listener,如果它实现了你需要的功能。
回顾一下,您将创建一个扩展LinkMovementMethod的新类,重写它的onTouchEvent方法,复制并粘贴这个源代码,并将您的调用粘贴到我注解的正确位置,确保您将TextView的移动方法设置为这个新子类,并且您应该设置。
再次编辑以避免可能的副作用查看ScrollingMovementMethod的源代码(LinkMovementMethod的父方法),您将看到它是一个调用静态方法
return Touch.onTouchEvent(widget, buffer, event);
的委托方法。这意味着您可以将其添加为方法中的最后一行,而避免调用super的(LinkMovementMethod的)onTouchEvent实现会复制您粘贴的内容,其他事件可能会按预期失败。ccgok5k53#
这里有一个简单的解决方案,它对我很有效
您可以使用Textview类的getSelectionStart()和getSelectionEnd()函数中的一种变通方法来实现这一点。
wh6knrhe4#
声明一个全局
boolean
变量:将
l
声明并初始化为final
:将
OnClickListener
添加到textView
:将
span
更改为:编辑:
考虑到用户KMDev的答案,下面的代码将满足您的规范。我们创建两个span:一个用于指定长度:
spannableString.setSpan(.., 0, 5, ..);
,另一个与余数:spannableString.setSpan(.., 6, spannableString.legth(), ..);
。第二个ClickableSpan
(span2)执行对RelativeLayout
的单击。此外,通过覆盖updateDrawState(TextPaint)
,我们能够为第二个span赋予非独特(非样式化)外观。而第一个span具有链接颜色并带有下划线。特别感谢用户KMDev注意到我最初的答案中的问题。不需要使用布尔变量执行(错误)检查,也不需要为TextView设置
OnclickListener
。xmq68pz95#
实现ClickableSpan最简单、最快捷的方法是:
在Android中添加常规的可点击跨度需要计算每个可点击文本的大小,当涉及到在TextView中添加大量可点击和常规的单词或句子时,就会变得一团糟。通过使用SmartClickableSpan,您将能够添加任何数量的可点击单词或句子,而无需担心每次更新时计算每个文本的长度。
智能点击范围Github:
https://github.com/HseinNd98/SmartClickableSpan