一个简单的Android富文本TextView实现

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 24.0px Helvetica; color: #555555 }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 22.0px Helvetica; color: #555555 }
p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica; color: #555555 }
p.p4 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica; color: #555555; min-height: 17.0px }
p.p5 { margin: 0.0px 0.0px 0.0px 0.0px; font: 20.0px Helvetica; color: #555555 }
p.p6 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Helvetica; color: #555555 }
p.p7 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 14.0px Helvetica; color: #555555 }
p.p8 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: right; font: 13.0px "PT Mono"; color: #666666 }
p.p9 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #999999 }
p.p10 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #cccccc }
p.p11 { margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Helvetica; color: #555555 }
p.p12 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #cccccc; min-height: 15.0px }
p.p13 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #f99157 }
p.p14 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #66cccc }
p.p21 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #cc99cc }
li.li7 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 14.0px Helvetica; color: #555555 }
li.li8 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: right; font: 13.0px "PT Mono"; color: #666666 }
li.li9 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #999999 }
li.li10 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #cccccc }
li.li15 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: right; font: 13.0px "PT Mono"; color: #666666 }
li.li16 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 14.0px Helvetica; color: #cccccc }
li.li17 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #99cc99 }
li.li18 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #f2777a }
li.li19 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 14.0px Helvetica; color: #cccccc }
li.li20 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; font: 14.0px Helvetica; color: #555555 }
li.li21 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px "PT Mono"; color: #cc99cc }
span.s1 { }
span.s2 { background-color: #2d2d2d }
span.s3 { color: #cc99cc; background-color: #2d2d2d }
span.s4 { color: #99cc99; background-color: #2d2d2d }
span.s5 { color: #f99157; background-color: #2d2d2d }
span.s6 { color: #cccccc; background-color: #2d2d2d }
span.s7 { color: #6599cc; background-color: #2d2d2d }
span.s8 { color: #66cccc; background-color: #2d2d2d }
span.s9 { color: #999999; background-color: #2d2d2d }
span.s10 { background-color: #2d2d2d }
span.s11 { background-color: #2d2d2d }
span.s12 { color: #f2777a; background-color: #2d2d2d }
span.s13 { color: #f2777a; background-color: #2d2d2d }
span.s14 { color: #cccccc; background-color: #2d2d2d }
span.s15 { background-color: #2d2d2d }
table.t1 { margin: 20.0px 0.0px 20.0px 0.0px; border-style: solid; border-width: 1.0px 1.0px 1.0px 1.0px; border-color: #dddddd #dddddd #dddddd #dddddd; border-collapse: collapse }
table.t2 { border-collapse: collapse }
td.td1 { width: 254.0px; border-style: solid; border-width: 0.0px 1.0px 3.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 10.0px 8.0px }
td.td2 { width: 411.0px; border-style: solid; border-width: 0.0px 1.0px 3.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 10.0px 8.0px }
td.td3 { width: 254.0px; background-color: #f9f9f9; border-style: solid; border-width: 0.0px 1.0px 1.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 8.0px 8.0px }
td.td4 { width: 411.0px; background-color: #f9f9f9; border-style: solid; border-width: 0.0px 1.0px 1.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 8.0px 8.0px }
td.td5 { width: 254.0px; border-style: solid; border-width: 0.0px 1.0px 1.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 8.0px 8.0px }
td.td6 { width: 411.0px; border-style: solid; border-width: 0.0px 1.0px 1.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 8.0px 8.0px }
td.td7 { width: 260.0px; border-style: solid; border-width: 0.0px 1.0px 3.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 10.0px 8.0px }
td.td8 { width: 405.0px; border-style: solid; border-width: 0.0px 1.0px 3.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 10.0px 8.0px }
td.td9 { width: 260.0px; background-color: #f9f9f9; border-style: solid; border-width: 0.0px 1.0px 1.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 8.0px 8.0px }
td.td10 { width: 405.0px; background-color: #f9f9f9; border-style: solid; border-width: 0.0px 1.0px 1.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 8.0px 8.0px }
td.td11 { width: 260.0px; border-style: solid; border-width: 0.0px 1.0px 1.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 8.0px 8.0px }
td.td12 { width: 405.0px; border-style: solid; border-width: 0.0px 1.0px 1.0px 0.0px; border-color: transparent #eeeeee #dddddd transparent; padding: 8.0px 8.0px 8.0px 8.0px }
td.td13 { width: 29.0px; background-color: #f9f9f9 }
td.td14 { width: 743.0px; background-color: #f9f9f9 }
td.td15 { width: 720.0px; background-color: #f9f9f9 }
td.td16 { width: 681.0px; background-color: #f9f9f9 }
td.td17 { width: 697.0px; background-color: #f9f9f9 }
td.td18 { width: 728.0px; background-color: #f9f9f9 }
td.td19 { width: 37.0px; background-color: #f9f9f9 }
td.td20 { width: 673.0px; background-color: #f9f9f9 }
td.td21 { width: 603.0px; background-color: #f9f9f9 }
td.td22 { width: 478.0px; background-color: #f9f9f9 }
td.td23 { width: 463.0px; background-color: #f9f9f9 }
td.td24 { width: 634.0px; background-color: #f9f9f9 }
td.td25 { width: 564.0px; background-color: #f9f9f9 }
ol.ol1 { list-style-type: decimal }

Android客户端富文本

现阶段问题

在Android客户端展现一个普通数据非常的方便,直接调用textview.setText()方法,但是当TextView比较复杂时(例如存在@用户,##话题,部分字符样式、网址链接等),普通的TextView就无法完成需求,需要自己封装一个富文本TextView。

Demo

Coding 冒泡示例

##

分析

1.一个TextView中不同类别的信息,需要由不同样式的显示,一般的用法textview.setTextColor(color)会将所有的文字变成同一种颜色,这显然是不符合要求的。为了实现这一个需求,我们将会用到SpannableStringBuilder这个类。

预备知识

SpannableStringBuilder:

基本概念

SpannableStringBuilder 和 StringBuilder类似,都可以存储字符串,不同的是SpannableStringBuilder有一个setSpan()函数,可以给存储的String添加不同的样式。如加下划线、背景色、字体颜色、字体大小等。

另外需要注意的是,当SpannableStringBuilder中存储了一个有样式的String,当把spannableStringBuilder展示在TextView、EditTextView中时,能显示这些样式;当展示在canvas上时,因为Canvas不支持SpannableStringBuilder的额外信息,所以会退化成一个普通的String,不显示样式信息。

setSpan()函数

void setSpan(Object what,int startIndex,int endIndex,int flag);

说明:

参数

说明

Object what

设置Span样式

int startIndex

样式开始的Index

int endIndex

样式结束的Index

int flag

新插入字符的样式设置

注意点:

  1. endIndex:字体样式结束的Index,该Index对应的字符不使用样式,比如有一个字符串为s = “abcd”,s.setSpan(span,0,2,flag),此时第0、1个字符ab使用了样式span,endIndex对应的字符c不使用。
  2. flag:取值如下

取值

说明

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE

前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式

Spannable.SPAN_EXCLUSIVE_INCLUSIVE

前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式

Spannable.SPAN_INCLUSIVE_EXCLUSIVE

前面包括,后面不包括。

Spannable.SPAN_INCLUSIVE_INCLUSIVE

前后都包括

简单示例

1

2

3

4

5

6

//设置字体颜色

textview1 = (TextView) findViewById(R.id.text1);

SpannableStringBuilder spannableStringBuilder1 = new SpannableStringBuilder("Android");

ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.BLUE);

spannableStringBuilder1.setSpan(foregroundColorSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview1.setText(spannableStringBuilder1);

效果:

多种Span

由以上的简单示例我们可以看出,设置一个样式的一般步骤是:

  1. 构造一个SpannableStringBuilder
  2. 构造一个Span,并设置在SpannableStringBuilder上
  3. 将SpannableStringBuilder绑定在TextView上展示

设置字体颜色

这个已经在简单示例中展示

设置字体背景颜色

1

2

3

4

5

6

//设置字体背景颜色

textview2 = (TextView) findViewById(R.id.text2);

SpannableStringBuilder spannableStringBuilder2 = new SpannableStringBuilder("Android");

BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(Color.RED);

spannableStringBuilder2.setSpan(backgroundColorSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview2.setText(spannableStringBuilder2);

效果:

设置字体大小

1

2

3

4

5

6

//设置字体大小

textview3 = (TextView) findViewById(R.id.text3);

SpannableStringBuilder spannableStringBuilder3 = new SpannableStringBuilder("Android");

AbsoluteSizeSpan absoluteSizeSpan = new AbsoluteSizeSpan(30);

spannableStringBuilder3.setSpan(absoluteSizeSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview3.setText(spannableStringBuilder3);

效果:

设置字体

1

2

3

4

5

6

//设置字体(加粗斜体)

textview4 = (TextView) findViewById(R.id.text4);

SpannableStringBuilder spannableStringBuilder4 = new SpannableStringBuilder("Android");

StyleSpan styleSpan = new StyleSpan(Typeface.BOLD_ITALIC);

spannableStringBuilder4.setSpan(styleSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview4.setText(spannableStringBuilder4);

效果:

设置下划线

1

2

3

4

5

6

//设置下划线

textview5 = (TextView) findViewById(R.id.text5);

SpannableStringBuilder spannableStringBuilder5 = new SpannableStringBuilder("Android");

UnderlineSpan underlineSpan = new UnderlineSpan();

spannableStringBuilder5.setSpan(underlineSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview5.setText(spannableStringBuilder5);

效果:

设置下划线

1

2

3

4

5

6

//设置删除线

textview6 = (TextView) findViewById(R.id.text6);

SpannableStringBuilder spannableStringBuilder6 = new SpannableStringBuilder("Android");

StrikethroughSpan strikethroughSpan = new StrikethroughSpan();

spannableStringBuilder6.setSpan(strikethroughSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview6.setText(spannableStringBuilder6);

效果:

设置多种样式

除了设置一个样式以外,SpannableStringBuilder还支持设置多个样式。

1

2

3

4

5

6

7

8

9

//设置多种样式

textview7 = (TextView) findViewById(R.id.text7);

SpannableStringBuilder spannableStringBuilder7 = new SpannableStringBuilder("Android");

spannableStringBuilder7.setSpan(foregroundColorSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

spannableStringBuilder7.setSpan(backgroundColorSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

spannableStringBuilder7.setSpan(underlineSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

spannableStringBuilder7.setSpan(absoluteSizeSpan, 3, 6, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

spannableStringBuilder7.setSpan(strikethroughSpan, 3, 6, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview7.setText(spannableStringBuilder7);

效果:

点击事件

实例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder("Android");

spannableStringBuilder.setSpan(

new ClickableSpan() {

@Override

public void onClick(View widget) {

//do something

}

@Override

public void updateDrawState(TextPaint ds) {

//设置一些样式

//ds.setUnderlineText(false);

//ds.setColor(color);

}

}, startIndex, endIndex,

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE

);

简单实现

    1. 以Listview为例,我们需要实现的是每一个Item和Adapter中的getView()函数。
    2. 布局文件
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  • android:layout_width="match_parent" android:layout_height="wrap_content">
  • <ImageView
  • android:layout_width="wrap_content"
  • android:layout_height="wrap_content"
  • android:src="@mipmap/ic_launcher"
  • android:layout_marginTop="16dp"
  • android:layout_marginLeft="16dp"
  • android:id="@+id/userhead"/>
  • <TextView
  • android:layout_width="match_parent"
  • android:layout_height="wrap_content"
  • android:layout_marginTop="16dp"
  • android:layout_marginLeft="8dp"
  • android:layout_marginRight="16dp"
  • android:layout_toRightOf="@id/userhead"
  • android:id="@+id/content"/>
  • </RelativeLayout>

ImageView表示该Item的发布人,和富文本没有关系,在Demo中直接写死数据。

主要内容在TextView中

    1. 因为每一个TextView中可能有多个不同样式的文本(#话题#,@其他用户,文本内容等),所以先封装一个文本类:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • public class TextObject {
  • private int mStartIndex;//样式的开始字符
  • private int mEndIndex;//结束字符
  • //private SpannableStringBuilder mSpannableStringBuilder;
  • private String content;//文本内容
  • private int mOptionType;//操作符
  • private int color;//字体颜色
  • private boolean isUnderline;//是否有下划线
  • //...其他需要的属性可继承TextObject自行添加
  • }

这个类封装的是一个Item中的某一个文本样式,所以我们还需要封装一个Item中的文本类:

1

2

3

4

5

6

public class Content {

private List<TextObject> mList;

private SpannableStringBuilder mSpannableStringBuilder;

}

这个类中有一个List成员变量,存放着该Item中所有的样式文本。

对于成员变量SpannableStringBuilder,有一个init函数,用于根据List中的TextObject拼接StringBuilder

1

2

3

4

5

6

7

8

9

//拼接SpannableStringBuilder

public void initStringBuilder(){

SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();

for (int i = 0;i<mList.size();i++){

spannableStringBuilder.append(mList.get(i).getContent());

}

setmSpannableStringBuilder(spannableStringBuilder);

}

到现在,数据准备工作已经全部完成,现在就要显示这些数据了。

  1. getView()函数

getview函数中就可以通过content.getSpannableStringBuilder()函数获取到正文内容,然后即可根据上面的预备知识对其进行设置不同的样式和点击事件。

设置完成后,需要调用

1

viewHolder.textview.setMovementMethod(LinkMovementMethod.getInstance());

使其响应点击事件。

Demo效果:

Demo地址

Github项目地址

上一篇:前缀树实现思路和例题


下一篇:Cookie-parser