在开发应用过程中经常会遇到显示一些不同的字体风格的信息犹如默认的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有:
/**
* 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中定义的常量,常用的有:
这里理解起来就好像数学中定义区间,开区间还是闭区间一样的。还有许多其他的Flag,可以参考这里。这里要重点说明下关于参数0,有很多时候,如果设置了上述的参数,那么Span会从start应用到Text结尾,而不是在start和end二者之间,这个时候就需要使用Flag 0。
另外,也可以对通过TextView.setAutoLink(int)设置其Linkify属性,其用处在于,TextView会自动检查其内容,会识别出phone number, web address or email address,并标识为超链接,可点击,点击后便跳转到相应的应用,如Dialer,Browser或Email。Linkify有几个常用选项,更多的请参考文档:
个人认为软件开发中最常见的问题不是某个技巧怎么使用的问题,而是何时该使用何技巧的问题,因为实现同一个目标可能有N种不同的方法,就要权衡利弊,选择最合适的一个,正如常言所云,没有最好的,只有最适合的。如前面所讨论的,要想用不同的字体展现不同的信息可能的解法,除了用Style Span外还可以用多个TextView。那么就需要总结下什么时候该使用StyleSpan,什么时候该使用多个TextView:
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: