在安卓開發(fā)中,我們經(jīng)常會(huì)遇到給一個(gè)TextView中的部分文字設(shè)置不同的樣式的情況,這之后我們不可能總是用好幾個(gè)TextView來拼出這樣一個(gè)效果,此時(shí),我們就需要用到'''SpannableString'''以及和各種'''Span'''的用法了,下面就讓我們看一下Android給我們提供了多少種Span,以及各自的用法:
SpannableString
首先來介紹SpannableString,官方解釋是:這是內(nèi)容不可變但可以附加和分離標(biāo)記對(duì)象的文本的類。所謂的標(biāo)記對(duì)象就是我們接下來要介紹的Span。對(duì)于他的用法:
SpannableString string = new SpannableString(str);
//設(shè)置TextView,可以被當(dāng)做字符串設(shè)置給TextView
textView.setText(string);
各種Span
- BackgroundColorSpan:給部分文字設(shè)置背景顏色
- ForegroundColorSpan:給部分文字設(shè)置前景色
- ClickableSpan:設(shè)置點(diǎn)擊事件
- URLSpan:設(shè)置鏈接,相當(dāng)于Html的標(biāo)簽
- MaskFilterSpan:文字的裝飾效果。分為兩種:BlurMaskFilter(模糊效果) 和 EmbossMaskFilter (浮雕效果)
- AbsoluteSizeSpan:設(shè)置字體大小
- RelativeSizeSpan:設(shè)置字體的相對(duì)大小
- ImageSpan:設(shè)置圖片
- ScaleXSpan:橫向壓縮
- SubscriptSpan:設(shè)置腳注
- SuperscriptSpan:上標(biāo),相當(dāng)于數(shù)學(xué)中的平方樣式
- TextAppearanceSpan:使用style來定義文本樣式
- TypefaceSpan:設(shè)置字體
- RasterizerSpan:設(shè)置光柵字樣
- StrikethroughSpan:刪除線,相當(dāng)于購(gòu)物網(wǎng)站上的劃掉的原價(jià)
- UnderlineSpan:下劃線。
以上都是Android源碼提供的效果,下面來大概寫一下用法,其實(shí)用法基本上都是一樣的:
String str = "大家好,下面來演示一下Span的用法";
SpannableString string = new SpannableString(str);
BackgroundColorSpan span = new BackgroundColorSpan(getResources().getColor(R.color.red));
//最后一句就是給String設(shè)置效果的
//第2個(gè)參數(shù)表示從哪個(gè)字符開始設(shè)置背景色
//第3個(gè)參數(shù)表示到哪個(gè)字符結(jié)束背景色的設(shè)置
//最后一個(gè)參數(shù)表示是否包含所設(shè)置的第二、三參數(shù)所代表的位置
//本例中設(shè)置的是前后都不包括
//相同的還有:
//Spanned.SPAN_INCLUSIVE_EXCLUSIVE(前面包括,后面不包括)、
//Spanned.SPAN_EXCLUSIVE_INCLUSIVE(前面不包括,后面包括)、
//Spanned.SPAN_INCLUSIVE_INCLUSIVE(前后都包括)
string.setSpan(span,0,3,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
其實(shí)看著android的文檔,或者上網(wǎng)上搜這些東西會(huì)有很多,大家可以跟著敲一遍代碼,運(yùn)行一下就能清楚地知道每個(gè)Span所代表的含義了。下面進(jìn)入主題:
公司最近要做一個(gè)練習(xí)的需求,就相當(dāng)于學(xué)生在App中做試卷似得,于是就有了下面的這個(gè)設(shè)計(jì)圖:

大概就是這種樣式的圖片,一段文字里面會(huì)嵌入很多的空格,并且還有背景色,還有文字,而且點(diǎn)擊的時(shí)候還有各種效果,為了實(shí)現(xiàn)這個(gè)功能,確實(shí)看了很多Span的樣式。
剛開始的時(shí)候準(zhǔn)備用BackgroundColorSpan+ClickableSpan來實(shí)現(xiàn)這樣的功能,后來發(fā)現(xiàn),BackgroundColorSpan不能完美的實(shí)現(xiàn)這個(gè)功能,于是在網(wǎng)上找了BackgroundImageSpan來實(shí)現(xiàn)的。
public class BackgroundImageSpan extends ReplacementSpan implements ParcelableSpan {
private static final String TAG = "BackgroundImageSpan";
private Drawable mDrawable;
private int mImageId;
private int mWidth = -1;
/**
* new BackgroundImageSpan use resource id and Drawable
* @param id the drawable resource id
* @param drawable Drawable related to the id
* @internal
* @hide
*/
public BackgroundImageSpan(int id, Drawable drawable) {
mImageId = id;
mDrawable = drawable;
}
/**
* @hide
* @internal
*/
public BackgroundImageSpan(Parcel src) {
mImageId = src.readInt();
}
/**
* @hide
* @internal
*/
public void draw(Canvas canvas, int width,float x,int top, int y, int bottom, Paint paint) {
if (mDrawable == null) {//if no backgroundImage just don't do any draw
Log.e(TAG, "mDrawable is null draw()");
return;
}
Drawable drawable = mDrawable;
canvas.save();
canvas.translate(x, top); // translate to the left top point
mDrawable.setBounds(0, 0, width, (bottom - top));
drawable.draw(canvas);
canvas.restore();
}
@Override
public void updateDrawState(TextPaint tp) {
}
/**
* return a special type identifier for this span class
* @hide
* @internal
* @Override
*/
public int getSpanTypeId() {
return 0;
}
/**
* describe the kinds of special objects contained in this Parcelable's marshalled representation
* @hide
* @internal
* @Override
*/
public int describeContents() {
return 0;
}
/**
* flatten this object in to a Parcel
* @hide
* @internal
* @Override
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mImageId);
}
/**
* @hide
* @internal
*/
public void convertToDrawable(Context context) {
if (mDrawable == null) {
mDrawable = context.getResources().getDrawable(mImageId);
}
}
/**
* convert a style text that contain BackgroundImageSpan, Parcek only pass resource id,
* after Parcel, we need to convert resource id to Drawable.
* @hide
* @internal
*/
public static void convert(CharSequence text , Context context) {
if (!(text instanceof SpannableStringBuilder)) {
return;
}
SpannableStringBuilder builder = (SpannableStringBuilder)text;
BackgroundImageSpan[] spans = builder.getSpans(0, text.length(), BackgroundImageSpan.class);
if (spans == null || spans.length == 0) {
return;
}
for (int i = 0; i < spans.length; i++) {
spans[i].convertToDrawable(context);
}
}
/**
* draw the span
* @hide
* @internal
* @Override
*/
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
// draw image
draw(canvas, mWidth,x,top, y, bottom, paint);
// draw text
// the paint is already updated
canvas.drawText(text,start,end, x,y, paint);
}
/**
* get size of the span
* @hide
* @internal
* @Override
*/
public int getSize(Paint paint, CharSequence text, int start, int end,
FontMetricsInt fm) {
float size = paint.measureText(text, start, end);
if (fm != null && paint != null) {
paint.getFontMetricsInt(fm);
}
mWidth = (int)size;
return mWidth;
}
}
以上這段代碼是谷歌的Git上提供的,最后會(huì)放上鏈接,接著說一下我實(shí)現(xiàn)上圖的過程,上代碼比較快:
//前兩行使用這則表達(dá)式來解析出一大段文本里面需要特殊設(shè)置的文本內(nèi)容
Matcher textMatcher;
SpannableString str = new SpannableString(contentStr);
textMatcher = PATTERN_TEXT_SRC.matcher(content);
while (textMatcher.find()) {
//然后開始循環(huán)所有的文本來進(jìn)行設(shè)置。
final String blank = textMatcher.group().trim();
int index = content.indexOf(blank);
ClickableSpan clickableSpan = new MyClickableSpan();
BackgroundImageSpan backgroundColorSpan;
//此處之所以有這個(gè)判斷,完全是為了用來設(shè)置點(diǎn)擊時(shí)候的樣式,而currentStart和currentEnd也是點(diǎn)擊的文字的第一字符的position和最后一個(gè)字符的position
if (currentStart == index && currentEnd == index + blank.length()) {
backgroundColorSpan = new BackgroundImageSpan(R.drawable.bg_answer_wrong, getResources().getDrawable(R.drawable.bg_answer_wrong));
} else {
backgroundColorSpan = new BackgroundImageSpan(R.drawable.bg_noanswer_unselected, getResources().getDrawable(R.drawable.bg_noanswer_unselected));
}
str.setSpan(backgroundColorSpan, index, index + blank.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
str.setSpan(clickableSpan, index, index + blank.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);}
//如果要設(shè)置點(diǎn)擊事件,那么久一低昂要設(shè)置下面這一行,否則點(diǎn)擊事件不起作用
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
mTextView.setText(str);
//此處是定義的ClickableSpan
private class MyClickableSpan extends ClickableSpan {
@Override
public void onClick(View widget) {
//這里面的代碼主要是用來獲取所點(diǎn)擊的那一部分的text,也可以根據(jù)下面的方法來獲取具體點(diǎn)擊的那部分文字內(nèi)容
TextView tv = (TextView) widget;
Spanned s = (Spanned) tv.getText();
int start = s.getSpanStart(this);
int end = s.getSpanEnd(this);
currentStart = start;
currentEnd = end;
updateBlank();
Toast.makeText(MainActivity.this, tv.getText(), Toast.LENGTH_SHORT).show(); }}
基本上就是這些內(nèi)容了,其實(shí)做起來不太難,主要是各種狀態(tài)判斷起來比較崩潰,下面一些就是收集個(gè)各種需要的效果:
基本上就是以上這么多了,謝謝這些鏈接的文章作者所提供的優(yōu)質(zhì)內(nèi)容,權(quán)當(dāng)記錄了吧。