TextView显示丰富多彩的文字(一)——如何使用CharacterStyle格式化字符

TextView是用于文字的控件,一般可以在布局文件中设置text属性或者在代码中使用setText()方法。但如果想做到格式化文字,比如像网页中将其中的URL、手机号码等等显示不同颜色,并且设置点击事件,可以直接跳转到浏览器或者电话,该如何实现呢?本篇博客重点介绍这些应该如何实现。

使用autoLink属性

TextView的autoLink属性用于控制文本中的URL、Email地址等是否自动被发现并转化为可点击的链接。默认是”none”。可以设置none、web。email、phone、map以及all值,如果想设置多个值则使用”|”。
想了解更多的可以参考官方文档

使用autoLink显示URL和手机号码
 <TextView
        android:id="@+id/url_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:autoLink="all"
        android:text="@string/url_text" />

    <TextView
        android:layout_below="@id/url_tv"
        android:autoLink="all"
        android:text="@string/tel_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

以下为字符串:

<string name="url_text"><a href="http://www.baidu.com">百度</a>  http://www.baidu.com</string>
<string name="tel_text">tel:13007147721</string>

下图为运行效果,并且红色链接都是可以点击的,点击URL可以跳转到浏览器,点击手机号跳转到拨号器
使用autoLink显示URL和手机号码

使用textColorLink改变链接颜色

textColorLink可以以”@[+][package:]type:name”或”?[package:][type:]name”引用别的资源或主题属性,也可以直接设置颜色值。下面将链接改为蓝色,比较下效果:

  <TextView
        android:textColorLink="#0000FF"
        android:id="@+id/url_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:autoLink="all"
        android:text="@string/url_text" />

    <TextView
        android:layout_below="@id/url_tv"
        android:autoLink="all"
        android:text="@string/tel_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

效果图:
使用textColorLink改变默认链接颜色
可以看到上面的链接颜色变为绿色,而下面还是默认的粉红色。

局限

只能显示url、email、phone等链接,像上述文字中将”百度”二字设为超链接,并不能点击。

使用Html文字

Html类用于将HTML字符串处理成可显示的格式化文字,但是并不是所有的标签都支持。那么我们只需要编辑Html文本,再设置给TextView显示就行了。

百度"
            +" 13007147721"
            +" 
H 1标题

" +"

H 1标题

" +"

H 1标题

" +"

H 1标题
" +""; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); urlTextView = (TextView) findViewById(R.id.url_tv); urlTextView.setText(Html.fromHtml(html)); urlTextView.setMovementMethod(LinkMovementMethod.getInstance()); }" data-snippet-id="ext.ac9d45436122fd4ee52c5c412a58e262" data-snippet-saved="false" data-codota-status="done"> private String html = "<a href=\"http://www.baidu.com\">百度</a>" +" <a href=\"tel:13007147721\">13007147721</a>" +" <h1>H 1标题<h1>" +" <h2>H 1标题<h2>" +" <h3>H 1标题<h3>" +" <h4>H 1标题<h4>" +"<img src=\"smail.png\"><img>"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); urlTextView = (TextView) findViewById(R.id.url_tv); urlTextView.setText(Html.fromHtml(html)); urlTextView.setMovementMethod(LinkMovementMethod.getInstance()); }

上述代码将html字符串转成Html文本后再设置给TextView,如果需要链接可以被点击,需要调用setMovementMethod(LinkMovementMethod.getInstance())。效果如下:
image
“百度”和“13007147721”称为超链接,并且可以点击,h1-h6标题是可以用的,其他标签没有做验证。img标签可以使用,但是上面可以看到我们想使用本地的图片但是没有显式出来。那么需要怎么做呢?

如何使用Html显式图片

Html类的文档可以参考
Html类有两个接口,一个ImageGetter用于为标签获取图片,一个TagHandler用于处理那些Html类不能处理的标签。
ImageGetter只有一个接口:
image
其中参数source就是img标签中的src属性的值。下面的代码用于显示上面的html文字中的图片

       //显示图片
        Spanned html = Html.fromHtml(text, new Html.ImageGetter() {
            @Override
            public Drawable getDrawable(String s) {
                Drawable drawable = getResources().getDrawable(R.drawable.smail);
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
                return drawable;
            }
        }, new Html.TagHandler() {
            @Override
            public void handleTag(boolean b, String s, Editable editable, XMLReader xmlReader) {

            }
        });

        urlTextView.setText(html);

有一点需要记住的是获取到Drawable,一定要记得调用setBounds方法设置图片的边界,否则不能显示图片。
效果如下:
这里写图片描述
如果有很多需要显示的项目中的文件,那么可以每个src中的属性设为R.drawable.xxx,那么ImageGetter的getDrawable就可以根据资源Id去获取图片了。

Html显示图片的实质

查看API,可以看到Html的转换方法的返回都是Spanned类型,那么Spanned类型是什么呢?下一部分将详细介绍Spanned格式化文字。

局限

可以格式化很多文本样式,但是需要将文字添加html标签,并且有些标签不支持。

使用Spanned格式化文字

类结构

Spanned类结构图
CharSequence是一组可读的Char序列,提供了操作Char序列的接口。
Spanned可以在文本范围内添加标记。不是所有文本类都有可变的标记和文字。Spannable接口有添加或移除标记,而Editable有可变的文字。
Spannable用于添加标记和移除标记。并不是所有的Spannable都有可变的文字,Editable拥有可变的文字。
SpannableString文本不可变,但是可以添加标记和移除标记。
Editable是文本可以改变接口。
SpannableStringBuilder是Editable的实现类。

SpannableString用法

使用方法为新建一个SpannableString对象,并将文本传入,再调用setSpan()方法设置各个部分的样式,setSpan的声明如下:

void setSpan (Object what, int start, int end, int flags)

其中Object代表标记类型,可以使用的对象为CharacterStyle、ParagraphStyle的实现类,具体可以参考官方文档;start表示文本该标记作用的起始位置,end表示该标记作用的结束位置,flags参数可以参考Spanned类。我使用的都是SPAN_INCLUSIVE_EXCLUSIVE,该参数包含起始位置,不包含结束位置。下面为SpannableString的用法,几乎使用了所有的CharacterStyle。下面的代码分别设置了链接、前景、背景、删除线、下划线绝对尺寸、相对尺寸、图片、字体。

private String text = "百度"
            + "13007147721"
            + "前景"
            + "背景"
            + "删除线"
            + "下划线"
            + "图片"
            + "H1H2H3H4H5H6"
            + "H1H2H3H4H5H6"
            + "X3X2X1"
            + "字体"
            +"正常字体";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        urlTextView = (TextView) findViewById(R.id.url_tv);

        SpannableString spannableString = new SpannableString(text);
        //设置URL链接
        spannableString.setSpan(new URLSpan("http://www.baidu.com"), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置电话号码链接
        spannableString.setSpan(new URLSpan("tel:13007147721"), 2, 13, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置前景色
        spannableString.setSpan(new ForegroundColorSpan(Color.parseColor("#0000FF")), 13, 15, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置背景色
        spannableString.setSpan(new BackgroundColorSpan(Color.RED), 15, 17, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置删除线
        spannableString.setSpan(new StrikethroughSpan(), 17, 20, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //下划线
        spannableString.setSpan(new UnderlineSpan(), 20, 23, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置图片
        spannableString.setSpan(new ImageSpan(this, BitmapFactory.decodeResource(getResources(), R.drawable.smail)), 23, 25, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置文本大小

        //绝对尺寸
        spannableString.setSpan(new AbsoluteSizeSpan(100), 25, 27, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(90), 27, 29, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(80), 29, 31, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(70), 31, 33, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(60), 33, 35, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(50), 35, 37, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //相对尺寸
        spannableString.setSpan(new RelativeSizeSpan(6), 37, 39, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(5), 39, 41, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(4), 41, 43, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(3), 43, 45, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(2), 45, 47, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(1), 47, 49, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置X方向的改变
        spannableString.setSpan(new ScaleXSpan(3), 49, 51, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new ScaleXSpan(2), 51, 53, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new ScaleXSpan(1), 53, 55, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置字体
        spannableString.setSpan(new TypefaceSpan("monospace"), 55, 57, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);


        urlTextView.setText(spannableString);
        urlTextView.setMovementMethod(LinkMovementMethod.getInstance());

    }

效果图:
SpannableString用法

SpannableString添加多个格式化对象效果会如何?

对于同一部分文字,只添加同一类型的对象一个,效果会将每个格式化对象的效果累加。下面是测试代码,对于“百度”两词,设置URLSpan、ForegroundColorSpan、BackgroundColorSpan。

//SpannableString设置多个Span
        SpannableString spannableString = new SpannableString(text);

        //设置URLSpan
        URLSpan baiduSpan = new URLSpan("http://www.baidu.com");
        spannableString.setSpan(baiduSpan, 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        //设置前景
        spannableString.setSpan(new ForegroundColorSpan(Color.BLUE), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        //设置背景
        spannableString.setSpan(new BackgroundColorSpan(Color.RED), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        urlTextView.setText(spannableString);
        urlTextView.setMovementMethod(LinkMovementMethod.getInstance());

下图是效果:
设置多个Span
可以看到“百度”前景蓝色,背景红色,并且可以点击出百度首页。
对于同一部分文字,如果每个类型的对象都添加两个,那么效果会是怎样呢?先看代码和效果,其中代码中,分别设置了两个URLSpan、ForegroundColorSpan、BackgroundColorSpan。

//SpannableString设置多个Span
        SpannableString spannableString = new SpannableString(text);

        //设置URLSpan
        URLSpan baiduSpan = new URLSpan("http://www.baidu.com");
        URLSpan taobaoSpan = new URLSpan("http://www.baidu.com");
        spannableString.setSpan(baiduSpan, 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(taobaoSpan, 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        //设置前景
        spannableString.setSpan(new ForegroundColorSpan(Color.BLUE), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new ForegroundColorSpan(Color.WHITE), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        //设置背景
        spannableString.setSpan(new BackgroundColorSpan(Color.RED), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new BackgroundColorSpan(Color.BLUE), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        urlTextView.setText(spannableString);
        urlTextView.setMovementMethod(LinkMovementMethod.getInstance());

效果如下图:
相同的Span设置两个
可以看到文字颜色为白色,背景为黑色,可以看出是后设置的起了效果;但是点击后还是显示百度首页,而没有显示淘宝首页,说明URLSpan是前面那个起了效果。

SpannableString删除标记

SpannableString可以调用removeSpan来删除标记。下面的示例用于删除上述代码中的百度URLSpan,看下效果。

//SpannableString设置多个Span
        SpannableString spannableString = new SpannableString(text);

        //设置URLSpan
        URLSpan baiduSpan = new URLSpan("http://www.baidu.com");
        URLSpan taobaoSpan = new URLSpan("http://www.baidu.com");
        spannableString.setSpan(baiduSpan, 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(taobaoSpan, 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        //设置前景
        spannableString.setSpan(new ForegroundColorSpan(Color.BLUE), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new ForegroundColorSpan(Color.WHITE), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        //设置背景
        spannableString.setSpan(new BackgroundColorSpan(Color.RED), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new BackgroundColorSpan(Color.BLUE), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //删除百度URLSpan
        spannableString.removeSpan(baiduSpan);

        urlTextView.setText(spannableString);
        urlTextView.setMovementMethod(LinkMovementMethod.getInstance());

效果如上图,但是点击事件将出现淘宝首页。

SpannableString用法总结

SpannableString可以设置多种Span用来格式化文字,但是文字内容一旦设置后就不可以更改。如果对同一文字设置多个效果是可以的,但是有些效果是会覆盖之前的效果,比如ForgroundColorSpan等,而有的效果会使用第一个,比如URLSpan。

SpannableStringBuilder用法

SpannableStringBuilder用法和SpannableString用法类型。下面是将SpannableString替换为SpannableStringBuilder的代码和效果图:

  //SpannableStringBuilder基本用法
        SpannableStringBuilder spannableString = new SpannableStringBuilder(text);
        URLSpan baiduSpan = new URLSpan("http://www.baidu.com");
        //设置URL链接
        spannableString.setSpan(baiduSpan, 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置电话号码链接
        spannableString.setSpan(new URLSpan("tel:13007147721"), 2, 13, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置前景色
        spannableString.setSpan(new ForegroundColorSpan(Color.parseColor("#0000FF")), 13, 15, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置背景色
        spannableString.setSpan(new BackgroundColorSpan(Color.RED), 15, 17, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置删除线
        spannableString.setSpan(new StrikethroughSpan(), 17, 20, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //下划线
        spannableString.setSpan(new UnderlineSpan(), 20, 23, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置图片
        spannableString.setSpan(new ImageSpan(this, BitmapFactory.decodeResource(getResources(), R.drawable.smail)), 23, 25, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置文本大小

        //绝对尺寸
        spannableString.setSpan(new AbsoluteSizeSpan(100), 25, 27, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(90), 27, 29, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(80), 29, 31, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(70), 31, 33, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(60), 33, 35, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new AbsoluteSizeSpan(50), 35, 37, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //相对尺寸
        spannableString.setSpan(new RelativeSizeSpan(6), 37, 39, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(5), 39, 41, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(4), 41, 43, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(3), 43, 45, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(2), 45, 47, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new RelativeSizeSpan(1), 47, 49, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置X方向的改变
        spannableString.setSpan(new ScaleXSpan(3), 49, 51, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new ScaleXSpan(2), 51, 53, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(new ScaleXSpan(1), 53, 55, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        //设置字体
        spannableString.setSpan(new TypefaceSpan("monospace"), 55, 57, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        urlTextView.setText(spannableString);
        urlTextView.setMovementMethod(LinkMovementMethod.getInstance());

SpannableStringBuilder用法

SpannableStringBuilder的特别之处

SpannableStringBuilder设置完文本内容后,可以改变文本内容。下面的代码在上述代码之后再加入更改文字、插入文字的操作,并从新设置TextView的文字。

 //改变文字
        spannableString.replace(0, 2, "淘宝");
        spannableString.insert(2, "电话号码:", 0, 5);

        urlTextView.setText(spannableString);

效果图如下:
SpannableStringBuilder修改文本内容
可以看到显示内容已经变为了“淘宝电话号码:13007147721”,但是对于电话号码而言,链接文字多了“电话号码:”部分,而其余部分效果并没有收到影响。下面试着将这些改变。首先将已有的链接对象移除后,再添加新的Span对象。将淘宝的链接改为”http://www.taobao.com“;将电话号码多余的效果去除

 //更改效果
        spannableString.removeSpan(baiduSpan);
        spannableString.removeSpan(telSpan);
        spannableString.setSpan(new URLSpan("http://www.taobao.com"), 0, 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString.setSpan(telSpan, 7, 18, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        urlTextView.setText(spannableString);

效果如下:
SpannableStringBuilder修改效果
点击淘宝后会出现淘宝首页。

SpannableStringBuilder总结

用法和SpannableString用法相同,只不过增加了可以更改文本内容的接口。

总结

TextView显示丰富多彩的文字有很多中方式,如果只是一些简单的url、手机号,那么可以使用autoLink属性;如果文本本身就是html文档,那么可以使用Html类将文本转成Spanned后显示;如果想实现普通文本的格式化,那么应该使用SpannableString或SpannableStringBuilder。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
StaticLayout.Builder可以通过设置width和text来构建一个StaticLayout对象,其中width表示TextView的宽度,text表示要显示的文本。在构建StaticLayout对象之前,需要先计算TextView一行显示最大字符数的索引。 具体的计算方法如下: 1. 获取TextView的Paint对象,通过Paint的getTextBounds方法获取一个字符的宽度。 2. 计算TextView一行的宽度,可以通过TextView的getWidth方法获取。 3. 根据一行的宽度和一个字符的宽度计算出一行最多可以显示字符数。 下面是一个示例代码,用于计算TextView一行最多可以显示字符数的索引: ```java public int getMaxLineCharsIndex(TextView textView) { Paint paint = textView.getPaint(); int charWidth = (int) Math.ceil(paint.measureText("一")); int lineWidth = textView.getWidth() - textView.getPaddingLeft() - textView.getPaddingRight(); int maxChars = lineWidth / charWidth; Layout layout = textView.getLayout(); int lineStart = layout.getLineStart(0); int lineEnd = layout.getLineEnd(0); int lineChars = lineEnd - lineStart; if (lineChars >= maxChars) { return lineStart + maxChars; } else { int index = lineEnd; while (index < textView.getText().length() && textView.getLayout().getLineForOffset(index) == 0) { index++; } return index; } } ``` 这个方法中,首先获取TextView的Paint对象,通过Paint的measureText方法获取一个字符的宽度。然后计算TextView一行的宽度,减去左右padding的宽度得到一行的实际宽度。根据一个字符的宽度和一行的宽度计算出一行最多可以显示字符数。接着获取TextView的Layout对象,通过Layout的getLineStart和getLineEnd方法获取第一行的起始位置和结束位置。如果第一行的字符数小于等于最多可以显示字符数,返回第一行的结束位置作为索引。否则,从第一行的结束位置开始向后遍历,直到找到下一行的起始位置为止,返回这个位置作为索引。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值