Android 按下时更改单个 ClickableSpan 的文本颜色,而不会影响同一 TextView 中的其他 ClickableSpans
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20856105/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Change the text color of a single ClickableSpan when pressed without affecting other ClickableSpans in the same TextView
提问by Steven Meliopoulos
I have a TextView with multiple ClickableSpans in it. When a ClickableSpan is pressed, I want it to change the color of its text.
我有一个包含多个 ClickableSpans 的 TextView。当按下 ClickableSpan 时,我希望它改变其文本的颜色。
I have tried setting a color state list as the textColorLink attribute of the TextView. This does not yield the desired result because this causes allthe spans to change color when the user clicks anywhereon the TextView.
我尝试将颜色状态列表设置为 TextView 的 textColorLink 属性。这不会产生所需的结果,因为当用户单击TextView 上的任意位置时,这会导致所有跨度更改颜色。
Interestingly, using textColorHighlight to change the background color works as expected: Clicking on a span changes only the background color of that span and clicking anywhere else in the TextView does nothing.
有趣的是,使用 textColorHighlight 更改背景颜色按预期工作:单击跨度仅更改该跨度的背景颜色,而单击 TextView 中的任何其他位置什么也不做。
I have also tried setting ForegroundColorSpans with the same boundaries as the ClickableSpans where I pass the same color state list as above as the color resource. This doesn't work either. The spans always keep the color of the default state in the color state list and never enter the pressed state.
我还尝试将 ForegroundColorSpans 设置为与 ClickableSpans 相同的边界,在那里我传递与上面相同的颜色状态列表作为颜色资源。这也行不通。跨度始终保持颜色状态列表中默认状态的颜色,从不进入按下状态。
Does anyone know how to do this?
有谁知道如何做到这一点?
This is the color state list I used:
这是我使用的颜色状态列表:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@color/pressed_color"/>
<item android:color="@color/normal_color"/>
</selector>
回答by Steven Meliopoulos
I finally found a solution that does everything I wanted. It is based on this answer.
我终于找到了一个解决方案,可以做我想做的一切。它基于这个答案。
This is my modified LinkMovementMethod that marks a span as pressed on the start of a touch event (MotionEvent.ACTION_DOWN) and unmarks it when the touch ends or when the touch location moves out of the span.
这是我修改后的 LinkMovementMethod,它在触摸事件 (MotionEvent.ACTION_DOWN) 开始时将跨度标记为按下,并在触摸结束或触摸位置移出跨度时取消标记。
public class LinkTouchMovementMethod extends LinkMovementMethod {
private TouchableSpan mPressedSpan;
@Override
public boolean onTouchEvent(TextView textView, Spannable spannable, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mPressedSpan = getPressedSpan(textView, spannable, event);
if (mPressedSpan != null) {
mPressedSpan.setPressed(true);
Selection.setSelection(spannable, spannable.getSpanStart(mPressedSpan),
spannable.getSpanEnd(mPressedSpan));
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
TouchableSpan touchedSpan = getPressedSpan(textView, spannable, event);
if (mPressedSpan != null && touchedSpan != mPressedSpan) {
mPressedSpan.setPressed(false);
mPressedSpan = null;
Selection.removeSelection(spannable);
}
} else {
if (mPressedSpan != null) {
mPressedSpan.setPressed(false);
super.onTouchEvent(textView, spannable, event);
}
mPressedSpan = null;
Selection.removeSelection(spannable);
}
return true;
}
private TouchableSpan getPressedSpan(
TextView textView,
Spannable spannable,
MotionEvent event) {
int x = (int) event.getX() - textView.getTotalPaddingLeft() + textView.getScrollX();
int y = (int) event.getY() - textView.getTotalPaddingTop() + textView.getScrollY();
Layout layout = textView.getLayout();
int position = layout.getOffsetForHorizontal(layout.getLineForVertical(y), x);
TouchableSpan[] link = spannable.getSpans(position, position, TouchableSpan.class);
TouchableSpan touchedSpan = null;
if (link.length > 0 && positionWithinTag(position, spannable, link[0])) {
touchedSpan = link[0];
}
return touchedSpan;
}
private boolean positionWithinTag(int position, Spannable spannable, Object tag) {
return position >= spannable.getSpanStart(tag) && position <= spannable.getSpanEnd(tag);
}
}
This needs to be applied to the TextView like so:
这需要像这样应用于 TextView:
yourTextView.setMovementMethod(new LinkTouchMovementMethod());
And this is the modified ClickableSpan that edits the draw state based on the pressed state set by the LinkTouchMovementMethod: (it also removes the underline from the links)
这是修改后的 ClickableSpan,它根据 LinkTouchMovementMethod 设置的按下状态编辑绘制状态:(它还从链接中删除了下划线)
public abstract class TouchableSpan extends ClickableSpan {
private boolean mIsPressed;
private int mPressedBackgroundColor;
private int mNormalTextColor;
private int mPressedTextColor;
public TouchableSpan(int normalTextColor, int pressedTextColor, int pressedBackgroundColor) {
mNormalTextColor = normalTextColor;
mPressedTextColor = pressedTextColor;
mPressedBackgroundColor = pressedBackgroundColor;
}
public void setPressed(boolean isSelected) {
mIsPressed = isSelected;
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(mIsPressed ? mPressedTextColor : mNormalTextColor);
ds.bgColor = mIsPressed ? mPressedBackgroundColor : 0xffeeeeee;
ds.setUnderlineText(false);
}
}
回答by zundi
Much simpler solution, IMO:
更简单的解决方案,IMO:
final int colorForThisClickableSpan = Color.RED; //Set your own conditional logic here.
final ClickableSpan link = new ClickableSpan() {
@Override
public void onClick(final View view) {
//Do something here!
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(colorForThisClickableSpan);
}
};
回答by Maksim Dmitriev
legr3c's answerhelped me a lot. And I'd like to add a few remarks.
legr3c 的回答对我帮助很大。我想补充几点。
Remark #1.
备注#1。
TextView myTextView = (TextView) findViewById(R.id.my_textview);
myTextView.setMovementMethod(new LinkTouchMovementMethod());
myTextView.setHighlightColor(getResources().getColor(android.R.color.transparent));
SpannableString mySpannable = new SpannableString(text);
mySpannable.setSpan(new TouchableSpan(), 0, 7, 0);
mySpannable.setSpan(new TouchableSpan(), 15, 18, 0);
myTextView.setText(mySpannable, BufferType.SPANNABLE);
I applied LinkTouchMovementMethod
to a TextView
with two spans. The spans were highlighted with blue when clicked them.
myTextView.setHighlightColor(getResources().getColor(android.R.color.transparent));
fixed the bug.
我申请LinkTouchMovementMethod
了一个TextView
有两个跨度的。单击它们时,跨度会以蓝色突出显示。
myTextView.setHighlightColor(getResources().getColor(android.R.color.transparent));
修复了错误。
Remark #2.
备注#2。
Don't forget to get colors from resources when passing normalTextColor
, pressedTextColor
, and pressedBackgroundColor
.
不要忘了路过的时候,从资源获取的颜色normalTextColor
,pressedTextColor
以及pressedBackgroundColor
。
回答by j2emanue
All these solutions are too much work.
所有这些解决方案工作量太大。
Just set android:textColorLink
in your TextView
to some selector. Then create a clickableSpan with no need to override updateDrawState(...). All done.
只需将android:textColorLink
您设置TextView
为某个选择器即可。然后创建一个 clickableSpan 而不需要覆盖 updateDrawState(...)。全部完成。
here a quick example:
这里有一个简单的例子:
In your strings.xml
have a declared string like this:
在你strings.xml
有一个这样的声明字符串:
<string name="mystring">This is my message%1$s these words are highlighted%2$s and awesome. </string>
then in your activity:
然后在您的活动中:
private void createMySpan(){
final String token = "#";
String myString = getString(R.string.mystring,token,token);
int start = myString.toString().indexOf(token);
//we do -1 since we are about to remove the tokens afterwards so it shifts
int finish = myString.toString().indexOf(token, start+1)-1;
myString = myString.replaceAll(token, "");
//create your spannable
final SpannableString spannable = new SpannableString(myString);
final ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(final View view) {
doSomethingOnClick();
}
};
spannable.setSpan(clickableSpan, start, finish, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
mTextView.setText(spannable);
}
and heres the important parts ..declare a selector like this calling it myselector.xml
:
和继承人的重要部分..声明一个这样的选择器调用它myselector.xml
:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@color/gold"/>
<item android:color="@color/pink"/>
</selector>
And last in your TextView
in xml do this:
最后在您TextView
的 xml 中执行以下操作:
<TextView
android:id="@+id/mytextview"
android:background="@android:color/transparent"
android:text="@string/mystring"
android:textColorLink="@drawable/myselector" />
Now you can have a pressed state on your clickableSpan.
现在您可以在 clickableSpan 上有一个按下状态。
回答by Carlos Gómez
This is my solution if you got many click elements (we need an interface): The Interface:
如果您有很多点击元素(我们需要一个界面),这是我的解决方案: 界面:
public interface IClickSpannableListener{
void onClickSpannText(String text,int starts,int ends);
}
The class who manage the event:
管理事件的班级:
public class SpecialClickableSpan extends ClickableSpan{
private IClickSpannableListener listener;
private String text;
private int starts, ends;
public SpecialClickableSpan(String text,IClickSpannableListener who,int starts, int ends){
super();
this.text = text;
this.starts=starts;
this.ends=ends;
listener = who;
}
@Override
public void onClick(View widget) {
listener.onClickSpannText(text,starts,ends);
}
}
In main class:
在主类中:
class Main extends Activity implements IClickSpannableListener{
//Global
SpannableString _spannableString;
Object _backGroundColorSpan=new BackgroundColorSpan(Color.BLUE);
private void setTextViewSpannable(){
_spannableString= new SpannableString("You can click ?here? or click ?in this position?");
_spannableString.setSpan(new SpecialClickableSpan("here",this,15,18),15,19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
_spannableString.setSpan(new SpecialClickableSpan("in this position",this,70,86),70,86, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextView tv = (TextView)findViewBy(R.id.textView1);
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setText(spannableString);
}
@Override
public void onClickSpannText(String text, int inicio, int fin) {
System.out.println("click on "+ text);
_spannableString.removeSpan(_backGroundColorSpan);
_spannableString.setSpan(_backGroundColorSpan, inicio, fin, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
((TextView)findViewById(R.id.textView1)).setText(_spannableString);
}
}
回答by pskink
try this custom ClickableSpan:
试试这个自定义 ClickableSpan:
class MyClickableSpan extends ClickableSpan {
private String action;
private int fg;
private int bg;
private boolean selected;
public MyClickableSpan(String action, int fg, int bg) {
this.action = action;
this.fg = fg;
this.bg = bg;
}
@Override
public void onClick(View widget) {
Log.d(TAG, "onClick " + action);
}
@Override
public void updateDrawState(TextPaint ds) {
ds.linkColor = selected? fg : 0xffeeeeee;
super.updateDrawState(ds);
}
}
and this SpanWatcher:
和这个 SpanWatcher:
class Watcher implements SpanWatcher {
private TextView tv;
private MyClickableSpan selectedSpan = null;
public Watcher(TextView tv) {
this.tv = tv;
}
private void changeColor(Spannable text, Object what, int start, int end) {
// Log.d(TAG, "changeFgColor " + what);
if (what == Selection.SELECTION_END) {
MyClickableSpan[] spans = text.getSpans(start, end, MyClickableSpan.class);
if (spans != null) {
tv.setHighlightColor(spans[0].bg);
if (selectedSpan != null) {
selectedSpan.selected = false;
}
selectedSpan = spans[0];
selectedSpan.selected = true;
}
}
}
@Override
public void onSpanAdded(Spannable text, Object what, int start, int end) {
changeColor(text, what, start, end);
}
@Override
public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart, int nend) {
changeColor(text, what, nstart, nend);
}
@Override
public void onSpanRemoved(Spannable text, Object what, int start, int end) {
}
}
test it in onCreate:
在 onCreate 中测试:
TextView tv = new TextView(this);
tv.setTextSize(40);
tv.setMovementMethod(LinkMovementMethod.getInstance());
SpannableStringBuilder b = new SpannableStringBuilder();
b.setSpan(new Watcher(tv), 0, 0, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
b.append("this is ");
int start = b.length();
MyClickableSpan link = new MyClickableSpan("link0 action", 0xffff0000, 0x88ff0000);
b.append("link 0");
b.setSpan(link, start, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
b.append("\nthis is ");
start = b.length();
b.append("link 1");
link = new MyClickableSpan("link1 action", 0xff00ff00, 0x8800ff00);
b.setSpan(link, start, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
b.append("\nthis is ");
start = b.length();
b.append("link 2");
link = new MyClickableSpan("link2 action", 0xff0000ff, 0x880000ff);
b.setSpan(link, start, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(b);
setContentView(tv);
回答by kathir
Place the java code as below :
将java代码放置如下:
package com.synamegames.orbs;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
public class CustomTouchListener implements View.OnTouchListener {
public boolean onTouch(View view, MotionEvent motionEvent) {
switch(motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
((TextView) view).setTextColor(0x4F4F4F);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
((TextView) view).setTextColor(0xCDCDCD);
break;
}
return false;
}
}
In the above code specify wat color you want .
在上面的代码中指定你想要的 wat 颜色。
Change the style .xml as you want.
根据需要更改样式 .xml。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MenuFont">
<item name="android:textSize">20sp</item>
<item name="android:textColor">#CDCDCD</item>
<item name="android:textStyle">normal</item>
<item name="android:clickable">true</item>
<item name="android:layout_weight">1</item>
<item name="android:gravity">left|center</item>
<item name="android:paddingLeft">35dp</item>
<item name="android:layout_width">175dp</item>
<item name="android:layout_height">fill_parent</item>
</style>
Try it out and say is this you want or something else . update me dude.
试试看,然后说这是你想要的还是别的什么。更新我伙计。