Android中Textview超链接实现方式
textview中的超链接可以通过几种方式实现:
一、html.fromhtml方式
textview,本身就支持部分的html格式标签。这其中包括常用的字体大小颜色设置,文本链接等。使用起来也比较方便,只需要使用html类转换一下即可: textview.settext(html.fromhtml(str));
代码如下:
public class test10activity extends activity { textview textview ; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test10); textview = (textview) findviewbyid(r.id.text); string weblinktext = "<a href='https://souly.cn'> html超链接测试</a>" ; textview.settext(html.fromhtml(weblinktext)); } }
这时候点击超链接并没有跳转效果,需要加上
textview.setmovementmethod(linkmovementmethod.getinstance());
之后才会跳转。 这样点击之后会用默认浏览器打开url。
现在字体颜色和下划线是默认样式,修改字体颜色比较简单,可以直接用标签:
string weblinktext = "<font color='#333333'><a href='https://souly.cn' style='text-decoration:none; color:#0000ff'> html超链接测试</a>" ;
但是想不借助其他类去掉下划线就没有办法了。
还有一种更简单的方法就是在textview的xml布局中加入autolink自动识别,这样做操作最简单,但是也不能修改样式:
<textview android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textsize="20sp" android:text="souly.cn" android:autolink="email|phone|web" />
二、spannable方式
在xml中设置了android:autolink="email|phone|web"后,url文字下面会有一条下划线,我们可以看urlspan所继承的类clickablespan 类的源码,如下:
public abstract class clickablespan extends characterstyle implements updateappearance { / * performs the click action associated with this span. */ public abstract void onclick(view widget); / * makes the text underlined and in the link color. */ @override public void updatedrawstate(textpaint ds) { ds.setcolor(ds.linkcolor); ds.setunderlinetext(true); } }
它调用ds.setunderlinetext(true); 设置了下划线。我们再设置一个没有下划线的spannable对象就可以了。 我们重写一个类继承underlinespan,和clickablespan一样,都是characterstyle的子类。
使用方法如下:
public class test10activity extends activity { textview textview ; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test10); textview = (textview) findviewbyid(r.id.text); string weblinktext = "我的博客-->https://souly.cn" ; textview.settext(weblinktext); nounderlinespan mnounderlinespan = new nounderlinespan(); if (textview.gettext() instanceof spannable) { spannable s = (spannable) textview.gettext(); s.setspan(mnounderlinespan, 0, s.length(), spanned.span_mark_mark); } } public static class nounderlinespan extends underlinespan { public nounderlinespan() {} public nounderlinespan(parcel src) {} @override public void updatedrawstate(textpaint ds) { super.updatedrawstate(ds); ds.setunderlinetext(false); } } }
这样textview在界面上就不会有下划线了,但却保留着autolink的功能。 这个超链接是默认颜色,如果需要改变颜色可以在xml中设置android:textcolorlink="#1e84fb", 或者在java代码中设置tv.setlinktextcolor(color);
上面的例子是在autolink设置的情况下自动识别超链接的,如果不需要自动识别,而是自己手动设置需要跳转的网址, 可以使用以下方法,首先删除xml中的android:autolink="email|phone|web"
public class test10activity extends activity { textview textview ; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test10); textview = (textview) findviewbyid(r.id.text); string weblinktext = "我的博客" ; spannablestring text = new spannablestring(weblinktext); nounderlinespan mnounderlinespan = new nounderlinespan("https://souly.cn") ; text.setspan(mnounderlinespan,0,text.length(),spanned.span_exclusive_exclusive); textview.settext(text); textview.setmovementmethod(linkmovementmethod.getinstance()); } public static class nounderlinespan extends urlspan { public nounderlinespan(string url) { super(url); } @override public void updatedrawstate(textpaint ds) { super.updatedrawstate(ds); ds.setunderlinetext(false); } } }
这里的nounderlinespan继承了urlspan而不是underlinespan,urlspan是clickablespan的子类。
有的时候我们需要自定义超链接点击事件,例如弹一个toast,那么重写clickablespan:
public class test10activity extends activity { textview textview; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test10); textview = (textview) findviewbyid(r.id.text); string weblinktext = "我的博客"; spannablestring spstr = new spannablestring(weblinktext); clickablespan clickspan = new nolineclickspan(spstr.tostring()); //设置超链接 spstr.setspan(clickspan, 0, spstr.length(), spanned.span_inclusive_exclusive); textview.append(spstr); textview.setmovementmethod(linkmovementmethod.getinstance()); } private class nolineclickspan extends clickablespan { string text; public nolineclickspan(string text) { super(); this.text = text; } @override public void updatedrawstate(textpaint ds) { ds.setcolor(ds.linkcolor); ds.setunderlinetext(false); //去掉下划线 } @override public void onclick(view widget) { processhyperlinkclick(text); //点击超链接时调用 } } private void processhyperlinkclick(string text){ toast.maketext(this,text,toast.length_short).show(); } }
三、linkify.addlinks方式
除了使用默认的web等模式之外,我们还可以通过linkify类的addlinks方法来添加自定义模式。linkify.addlinks也是功能最 强大的一种方式。
在讲这种方式之前先科普一下intent filters的知识。
android中的activity,service,broadcast receivers都可以设置intent过滤器,它们可以有一个或多个intent过滤器。 每个过滤器描述组件的一种能力,即告知系统能够处理哪些隐式intent。个显式intent总是能够传递到它的目标组件,不管它包含什么; 不考虑过滤器。但是一个隐式intent,仅当它能够通过组件的过滤器之一才能够传递给它。
一个intent过滤器是一个intentfilter类的实例。因为android系统在启动一个组件之前必须知道它的能力,但是intent过滤器通常不在java 代码中设置,而是在应用程序的清单文件(androidmanifest.xml)中以元素设置。但有一个例外, 广播接收者的过滤器通过调用context.registerreceiver()动态地注册,它直接创建一个intentfilter对象。
一个过滤器有对应于intent对象的动作、数据、种类的字段。过滤器要检测隐式intent的所有这三个字段,其中任何一个失败, android系统都不会传递intent给组件。然而,因为一个组件可以有多个intent过滤器,一个intent通不过组件的过滤器检测, 其它的过滤器可能通过检测。
*动作检测
清单文件中的元素以子元素列出动作,例如:
<intent-filter> <action android:name="com.example.project.show_current" /> <action android:name="com.example.project.show_recent" /> <action android:name="com.example.project.show_pending" /> . . . </intent-filter>
像例子所展示,虽然一个intent对象仅是单个动作,但是一个过滤器可以列出不止一个。这个列表不能够为空,一个过滤器必须至少包含一个 子元素,否则它将阻塞所有的intents。
要通过检测,intent对象中指定的动作必须匹配过滤器的动作列表中的一个。如果对象或过滤器没有指定一个动作,结果将如下:
如果过滤器没有指定动作,没有一个intent将匹配,所有的intent将检测失败,即没有intent能够通过过滤器。 如果intent对象没有指定动作,将自动通过检查(只要过滤器至少有一个子元素,否则就是上面的情况了)
*种类检测
类似的,清单文件中的元素以子元素列出种类,例如:
<intent-filter> <category android:name="android.intent.category.default" /> <category android:name="android.intent.category.browsable" /> . . . </intent-filter>
对于一个intent要通过种类检测,intent对象中的每个种类必须匹配过滤器中的一个。即过滤器能够列出额外的种类,但是intent对象中的种类都必须能够在过滤器中找到, 只要一个种类在过滤器列表中没有,就算种类检测失败!
因此,原则上如果一个intent对象中没有种类(即种类字段为空)应该总是通过种类测试,而不管过滤器中有什么种类。但是有个例外, android对待所有传递给context.startactivity()的隐式intent好像它们至少包含"android.intent.category.default" (对应category_default常量)。因此,activity想要接收隐式intent必须要在intent过滤器中包含"android.intent.category.default"。
注意:"android.intent.action.main" 和 "android.intent.category.launcher"设置,它们分别标记活动开始新的任务和带到启动列表界面。 它们可以包含"android.intent.category.default"到种类列表,也可以不包含。
*数据检测
类似的,清单文件中的元素以子元素列出数据,例如:
<intent-filter> <data android:mimetype="video/mpeg" android:scheme="http" /> <data android:mimetype="audio/mpeg" android:scheme="http" /> . . . </intent-filter>
每个元素指定一个uri和数据类型(mime类型)。它有几个属性scheme、host、port、path、query、fragment对应于uri的每个部分: scheme://host:port/path?query#fragment或者scheme://userinfo@host:port/path?query#fragment。
scheme是content,host是"com.example.project",port是200,path是"folder/subfolder/etc"。host和port一起构成uri的凭据 (authority),如果host没有指定,port也被忽略。 这几个属性都是可选的,但它们之间并不都是完全独立的。要让authority有意义, scheme必须也要指定。要让path有意义,scheme和authority也都必须要指定。
当比较intent对象和过滤器的uri时,仅仅比较过滤器中出现的uri属性。例如,如果一个过滤器仅指定了scheme,所有有此scheme的uris都匹配过滤器; 如果一个过滤器指定了scheme和authority,但没有指定path,所有匹配scheme和authority的uris都通过检测,而不管它们的path; 如果几个属性都指定了,要都匹配才能算是匹配。然而,过滤器中的path可以包含通配符来要求匹配path中的一部分。
元素的type属性指定数据的mime类型。intent对象和过滤器都可以用""通配符匹配子类型字段,例如"text/","audio/*"表示任何子类型。
数据检测既要检测uri,也要检测数据类型。规则如下:
1.一个intent对象既不包含uri,也不包含数据类型:仅当过滤器也不指定任何uris和数据类型时,才不能通过检测;否则都能通过。 2.一个intent对象包含uri,但不包含数据类型:仅当过滤器也不指定数据类型,同时它们的uri匹配,才能通过检测。例如,mailto:和tel:都不指定实际数据。 3.一个intent对象包含数据类型,但不包含uri:仅当过滤也只包含数据类型且与intent相同,才通过检测。 4.一个intent对象既包含uri,也包含数据类型(或数据类型能够从uri推断出):数据类型部分,只有与过滤器中之一匹配才算通过;uri部分,它的uri要出现在过滤器中,或者它有content :或file: uri,又或者过滤器没有指定uri。换句话说,如果它的过滤器仅列出了数据类型,组件假定支持content:和file: 。 如果一个intent能够通过不止一个活动或服务的过滤器,用户可能会被问那个组件被激活。如果没有目标找到,会产生一个异常。
下面写一个例子来通过定义scheme接收特定uri开启activity
首先在androidmanifast.xml要给被指定scheme的activity下设置如下参数
<intent-filter> <category android:name="android.intent.category.default"></category> <action android:name="android.intent.action.view"></action> <data android:host="profile" android:scheme="mxn"/> </intent-filter>
这样即指定了接收uri的scheme为“mxn”,host为“profile” 且 action为view的intent。 可以利用如下intent调用activity: startactivity(new intent(intent.action_view, uri.parse("mxn://profile?uid=1"))); 传递一个参数uid=1.
在设置了filter的activity中接受参数:
public class test9activity extends activity { private string uid; private static final uri profile_uri = uri.parse("mxn://profile"); @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test8); extractuidfromuri(); } private void extractuidfromuri() { uri uri = getintent().getdata(); if (uri != null && profile_uri.getscheme().equals(uri.getscheme())) { uid = uri.getqueryparameter("uid"); log.d("=====", "uid from url: " + uid); } } }
下面借助linkify实现超链接,实现微博中@的超链接功能,布局文件如下
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" > <textview android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textsize="20sp" android:text="【谷歌母公司alphabet市值超苹果:全球第一】据外媒报道,@谷歌 母公司 @alphabet 通过公司分拆以及搜索广告业务的持续强势,股价继续攀升。美国时间周一,alphabet的公司市值已超过了 @苹果 , 成为全世界市值最大的公司. www.google.com" /> </relativelayout>
当需要使自定义模式和内置模式web,phone等一起被识别时,一定要先声明内置模式,然后再声明自定义模式,而且不能在xml中通过autolink属性声明, 否则自定义模式不起作用。因为在设置内置模式时,会先删除已有模式。 下面使用正则匹配带“@”的用户名,然后作为uid传递到下一个页面:
public class test10activity extends activity { textview textview ; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test10); textview = (textview) findviewbyid(r.id.text); pattern p = pattern.compile("@(\w+?)(?=\w|$)(.)"); linkify.addlinks(textview, p, "mxn://profile?uid="); } }
接收的时候跟上面一样:
public class test9activity extends activity { private string uid; private static final uri profile_uri = uri.parse("mxn://profile"); @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test8); extractuidfromuri(); } private void extractuidfromuri() { uri uri = getintent().getdata(); if (uri != null && profile_uri.getscheme().equals(uri.getscheme())) { uid = uri.getqueryparameter("uid"); log.d("=====", "uid from url: " + uid); } } }
效果如下:
其实在上面的图上可以看到,并没有被识别为链接(因为我们没有设置web模式)。需要注意的是,当需要使自定义 模式和内置模式web,phone等一起被识别时,一定要先声明内置模式,然后再声明自定义模式,而且不能在xml中通过autolink属性声明, 否则自定义模式不起作用。因为在设置内置模式时,会先删除已有模式。
将上面的java代码改为:
public class test10activity extends activity { textview textview ; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test10); textview = (textview) findviewbyid(r.id.text); pattern p = pattern.compile("@(\w+?)(?=\w|$)(.)"); linkify.addlinks(textview, linkify.web_urls); linkify.addlinks(textview, p, "mxn://profile?uid="); } }
现在就可以同时识别web以及自定义模式了。
同样的,这个超链接是默认颜色,如果需要改变颜色可以在xml中设置android:textcolorlink="#1e84fb",或者在java代码中 设置tv.setlinktextcolor(color);
这时超链接还是带下划线的,如果想去掉,还是使用上面的思路,重写urlspan,
public class test10activity extends activity { textview textview ; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test10); textview = (textview) findviewbyid(r.id.text); pattern mentionspattern = pattern.compile("@(\w+?)(?=\w|$)(.)"); string mentionsscheme = string.format("%s/?%s=", "mxn://profile", "uid"); linkify.addlinks(textview, linkify.web_urls); linkify.addlinks(textview, mentionspattern, mentionsscheme) ; stripunderlines(textview) ; } private class urlspannounderline extends urlspan { public urlspannounderline(string url) { super(url); } @override public void updatedrawstate(textpaint ds) { super.updatedrawstate(ds); ds.setunderlinetext(false); } } private void stripunderlines(textview textview) { spannable s = (spannable)textview.gettext(); urlspan[] spans = s.getspans(0, s.length(), urlspan.class); for (urlspan span: spans) { int start = s.getspanstart(span); int end = s.getspanend(span); s.removespan(span); span = new urlspannounderline(span.geturl()); s.setspan(span, start, end, 0); } textview.settext(s); } }
linkify还支持tranformfilter和matchfilter接口。它们提供一些对目标uri的额外控制和定义匹配字符串,它们的使用如下的框架代码所示: linkify.addlinks(mytextview, pattern, prefixwith, new mymatchfilter(), new mytransformfilter());
使用match filter
在你定义的matchfilter中实现acceptmatch方法,来为regex样式匹配添加额外的条件。当一个潜在的匹配发现时,acceptmatch被触发, 匹配的开始点和结束点(包括被查找的整个文本)以参数的形式传入。接下来的代码显示了一个matchfilter的实现,它取消任何之前已“.”结尾的匹配。
class mymatchfilter implements matchfilter { public boolean acceptmatch(charsequence s, int start, int end) { return s.charat(end-1) != '.'; } }
使用transform filter
transform filter为格式化文本字符串提供了更大的*度,允许你修改由链接文本自动生成的隐式uri。 减少链接文本和目标uri的耦合能更加*地决定如何显示数据字符串给用户。
使用transform filter,在你定义的transformfilter中实现transformurl方法。当linkify找到正确的匹配后,它会调用transformurl, 传入使用的regex样式和它创建的默认uri字符串。你可以修改匹配的字符串,然后返回一个适合给其它android应用程序的uri。 利用transform filter的修改功能可以实现点击用户名,传递的是用户id的功能,在transformurl中返回你需要传递的内容
下面的transformfilter实现将匹配的文本转换成小写的uri:
class mytransformfilter implements transformfilter { public string transformurl(matcher match, string url) { return url.tolowercase(); } }
现在我们加上match filter和transform filter,上面的代码可以修改成如下的样子:
public class test10activity extends activity { textview textview ; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_test10); textview = (textview) findviewbyid(r.id.text); pattern mentionspattern = pattern.compile("@(\w+?)(?=\w|$)(.)"); string mentionsscheme = string.format("%s/?%s=", "mxn://profile", "uid"); linkify.addlinks(textview, linkify.web_urls); // linkify.addlinks(textview, mentionspattern, mentionsscheme) ; linkify.addlinks(textview, mentionspattern, mentionsscheme, new linkify.matchfilter() { @override public boolean acceptmatch(charsequence s, int start, int end) { return s.charat(end-1) != '.'; } }, new linkify.transformfilter() { @override public string transformurl(matcher match, string url) { return url.tolowercase(); } }); stripunderlines(textview) ; } private class urlspannounderline extends urlspan { public urlspannounderline(string url) { super(url); } @override public void updatedrawstate(textpaint ds) { super.updatedrawstate(ds); ds.setunderlinetext(false); } } private void stripunderlines(textview textview) { spannable s = (spannable)textview.gettext(); urlspan[] spans = s.getspans(0, s.length(), urlspan.class); for (urlspan span: spans) { int start = s.getspanstart(span); int end = s.getspanend(span); s.removespan(span); span = new urlspannounderline(span.geturl()); s.setspan(span, start, end, 0); } textview.settext(s); } }
以上就是本文的全部内容,希望对大家的学习有所帮助。
推荐阅读
-
Android中Textview超链接实现方式
-
Android TextView跑马灯效果实现方法
-
安卓Android 7.1.1 shortcut实现桌面图标快捷方式跳转,类似IOS 3d touch
-
[原创] 如何在android中实现shake的动作检测 - part 1 博客分类: Android AndroidHTML5咨询浏览器Flash
-
[原创] 如何在android中实现shake的动作检测 - part 1 博客分类: Android AndroidHTML5咨询浏览器Flash
-
[原创] 如何在android中实现swipe的手势功能及页面拖动动画 博客分类: Android AndroidSymbianC#C++C
-
android中UIColletionView瀑布流布局实现思路以及封装的实现
-
Android中控件GridView实现设置行列分割线的方法示例
-
Android使用selector修改TextView中字体颜色和背景色的方法
-
Android viewpager中动态添加view并实现伪无限循环的方法