Java 如何使用ClickableSpan切换可调前景色

Java 如何使用ClickableSpan切换可调前景色,java,android,Java,Android,我使用一个方法创建了这个类,该方法接收可扩展对象,并将样式应用于它接收的任何对象。我在一个论坛消息解析器上使用它,它有这些扰流板标签,只有当鼠标光标在上面时,内容才会显示出来。对于android,我想通过点击隐藏区域使其工作,为此我写了以下内容: public static class TextRuleStartSpoiler extends TextRuleStart { protected TextRuleStartSpoiler() { super("spo

我使用一个方法创建了这个类,该方法接收可扩展对象,并将样式应用于它接收的任何对象。我在一个论坛消息解析器上使用它,它有这些扰流板标签,只有当鼠标光标在上面时,内容才会显示出来。对于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;
    }
}
但这并没有改变什么。我添加了一些调试日志打印,我确信这些方法是用正确的参数调用的,但它没有以正确的方式更新,即使我尝试在onClick之后立即使用onClick上的view v和TextView-tv-on-apply使视图无效

由于我编写的代码的性质,我不可能对TextView进行太多的处理,因为它将充满其他可扩展的对象,其中的规则在特定方面与此完全无关

更新 事实上,问题不在代码的这一部分,而是我如何在TextView中插入可扩展对象。我迭代了多个spannable,这使我认为我可以使用该方法,但是,这将BufferType更改为EDITABLE,这将禁用文本中的视觉更新,因此我将其更改为

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

您应该做的是完全覆盖updateDrawState方法的默认实现,不要调用超级方法

示例代码(扰流板span):

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;
        }
    }
}
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);
用法:

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;
        }
    }
}
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);
结果: