您的位置:首页 > 移动开发 > Android开发

TextView 中SpannableString应用,让你灵活玩转TextView

2017-08-28 15:06 323 查看
设置TextView的背景颜色

给文本设置点击事件

设置文本颜色

设置删除线效果

设置下划线效果

在TextView中设置图片

方法一

方法二

方法三

基于X轴的缩放

设置字体粗体样式

上下标的使用

设置超链接

TextView算是Android开发中最最常用的控件了,有的时候,我们要给一个TextView中的显示的文字设置不同的样式或者响应事件,比如同一个TextView中,有的字是红色,有的字是蓝色,有的字点击之后有响应事件,有的点击之后没有响应事件,甚至我们想在TextView中显示一个数学公式等等,那么对于形形色色的需求我们有没有解决方案呢?当然有,一种是使用HTML来解决,另一种就是使用SpannableString,HTML比较简单,本文主要介绍后者。SpannableString可以用来显示复合文本,我们可以通过SpannableString给文本设置各种各样的样式,下面我们就来看看SpannableString的一些常见用法。

1.设置TextView的背景颜色

要想在TextView中设置不同颜色、大小、图文混排的效果,必须通过SpannableString的setSpan()方法来实现。

//SpannableString.java
public void setSpan(Object what, int start, int end, int flags) {
super.setSpan(what, start, end, flags);
}


其中:

what传入各种Span类型的实例;

start和end标记要替代的文字内容的范围

flags是用来标识在Span范围内的文本前后输入新的字符时是否把它们也应用这个效果,可以传入Spanned.SPAN_EXCLUSIVE_EXCLUSIVE、Spanned.SPAN_INCLUSIVE_EXCLUSIVE、Spanned.SPAN_EXCLUSIVE_INCLUSIVE、Spanned.SPAN_INCLUSIVE_INCLUSIVE几个参数,INCLUSIVE表示应用该效果EXCLUSIVE表示不应用该效果,如Spanned.SPAN_INCLUSIVE_EXCLUSIVE表示对前面的文字应用该效果,而对后面的文字不应用该效果

给TextView设置背景颜色这本身是很简单的,在XML文件中直接加入background属性即可,下面我们来看看怎么样通过SpannableString来给TextView设置背景颜色:

tv1 = (TextView) this.findViewById(R.id.tv1);
SpannableString ss1 = new SpannableString("设置背景颜色");
ss1.setSpan(new BackgroundColorSpan(Color.parseColor("#FFD700")), 0,
ss1.length(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
tv1.setText(ss1);


首先拿到一个TextView,然后构造一个SpannableString,构造方法中传入的参数就是我们要显示的文字,然后就是一个最终要的方法,通过setSpan来设置背景色,第一个参数是我们要设置的背景颜色,第二第三个参数是我们要给哪一段的文字设置背景(该段文字的startIndex和endIndex),最后一个参数有四个值:

/**
* Non-0-length spans of type SPAN_INCLUSIVE_EXCLUSIVE expand
* to include text inserted at their starting point but not at their
* ending point.  When 0-length, they behave like marks.
*/
public static final int SPAN_INCLUSIVE_EXCLUSIVE = SPAN_MARK_MARK;

/**
* Spans of type SPAN_INCLUSIVE_INCLUSIVE expand
* to include text inserted at either their starting or ending point.
*/
public static final int SPAN_INCLUSIVE_INCLUSIVE = SPAN_MARK_POINT;

/**
* Spans of type SPAN_EXCLUSIVE_EXCLUSIVE do not expand
* to include text inserted at either their starting or ending point.
* They can never have a length of 0 and are automatically removed
* from the buffer if all the text they cover is removed.
*/
public static final int SPAN_EXCLUSIVE_EXCLUSIVE = SPAN_POINT_MARK;

/**
* Non-0-length spans of type SPAN_EXCLUSIVE_INCLUSIVE expand
* to include text inserted at their ending point but not at their
* starting point.  When 0-length, they behave like points.
*/
public static final int SPAN_EXCLUSIVE_INCLUSIVE = SPAN_POINT_POINT;


这四个值分别表示

1.前面包括,后面不包括,即在文本前插入新的文本会应用该样式,而在文本后插入新文本不会应用该样式

2.前面包括,后面包括,即在文本前插入新的文本会应用该样式,而在文本后插入新文本也会应用该样式

3.前面不包括,后面不包括

4.前面不包括,后面包括

好了,我们这里设置的是前面不包括,后面包括,我们看看效果:



当我们点击按钮的时候会不断往tv1中追加新的文本,这时新的文本会自动使用这个背景样式,这是因为我们的最后一个参数设置为了Spanned.SPAN_EXCLUSIVE_INCLUSIVE,我们的点击事件是这样的:

tv1.append("1234");


2.给文本设置点击事件

文本设置点击事件本身也是非常简单,之间在XML文件中设置clickable属性为true,然后就可以像给Button设置点击事件一样给TextView设置点击事件了,但是如果我们只想给一个TextView中的某几个文字设置点击事件,而不想给整个TextView设置点击事件,那么该怎么做?看下面的代码:

tv2 = (TextView) this.findViewById(R.id.tv2);
SpannableString ss2 = new SpannableString("点我吧123456");
ss2.setSpan(new ClickableSpan() {

@Override
public void onClick(View widget) {
Log.i("lenve", "tv2---onClick");
Toast.makeText(MainActivity.this, "点我呀", Toast.LENGTH_SHORT)
.show();
}
}, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv2.setText(ss2);
// 设置tv2为可点击状态
tv2.setMovementMethod(LinkMovementMethod.getInstance());


大部分和1中的代码一样,我们主要来说说setSpan方法,第一个参数是一个ClickableSpan对象,这里有一个onClick方法,该方法中就是我们对点击事件的响应,后面几个参数和前文一样,我们来看看效果:



同一个TextView中,只有前三个文字会响应点击事件,其余文字都不会响应该事件。这里要特别注意最后一行代码,我们要设置该TextView为可点击状态。

3.设置文本颜色

在1中我们设置了TextView的背景颜色,这里我们看看怎么设置文本的颜色:

tv3 = (TextView) this.findViewById(R.id.tv3);
SpannableString ss3 = new SpannableString("设置文本颜色");
ss3.setSpan(new ForegroundColorSpan(Color.parseColor("#FF3030")), 0,
ss3.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv3.setText(ss3);


setSpan函数第一个参数是我们要设置的文本颜色,后面几个参数含义和之前介绍的一样,我们看看效果:



4.设置删除线效果

删除线效果在一些电商App中会用到,不要1999(一个删除线),不要998(一个删除线),只要XXX,我们看看怎么实现:

tv6 = (TextView) this.findViewById(R.id.tv6);
SpannableString ss6 = new SpannableString("删除线");
ss6.setSpan(new StrikethroughSpan(), 0, ss6.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv6.setText(ss6);


setSpan方法的第一个参数传入new StrikethroughSpan()即可,看看效果:



5.设置下划线效果

tv7 = (TextView) this.findViewById(R.id.tv7);
SpannableString ss7 = new SpannableString("下划线");
ss7.setSpan(new UnderlineSpan(), 0, ss7.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv7.setText(ss7);


setSpan方法第一个参数传入new UnderlineSpan()即可。

6.在TextView中设置图片

经常有人会问能不能在TextView中设置图片,如果能,要怎么设置,其实用SpannableString这个效果很容易实现,我们看看代码:

tv8 = (TextView) this.findViewById(R.id.tv8);
SpannableString ss8 = new SpannableString("设置图片");
ss8.setSpan(
// DynamicDrawableSpan.ALIGN_BASELINE表示依照基线对齐
// DynamicDrawableSpan.ALIGN_BOTTOM表示依照底部对齐
new DynamicDrawableSpan(DynamicDrawableSpan.ALIGN_BOTTOM) {

@Override
public Drawable getDrawable() {
Drawable d = getResources().getDrawable(
R.drawable.ic_launcher);
d.setBounds(0, 0, 150, 150);
return d;
}
}, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);// 这里的参数0,1表示将“设”字替换为图片
tv8.setText(ss8);


setSpan方法的第一个参数传入一个DynamicDrawableSpan对象,其中这个对象的构造函数是图片的对齐方式,一共有两种,如注释,实现该类的getDrawable方法,返回一个Drawable对象即可,注意这里的图片会替换掉文字(如果我们使用这种效果,很多情况下就是要让图片替换掉文字,所以这并不算一个问题),看效果图:



“设”字被图片替换掉了。

这里我们用ImageSpan实现图文并排效果:

SpannableString spanString = new SpannableString(str);
Drawable drawable = mContext.getResources().getDrawable(R.drawable.ic_launcher);
//这行不能少 设置固有宽高
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),drawable.getIntrinsicHeight());
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
spanString.setSpan(imageSpan, spanString.length() - 1, spanString.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);


查看源码发现ImageSpan只带有两个对齐方式,分别是:ALIGN_BASELINEALIGN_BOTTOM。若我们要实现图文居中对齐呢,怎么办,只能改源码了,先看下系统是怎么实现的:

//DynamicDrawableSpan.java
@Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();

int transY = bottom - b.getBounds().bottom;
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
}

canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}


方法一:

这段绘制的代码在ImageSpan的父类中,可以看到它并没有对居中对齐进行处理,这里,我们就通过继承ImageSpan类,然后修改它的draw方法来实现图文的居中对齐:

@Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();

int transY;
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
} else if (mVerticalAlignment == ALIGN_BOTTOM) {
transY = bottom - b.getBounds().bottom;
} else {
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
transY = (y + fm.descent + y + fm.ascent) / 2
- b.getBounds().bottom / 2;
}

canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}


这样,基本就能达到图文的居中效果了,使用时只要传入的不是ALIGN_BASELINE和ALIGN_BOTTOM,它就会默认显示为图文居中。

方法二:

public class CenteredImageSpan extends ImageSpan {

public CenteredImageSpan(Context context, final int drawableRes) {
super(context, drawableRes);
}

@Override
public void draw(@NonNull Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
// image to draw
Drawable b = getDrawable();
// font metrics of text to be replaced
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
int transY = (y + fm.descent + y + fm.ascent) / 2
- b.getBounds().bottom / 2;

canvas.save();
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
}


方法三:

public class MyIm extends ImageSpan
{
public MyIm(Context arg0,int arg1) {
super(arg0, arg1);
}
public int getSize(Paint paint, CharSequence text, int start, int end,
FontMetricsInt fm) {
Drawable d = getDrawable();
Rect rect = d.getBounds();
if (fm != null) {
FontMetricsInt fmPaint=paint.getFontMetricsInt();
int fontHeight = fmPaint.bottom - fmPaint.top;
int drHeight=rect.bottom-rect.top;

int top= drHeight/2 - fontHeight/4;
int bottom=drHeight/2 + fontHeight/4;

fm.ascent=-bottom;
fm.top=-bottom;
fm.bottom=top;
fm.descent=top;
}
return rect.right;
}

@Override
public void draw(Canvas canvas, CharSequence text, int start, int end,
float x, int top, int y, int bottom, Paint paint) {
Drawable b = getDrawable();
canvas.save();
int transY = 0;
transY = ((bottom-top) - b.getBounds().bottom)/2+top;
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
}


7.基于X轴的缩放

对于TextView中的文本我们也可以执行一些缩放操作,我们看看代码:

tv9 = (TextView) this.findViewById(R.id.tv9);
SpannableString ss9 = new SpannableString("基于X轴缩放");
// ScaleXSpan中的参数大于1表示横向扩大,小于1大于0表示缩小,等于1表示正常显示
ss9.setSpan(new ScaleXSpan(2), 0, ss9.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv9.setText(ss9);


只需要在setSpan方法中传入一个ScaleXSpan对象即可,里边参数含义看注释。

效果图如下:



8.设置字体粗体样式

看代码:

tv10 = (TextView) this.findViewById(R.id.tv10);
SpannableString ss10 = new SpannableString("字体样式,粗体、斜体等");
ss10.setSpan(new StyleSpan(Typeface.BOLD), 5, 7,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv10.setText(ss10);


效果图:



9.上下标的使用

上下标的使用可以让我们很好的表达一个数学公式,比如完全平方差公式,这次我们先来看看效果图:



不错吧,那么我们再来看看代码实现:

tv11 = (TextView) this.findViewById(R.id.tv11);
SpannableString ss11 = new SpannableString("(x1 + x2)2 = x12+x22+2x1x2");
// 设置下标
ss11.setSpan(new SubscriptSpan(), 2, 3,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置下标字体大小
ss11.setSpan(new AbsoluteSizeSpan(30), 2, 3,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new SubscriptSpan(), 7, 8,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new AbsoluteSizeSpan(30), 7, 8,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new SubscriptSpan(), 14, 15,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new AbsoluteSizeSpan(30), 14, 15,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new SubscriptSpan(), 18, 19,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new AbsoluteSizeSpan(30), 18, 19,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new SubscriptSpan(), 23, 24,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new AbsoluteSizeSpan(30), 23, 24,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new SubscriptSpan(), 25, 26,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new AbsoluteSizeSpan(30), 25, 26,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置上标
ss11.setSpan(new SuperscriptSpan(), 9, 10,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new AbsoluteSizeSpan(30), 9, 10,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new SuperscriptSpan(), 15, 16,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new AbsoluteSizeSpan(30), 15, 16,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new SuperscriptSpan(), 19, 20,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss11.setSpan(new AbsoluteSizeSpan(30), 19, 20,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv11.setText(ss11);


这里代码有点长,不过大部分都是重复的,重要的都有注释,不赘述。

10.设置超链接

SpannableString也可以用来设置超链接,我们看看代码:

tv13 = (TextView) this.findViewById(R.id.tv13);
SpannableString ss13 = new SpannableString("打电话,发短信,发邮件,打开网页");
ss13.setSpan(new URLSpan("tel:13534884482"), 0, 3,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss13.setSpan(new URLSpan("smsto:13534884482"), 4, 7,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss13.setSpan(new URLSpan("mailto:584991843@qq.com"), 8, 11,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss13.setSpan(new URLSpan("http://www.baidu.com"), 12, 16,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv13.setText(ss13);
tv13.setMovementMethod(LinkMovementMethod.getInstance());


使用不同的协议可以分别跳转到打电话页面,发短信页面,发邮件页面以及打开网页,我们看看效果图:



结束收工
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息