背景介紹
在開發應用過程中經常會遇到顯示一些不同的字體風格的信息猶如默認的lockscreen上面的時間和充電信息。對于類似的情況,可能第一反應就是用不同的多個textview來實現,對于每個textview設置不同的字體風格以滿足需求。
這里推薦的做法是使用android.text.*;和android.text.style.*;下面的組件來實現richtext:也即在同一個textview中設置不同的字體風格。對于某些應用,比如文本編輯,記事本,彩信,短信等地方,還必須使用這些組件才能達到想到的顯示效果。
主要的基本工具類有android.text.spanned; android.text.spannablestring; android.text.spannablestringbuilder;使用這些類來代替常規string。spannablestring和spannablestringbuilder可以用來設置不同的span,這些span便是用于實現rich text,比如粗體,斜體,前景色,背景色,字體大小,字體風格等等,android.text.style.*中定義了很多的span類型可供使用。
這是相關的api的class general hierarchy:
因為spannable等最終都實現了charsequence接口,所以可以直接把spannablestring和spannablestringbuilder通過textview.settext()設置給textview。
使用方法
當要顯示rich text信息的時候,可以使用創建一個spannablestring或spannablestringbuilder,它們的區別在于spannablestring像一個string一樣,構造對象的時候傳入一個string,之后再無法更改string的內容,也無法拼接多個spannablestring;而spannablestringbuilder則更像是stringbuilder,它可以通過其append()方法來拼接多個string:
復制代碼 代碼如下:
spannablestring word = new spannablestring("the quick fox jumps over the lazy dog");
spannablestringbuilder multiword = new spannablestringbuilder();
multiword.append("the quick fox");
multiword.append("jumps over");
multiword.append("the lazy dog");
創建完spannable對象后,就可以為它們設置span來實現想要的rich text了,常見的span有:
•absolutesizespan(int size) ---- 設置字體大小,參數是絕對數值,相當于word中的字體大小
•relativesizespan(float proportion) ---- 設置字體大小,參數是相對于默認字體大小的倍數,比如默認字體大小是x, 那么設置后的字體大小就是x*proportion,這個用起來比較靈活,proportion>1就是放大(zoom in), proportion<1就是縮小(zoom out)
•scalexspan(float proportion) ---- 縮放字體,與上面的類似,默認為1,設置后就是原來的乘以proportion,大于1時放大(zoon in),小于時縮小(zoom out)
•backgroundcolorspan(int color) ----背景著色,參數是顏色數值,可以直接使用android.graphics.color里面定義的常量,或是用color.rgb(int, int, int)
•foregroundcolorspan(int color) ----前景著色,也就是字的著色,參數與背景著色一致
•typefacespan(string family) ----字體,參數是字體的名字比如“sans", "sans-serif"等
•stylespan(typeface style) -----字體風格,比如粗體,斜體,參數是android.graphics.typeface里面定義的常量,如typeface.bold,typeface.italic等等。
•strikethroughspan----如果設置了此風格,會有一條線從中間穿過所有的字,就像被劃掉一樣
對于這些sytle span在使用的時候通常只傳上面所說明的構造參數即可,不需要設置其他的屬性,如果需要的話,也可以對它們設置其他的屬性,詳情可以參見<>。
spannablestring和spannablestringbuilder都有一個設置上述span的方法:
復制代碼 代碼如下:
/**
* set the style span to spannable, such as spannablestring or spannablestringbuilder
* @param what --- the style span, such as stylespan
* @param start --- the starting index of characters to which the style span to apply
* @param end --- the ending index of characters to which the style span to apply
* @param flags --- the flag specified to control
*/
setspan(object what, int start, int end, int flags);
其中參數what是要設置的style span,start和end則是標識string中span的起始位置,而 flags是用于控制行為的,通常設置為0或spanned中定義的常量,常用的有:
•spanned.span_exclusive_exclusive --- 不包含兩端start和end所在的端點
•spanned.span_exclusive_inclusive --- 不包含端start,但包含end所在的端點
•spanned.span_inclusive_exclusive --- 包含兩端start,但不包含end所在的端點
•spanned.span_inclusive_inclusive--- 包含兩端start和end所在的端點
這里理解起來就好像數學中定義區間,開區間還是閉區間一樣的。還有許多其他的flag,可以參考<這里>。這里要重點說明下關于參數0,有很多時候,如果設置了上述的參數,那么span會從start應用到text結尾,而不是在start和end二者之間,這個時候就需要使用flag 0。
linkify
另外,也可以對通過textview.setautolink(int)設置其linkify屬性,其用處在于,textview會自動檢查其內容,會識別出phone number, web address or email address,并標識為超鏈接,可點擊,點擊后便跳轉到相應的應用,如dialer,browser或email。linkify有幾個常用選項,更多的請參考<文檔>
•linkify.email_address -- 僅識別出textview中的email在址,標識為超鏈接,點擊后會跳到email,發送郵件給此地址
•linkify.phone_numbers -- 僅識別出textview中的電話號碼,標識為超鏈接,點擊后會跳到dialer,call這個號碼
•linkify.web_urls-- 僅識別出textview中的網址,標識為超鏈接,點擊后會跳到browser打開此url
•linkify.all -- 這個選項是識別出所有系統所支持的特殊uri,然后做相應的操作
權衡選擇
個人認為軟件開發中最常見的問題不是某個技巧怎么使用的問題,而是何時該使用何技巧的問題,因為實現同一個目標可能有n種不同的方法,就要權衡利弊,選擇最合適的一個,正如常言所云,沒有最好的,只有最適合的。如前面所討論的,要想用不同的字體展現不同的信息可能的解法,除了用style span外還可以用多個textview。那么就需要總結下什么時候該使用stylespan,什么時候該使用多個textview:
1.如果顯示的是多個不同類別的信息,就應該使用多個textview,這樣也方便控制和改變各自的信息,例子就是默認lockscreen上面的日期和充電信息,因為它們所承載不同的信息,所以應該使用多個textview來分別呈現。
2.如果顯示的是同一類信息,或者同一個信息,那么應該使用stylespan。比如,短信息中,要把聯系人的相關信息突出顯示;或是想要highlight某些信息等。
3.如果要實現rich text,沒辦法,只能使用style span。
4.如果要實現某些特效,也可以考慮使用stylespan。設置不同的字體風格只是style span的初級應用,如果深入研究,可以發現很多奇妙的功效。
實例
復制代碼 代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<linearlayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<scrollview
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<linearlayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<textview
android:id="@+id/text_view_font_1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<textview
android:id="@+id/text_view_font_2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<textview
android:id="@+id/text_view_font_3"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<textview
android:id="@+id/text_view_font_4"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<textview
android:id="@+id/text_view_font_5"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</linearlayout>
</scrollview>
</linearlayout>
source code:
復制代碼 代碼如下:
package com.android.effective;
import java.util.regex.matcher;
import java.util.regex.pattern;
import android.app.activity;
import android.graphics.color;
import android.graphics.typeface;
import android.os.bundle;
import android.text.spannable;
import android.text.spannablestring;
import android.text.spannablestringbuilder;
import android.text.style.absolutesizespan;
import android.text.style.backgroundcolorspan;
import android.text.style.foregroundcolorspan;
import android.text.style.quotespan;
import android.text.style.relativesizespan;
import android.text.style.scalexspan;
import android.text.style.strikethroughspan;
import android.text.style.stylespan;
import android.text.style.typefacespan;
import android.text.style.urlspan;
import android.text.util.linkify;
import android.widget.textview;
public class textviewfontactivity extends activity {
@override
public void oncreate(bundle bundle) {
super.oncreate(bundle);
setcontentview(r.layout.textview_font_1);
// demonstration of basic spannablestring and spans usage
final textview textwithstring = (textview) findviewbyid(r.id.text_view_font_1);
string w = "the quick fox jumps over the lazy dog";
int start = w.indexof('q');
int end = w.indexof('k') + 1;
spannable word = new spannablestring(w);
word.setspan(new absolutesizespan(22), start, end,
spannable.span_inclusive_inclusive);
word.setspan(new stylespan(typeface.bold), start, end,
spannable.span_inclusive_inclusive);
word.setspan(new backgroundcolorspan(color.red), start, end,
spannable.span_inclusive_inclusive);
textwithstring.settext(word);
// demonstration of basic spannablestringbuilder and spans usage
final textview textwithbuilder = (textview) findviewbyid(r.id.text_view_font_2);
spannablestringbuilder word2 = new spannablestringbuilder();
final string one = "freedom is nothing but a chance to be better!";
final string two = "the quick fox jumps over the lazy dog!";
final string three = "the tree of liberty must be refreshed from time to time with " +
"the blood of patroits and tyrants!";
word2.append(one);
start = 0;
end = one.length();
word2.setspan(new stylespan(typeface.bold_italic), start, end, spannable.span_exclusive_exclusive);
word2.append(two);
start = end;
end += two.length();
word2.setspan(new foregroundcolorspan(color.cyan), start, end,
spannable.span_exclusive_exclusive);
word2.append(three);
start = end;
end += three.length();
word2.setspan(new urlspan(three), start, end, spannable.span_exclusive_exclusive);
textwithbuilder.settext(word2);
// troubleshooting when using spannablestringbuilder
final textview texttroubles = (textview) findviewbyid(r.id.text_view_font_3);
spannablestringbuilder word3 = new spannablestringbuilder();
start = 0;
end = one.length();
// caution: must first append or set text to spannablestringbuilder or spannablestring
// then set the spans to them, otherwise, indexoutofboundexception is thrown when setting spans
word3.append(one);
// for absolutesizespan, the flag must be set to 0, otherwise, it will apply this span to until end of text
word3.setspan(new absolutesizespan(22), start, end, 0);//spannable.span_inclusive_inclusive);
// for backgroundcolorspanspan, the flag must be set to 0, otherwise, it will apply this span to end of text
word3.setspan(new backgroundcolorspan(color.dkgray), start, end, 0); //spannable.span_inclusive_inclusive);
word3.append(two);
start = end;
end += two.length();
word3.setspan(new typefacespan("sans-serif"), start, end,
spannable.span_inclusive_inclusive);
// todo: sometimes, flag must be set to 0, otherwise it will apply the span to until end of text
// which might has nothing to do with specific span type.
word3.setspan(new stylespan(typeface.bold_italic), start, end, 0);//spannable.span_inclusive_inclusive);
word3.setspan(new scalexspan(0.618f), start, end, spannable.span_inclusive_inclusive);
word3.setspan(new strikethroughspan(), start, end, 0);//spannable.span_inclusive_inclusive);
word3.setspan(new foregroundcolorspan(color.cyan), start, end, spannable.span_inclusive_inclusive);
word3.setspan(new quotespan(), start, end, 0); //spannable.span_inclusive_inclusive);
word3.append(three);
start = end;
end += three.length();
word3.setspan(new relativesizespan((float) math.e), start, end, spannable.span_inclusive_inclusive);
word3.setspan(new foregroundcolorspan(color.blue), start, end, spannable.span_inclusive_inclusive);
texttroubles.settext(word3);
// highlight some patterns
final string four = "the gap between the best software engineering " +
"practice and the average practice is very wide¡ªperhaps wider " +
" than in any other engineering discipline. a tool that disseminates " +
"good practice would be important.¡ªfred brooks";
final pattern highlight = pattern.compile("the");
final textview texthighlight = (textview) findviewbyid(r.id.text_view_font_4);
spannablestring word4 = new spannablestring(four);
matcher m = highlight.matcher(word4.tostring());
while (m.find()) {
word4.setspan(new stylespan(typeface.bold_italic), m.start(), m.end(),
spannable.span_inclusive_inclusive);
word4.setspan(new foregroundcolorspan(color.red), m.start(), m.end(),
spannable.span_inclusive_inclusive);
word4.setspan(new strikethroughspan(), m.start(), m.end(),
spannable.span_inclusive_inclusive);
}
texthighlight.settext(word4);
// set numbers, urls and e-mail address to be clickable with textview#setautolinkmask
final textview textclickable = (textview) findviewbyid(r.id.text_view_font_5);
final string contact = "email: [email protected]\n" +
"phone: +47-24885883\n" +
"fax: +47-24885883\n" +
"http: www.microsoft.com/mvp.asp";
// set the attribute first, then set the text. otherwise, it won't work
textclickable.setautolinkmask(linkify.all); // or set 'android:autolink' in layout xml
textclickable.settext(contact);
}
}
the results: