TextView中UrlSpan与文本中的超链接冲突问题

一、   Android的TextView中展示超链接有三种方式:

 

1.  使用Html.fromHtml(source)方法将html的如下文本转换为android支持的Spannable

Spanned spanned = Html.fromHtml("<ahref=\"http://www.google.com\">谷歌</a>");
textView.append(spanned);

 

2.   在Java代码中使用UrlSpan

SpannableString ss = new SpannableString("谷歌");

ss.setSpan(newURLSpan("http://www.google.com\"), 0, 2,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

textView.setText(ss);



3.   直接将带有url的字符串设置到TextView中

textView.setText("http://www.google.com\");前提是已设置TextView的autoLink属性

或textView.append(Linkify.addLinks("http://www.google.com\",Linkify.WEB_URLS));无需设置autoLink属性

 

二、设置TextView的autoLink属性的方法:

<TextView

   android:id="@+id/testweb"

   android:layout_width="fill_parent"

   android:layout_height="wrap_content"

   android:autoLink="web" //是将文本的web网址解释成超链接,也可以使用TextView.setAutoLinkMask(mask)方法设置该属性

   android:text="@string/link_text_auto"/>

 

三、我在开发过程中遇到的问题

类似微博、贴吧的android客户端开发时,对于超链接富文本在android端我直接使用上面的方法2(UrlSpan)展示所有帖子的超链接。

SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
        spannableStringBuilder.append(urlSpan);
        textView.setText(spannableStringBuilder);

未设置autoLink属性,超链接正常展示。但突然有一天发现TextView中有一个url纯文本未展示位超链接。


最近项目中遇到一个问题,卡了半天,和大家分享下。

    我们的android应用需要在TextView中展示超链接(与我们写博客中的超链接功能一样),这个超链接也是用户设置的包括超链接的显示名称和url。富文本格式为[url=www.baidu.com]百度[/url]

1 SpannableStringBuilder sb = new SpannableStringBuilder();
2 URLSpan span = new URLSpan(url);
3 SpannableString ss = new SpannableString(value);
4 ss.setSpan(span, 0, value.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
5 sb.append(ss);
6 ... //可能有很多url超链接,故使用SpannableStringBuilder(它不仅仅包括urlSpan还包括纯文本内容数据)
7 textView.setText(sb);
    上面的TextView是用来展示帖子的全部内容的,正常情况下超链接都显示OK。但若帖子内容中用户直接粘贴了链接的url而没有使用[url]富文本格式,此时该url就不能自动识别、和展示位超链接了。

    于是在网上搜索了下,发现要实现自动识别url很简单,只要设置TextView的autoLink属性为"web"即可。于是乎我设置了android:autoLink="web",发现url可以自动识别为超链接,但突然使用UrlSpan设置的超链接没了,这么诡异。。。。

    查看了源码才发现,先看看setText()方法实现

01 private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {
02         ...
03         if (mAutoLinkMask != 0) { // 自动链接掩码 
04             Spannable s2;
05  
06             if (type == BufferType.EDITABLE || text instanceof Spannable) { // Spannable富文本
07                 s2 = (Spannable) text;
08             else {
09                 s2 = mSpannableFactory.newSpannable(text);
10             }
11  
12             if (Linkify.addLinks(s2, mAutoLinkMask)) {
13                 text = s2;
14                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE :BufferType.SPANNABLE;
15  
16                 /*
17                  * We must go ahead(先执行) and set the text before changing the
18                  * movement method, because setMovementMethod() may call
19                  * setText() again to try to upgrade(升级) the buffer type.
20                  */
21                 mText = text;
22  
23                 // Do not change the movement method for text that support text selection as it would prevent an arbitrary cursor displacement.
25                 if (mLinksClickable && !textCanBeSelected()) {
26                     setMovementMethod(LinkMovementMethod.getInstance());
27                 }
28             }
29         }
30         ...
31     }

    上面可以看到mAutoLinkMask属性(可以再xml中设置android:autoLink="web"或使用TextView.setAutoLink()方法设置)不为空时,使用Linkify.addLinks(s2, mAutoLinkMask)将TextView文本中的url自动识别并转换为UrlSpan.

01 /**
02      *  Scans the text of the provided Spannable and turns all occurrences
03      *  of the link types indicated in the mask into clickable links.
04      *  If the mask is nonzero, it also removes any existing URLSpans
05      *  attached to the Spannable, to avoid problems if you call it
06      *  repeatedly on the same text.
07      */
08     public static final boolean addLinks(Spannable text, int mask) {
09         if (mask == 0) {
10             return false;
11         }
12  
13         URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
14  
15         for (int i = old.length - 1; i >= 0; i--) {
16             text.removeSpan(old[i]);// 移除原来所有的Spanable
17         }
18  
19         ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
20  
21         if ((mask & WEB_URLS) != 0) {
22             gatherLinks(links, text, Patterns.WEB_URL,
23                 new String[] { "http://""https://""rtsp://" },
24                 sUrlMatchFilter, null);
25         }
26  
27         if ((mask & EMAIL_ADDRESSES) != 0) {
28             gatherLinks(links, text, Patterns.EMAIL_ADDRESS,
29                 new String[] { "mailto:" },
30                 nullnull);
31         }
32  
33         if ((mask & PHONE_NUMBERS) != 0) {
34             gatherLinks(links, text, Patterns.PHONE,
35                 new String[] { "tel:" },
36                 sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
37         }
38  
39         if ((mask & MAP_ADDRESSES) != 0) {
40             gatherMapLinks(links, text);
41         }
42  
43         pruneOverlaps(links);
44  
45         if (links.size() == 0) {
46             return false;
47         }
48  
49         for (LinkSpec link: links) {
50             applyLink(link.url, link.start, link.end, text);
51         }
52  
53         return true;
54     }

        上面是Linkify.addLinks()方法源码,其中做了三件事情:

1.删除text文本中原有的Spanable

2.使用gatherLinks()是使用正则表达式匹配各种类型的url

3.最终使用applyLink()将2中匹配到的url生成UrlSpan。

下面是Linkify.applyLink()源码

1 private static final void applyLink(String url, int start, int end, Spannable text) {
2         URLSpan span = new URLSpan(url);
3  
4         text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
5 }
    可以看出 Linkify.applyLink()方法其实就是将前面使用正则表达式匹配出来的各种类型( WEB_URLS、 EMAIL_ADDRESSES、 MAP_ADDRESSES等 )超链接字符串来构造UrlSpan。

    这也就解释了为何我设置了autoLink属性,但我自行构造的UrlSpan都不展示为超链接的原因了,被删除了。

    再看看TextView::append()方法

01 /**
02      * Convenience method: Append the specified text slice to the TextView's
03      * display buffer, upgrading it to BufferType.EDITABLE if it was
04      * not already editable.
05      */
06     public void append(CharSequence text, int start, int end) {
07         if (!(mText instanceof Editable)) {
08             setText(mText, BufferType.EDITABLE);
09         }
10  
11         ((Editable) mText).append(text, start, end);
12     }

    可以看出,首次调用append()方法首先会把TextView的BufferType设置为BufferType.EDITABLE。然后追加文本,并且追加文本时不会进行Linkify.addLinks()操作,也即append()追加的文本不会自动识别url。


我最终实现超链接方法如下:

01 while (/*循环条件*/) {
02     if (/*富文本超链接*/) {
03     ...
04     if (!TextUtils.isEmpty(url)) {
05         URLSpan span = new URLSpan(url);
06         SpannableString ss = new SpannableString(value);
07         ss.setSpan(span, 0, value.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
08         textView.append(ss);
09     else if (/*非图片附件*/) {
10         ...
11         URLSpan span = new URLSpan(attachmentUrl);
12         SpannableString ss = new SpannableString(attachmentName);
13         ss.setSpan(span, 0, attachmentName.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
14         textView.append(ss);
15     else {               
16         ...
17         SpannableString spannableString = new SpannableString(textElement.getValue());
18         Linkify.addLinks(spannableString, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);
19         textView.append(spannableString);
20     }
21 }

即自定调用Linkify.addLink()方法转化url字符串为UrlSpan。

问题2:自定义超链接字体颜色

方法1:继承URLSpan类并复写updateDrawState()方法

	@Override
	public void updateDrawState(TextPaint ds) {
		ds.setColor(Color.rgb(40, 192, 198));//#28C0C6
		ds.setUnderlineText(true);
	}
当然也可以复写URLSpan::onClick()自定义超链接的点击事件处理。


方法2:设置TextView的android:textAppearance属性

android:textAppearance="@style/CustomTextAppearance"

或textView.setTextAppearance(mContext, R.style.CustomTextAppearance);

<style name="CustomTextAppearance" parent="@android:style/TextAppearance.Holo">
        <item name="android:textColor">@color/text_color_dark_black</item>
        <item name="android:textColorLink">@color/text_color_blue</item>
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值