Android如何通过TextView实现超链接的跳转

前段时间在开发群里看到有人问android的TextView该如何自定义超链接的跳转,如:有字符串“使用该软件,即表示您同意该软件的使用条款和隐私政策”,现希望当点击“使用条款”或“隐私政策”时可以跳转到相应的说明页面,我还记得当时有一大堆人在讨论然后提了一大堆的方法,比如:用多个TextView组合,给相应的TextView添加点击事件、给TextView添加autoLink属性、通过给相应的内容添加标签、借助Spannable类、Linkfy类等等,当然最后提问者采用哪种方法我就不得而知了,我呢也刚好最近几天比较有空,然后翻了下Api文档,于是通过几个晚上的总结形成了今天的这篇博客,内容比较多,还望大家能够耐心点,相信大家看完肯定对TextView的各种超链接的跳转及实现水到渠来。

1)autoLink属性

对TextView属性比较熟悉的开发者应该都知道TextView有一个叫做autoLink的属性可以将符合指定格式的文本转换为可单击的超链接形式,在帮助文档中也可以发现Android给我们提供了如下几种格式:

1、none:表示不进行任何匹配,默认;

2、Web:表示匹配Web Url,如:内容中的http://www.baidu.com会成为可单击跳转的超链接;

3、Email:表示匹配邮件地址:如:邮件地址为hello@com.cn会成为可单击的超链接;

4、Phone:表示匹配电话号码:如:点击号码10086会跳到拨号界面;

5、Map:表示匹配地图地址;

6、All:表示将会匹配web、email、phone、map;

为了验证android给我们提供的几种格式,我在布局中添加了几个TextView并且分别设置了autoLink属性及相应的值,运行程序后可以发现,内容中符合格式的都带上了下划线并且有相应的颜色,如下所示:

1)拦截超链接

虽然通过设置autoLink属性可以符合格式的文本转换为可单击的超链接形式,但是,有一点需要注意的是,当点击web地址时打开后跳转的是手机自带的浏览器,如果希望点击web地址时可以跳转到应用本身的一个WebView界面,那么此时又该如何实现呢?如果不知道怎么实现的话,我们可以点击TextView进去查看一下TextView的源码看一下autoLink的是如何实现的,通过ctrl+f查找autoLink可以发现如下代码:

case com.android.internal.R.styleable.TextView_autoLink:

mAutoLinkMask = a.getInt(attr, 0);

break;

继续通过ctrl+f查找mAutoLinkMask变量可以发现setText方法中有如下代码:

if (mAutoLinkMask != 0) {

Spannable s2;

if (type == BufferType.EDITABLE || text instanceof Spannable) {

s2 = (Spannable) text;

} else {

s2 = mSpannableFactory.newSpannable(text);

}

if (Linkify.addLinks(s2, mAutoLinkMask)) {

text = s2;

type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;

/*

* We must go ahead and set the text before changing the

* movement method, because setMovementMethod() may call

* setText() again to try to upgrade the buffer type.

*/

mText = text;

// Do not change the movement method for text that support text selection as it

// would prevent an arbitrary cursor displacement.

if (mLinksClickable && !textCanBeSelected()) {

setMovementMethod(LinkMovementMethod.getInstance());

}

}

}

在代码中可以看到有一个if (Linkify.addLinks(s2, mAutoLinkMask))的判断,点击进去可以发现Linkify.addLinks方法别有洞天,代码如下所示:

public static final boolean addLinks(Spannable text, int mask) {

if (mask == 0) {

return false;

}

URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);

for (int i = old.length - 1; i >= 0; i--) {

text.removeSpan(old[i]);

}

ArrayListlinks = new ArrayList();

if ((mask & WEB_URLS) != 0) {

gatherLinks(links, text, Patterns.WEB_URL,

new String[] { "http://", "https://", "rtsp://" },

sUrlMatchFilter, null);

}

......此处省略若干省略若干行代码

for (LinkSpec link: links) {

applyLink(link.url, link.start, link.end, text);

}

return true;

}

然后我们点击进到applyLink方法中可以看到有如下实现:

private static final void applyLink(String url, int start, int end, Spannable text) {

URLSpan span = new URLSpan(url);

text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

}

如果大家对里面的其它方法比较感兴趣的话也可以一一点击进去查看相应的实现,我这里就不再一一介绍了,额,貌似有点扯远了,我们回到正题,总之在经过一系列的翻阅跟TextView相关的源码和帮助文档后,发现我们可以通过借助Spannable来获取URLSpan数组然后可以通过遍历获取所有的url地址,最后通过给Spannable设置自定义的ClickableSpan来进行跳转,MainActivity的主要代码如下所示:

public class MainActivity extends AppCompatActivity {

private TextView tv_content;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

tv_content = (TextView) findViewById(R.id.tv_content);

interceptHyperLink(tv_content);

}

/**

* 拦截超链接

* @param tv

*/

private void interceptHyperLink(TextView tv) {

tv.setMovementMethod(LinkMovementMethod.getInstance());

CharSequence text = tv.getText();

if (text instanceof Spannable) {

int end = text.length();

Spannable spannable = (Spannable) tv.getText();

URLSpan[] urlSpans = spannable.getSpans(0, end, URLSpan.class);

if (urlSpans.length == 0) {

return;

}

SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);

// 循环遍历并拦截 所有http://开头的链接

for (URLSpan uri : urlSpans) {

String url = uri.getURL();

if (url.indexOf("http://") == 0) {

CustomUrlSpan customUrlSpan = new CustomUrlSpan(this,url);

spannableStringBuilder.setSpan(customUrlSpan, spannable.getSpanStart(uri),

spannable.getSpanEnd(uri), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);

}

}

tv.setText(spannableStringBuilder);

}

}

}

自定义ClickableSpan子类的CustomUrlSpan的主要代码如下所示:

public class CustomUrlSpan extends ClickableSpan {

private Context context;

private String url;

public CustomUrlSpan(Context context,String url){

this.context = context;

this.url = url;

}

@Override

public void onClick(View widget) {

// 在这里可以做任何自己想要的处理

Intent intent = new Intent(context,WebViewActivity.class);

intent.putExtra(WebViewActivity.WEB_URL,url);

context.startActivity(intent);

}

}

在CustomUrlSpan类的onClick方法中进行跳转时用到的WebViewActivity代码这里就不再贴出来,主要就是一个用来加载网页的WebView,如果有需要的可以在文章末尾下载源码查看;

2)去除超链接的下划线

众所周知,超链接都带有一条下划线表示可点击的,那么如果想去除超链接的下划线又该如何实现呢?既然下划线是用来表示可点击的,那么就说明跟点击事件有关,从上面拦截超链接的实现中知道点击超链接进行跳转是借助ClickableSpan类实现的,进到ClickableSpan类中可以发现该类出奇的简单,如下所示:

从源码中可以发现,超链接中的下划线是通过TextPaint的setUnderlineText方法来实现的,也就是说如果我们想去除超链接中的下划线的话可以通过自定义一个继承自ClickableSpan的类然后重写其updateDrawState方法,在该方法中将TextPaint的setUnderlineText方法设为false,最后再将该自定义的ClickableSpan设置到的相应的TextView中即可,原则上来说通过自定义一个继承自ClickableSpan的类是可以去除超链接的下划线,但是,在这里我将使用跟ClickableSpan类似的一个类UnderlineSpan来实现,至于原因想必大家看类名就很容易知道了。

1)首先自定义一个继承自UnderlineSpan类的NoUnderlineSpan类并重写父类的updateDrawState方法,然后在该方法中将TextPaint的setUnderlineText方法设为false,主要代码如下:

public class NoUnderlineSpan extends UnderlineSpan {

@Override

public void updateDrawState(TextPaint ds) {

ds.setUnderlineText(false);

}

}

2)在需要去除超链接下划线的Activity中的相应TextView后设置如下内容:

private void removeHyperLinkUnderline(TextView tv) {

CharSequence text = tv.getText();

if(text instanceof Spannable){

Log.i("test","true");

Spannable spannable = (Spannable) tv.getText();

NoUnderlineSpan noUnderlineSpan = new NoUnderlineSpan();

spannable.setSpan(noUnderlineSpan,0,text.length(), Spanned.SPAN_MARK_MARK);

}

}

运行后效果如下所示:

2)自定义链接

除了以上通过添加autoLink属性并设置web值实现超链接以外,android还给我们提供了一个Linkify类来自定义超链接,并且从帮助文档中可以看出Linkify还提供了一大堆添加自定义模式的方法,如下所示:

这里为了方便,暂且用有3个参数的构造方法即最后一个,其中第一个参数TextView即需要自定义模式的对象,第二个参数Pattern表示自己定义的用来匹配第一个参数TextView中内容的正则表达式,最后一个参数Scheme我理解为当点击自定义链接时跳转的界面,如:在布局文件中新增一个TextView并设置一些默认的内容,然后在代码如下通过如下方式设置自定义链接:

TextView  tv_customHyperLink = (TextView) findViewById(R.id.tv_customHyperLink);

//配置的正则表达式

Pattern p = Pattern.compile("abc://\\S*");

Linkify.addLinks(tv_customHyperLink, p, "abc");

为了当点击自定义链接时点击能够响应,在这里我新建了一个TargetActivity类专门用来处理响应,但是有一点需要注意是需要在AndroidManifest清单文件中相应的activity节点下添加如下代码:

<activity android:name=".TargetActivity">

       <intent-filter>

                 <action android:name="android.intent.action.VIEW"/>

                 <category android:name="android.intent.category.DEFAULT"/>

                 <data android:scheme="abc"/>

       </intent-filter>

</activity>

此时,运行程序,当点击自定义链接时便会跳转到能够响应的scheme为“abc”的界面中:

点击链接跳转后的页面为:

当然,我们也可以通过这种方法实现上面所实现的拦截超链接的功能,这里就不再详细说明了,另外,当一个TextView即需要使用内置模式又需要使用自定义模式时必须先声明内置模式然后再声明自定义模式,并且经测试发现:不能在xml布局文件中通过autoLink属性来声明内置模式,否则自定义模式不起作用,据说是因为:在设置内置模式时会先删除已有的模式,那么此时就只能通过在代码中设置了,主要代码如下所示:

//多种模式

TextView  tv_multiHyperLink = (TextView) findViewById(R.id.tv_multiHyperLink);

Linkify.addLinks(tv_multiHyperLink,Linkify.PHONE_NUMBERS);

Pattern pattern = Pattern.compile("abc://\\S*");

Linkify.addLinks(tv_multiHyperLink, pattern, "abc");

运行程序,结果如下所示:

3)借助Html实现文字的超链接

细心的你们也许会发现以上都是对一些链接进行的操作,当然你们也许会说可以通过自定义链接的形式对指定的文字进行正则匹配来实现,但是通过正则匹配中文的话应该比较难实现吧,所以,我们可以通过类似于html中超链接(即a标签)的方式来实现,考虑到字符串的来源及格式,于是总结出了比较常用的以下3种,主要代码如下所示:

//通过html的形式实现超链接

String csdnLink1 = "我的CSDN博客";

TextView  tv_html1 = (TextView) findViewById(R.id.tv_html1);

tv_html1.setText(Html.fromHtml(csdnLink1));

//设置超链接可点击

tv_html1.setMovementMethod(LinkMovementMethod.getInstance());

String csdnLink2 = "http://blog.csdn.net/zhangjinhuang我的CSDN博客";

TextView  tv_html2 = (TextView) findViewById(R.id.tv_html2);

tv_html2.setText(Html.fromHtml(csdnLink2));

//设置超链接可点击

tv_html2.setMovementMethod(LinkMovementMethod.getInstance());

String csdnLink3 = getResources().getString(R.string.csdn);

TextView  tv_html3 = (TextView) findViewById(R.id.tv_html3);

tv_html3.setText(Html.fromHtml(csdnLink3));

//设置超链接可点击

tv_html3.setMovementMethod(LinkMovementMethod.getInstance());

运行程序后可以发现只有第一种的写法才能被Html的fromHtml方法格式化为超链接,如下所示:

4)借助SpannableString定制超链接的跳转

在上面我们通过Html类的fromHtml方法来格式化a标签中的内容从而实现文字的超链接,但是依旧还是跟web地址关联在一起,也就是说如果只是单纯的点击某个文字然后跳转到指定某个界面的话还是无法实现的,因此,在这里我将通过SpannableString类来实现类似文章开头谈到的 当点击“使用该软件,即表示您同意该软件的使用条款和隐私政策”,中的“使用条款”或“隐私政策”时可以跳转到相应的说明页面的功能。相信很多人都对SpannableString并不陌生,因为当想让一个字符串中的指定字符变大或者改变颜色再或者设置下划线等时都需要借助该类来实现,其实在一开始讲解“拦截超链接”时就已经使用了相应的功能了,但是在这里还是通过代码来着重的实现一下,首先对相应的TextView进行如下设置:

//借助SpannableString类实现超链接文字

tv_customMultiHyperLink = (TextView) findViewById(R.id.tv_customMultiHyperLink);

tv_customMultiHyperLink.setText(getClickableSpan());

//设置超链接可点击

tv_customMultiHyperLink.setMovementMethod(LinkMovementMethod.getInstance());

其中getClickableSpan方法的主要代码如下所示:

/**

* 获取可点击的SpannableString

* @return

*/

private SpannableString getClickableSpan() {

SpannableString spannableString = new SpannableString("使用该软件,即表示您同意该软件的使用条款和隐私政策");

//设置下划线文字

spannableString.setSpan(new UnderlineSpan(), 16, 20, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

//设置文字的单击事件

spannableString.setSpan(new ClickableSpan() {

@Override

public void onClick(View widget) {

Toast.makeText(MainActivity.this,"使用条款",Toast.LENGTH_SHORT).show();

}

}, 16, 20, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

//设置文字的前景色

spannableString.setSpan(new ForegroundColorSpan(Color.RED), 16, 20, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

//设置下划线文字

spannableString.setSpan(new UnderlineSpan(), 21, 25, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

//设置文字的单击事件

spannableString.setSpan(new ClickableSpan() {

@Override

public void onClick(View widget) {

Toast.makeText(MainActivity.this,"隐私政策",Toast.LENGTH_SHORT).show();

}

}, 21, 25, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

//设置文字的前景色

spannableString.setSpan(new ForegroundColorSpan(Color.RED), 21, 25, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

return spannableString;

}

运行程序,可以看到想要实现的文字超链接已经实现了,为了方便,这里当点击相应的文字时通过弹出相应的提示来说明,如下所示:

源码下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容