Android中SpannableString使用(二)---I
2018-11-30,发现又是周五啦,心情美美哒。想着再过几个小时就要下班过周末了,感觉自己立马兴奋无比了,再想想明天和小伙伴约了篮球,又要暴虐他们,一下就要膨胀了。但是一直秉持着做人要低调的我,还是要冷静一下,毕竟还没下班么。所以先来写一个文章吧。
上篇Android中SpannableString使用(一)---如何在一个TextView中显示不同样式的文字中,简单的介绍了SpannableString在Android开发中的一些简单使用,还有一些Span的简单使用。
这一篇我就来介绍一个
Android中使用ImageSpan实现简单的图文展示方案
最近项目中有一个这个的页面需求:
需求效果图
其实这个要注意的也就是如何实现小图标在文字的最后面显示出来。
第一反应想到的方法就是给TextView加上drawableRight,于是就撸了一波代码
<TextView
android:id="@+id/tv1"
android:layout_weight="1"
android:gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/sp_14"
android:textColor="@color/text_333333"
android:text="花和尚 送 小二哥 一个鼓掌"
android:drawableRight="@mipmap/aaaa"
android:drawablePadding="@dimen/dp_5"/>
<TextView
android:id="@+id/tv2"
android:layout_weight="1"
android:gravity="center_vertical"
android:textSize="@dimen/sp_14"
android:textColor="@color/text_333333"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌"
android:drawableRight="@mipmap/aaaa"
android:drawablePadding="@dimen/dp_5"/>
drawableRight方式效果
很显然用这种方式实现的时候,当文字只有一行的时候,可以达到想要的效果,可是换行之后就不可以了。所以继续想想其他的方法呗。
接下来就说说使用ImageSpan的方式实现
直接先上代码
String s = "花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 一个鼓掌花和尚 送 小二哥 ";
//创建SpannableString对象
SpannableString string = new SpannableString(s);
//创建ImageSpan对象
ImageSpan imageSpan = new ImageSpan(mContext, R.mipmap.aaaa, DynamicDrawableSpan.ALIGN_BASELINE);
//给SpannableString对象设置ImageSpan
string.setSpan(imageSpan, s.length() - 1, s.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
tv3.setText(string);
简单的几句代码就可以实现图标一直确保在最后一个文字后面
使用ImageSpan的效果
看完这张图,很明显我在图上标注了两条线。仔细观察,我画的线都是放在小图标的下边缘,然而却在文字的不同高度上。这说明小图标和文字的对齐方式不同,这是为什么呢?我们就来看看ImageSpan的源码吧。
public ImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment)
ImageSpan的构造方法有好几种,因为实例代码中使用的这种,就说说这个吧。
三个参数
1,Context context:上下文
2,@DrawableRes int resourceId:DrawableResId
3,int verticalAlignment:对齐方式(系统中提供了两种)
/**
* A constant indicating that the bottom of this span should be aligned
* with the bottom of the surrounding text, i.e., at the same level as the
* lowest descender in the text.
*/
public static final int ALIGN_BOTTOM = 0;
/**
* A constant indicating that the bottom of this span should be aligned
* with the baseline of the surrounding text.
*/
public static final int ALIGN_BASELINE = 1;
- DynamicDrawableSpan.ALIGN_BASELINE:以基线对齐
- DynamicDrawableSpan.ALIGN_BOTTOM:以底部对齐
这个就是上面图中的两条线位置不同的原因。
写在这里有突然想到一个问题,我们在实际开发中布局的时候,很多时候用到的是垂直居中。而ImageSpan中只提供了ALIGN_BASELINE和ALIGN_BOTTOM,我们怎么实现垂直居中的效果呢。接下来继续逼逼。
首先看看ImageSpan的源码
public class ImageSpan extends DynamicDrawableSpan{}
并没有什么特别的东西
再看看其父类DynamicDrawableSpan
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
if (fm != null) {
fm.ascent = -rect.bottom;
fm.descent = 0;
fm.top = fm.ascent;
fm.bottom = 0;
}
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 = 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();
}
getSize(...)函数:返回一个int的rect.right,也就是图片的宽度。同时在这个函数中也对文字的ascent和descent两个值进行了修改。
/**
* The recommended distance above the baseline for singled spaced text.
*/
public int ascent;
/**
* The recommended distance below the baseline for singled spaced text.
*/
public int descent;
(注:ascent,descent等参数的含义在文章的最后面有图文解释)
draw(...)函数:在这个函数里面是根据不同的对齐参数进行图片的绘制。
就从这两个函数入手修改竖直居中的对齐方式
- 我们得创建一个VerticalImageSpan继承ImageSpan,并重写以上的两个函数。
public class VerticalImageSpan extends ImageSpan {
public VerticalImageSpan(Context context, int resourceId) {
super(context, resourceId);
}
}
- 修改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 = getDrawable();
canvas.save();
int transY = 0;
//获得将要显示的文本高度 - 图片高度除2 = 居中位置+top(换行情况)
transY = ((bottom - top) - b.getBounds().bottom) / 2 + top;
//偏移画布后开始绘制
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
- 还要修改getSize(...)函数,重新设置文字的ascent和descent
@Override
public int getSize(Paint paint, CharSequence text, int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getDrawable();
Rect rect = d.getBounds();
if (fm != null) {
Paint.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;
}
现在就可以直接使用了。看看三种对齐方式的效果图(为了更加明显我换了一个大一点的图标)
三种不同的对齐方式
好了,到这里基本已经结束了。
效果图中文字还需要改变颜色,这里就不做了。上篇中我已经提到过不同的span可以组合使用实现复杂的展示效果,我们只需要再给SpannableString设置一个ForegroundColorSpan就可以实现改变颜色了。
最后再补充一点Android中TextView编辑时结构
textview的结构- 基准点是baseline
- ascent:是baseline之上至字符最高处的距离
- descent:是baseline之下至字符最低处的距离
- leading:是上一行字符的descent到下一行的ascent之间的距离,也就是相邻行间的空白距离
- top:是指的是最高字符到baseline的值,即ascent的最大值
- bottom:是指最低字符到baseline的值,即descent的最大值