从Html.fromHtml跨越,但使用自定义ClickableSpan进行自定义方案

时间:2013-04-11 07:43:37

标签: android html textview spanned

我有一个显示一些HTML代码的TextView(包括图像,ImageGetter)。 html不是我的,但我可以要求他们包含自定义方案链接,甚至可能包含标签
目的:显示一些动态生成的内容,而无需使用嵌套的Android布局 问题:必须在应用程序中处理一些链接(加载新的片段) 不能使用receiver for action.VIEW,因为它是一个活动意图,而不是一个广播,它的使用将是非常上下文的,所以只有一个以编程方式注册的接收器才会这样做。

我正在使用textView.setText(Html.fromHtml(content.content, imageGetter, null)。需要一切都保持不变,除了一些跨度应该有我自己的onClick。我对Spanned不太熟悉,所以我看到了这些选项:

  1. 编辑从Html.fromHtml返回的SpannableStringBuilder,并用自定义ClickableSpan替换我想要的URLSpans(如何?)
  2. 如上所述,但是将所有内容复制到新构建器,为我自己交换URLSpans(如何?append只接受CharSequence,我得到RelativeSizeSpan,StyleSpan,ImageSpan,URLSpan ......)
  3. 手动创建跨区域。我可以为自定义方案执行此操作,但是如何为所有其他方法复制Html.fromHtml(或足够接近)的效果?
  4. [编辑] 感谢MH。信息。我之前尝试过,但失败了。现在我回到了它,我发现我犯了一个错误,将错误的项目传递给了setSpan的第一个参数 如果有人有兴趣,我现在用这个:

    public static interface OnSpanClickListener {
        void onClick(String url);
    }
    public static SpannableStringBuilder getSpannable(String source, ImageGetter imageGetter, String scheme, final OnSpanClickListener clickHandler){
        SpannableStringBuilder b = (SpannableStringBuilder) Html.fromHtml(source, imageGetter, null);
        for(URLSpan s : b.getSpans(0, b.length(), URLSpan.class)){
            String s_url = s.getURL();
            if(s_url.startsWith(scheme+"://")){
                URLSpan newSpan = new URLSpan(s_url.substring(scheme.length()+3)){
                    public void onClick(View view) {
                        clickHandler.onClick(getURL());
                    }
                };
                b.setSpan(newSpan, b.getSpanStart(s), b.getSpanEnd(s), b.getSpanFlags(s));
                b.removeSpan(s);
            }
        }
        return b;
    }
    

    (...)

    body.setText(getSpannable(content.content, imageGetter, getResources().getString(R.string.poster_scheme), new OnSpanClickListener(){
    public void onClick(String url) {
        // do whatever
    }
    }));
    

2 个答案:

答案 0 :(得分:3)

选项1可能是最直接的,并且之前的大部分艰苦工作已经完成。您的一般想法是正确的:在处理完HTML后,您可以请求所有生成的URLSpan实例并循环遍历它们。然后,您可以使用自定义的可点击范围替换它,以获得对任何跨度点击的完全控制。

在下面的示例中,我只是将每个URLSpan替换为带有原始url的类的简单扩展(实际上,我应该说'uri')并替换其scheme部分。我已经离开了实际的onClick()逻辑未实现,但我会把它留给你的想象力。

SpannableStringBuilder builder = ...
URLSpan[] spans = builder .getSpans(0, builder .length(), URLSpan.class);
for (URLSpan span : spans) {
    int start = builder .getSpanStart(span);
    int end = builder .getSpanEnd(span);
    s.removeSpan(span);
    span = new CustomURLSpan(span.getURL().replace("http://", "scheme://"));
    s.setSpan(span, start, end, 0);
}
textView.setText(builder);

如前所述,这里CustomURLSpan类是URLSpan的简单扩展,它接受一个url并覆盖onClick()方法,因此我们自己的逻辑可以在那里执行。

public class CustomURLSpan extends URLSpan {

    public CustomURLSpan(String url) {
        super(url);
    }

    @Override public void onClick(View widget) {
        // custom on click behaviour here
    }
}

一些相关的Q& A基本上做了类似的事情(可能对一些更多的灵感有帮助):

答案 1 :(得分:1)

由于您将使用 TextView.setMovementMethod(LinkMovementMethod.getInstance())来响应部分点击,因此您可以实施自定义 LinkMovementMethod < / strong>拦截click事件并处理你自己的逻辑。

public class CustomLinkMovementMethod extends LinkMovementMethod {

    private HashMap<Class, SpanProxy> spansProxy = new HashMap<>();

    public CustomLinkMovementMethod setSpanProxy(Class spanClazz, SpanProxy proxy) {
        spansProxy.put(spanClazz, proxy);
        return this;
    }

    public CustomLinkMovementMethod removeSpanProxy(Class spanClazz) {
        spansProxy.remove(spanClazz);
        return this;
    }

    @Override
    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {

        int action = event.getAction();

        if (action == MotionEvent.ACTION_UP ||
                action == MotionEvent.ACTION_DOWN) {
            int x = (int) event.getX();
            int y = (int) event.getY();

            x -= widget.getTotalPaddingLeft();
            y -= widget.getTotalPaddingTop();

            x += widget.getScrollX();
            y += widget.getScrollY();

            Layout layout = widget.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);

            ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
            if (link.length != 0) {
                if (action == MotionEvent.ACTION_UP) {
                    SpanProxy spanProxy = spansProxy.get(ClickableSpan.class);
                    if(spanProxy != null) {
                        spanProxy.proxySpan(link[0], widget);
                    } else {
                        link[0].onClick(widget);
                    }
                    return true;
                }
            }
        }
            return super.onTouchEvent(widget, buffer, event);
    }


    public interface SpanProxy {
        void proxySpan(Object span, View widget);
    }
}

并像这样使用:

textView.setMovementMethod(new CustomLinkMovementMethod().setSpanProxy(ClickableSpan.class,
        new CustomLinkMovementMethod.SpanProxy() {
            @Override
            public void proxySpan(Object span, View widget) {
                if (span instanceof URLSpan) {
                    URLSpan urlSpan = (URLSpan) span;
                    String url = urlSpan.getURL();

                    // do what you want with the link
                    // tbd
                    return;
                }

                if (span instanceof ClickableSpan) {
                    ((ClickableSpan) span).onClick(widget);
                }
            }
        }));