前段時間在開發群裡看到有人問android的TextView該如何自定義超連結的跳轉,如:有字元串“使用該軟體,即表示您同意該軟體的使用條款和隐私政策”,現希望當點選“使用條款”或“隐私政策”時可以跳轉到相應的說明頁面,我還記得當時有一大堆人在讨論然後提了一大堆的方法,比如:用多個TextView組合,給相應的TextView添加點選事件、給TextView添加autoLink屬性、通過給相應的内容添加<a></a>标簽、借助Spannable類、Linkfy類等等,當然最後提問者采用哪種方法我就不得而知了,我呢也剛好最近幾天比較有空,然後翻了下Api文檔,于是通過幾個晚上的總結形成了今天的這篇部落格,内容比較多,還望大家能夠耐心點,相信大家看完肯定對TextView的各種超連結的跳轉及實作水到渠來。
1)autoLink屬性
對TextView屬性比較熟悉的開發者應該都知道TextView有一個叫做autoLink的屬性可以将符合指定格式的文本轉換為可單擊的超連結形式,在幫助文檔中也可以發現Android給我們提供了如下幾種格式:
1、none:表示不進行任何比對,預設;
2、Web:表示比對Web Url,如:内容中的http://www.baidu.com會成為可單擊跳轉的超連結;
3、Email:表示比對郵件位址:如:郵件位址為[email protected]會成為可單擊的超連結;
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;
-
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]);
-
}
-
ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
-
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);
-
}
-
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"/>
-
<!--必須和代碼中設定scheme一樣-->
-
<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 = "<a href=\"http://blog.csdn.net/zhangjinhuang\">我的CSDN部落格</a>";
-
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方法的主要代碼如下所示:
-
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;
-
}
運作程式,可以看到想要實作的文字超連結已經實作了,為了友善,這裡當點選相應的文字時通過彈出相應的提示來說明,如下所示:
源碼下載下傳