简体   繁体   English

如何使用ClickableSpan切换可扩展前景色

[英]How to toggle Spannable foreground color using ClickableSpan

I created this class with a method that receives an Spannable object and applies an style to whatever objects it receives. 我使用接收Spannable对象的方法创建了此类,该方法将样式应用于其接收的任何对象。 I am using it on a forum message parser which have these Spoiler tags, where the content is only shown when the mouse cursor is over it. 我在具有这些Spoiler标签的论坛消息解析器上使用它,其中的内容仅在鼠标光标位于其上方时才显示。 For android, I wanted to make it work with clicks on the hidden area, and for that I wrote this: 对于android系统,我想通过单击隐藏区域使其起作用,为此,我这样写:

public static class TextRuleStartSpoiler extends TextRuleStart
{
    protected TextRuleStartSpoiler()
    {
        super("spoiler");
    }

    ArrayList<Spannable> hiddenSpannables = new ArrayList<Spannable>();
    boolean hidden = false;

    @Override
    public void apply(Spannable s, TextView tv)
    {
        hiddenSpannables.add(s);

        s.setSpan(new BackgroundColorSpan(Color.parseColor("#0A1238")),0,s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        s.setSpan(getForegroundColorSpanShown(), 0, s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        s.setSpan(getClickableSpanLink(),0,s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    public static TextRuleEnd getRuleEnd()
    {
        return new TextRuleEnd("spoiler");
    }

    ForegroundColorSpan foregroundColorSpanHidden = null;
    private synchronized ForegroundColorSpan getForegroundColorSpanHidden()
    {
        if(foregroundColorSpanHidden == null)
        {
            foregroundColorSpanHidden = new ForegroundColorSpan(Color.parseColor("#0A1238"));
        }

        return foregroundColorSpanHidden;
    }

    ForegroundColorSpan foregroundColorSpanShown = null;
    private synchronized ForegroundColorSpan getForegroundColorSpanShown()
    {
        if(foregroundColorSpanShown == null)
        {
            foregroundColorSpanShown = new ForegroundColorSpan(Color.WHITE);
        }

        return foregroundColorSpanShown;
    }

    ClickableSpan clickableSpan = null;
    private synchronized ClickableSpan getClickableSpanLink()
    {
        if(clickableSpan == null)
        {
            clickableSpan = new ClickableSpan() {
                @Override
                public void onClick(View widget)
                {
                    hidden = !hidden;

                    if(hidden)
                    {
                        for(Spannable s : hiddenSpannables)
                        {
                            s.setSpan(getForegroundColorSpanHidden(), 0, s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                            s.removeSpan(getForegroundColorSpanShown());
                        }
                    }
                    else
                    {
                        for(Spannable s : hiddenSpannables)
                        {
                            s.setSpan(getForegroundColorSpanShown(), 0, s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                            s.removeSpan(getForegroundColorSpanHidden());
                        }
                    }

                    widget.invalidate();
                }

                @Override
                public void updateDrawState(TextPaint ds)
                {
                    //super.updateDrawState(ds);
                }
            };
        }

        return clickableSpan;
    }
}

However, no matter how much times I click on it, nothing happens. 但是,无论我单击多少次,都不会发生任何事情。 I have also tried, instead of setting and removing ForegroundColorSpans, overriding the method updateDrawState to something like this: 我也尝试过,而不是设置和删除ForegroundColorSpans,而是将方法updateDrawState重写为如下所示:

@Override
public void updateDrawState(TextPaint ds)
{
    if(hidden)
    {
        ds.linkColor = Color.parseColor("#0A1238");
    }
    else
    {
        ds.linkColor = Color.WHITE;
    }

    super.updateDrawState(ds);
}

But it did not change anything too. 但这并没有改变任何东西。 I added some debug Log prints, and I know for sure that these methods are being called with the correct arguments, but it is not updated the right way, even if I try to invalidate the view right after onClick, using both the View v on onClick and the TextView tv on apply. 我添加了一些调试日志打印,并且可以确定使用正确的参数调用了这些方法,但是即使我尝试同时使用View v和onClick尝试使视图无效,也无法以正确的方式对其进行更新。 onClick和TextView电视均适用。

Due to the nature of the code I wrote, it is not possible for me to mess too much with the TextView, since it will be full of other Spannable objects with rules completely unrelated with this one in specific. 由于我编写的代码的本质,我不可能对TextView弄得太多,因为它将充满其他Spannable对象,而这些对象的规则与该对象完全无关。

Update 更新

In fact, the problem was not in this part of the code, but actually on how I was inserting the Spannable objects in the TextView. 实际上,问题不在于代码的这一部分,而在于我如何在TextView中插入Spannable对象。 I was iterating over multiple Spannables, which lead me to think I could use the TextView.append method, however, this changes the BufferType to EDITABLE, which disables visual updates in the text, so I changed it to 我正在遍历多个Spannable,这使我认为我可以使用TextView.append方法,但是,这将BufferType更改为EDITABLE,从而禁用了文本中的可视更新,因此我将其更改为

tv.setText(TextUtils.concat(tv.getText(), s), TextView.BufferType.SPANNABLE);

What you should do is override the default implementation of the updateDrawState method completely, don't call the super method. 您应该做的是完全覆盖updateDrawState方法的默认实现,不要调用super方法。

Example code (SpoilerSpan): 示例代码(SpoilerSpan):

public class SpoilerSpan extends ClickableSpan {

    private boolean shown = false;

    public void setShown(boolean shown){
        this.shown = shown;
    }

    public boolean getShown(){
        return this.shown;
    }

    @Override
    public void onClick(View widget) {
        //Toggle the shown state
        setShown(!getShown());
        //Invalidate the view
        widget.invalidate();
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        //Don't call the super method otherwise this may override our settings!
        //super.updateDrawState(ds);

        //No need to disable the default underline style because the super method isn't called.
        //ds.setUnderlineText(false);

        if(getShown()){
            ds.setColor(Color.BLACK);
            ds.bgColor = 0xFFE7DAC2;
        } else {
            //Spoiler is not shown, make the text color the same as the background color
            ds.setColor(0xFFE7DAC2);
            ds.bgColor = 0xFFE7DAC2;
        }
    }
}

Usage: 用法:

TextView tv = (TextView) findViewById(R.id.test);
tv.setMovementMethod(LinkMovementMethod.getInstance());

SpannableString testText = new SpannableString("This is some text. This is a spoiler and this isn't.");
testText.setSpan(new SpoilerSpan(), 19, 36, Spannable.SPAN_POINT_MARK);

tv.setText(testText, BufferType.SPANNABLE);

Result: 结果: 在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM