一個簡單的Android富文本TextView實現


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項目地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM