【问题标题】:Change the text color of a single ClickableSpan when pressed without affecting other ClickableSpans in the same TextView按下时更改单个 ClickableSpan 的文本颜色,而不影响同一 TextView 中的其他 ClickableSpan
【发布时间】:2014-01-18 07:35:43
【问题描述】:

我有一个 TextView,里面有多个 ClickableSpan。当按下 ClickableSpan 时,我希望它改变其文本的颜色。

我尝试将颜色状态列表设置为 TextView 的 textColorLink 属性。这不会产生预期的结果,因为当用户在 TextView 上单击 anywhere 时,这会导致 所有跨度改变颜色。

有趣的是,使用 textColorHighlight 更改背景颜色可以按预期工作:单击 span 只会更改该 span 的背景颜色,而单击 TextView 中的其他任何地方都不会执行任何操作。

我还尝试将 ForegroundColorSpans 设置为与 ClickableSpans 相同的边界,其中我传递与上面相同的颜色状态列表作为颜色资源。这也不起作用。 span 始终保持颜色状态列表中默认状态的颜色,永远不会进入按下状态。

有人知道怎么做吗?

这是我使用的颜色状态列表:

<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>

【问题讨论】:

  • 你需要在这里使用 Spannable 对象作为文本,有一个例子。 API:developer.android.com/reference/android/text/Spannable.htmlstackoverflow.com/questions/3282940/….
  • 我当然是在 Spannable 上完成以上所有工作。并且链接的示例仅显示了如何设置跨度的颜色,这不是这里的问题。我希望跨度的颜色在按下时改变。
  • afaik ClickableSpan 不直接支持
  • 那太可惜了。似乎是一件很简单的事情,来自 CSS。在这种情况下,我可能只会使用 textColorHighlight。
  • 幸运的是我错了,更重要的是,您不仅可以更改链接颜色(updateDrawState),还可以更改背景颜色(SpanWatcher)

标签: android layout textview textcolor


【解决方案1】:

我终于找到了一个可以满足我所有需求的解决方案。它基于this answer

这是我修改后的 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);
        }
    }

这需要像这样应用于 TextView:

    yourTextView.setMovementMethod(new LinkTouchMovementMethod());

这是修改后的 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);
    }
}

【讨论】:

  • 很好,一件事:将 ds.bgColor 设置为 0xffffffff 完全隐藏了默认选择(在使用 dpad 箭头浏览链接时很有用)
  • 感谢您的提示。我现在将其更改为 0xffeeeeee ,就像您现在的示例一样。为什么这会有所作为?它们不是完全透明的吗?
  • 从 0xff****** 开始它们完全不透明,应该类似于 0xaa******
  • 很好的解决方案!为了避免创建无数的LinkTouchMovementMethod 对象,我将覆盖静态LinkMovementMethod.getInstance() 方法以随时返回单个实例。
  • @steven Meliopoulos,感谢你漂亮的代码,但它不适用于android M,请查看问题,我在stackoverflow上提出了一个问题,其链接如下,stackoverflow.com/questions/34737514/…
【解决方案2】:

更简单的解决方案,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);
    }
};

【讨论】:

  • 这不仅设置按下状态的颜色,而且设置所有的颜色
  • 这并不能解决问题
【解决方案3】:

所有这些解决方案都工作量太大。

只需将android:textColorLink 中的TextView 设置为某个选择器。然后创建一个不需要覆盖 updateDrawState(...) 的 clickableSpan。全部完成。

这里是一个简单的例子:

在你的strings.xml 有一个这样的声明字符串:

<string name="mystring">This is my message%1$s these words are highlighted%2$s and awesome. </string>

然后在你的活动中:

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);
}

这是重要的部分..声明一个像这样的选择器,称它为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>

最后在你的TextView in xml 中这样做:

 <TextView
     android:id="@+id/mytextview"
     android:background="@android:color/transparent"
     android:text="@string/mystring"
     android:textColorLink="@drawable/myselector" />

现在您可以在 clickableSpan 上设置为按下状态。

【讨论】:

  • selector color 只有在我们只有 1 个链接时才能正常工作,如果我们有 2 个链接,当我们按下时,我们会同时突出显示两个链接
【解决方案4】:

legr3c's answer 帮了我很多。我想补充几点。

备注 #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);

我将LinkTouchMovementMethod 应用于具有两个跨度的TextView。单击它们时,跨度以蓝色突出显示。 myTextView.setHighlightColor(getResources().getColor(android.R.color.transparent)); 修复了这个错误。

备注 #2。

在传递normalTextColorpressedTextColorpressedBackgroundColor 时不要忘记从资源中获取颜色。

Should pass resolved color instead of resource id here

【讨论】:

    【解决方案5】:

    试试这个自定义 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);
        }
    }
    

    还有这个 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) {
        }
    }
    

    在 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);
    

    【讨论】:

    • 感谢您的回答。我试过了,但它并没有像我预期的那样工作。具体来说,当链接被释放或触摸位置移出链接时,链接并没有回到原来的状态,所以最后我选择了我上面发布的版本。
    【解决方案6】:

    如果你有很多点击元素,这是我的解决方案(我们需要一个界面): 界面:

    public interface IClickSpannableListener{
      void onClickSpannText(String text,int starts,int ends);
    }
    

    管理事件的班级:

    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);
      }
    }
    

    在主类中:

    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);
      }
    }
    

    【讨论】:

      【解决方案7】:

      将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;   
      } 
      }
      

      在上面的代码中指定你想要的颜色。

      根据需要更改样式 .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>
      

      试一试,说这是你想要的还是别的。更新我哥们。

      【讨论】:

      • 这不会改变整个TextView的颜色吗?
      • 目标是只改变被按下的 ClickableSpan 的颜色。 TextView 的其余部分应保持不变。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-21
      • 2017-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-05
      相关资源
      最近更新 更多