前段時間在開發群里看到有人問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;
}
運行程序,可以看到想要實現的文字超鏈接已經實現了,為了方便,這里當點擊相應的文字時通過彈出相應的提示來說明,如下所示: