Android实战技巧:用TextView实现Rich Text---在同一个TextView中设置不同的字体风格

背景介绍

在开发应用过程中经常会遇到显示一些不同的字体风格的信息犹如默认的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:

[java]  view plain copy print ?
  1. SpannableString word = new SpannableString("The quick fox jumps over the lazy dog");  
  2.   
  3. SpannableStringBuilder multiWord = new SpannableStringBuilder();  
  4. multiWord.append("The Quick Fox");  
  5. multiWord.append("jumps over");  
  6. 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的方法:
[java]  view plain copy print ?
  1. /** 
  2.  * Set the style span to Spannable, such as SpannableString or SpannableStringBuilder 
  3.  * @param what --- the style span, such as StyleSpan 
  4.  * @param start --- the starting index of characters to which the style span to apply 
  5.  * @param end --- the ending index of characters to which the style span to apply 
  6.  * @param flags --- the flag specified to control 
  7.  */  
  8. 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的初级应用,如果深入研究,可以发现很多奇妙的功效。

实例

[html]  view plain copy print ?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout  
  3.   xmlns:android="http://schemas.android.com/apk/res/android"  
  4.   android:layout_width="wrap_content"  
  5.   android:layout_height="wrap_content"  
  6.   android:orientation="vertical">  
  7.   <ScrollView  
  8.     android:layout_width="fill_parent"  
  9.     android:layout_height="wrap_content">  
  10.         <LinearLayout  
  11.             android:layout_width="fill_parent"  
  12.             android:layout_height="wrap_content"  
  13.             android:orientation="vertical">  
  14.               <TextView  
  15.                 android:id="@+id/text_view_font_1"  
  16.                 android:layout_width="fill_parent"  
  17.                 android:layout_height="wrap_content"  
  18.                 />  
  19.               <TextView  
  20.                 android:id="@+id/text_view_font_2"  
  21.                 android:layout_width="fill_parent"  
  22.                 android:layout_height="wrap_content"  
  23.                 />  
  24.               <TextView  
  25.                 android:id="@+id/text_view_font_3"  
  26.                 android:layout_width="fill_parent"  
  27.                 android:layout_height="wrap_content"  
  28.                 />  
  29.               <TextView  
  30.                 android:id="@+id/text_view_font_4"  
  31.                 android:layout_width="fill_parent"  
  32.                 android:layout_height="wrap_content"  
  33.                 />  
  34.               <TextView  
  35.                 android:id="@+id/text_view_font_5"  
  36.                 android:layout_width="fill_parent"  
  37.                 android:layout_height="wrap_content"  
  38.                 />  
  39.        </LinearLayout>  
  40.     </ScrollView>  
  41. </LinearLayout>  
Source code:
[java]  view plain copy print ?
  1. package com.android.effective;  
  2.   
  3.   
  4.   
  5. import java.util.regex.Matcher;  
  6.   
  7. import java.util.regex.Pattern;  
  8.   
  9.   
  10.   
  11. import android.app.Activity;  
  12.   
  13. import android.graphics.Color;  
  14.   
  15. import android.graphics.Typeface;  
  16.   
  17. import android.os.Bundle;  
  18.   
  19. import android.text.Spannable;  
  20.   
  21. import android.text.SpannableString;  
  22.   
  23. import android.text.SpannableStringBuilder;  
  24.   
  25. import android.text.style.AbsoluteSizeSpan;  
  26.   
  27. import android.text.style.BackgroundColorSpan;  
  28.   
  29. import android.text.style.ForegroundColorSpan;  
  30.   
  31. import android.text.style.QuoteSpan;  
  32.   
  33. import android.text.style.RelativeSizeSpan;  
  34.   
  35. import android.text.style.ScaleXSpan;  
  36.   
  37. import android.text.style.StrikethroughSpan;  
  38.   
  39. import android.text.style.StyleSpan;  
  40.   
  41. import android.text.style.TypefaceSpan;  
  42.   
  43. import android.text.style.URLSpan;  
  44.   
  45. import android.text.util.Linkify;  
  46.   
  47. import android.widget.TextView;  
  48.   
  49.   
  50.   
  51. public class TextViewFontActivity extends Activity {  
  52.   
  53.     @Override  
  54.   
  55.     public void onCreate(Bundle bundle) {  
  56.   
  57.         super.onCreate(bundle);  
  58.   
  59.         setContentView(R.layout.textview_font_1);  
  60.   
  61.           
  62.   
  63.         // Demonstration of basic SpannableString and spans usage  
  64.   
  65.         final TextView textWithString = (TextView) findViewById(R.id.text_view_font_1);  
  66.   
  67.         String w = "The quick fox jumps over the lazy dog";  
  68.   
  69.         int start = w.indexOf('q');  
  70.   
  71.         int end = w.indexOf('k') + 1;  
  72.   
  73.         Spannable word = new SpannableString(w);  
  74.   
  75.         word.setSpan(new AbsoluteSizeSpan(22), start, end,   
  76.   
  77.                 Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  78.   
  79.         word.setSpan(new StyleSpan(Typeface.BOLD), start, end,   
  80.   
  81.                 Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  82.   
  83.         word.setSpan(new BackgroundColorSpan(Color.RED), start, end,   
  84.   
  85.                 Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  86.   
  87.         textWithString.setText(word);  
  88.   
  89.           
  90.   
  91.         // Demonstration of basic SpannableStringBuilder and spans usage  
  92.   
  93.         final TextView textWithBuilder = (TextView) findViewById(R.id.text_view_font_2);  
  94.   
  95.         SpannableStringBuilder word2 = new SpannableStringBuilder();  
  96.   
  97.         final String one = "Freedom is nothing but a chance to be better!";  
  98.   
  99.         final String two = "The quick fox jumps over the lazy dog!";  
  100.   
  101.         final String three = "The tree of liberty must be refreshed from time to time with " +  
  102.   
  103.                 "the blood of patroits and tyrants!";  
  104.   
  105.         word2.append(one);  
  106.   
  107.         start = 0;  
  108.   
  109.         end = one.length();  
  110.   
  111.         word2.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
  112.   
  113.   
  114.   
  115.         word2.append(two);  
  116.   
  117.         start = end;  
  118.   
  119.         end += two.length();  
  120.   
  121.         word2.setSpan(new ForegroundColorSpan(Color.CYAN), start, end,   
  122.   
  123.                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
  124.   
  125.         word2.append(three);  
  126.   
  127.         start = end;  
  128.   
  129.         end += three.length();  
  130.   
  131.         word2.setSpan(new URLSpan(three), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
  132.   
  133.         textWithBuilder.setText(word2);  
  134.   
  135.           
  136.   
  137.         // Troubleshooting when using SpannableStringBuilder  
  138.   
  139.         final TextView textTroubles = (TextView) findViewById(R.id.text_view_font_3);  
  140.   
  141.         SpannableStringBuilder word3 = new SpannableStringBuilder();  
  142.   
  143.         start = 0;  
  144.   
  145.         end = one.length();  
  146.   
  147.         // Caution: must first append or set text to SpannableStringBuilder or SpannableString  
  148.   
  149.         // then set the spans to them, otherwise, IndexOutOfBoundException is thrown when setting spans  
  150.   
  151.         word3.append(one);  
  152.   
  153.         // For AbsoluteSizeSpan, the flag must be set to 0, otherwise, it will apply this span to until end of text  
  154.   
  155.         word3.setSpan(new AbsoluteSizeSpan(22), start, end, 0);//Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  156.   
  157.         // For BackgroundColorSpanSpan, the flag must be set to 0, otherwise, it will apply this span to end of text  
  158.   
  159.         word3.setSpan(new BackgroundColorSpan(Color.DKGRAY), start, end, 0); //Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  160.   
  161.         word3.append(two);  
  162.   
  163.         start = end;  
  164.   
  165.         end += two.length();  
  166.   
  167.         word3.setSpan(new TypefaceSpan("sans-serif"), start, end,   
  168.   
  169.                 Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  170.   
  171.         // TODO: sometimes, flag must be set to 0, otherwise it will apply the span to until end of text  
  172.   
  173.         // which MIGHT has nothing to do with specific span type.  
  174.   
  175.         word3.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, 0);//Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  176.   
  177.         word3.setSpan(new ScaleXSpan(0.618f), start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  178.   
  179.         word3.setSpan(new StrikethroughSpan(), start, end, 0);//Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  180.   
  181.         word3.setSpan(new ForegroundColorSpan(Color.CYAN), start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  182.   
  183.         word3.setSpan(new QuoteSpan(), start, end, 0); //Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  184.   
  185.         word3.append(three);  
  186.   
  187.         start = end;  
  188.   
  189.         end += three.length();  
  190.   
  191.         word3.setSpan(new RelativeSizeSpan((float) Math.E), start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  192.   
  193.         word3.setSpan(new ForegroundColorSpan(Color.BLUE), start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  194.   
  195.         textTroubles.setText(word3);  
  196.   
  197.           
  198.   
  199.         // Highlight some patterns  
  200.   
  201.         final String four = "The gap between the best software engineering " +  
  202.   
  203.                 "practice and the average practice is very wide¡ªperhaps wider " +  
  204.   
  205.                 " than in any other engineering discipline. A tool that disseminates " +  
  206.   
  207.                 "good practice would be important.¡ªFred Brooks";  
  208.   
  209.         final Pattern highlight = Pattern.compile("the");  
  210.   
  211.         final TextView textHighlight = (TextView) findViewById(R.id.text_view_font_4);  
  212.   
  213.         SpannableString word4 = new SpannableString(four);  
  214.   
  215.         Matcher m = highlight.matcher(word4.toString());  
  216.   
  217.         while (m.find()) {  
  218.   
  219.             word4.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), m.start(), m.end(),   
  220.   
  221.                     Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  222.   
  223.             word4.setSpan(new ForegroundColorSpan(Color.RED), m.start(), m.end(),   
  224.   
  225.                     Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  226.   
  227.             word4.setSpan(new StrikethroughSpan(), m.start(), m.end(),   
  228.   
  229.                     Spannable.SPAN_INCLUSIVE_INCLUSIVE);  
  230.   
  231.         }  
  232.   
  233.         textHighlight.setText(word4);  
  234.   
  235.           
  236.   
  237.         // Set numbers, URLs and E-mail address to be clickable with TextView#setAutoLinkMask  
  238.   
  239.         final TextView textClickable = (TextView) findViewById(R.id.text_view_font_5);    
  240.   
  241.         final String contact = "Email: mvp@microsoft.com\n" +  
  242.   
  243.                 "Phone: +47-24885883\n" +  
  244.   
  245.                 "Fax: +47-24885883\n" +  
  246.   
  247.                 "HTTP: www.microsoft.com/mvp.asp";  
  248.   
  249.         // Set the attribute first, then set the text. Otherwise, it won't work  
  250.   
  251.         textClickable.setAutoLinkMask(Linkify.ALL); // or set 'android:autoLink' in layout xml  
  252.   
  253.         textClickable.setText(contact);  
  254.     }  
  255. }  
The results:


Android Ticks: display text vertically

TextView of Android is a text label to display text. But it can show text only horizontally by default, left to right or right to left. There are some chances that we would like to show text vertically, top to bottom or bottom to top, for layout of modern smart phone in particular. In landscape layout, the height is not enough to show all information, we have to arrange them horizontally because width is enough to hold them. But for some label, like date and time label, it would be nicer to display them in only one line, which is impossible to do in landscape. The text is one line though, it grows horizontally to push following layouts out of screen, just like this:

But the ideal layout would be like this: one line label shows itself vertical so in horizontal, it takes up only its height and leaves much free space to put other views:

Fortunately, this is totally possible in Android only with a little extra efforts. With graphics library we can draw text in any way we want: rotate in some direction, following a path and so forth. An example TextAlign in APIDemos just shows us the way to draw text following a customized Path. We can inherite TextView and override its onDraw(), then do what ever we like in onDraw. In here, of course, to draw the text in a Path that vertically up from bottom. Here is the code:

[java]  view plain copy print ?
  1. /* 
  2.  * for the attributes of TextView, some works some not. 
  3.  * 1. setTextsize works 
  4.  * 2. setBackgroundColor works 
  5.  * 3. setTextColor also works 
  6.  * You can adjust the size of TextView by margins and the drawing area by paddings(only paddingTop and paddingBottom works). 
  7.  * For other attributes, like lines or maxLines, or ellipsize are not supported currently. To support them, you should get  
  8.  * the attributes value before drawText and apply them. 
  9.  */  
  10. public class VerticalTextView extends TextView {  
  11.     private static final String TAG = "VerticalTextView";  
  12.   
  13.     public VerticalTextView(Context context, AttributeSet attrs, int defStyle) {  
  14.         super(context, attrs, defStyle);  
  15.     }  
  16.   
  17.     public VerticalTextView(Context context, AttributeSet attrs) {  
  18.         super(context, attrs);  
  19.     }  
  20.   
  21.     public VerticalTextView(Context context) {  
  22.         super(context);  
  23.     }  
  24.       
  25.     @Override  
  26.     protected void onDraw(Canvas canvas) {  
  27.         final ColorStateList csl = getTextColors();  
  28.         final int color = csl.getDefaultColor();  
  29.         final int paddingBottom = getPaddingBottom();  
  30.         final int paddingTop = getPaddingTop();  
  31.         final int viewWidth = getWidth();  
  32.         final int viewHeight = getHeight();  
  33.         final TextPaint paint = getPaint();  
  34.         paint.setColor(color);  
  35.         final float bottom = viewWidth * 9.0f / 11.0f;  
  36.         Path p = new Path();  
  37.         p.moveTo(bottom, viewHeight - paddingBottom - paddingTop);  
  38.         p.lineTo(bottom, paddingTop);  
  39.         canvas.drawTextOnPath(getText().toString(), p, 00, paint);  
  40.     }  
  41. }  

This VerticalTextView works much like a TextView, it supports most usual attributes like color, size, background, margin and padding(only paddingTop and paddingBottom). For others, I did not implement but you can get the attributes in onDraw and apply them. Cost efforts though, it is feasible. Here is an example to use it:

[html]  view plain copy print ?
  1. <com.hilton.todo.VerticalTextView  
  2.     android:id="@+id/header"  
  3.     android:layout_height="fill_parent"  
  4.     android:layout_width="30dip"  
  5.     android:textColor="#ffff00"  
  6.     android:textSize="24sp"  
  7.     android:background="#a50909"  
  8.     android:text="Hello, world"  
  9.     android:paddingBottom="40dip" />  

There might be some other ways to achieve this like rotation, which I tried first but not with success. This example really works for me and if you ever find another way please share to us.

From this example, we learn that Android is so flexible that we can achieve almost anything we want and the only cost is your efforts.


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现英文两端对齐,可以使用Android的JustifiedTextView控件。JustifiedTextView是一个自定义TextView,它可以将文本对齐到视图的左右边缘。这个控件使用了一个开源的库android-textview-align库,你可以在项目引入该库,也可以将其源代码拷贝到你的项目。 下面是使用JustifiedTextView控件实现英文两端对齐的步骤: 1. 在项目添加android-textview-align库。 2. 在布局文件使用JustifiedTextView控件。 3. 在代码设置文本和对齐方式。 示例代码如下: ```xml <com.codesgood.views.JustifiedTextView android:id="@+id/justified_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Hello world, this is a sample text to demonstrate the usage of JustifiedTextView." /> ``` ```java JustifiedTextView justifiedText = (JustifiedTextView) findViewById(R.id.justified_text); justifiedText.setText("Hello world, this is a sample text to demonstrate the usage of JustifiedTextView."); justifiedText.setAlignment(Paint.Align.LEFT); ``` 在上面的代码,我们将对齐方式设置为左对齐,这将使文本左右两端对齐。你也可以将对齐方式设置央对齐、右对齐等。 需要注意的是,JustifiedTextView控件不支持在文本使用HTML标记,如果你需要在文本使用HTML标记,可以使用Android的Html.fromHtml()方法来解析HTML标记,然后将解析后的文本设置到JustifiedTextView控件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值