背景
最近项目上要实现一个功能(红色选中部分),先看图:

分析
大家仔细看红色选中部分,会发现文字内容 大小不一样、颜色也不一样,数字199还带有一条删除线,那要怎么实现这种效果呢?
实现方法一
¥189为一个TextView,次/人为一个TextView,- 原价¥一个TextView,199一个TextView,四个TextView横排在一起就可以达到我们想要的效果。
实现方法二
只用一个TextView来实现,把文本内容分成4块,分别为
¥189、次/人、- 原价¥、199。
然后用SpannableStringBuilder的append方法把4块拼接起来即可。
对比两种实现方法,我们肯定是选择第二种了,那开始具体实现之前我们先来普及下SpannableStringBuilder与SpannableString的基本知识。
SpannableString与SpannableStringBuilder使用
-
SpannableString、SpannableStringBuilder与String的关系
首先SpannableString、SpannableStringBuilder基本上与String差不多,也是用来存储字符串,但它们俩的特殊就在于有一个SetSpan函数,能给这些存储的String添加各种格式或者称样式(Span),将原来的String以不同的样式显示出来,比如在原来String上加下划线、加背景色、改变字体颜色、用图片把指定的文字给替换掉,等等。
所以,总而言之,SpannableString、SpannableStringBuilder与String一样, 首先也是传字符串,
但SpannableString、SpannableStringBuilder可以对这些字符串添加额外的样式信息,而String则不行。
注意:如果这些额外信息能被所用的方式支持,比如将
SpannableString传给TextView;
也有对这些额外信息不支持的,比如Canvas绘制文字,对于不支持的情况,SpannableString和SpannableStringBuilder就是退化为String类型,直接显示原来的String字符串,而不会再显示这些附加的额外信息,即添加的样式无效果。
-
SpannableString与SpannableStringBuilder区别
它们的区别在于 SpannableString像一个String一样,构造对象的时候传入一个String,之后再无法更改String的内容,也无法拼接多个 SpannableString;而SpannableStringBuilder则更像是StringBuilder,它可以通过其append()方法来拼接多个String;
-
setSpan方法
void setSpan (Object what, int start, int end, int flags)
函数意义:给SpannableString或SpannableStringBuilder特定范围的字符串设定Span样式,可以设置多个(比如同时加上下划线和删除线等),flags参数标识了当在所标记范围前和标记范围后紧贴着插入新字符时的动作,即是否对新插入的字符应用同样的样式。
参数说明:
object what:对应的各种Span,后面会提到;
int start:开始应用指定Span的位置,索引从0开始
int end:结束应用指定Span的位置,特效并不包括这个位置。比如如果这里数为3(即第4个字符),第4个字符不会有任何特效。从下面的例子也可以看出来。
int flags:取值有如下四个
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:不包括start、end位置,即start、end位置不会应用设置的样式。
Spannable.SPAN_EXCLUSIVE_INCLUSIVE:不包括start位置,包括end位置。即start位置不会应用设置的样式,end位置应用设置的样式。
Spannable.SPAN_INCLUSIVE_EXCLUSIVE:包括start位置,不包括end位置。即start位置会应用设置的样式,end位置不会应用设置的样式。
Spannable.SPAN_INCLUSIVE_INCLUSIVE :包括start、end位置,即start、end位置都会应用设置的样式。
具体实现
因为实现起来没那么复杂,主要就是调用setSpan创建各种样式span,直接上代码,代码中有关键注释:
//中等字体
int middleFontSize = (int) DensityHelper.spToPx(context, 18);
//小字体
int smallFontSize = (int) DensityHelper.spToPx(context, 14);
//红色 #F65236
int colorRed = ContextCompat.getColor(context, R.color.couponColor);
//灰色 #999999
int textColorThird = ContextCompat.getColor(context, R.color.textColorThird);
SpannableStringBuilder spanBuilder = new SpannableStringBuilder();
String price = "¥189";
int start = 0;
int end = price.length();
spanBuilder.append(price);
//添加字体大小样式span
spanBuilder.setSpan(new AbsoluteSizeSpan(middleFontSize), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
//添加文本颜色样式span
spanBuilder.setSpan(new ForegroundColorSpan(colorRed), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
String priceUnit = "次/人";
start = end;
end += priceUnit.length();
spanBuilder.append(priceUnit);
spanBuilder.setSpan(new AbsoluteSizeSpan(smallFontSize), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spanBuilder.setSpan(new ForegroundColorSpan(colorRed), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
String line = " - 原价¥";
start = end;
end += line.length();
spanBuilder.append(line);
spanBuilder.setSpan(new AbsoluteSizeSpan(smallFontSize), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spanBuilder.setSpan(new ForegroundColorSpan(textColorThird), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
String originalPricePrefix = "199";
start = end;
end += originalPricePrefix.length();
spanBuilder.append(originalPricePrefix);
spanBuilder.setSpan(new AbsoluteSizeSpan(smallFontSize), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spanBuilder.setSpan(new ForegroundColorSpan(textColorThird), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
//添加删除线样式span
spanBuilder.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
//最终把SpannableStringBuilder设置给TextView
tvPriceInfo.setText(spanBuilder);
以上由于项目需求,只介绍了如何创建字体大小span、文本颜色span、加删除线span,还有很多比如文字模糊、浮雕、文字背景颜色、文字粗体斜体、下划线、超链接等等,大家敲敲代码感受下。
最后,如果觉得对你有用的话,那就动动手指给个赞吧。有疑问请留言,一起进步哈!
附上DensityHelper工具类的实现,主要实现dp、sp与px的互相转换。
public final class DensityHelper {
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static float dipToPx(Context context, float dp) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
return px;
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static float pxToDip(Context context, float px) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float dp = px / ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
return dp;
}
/**
* 将px值转换为sp值,保证文字大小不变
*
* @param px 像素值
* @param context 上下文
* @return px转换后的sp
*/
public static float pxToSp(Context context, float px) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float sp = px / metrics.scaledDensity + 0.5f;
return sp;
}
/**
* 将sp值转换为px值,保证文字大小不变
*
* @param sp 字体sp大小
* @param context (上下文
* @return sp转换后的px值
*/
public static float spToPx(Context context, float sp) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = sp * metrics.scaledDensity + 0.5f;
return px;
}
}