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 |
新插入字符的樣式設置 |
注意點:
- endIndex:字體樣式結束的Index,該Index對應的字符不使用樣式,比如有一個字符串為s = “abcd”,s.setSpan(span,0,2,flag),此時第0、1個字符ab使用了樣式span,endIndex對應的字符c不使用。
- 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
由以上的簡單示例我們可以看出,設置一個樣式的一般步驟是:
- 構造一個SpannableStringBuilder
- 構造一個Span,並設置在SpannableStringBuilder上
- 將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 ); |
簡單實現
- 以Listview為例,我們需要實現的是每一個Item和Adapter中的getView()函數。
- 布局文件
|
|
ImageView表示該Item的發布人,和富文本沒有關系,在Demo中直接寫死數據。
主要內容在TextView中
- 因為每一個TextView中可能有多個不同樣式的文本(#話題#,@其他用戶,文本內容等),所以先封裝一個文本類:
|
|
這個類封裝的是一個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); } |
到現在,數據准備工作已經全部完成,現在就要顯示這些數據了。
- getView()函數
getview函數中就可以通過content.getSpannableStringBuilder()函數獲取到正文內容,然后即可根據上面的預備知識對其進行設置不同的樣式和點擊事件。
設置完成后,需要調用
| 1 |
viewHolder.textview.setMovementMethod(LinkMovementMethod.getInstance()); |
使其響應點擊事件。
Demo效果:
Demo地址
