前言
看了某大神生动有趣的博客之后,萌生了把自己在项目中所遇到的一些技术点记录下来,一般都是网上很少教程的一些技术。这是我人生中的第一篇博客。写完之后看看能不能去杀个程序员祭天!!

某天,老板叫我到办公室去唠嗑,他说小陈啊,最近小学生沉迷网络,我们是否可以做一款考试的软件来解救一下祖国花朵啊!
我陷入了沉默,皱了皱眉一副若有所思的摸样。老板见状,以我在思考,便没有打断我。随后我说:嗯 我回去想想!
其实不就是一个EditText加一个TextView的事情吗,哈哈哈.....机智如我。

马不停蹄的就把这个屁屁给撸了出来。屁颠屁颠的跑过去跟他怼了一波,这么简单完全不能体现本公司的逼格。你回去看看猿题库是怎么做的。我就知道事情没有那么简单!

进入主题吧!
一开始是想用Html.formHtml()
来实现的。但是这只能实现图文混排,不能增加输入框。
所以自己写了一个自定义View。

这个自定义View就是在onDraw()
里面绘制文字,当碰到换行符或者绘制满一行时就换行,当碰到透明占位图时候绘制一个透明的图片,然后得到该占位图位置的坐标,然后设置EditText的位置就可以了。
Html.ImageGetter
可以识别Html的标签,所以占位符的位置和占位符命名格式可以由我们自己来定义。以下是我自己定义的题目:
"一、填空题(共4小题)。 2.决定花儿的生长因素有() () ()
这里的input是自己定义的名字,我这里的input代表着这是一个edittext输入框,数字" 1 "代表是第几个输入框,数字" 4 "代表该输入框里面的字符数是多少,用来定义输入框的长度。
当然你也可以增加题目所需要的图片:
代码走一波:
首先对题目处理一下,因为占位图的话需要两个符号来替换:
String subject = "一、填空题(共4小题)。" +
" 2.决定花儿的生长因素有(<img src=\"input_1_4\">) (<img src=\"input_2_4\">) (<img src=\"input_3_4\">)";
//动态在图片标签前面加两个空格来作为图片插入时替换的无用字符
//否则会替换掉原有的字符
StringBuilder sb = new StringBuilder();
for (int i = 0; i < subject.length(); i++) {
char cha = subject.charAt(i);
String chaStr = String.valueOf(cha);
if (chaStr.equals("<")) {
sb.append(" ");
}
sb.append(subject.charAt(i));
}
mSubJectSb = new SpannableString(sb.toString());
这里要注意的是img标签前面必须要加两个空格以备占位图替换,否则会出现题目不全的问题
接下来要处理一下img标签了:
Html.fromHtml(sb.toString(), getImageGetterEdtText(getActivity(), mSubjectView, mAnswer.answer),
new MTagHandler());
getImageGetterEdtText()
方法:
/**
* ImageGetter用处处理标签
* @param context
* @param mSubjectView
* @param answer 该题目的答案
* @return
*/
public static Html.ImageGetter getImageGetterEdtText(final Context context, final TextView mtextView, final String answer) {
DisplayMetrics mDisplayMetrics = new DisplayMetrics();
((Activity) context).getWindowManager()
.getDefaultDisplay().getRealMetrics(mDisplayMetrics);
final int screenWidth = mDisplayMetrics.widthPixels; // 屏幕宽(像素,如:480px)
final int screenHeight = mDisplayMetrics.heightPixels; // 屏幕高
/**
* 返回图片的数据
*/
Html.ImageGetter imgGetter = new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
Drawable d = null;
if (source.contains("input_") || source.contains("note_")) {
//这里是打开一张透明的图片
d = context.getResources().getDrawable(R.drawable.edttext_bg);
//得到该占位图的长度,比如之前的数字4
int size = Integer.valueOf(source.substring(source.lastIndexOf("_") + 1, source.length()));
//这里的note我是自己定义的另一个标签,可忽略
if (source.contains("note_"))
size = 1;
int width;
String[] answers = answer.split("\\|\\|");
float ratio = (float) 5 / size;
if (ratio < 1)
size = (int) (size * ratio);
if (size > 15)
size = 15;
if (size == 1) {
width = (int) (mtextView.getTextSize() * size + mtextView.getTextSize() / 2);
} else {
width = (int) (mtextView.getTextSize() * size);
if (source.contains("input_")) {
int sizeWidth = Integer.valueOf(source.substring(source.lastIndexOf("_") + 1, source.length()));
if (answers.length > 0) {
int mun = Integer.valueOf(source.substring(source.lastIndexOf("input_"), source.lastIndexOf("_")).replaceAll("input_", ""));
if (!checkChina(answers[mun - 1])) {
width = (int) (mtextView.getTextSize() / 1.5 * sizeWidth);
}
} else {
if (!checkChina(answer)) {
width = (int) (mtextView.getTextSize() / 1.5 * sizeWidth);
}
}
}
}
// int height = (int) (mtextView.getTextSize() * 1.5);
int height = (int) (mtextView.getTextSize());
if (screenWidth > screenHeight) {
float ratioWidth = screenWidth / 1280f;
width = Math.round(width * ratioWidth);
} else {
float ratioWidth = screenWidth / 800f;
width = Math.round(width * ratioWidth);
}
//设置该占位图的宽高
d.setBounds(0, 0, width, height);
} else if (source.contains("sup_") || source.contains("sub_")) {
} else {
d = Drawable.createFromPath(source);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(source, options); // 此时返回的bitmap为null
if (d != null) {
int width;
int height = options.outHeight;
if (options.outWidth > screenWidth) {
width = screenWidth - 100;
} else {
if (screenWidth > screenHeight) {
width = options.outWidth;
// float ratioWidth = screenWidth / 1280f;
float ratioWidth = screenWidth / 900f;
width = Math.round(width * ratioWidth);
float ratioHeight = (float) width / options.outWidth;
height = Math.round(height * ratioHeight);
} else {
width = options.outWidth;
// float ratioWidth = screenWidth / 800f;
float ratioWidth = screenWidth / 600f;
width = Math.round(width * ratioWidth);
float ratioHeight = (float) width / options.outWidth;
height = Math.round(height * ratioHeight);
}
}
if (width >= screenWidth) {
width = width - 50;
}
d.setBounds(0, 0, width, height);
}
}
return d;
}
};
return imgGetter;
}
MTagHandler()
走一波:
class MTagHandler implements Html.TagHandler {
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
// 处理标签<img>
if (tag.toLowerCase(Locale.getDefault()).equals("img")) {
// 获取长度
int len = output.length();
// 获取图片地址
ImageSpan[] images = output.getSpans(len - 1, len, ImageSpan.class);
String imgURL = images[0].getSource();
//插入图片
mSubJectSb.setSpan(images[0], len - 2, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (imgURL.contains("input_")) {
EditText editText = new EditText(getActivity());
int size = Integer.valueOf(imgURL.substring(imgURL.lastIndexOf("_") + 1, imgURL.length()));
//将输入框保存到集合里
mEditTextList.add(editText);
editText.setTextColor(Color.BLACK);
//限定输入框能输入的字符数量
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(size)});
editText.setSingleLine();
editText.setTextSize(25);
editText.setPadding(2, 2, 2, 2);
editText.setGravity(Gravity.CENTER);
editText.setHeight((int) (mSubjectView.getTextSize() * 1.5));
//设置虚拟键盘最下面的按钮是下一个
editText.setImeOptions(EditorInfo.IME_ACTION_NEXT);
//将输入框加入到布局容器
mSubjectRlMain.addView(editText);
}
// 使图片可点击并监听点击事件
// output.setSpan(new MyTagHandler.ClickableImage(mContext, imgURL), len - 1, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
数据都处理好了,接下来就是显示数据了:
mSubjectView.setMText(mSubJectSb);
mSubjectView.setEdtText(mEditTextList);
//刷新控件,重走一遍onDraw
mSubjectView.invalidate();

是不是觉得很丑,确实是挺丑的,UI就这样啦,将就将就吧,你可以换一下EditText的背景啊,设置一下有焦点的状态无焦点的状态有内容的状态。
那么这个View里面到底做了什么呢?测量高度什么的就不详细说了,来说一下比较重要的onDraw()
里面的代码。
因为canvas是逐步绘制嘛,首先先判断当前要绘制的内容格式是什么,如果内容的String的话,就直接canvas.drawText()
;
但是如果当前行有图片的话,当前行高就会比文字高度高出很多,所以绘制的文字是在最底部,想要文字居中的话(上图的效果)就要做一下判断:
if (ob instanceof String) {
//总高度大于文字高度,有图片在本行,文字居中
if (aContentList.height > (paint.getFontMetrics().descent * 2 + Math.abs(paint.getFontMetrics().ascent))) {
float textX =(height +
(aContentList.height + Math.abs(paint.getFontMetrics().ascent)
- paint.getFontMetrics().descent) / 2);
canvas.drawText((String) ob, realDrawedWidth, textX, paint);
} else {
canvas.drawText((String) ob, realDrawedWidth, y, paint);
}
realDrawedWidth += width;
//结尾时换行符/n 则换行
if (((String) ob).endsWith("\n") && j == aContentList.line.size() - 1) {
newParagraph = true;
}
}
如果内容是图片的也需要判断,因为可能该行有两张图片,一张大一张小的,为了美观,小的要居中:
if (ob instanceof SpanObject) { //内容是标签
Object span = ((SpanObject) ob).span;
if (span instanceof ImageSpan) {
String source = ((ImageSpan) span).getSource();
int start = ((Spannable) text).getSpanStart(span);
int end = ((Spannable) text).getSpanEnd(span);
int picHeight = ((ImageSpan) span).getDrawable().getBounds().height();
if (aContentList.height > (picHeight * 1.3)) {
float textY = getTextHeight(height,aContentList)+picHeight/3;
float textTop = textY - aContentList.height;
float textBottom = textY + mFontMetrics.descent;
((DynamicDrawableSpan) span).draw(canvas, text, start, end, (int) x, (int) textTop, (int) textY, (int) textBottom, paint);
} else {
((DynamicDrawableSpan) span).draw(canvas, text, start, end, (int) x, (int) top, (int) y, (int) bottom, paint);
/**
* 图片的X轴=x , y轴=top
* 加上宽高等于范围
* 数据存起来,在onTouch里面判断处理,点击可以放大图片
* */
}
realDrawedWidth += width;
}
如果内容是图片,并且图片是占位图的话则需要设置对应的Edittext的位置:
if (source.contains("input_")) { //如果是填空标签则edttext 定义位置
if (mEdtText != null && mEdtText.size() > 0) {
String mun = source.substring(source.lastIndexOf("input_"), source.lastIndexOf("_")).replaceAll("input_", "");
EditText editText = mEdtText.get(Integer.valueOf(mun) - 1 - currentIndex);
editText.setWidth(((ImageSpan) span).getDrawable().getBounds().width());
int imgWidth = ((ImageSpan) span).getDrawable().getBounds().width();
int imgHeight = ((ImageSpan) span).getDrawable().getBounds().height();
RelativeLayout.LayoutParams params = mEdtTextParams.get(Integer.valueOf(mun) - 1 - currentIndex);
if (aContentList.height > (imgHeight * 1.5)) {
float textY = getTextHeight(height,aContentList);
float textTop = textY - imgHeight;
params.setMargins((int) (x), (int) textTop, 0, 0);
} else {
if (aContentList == contentList.get(0)) {
params.setMargins((int) (x), (int) top + imgHeight / 4, 0, 0);
} else {
params.setMargins((int) (x), (int) top, 0, 0);
}
}
editText.setLayoutParams(params);
}
}
我只能说到这里了,剩下的就靠你们自己的造化了,下面是SubjectView的完整代码:
public class SubjectView extends TextView {
/**
* 缓存测量过的数据
*/
private static HashMap<String, SoftReference<MeasuredData>> measuredData = new HashMap<String, SoftReference<MeasuredData>>();
private static int hashIndex = 0;
/**
* 存储当前文本内容,每个item为一行
*/
ArrayList<LINE> contentList = new ArrayList<LINE>();
private Context context;
/**
* 用于测量字符宽度
*/
private TextPaint paint = new TextPaint();
/**
* 用于测量上下标字符宽度
*/
private TextPaint upDownPaint = new TextPaint();
/**
* 用于测量span高度
*/
private Paint.FontMetricsInt mSpanFmInt = new Paint.FontMetricsInt();
/**
* 临时使用,以免在onDraw中反复生产新对象
*/
private FontMetrics mFontMetrics = new FontMetrics();
// private float lineSpacingMult = 0.5f;
private int textColor = Color.BLACK;
//行距
private float lineSpacing;
private int lineSpacingDP = 5;
/**
* 段间距,-1为默认
*/
private int paragraphSpacing = -1;
/**
* 最大宽度
*/
private int maxWidth;
/**
* 只有一行时的宽度
*/
private int oneLineWidth = -1;
/**
* 已绘的行中最宽的一行的宽度
*/
private float lineWidthMax = -1;
/**
* 存储当前文本内容,每个item为一个字符或者一个SpanObject
*/
private ArrayList<Object> obList = new ArrayList<Object>();
/**
* 是否使用默认{@link #onMeasure(int, int)}和{@link #onDraw(Canvas)}
*/
private boolean useDefault = false;
protected CharSequence text = "";
private int minHeight;
/**
* 用以获取屏幕高宽
*/
private DisplayMetrics displayMetrics;
/**
* {@link BackgroundColorSpan}用
*/
private Paint textBgColorPaint = new Paint();
/**
* {@link BackgroundColorSpan}用
*/
private Rect textBgColorRect = new Rect();
private List<EditText> mEdtText;
private int edtTextMun = 0;
private List<RelativeLayout.LayoutParams> mEdtTextParams;
private SpannableStringBuilder mStringBuilder = new SpannableStringBuilder("");
private int mViewWidth;
private List<ImageView> mImageViews;
private List<RelativeLayout.LayoutParams> mImageViewParams;
private int currentIndex = 0;
private float mHeighRatio;
public SubjectView(Context context) {
super(context);
init(context);
}
public SubjectView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SubjectView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public void init(Context context) {
this.context = context;
paint.setAntiAlias(true);
upDownPaint.setAntiAlias(true);
lineSpacing = dip2px(context, lineSpacingDP);
minHeight = dip2px(context, 30);
displayMetrics = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
mHeighRatio = 0f;
if (displayMetrics.heightPixels > displayMetrics.widthPixels) {
float ratio = (displayMetrics.heightPixels / 800f);
if (ratio > 1.1) {
mHeighRatio = 5 * ratio;
}
}
}
public static int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@Override
public void setMaxWidth(int maxpixels) {
super.setMaxWidth(maxpixels);
maxWidth = maxpixels;
}
@Override
public void setMinHeight(int minHeight) {
super.setMinHeight(minHeight);
this.minHeight = minHeight;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (useDefault) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int width = 0, height = 0;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
width = displayMetrics.widthPixels;
break;
default:
break;
}
if (maxWidth > 0) {
width = Math.min(width, maxWidth);
}
paint.setTextSize(this.getTextSize());
paint.setColor(textColor);
upDownPaint.setTextSize(this.getTextSize() / 2);
upDownPaint.setColor(textColor);
int realHeight = measureContentHeight(width);
//如果实际行宽少于预定的宽度,减少行宽以使其内容横向居中
int leftPadding = getCompoundPaddingLeft();
int rightPadding = getCompoundPaddingRight();
width = Math.min(width, (int) lineWidthMax + leftPadding + rightPadding);
if (oneLineWidth > -1) {
width = oneLineWidth;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = realHeight;
break;
case MeasureSpec.UNSPECIFIED:
height = realHeight;
break;
default:
break;
}
height += getCompoundPaddingTop() + getCompoundPaddingBottom();
//控件的高度
height = (Math.max(height, minHeight));
// height = (int) (Math.max(height, minHeight) + contentList.get(0).height*1.2);
mViewWidth = width;
setMeasuredDimension(width, height);
}
private float getTextHeight(float height, LINE aContentList) {
return (height + (aContentList.height + Math.abs(paint.getFontMetrics().ascent) - paint.getFontMetrics().descent) / 2);
}
@Override
protected void onDraw(Canvas canvas) {
if (useDefault) {
super.onDraw(canvas);
return;
}
if (contentList.isEmpty()) {
return;
}
int width;
Object ob;
int leftPadding = getCompoundPaddingLeft();
int topPadding = getCompoundPaddingTop();
float height = 0 + topPadding + lineSpacing;
//只有一行时
if (oneLineWidth != -1) {
height = getMeasuredHeight() / 2 - contentList.get(0).height / 2;
}
for (LINE aContentList : contentList) {
//绘制一行
float realDrawedWidth = leftPadding;
/** 是否换新段落*/
boolean newParagraph = false;
for (int j = 0; j < aContentList.line.size(); j++) {
ob = aContentList.line.get(j);
width = aContentList.widthList.get(j);
paint.getFontMetrics(mFontMetrics);
float x = realDrawedWidth;
float y = height + aContentList.height - paint.getFontMetrics().descent;
float top = y - aContentList.height;
float bottom = y + mFontMetrics.descent;
if (ob instanceof String) {
//总高度大于文字高度,有图片在本行,文字居中
if (aContentList.height > (paint.getFontMetrics().descent * 2 + Math.abs(paint.getFontMetrics().ascent))) {
float textX =(height +
(aContentList.height + Math.abs(paint.getFontMetrics().ascent)
- paint.getFontMetrics().descent) / 2);
canvas.drawText((String) ob, realDrawedWidth, textX, paint);
} else {
canvas.drawText((String) ob, realDrawedWidth, y, paint);
}
realDrawedWidth += width;
//结尾时换行符/n 则换行
if (((String) ob).endsWith("\n") && j == aContentList.line.size() - 1) {
newParagraph = true;
}
} else if (ob instanceof SpanObject) { //内容是标签
Object span = ((SpanObject) ob).span;
if (span instanceof ImageSpan) {
String source = ((ImageSpan) span).getSource();
int start = ((Spannable) text).getSpanStart(span);
int end = ((Spannable) text).getSpanEnd(span);
int picHeight = ((ImageSpan) span).getDrawable().getBounds().height();
if (aContentList.height > (picHeight * 1.3)) {
float textY = getTextHeight(height,aContentList)+picHeight/3;
float textTop = textY - aContentList.height;
float textBottom = textY + mFontMetrics.descent;
((DynamicDrawableSpan) span).draw(canvas, text, start, end, (int) x, (int) textTop, (int) textY, (int) textBottom, paint);
} else {
((DynamicDrawableSpan) span).draw(canvas, text, start, end, (int) x, (int) top, (int) y, (int) bottom, paint);
/**
* 图片的X轴=x , y轴=top
* 加上宽高等于范围
* 数据存起来,在onTouch里面判断处理,点击可以放大图片
* */
}
realDrawedWidth += width;
if (source.contains("input_")) { //如果是填空标签则edttext 定义位置
if (mEdtText != null && mEdtText.size() > 0) {
String mun = source.substring(source.lastIndexOf("input_"), source.lastIndexOf("_")).replaceAll("input_", "");
EditText editText = mEdtText.get(Integer.valueOf(mun) - 1 - currentIndex);
editText.setWidth(((ImageSpan) span).getDrawable().getBounds().width());
int imgWidth = ((ImageSpan) span).getDrawable().getBounds().width();
int imgHeight = ((ImageSpan) span).getDrawable().getBounds().height();
RelativeLayout.LayoutParams params = mEdtTextParams.get(Integer.valueOf(mun) - 1 - currentIndex);
if (aContentList.height > (imgHeight * 1.5)) {
float textY = getTextHeight(height,aContentList);
float textTop = textY - imgHeight;
params.setMargins((int) (x), (int) textTop, 0, 0);
} else {
if (aContentList == contentList.get(0)) {
params.setMargins((int) (x), (int) top + imgHeight / 4, 0, 0);
} else {
params.setMargins((int) (x), (int) top, 0, 0);
}
}
editText.setLayoutParams(params);
}
} else if (source.contains("note_")) { //如果是图片填空标签则imageview 定义位置
if (mImageViews != null && mImageViews.size() > 0) {
String mun = source.substring(source.lastIndexOf("note_"), source.lastIndexOf("_")).replaceAll("note_", "");
ImageView imageView = mImageViews.get(Integer.valueOf(mun) - 1 - currentIndex);
imageView.setMaxWidth(((ImageSpan) span).getDrawable().getBounds().width());
int imgWidth = ((ImageSpan) span).getDrawable().getBounds().width();
int imgHeight = ((ImageSpan) span).getDrawable().getBounds().height();
RelativeLayout.LayoutParams params = mImageViewParams.get(Integer.valueOf(mun) - 1 - currentIndex);
if (aContentList.height > imgHeight * 1.5) {
float textY = getTextHeight(height,aContentList);
float textTop = textY - imgHeight;
params.setMargins((int) (x), (int) textTop, 0, 0);
} else {
if (aContentList == contentList.get(0)) {
params.setMargins((int) (x), (int) top + imgHeight / 4, 0, 0);
} else {
params.setMargins((int) (x), (int) top, 0, 0);
}
}
imageView.setLayoutParams(params);
}
}
} else if (span instanceof BackgroundColorSpan) { //如果标签是背景色
textBgColorPaint.setColor(((BackgroundColorSpan) span).getBackgroundColor());
textBgColorPaint.setStyle(Style.FILL);
textBgColorRect.left = (int) realDrawedWidth;
int textHeight = (int) getTextSize();
textBgColorRect.top = (int) (height + aContentList.height - textHeight - mFontMetrics.descent);
textBgColorRect.right = textBgColorRect.left + width;
textBgColorRect.bottom = (int) (height + aContentList.height + lineSpacing - mFontMetrics.descent);
canvas.drawRect(textBgColorRect, textBgColorPaint);
canvas.drawText(((SpanObject) ob).source.toString(), realDrawedWidth, height + aContentList.height - mFontMetrics.descent, paint);
realDrawedWidth += width;
} else//做字符串处理
{
canvas.drawText(((SpanObject) ob).source.toString(), realDrawedWidth, height + aContentList.height - mFontMetrics.descent, paint);
realDrawedWidth += width;
}
}
}
//如果要绘制段间距
if (newParagraph) {
height += aContentList.height + paragraphSpacing + getTextSize() / 2;
} else {
height += aContentList.height + lineSpacing + getTextSize() / 2;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
return true;
}
@Override
public void setTextColor(int color) {
super.setTextColor(color);
textColor = color;
}
/**
* 用于带ImageSpan的文本内容所占高度测量
*
* @param width 预定的宽度
* @return 所需的高度
*/
private int measureContentHeight(int width) {
int cachedHeight = getCachedData(text.toString(), width);
if (cachedHeight > 0) {
return cachedHeight;
}
// 已绘的宽度
float obWidth = 0;
float obHeight = 0;
float textSize = this.getTextSize();
FontMetrics fontMetrics = paint.getFontMetrics();
//行高
float lineHeight = fontMetrics.bottom - fontMetrics.top;
//计算出的所需高度
float height = lineSpacing;
int leftPadding = getCompoundPaddingLeft();
int rightPadding = getCompoundPaddingRight();
float drawedWidth = 0;
boolean splitFlag = false;//BackgroundColorSpan拆分用
width = width - leftPadding - rightPadding;
oneLineWidth = -1;
contentList.clear();
StringBuilder sb;
LINE line = new LINE();
for (int i = 0; i < obList.size(); i++) {
Object ob = obList.get(i);
if (ob instanceof String) {
obWidth = paint.measureText((String) ob);
obHeight = textSize;
if ("\n".equals(ob)) { //遇到"\n"则换行
obWidth = width - drawedWidth;
}
} else if (ob instanceof SpanObject) {
Object span = ((SpanObject) ob).span;
if (span instanceof DynamicDrawableSpan) {
int start = ((Spannable) text).getSpanStart(span);
int end = ((Spannable) text).getSpanEnd(span);
obWidth = ((DynamicDrawableSpan) span).getSize(getPaint(), text, start, end, mSpanFmInt);
obHeight = Math.abs(mSpanFmInt.top) + Math.abs(mSpanFmInt.bottom);
if (obHeight > lineHeight) {
lineHeight = obHeight;
}
} else if (span instanceof BackgroundColorSpan) {
String str = ((SpanObject) ob).source.toString();
obWidth = paint.measureText(str);
obHeight = textSize;
//如果太长,拆分
int k = str.length() - 1;
while (width - drawedWidth < obWidth) {
obWidth = paint.measureText(str.substring(0, k--));
}
if (k < str.length() - 1) {
splitFlag = true;
SpanObject so1 = new SpanObject();
so1.start = ((SpanObject) ob).start;
so1.end = so1.start + k;
so1.source = str.substring(0, k + 1);
so1.span = ((SpanObject) ob).span;
SpanObject so2 = new SpanObject();
so2.start = so1.end;
so2.end = ((SpanObject) ob).end;
so2.source = str.substring(k + 1, str.length());
so2.span = ((SpanObject) ob).span;
ob = so1;
obList.set(i, so2);
i--;
}
}//做字符串处理
else {
String str = ((SpanObject) ob).source.toString();
obWidth = paint.measureText(str);
obHeight = textSize;
}
}
//这一行满了,存入contentList,新起一行
if (width - drawedWidth < obWidth || splitFlag) {
splitFlag = false;
contentList.add(line);
if (drawedWidth > lineWidthMax) {
lineWidthMax = drawedWidth;
}
drawedWidth = 0;
//判断是否有分段
int objNum = line.line.size();
if (paragraphSpacing > 0
&& objNum > 0
&& line.line.get(objNum - 1) instanceof String
&& "\n".equals(line.line.get(objNum - 1))) {
height += line.height + paragraphSpacing + getTextSize() / 2;
} else {
height += line.height + lineSpacing + getTextSize() / 2;
}
lineHeight = obHeight;
line = new LINE();
}
drawedWidth += obWidth;
if (ob instanceof String && line.line.size() > 0 && (line.line.get(line.line.size() - 1) instanceof String)) {
int size = line.line.size();
sb = new StringBuilder();
sb.append(line.line.get(size - 1));
sb.append(ob);
ob = sb.toString();
obWidth = obWidth + line.widthList.get(size - 1);
line.line.set(size - 1, ob);
line.widthList.set(size - 1, (int) obWidth);
line.height = (int) lineHeight;
} else {
line.line.add(ob);
line.widthList.add((int) obWidth);
line.height = (int) lineHeight;
}
}
if (drawedWidth > lineWidthMax) {
lineWidthMax = drawedWidth;
}
if (line != null && line.line.size() > 0) {
contentList.add(line);
height += lineHeight + lineSpacing;
}
if (contentList.size() <= 1) {
oneLineWidth = (int) drawedWidth + leftPadding + rightPadding;
height = lineSpacing + lineHeight + lineSpacing;
}
cacheData(width, (int) height);
return (int) height;
}
/**
* 获取缓存的测量数据,避免多次重复测量
*
* @param text
* @param width
* @return height
*/
@SuppressWarnings("unchecked")
private int getCachedData(String text, int width) {
SoftReference<MeasuredData> cache = measuredData.get(text);
if (cache == null) {
return -1;
}
MeasuredData md = cache.get();
if (md != null && md.textSize == this.getTextSize() && width == md.width) {
lineWidthMax = md.lineWidthMax;
contentList = (ArrayList<LINE>) md.contentList.clone();
oneLineWidth = md.oneLineWidth;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < contentList.size(); i++) {
LINE line = contentList.get(i);
sb.append(line.toString());
}
return md.measuredHeight;
} else {
return -1;
}
}
/**
* 缓存已测量的数据
*
* @param width
* @param height
*/
@SuppressWarnings("unchecked")
private void cacheData(int width, int height) {
MeasuredData md = new MeasuredData();
md.contentList = (ArrayList<LINE>) contentList.clone();
md.textSize = this.getTextSize();
md.lineWidthMax = lineWidthMax;
md.oneLineWidth = oneLineWidth;
md.measuredHeight = height;
md.width = width;
md.hashIndex = ++hashIndex;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < contentList.size(); i++) {
LINE line = contentList.get(i);
sb.append(line.toString());
}
SoftReference<MeasuredData> cache = new SoftReference<MeasuredData>(md);
measuredData.put(text.toString(), cache);
}
/**
* 用本函数代替{@link #setText(CharSequence)}
*
* @param cs
*/
public void setMText(CharSequence cs) {
text = cs;
obList.clear();
ArrayList<SpanObject> isList = new ArrayList<SpanObject>();
useDefault = false;
if (cs instanceof Spannable) {
CharacterStyle[] spans = ((Spannable) cs).getSpans(0, cs.length(), CharacterStyle.class);
for (int i = 0; i < spans.length; i++) {
int s = ((Spannable) cs).getSpanStart(spans[i]);
int e = ((Spannable) cs).getSpanEnd(spans[i]);
SpanObject iS = new SpanObject();
iS.span = spans[i];
iS.start = s;
iS.end = e;
iS.source = cs.subSequence(s, e);
isList.add(iS);
}
}
//对span进行排序,以免不同种类的span位置错乱
SpanObject[] spanArray = new SpanObject[isList.size()];
isList.toArray(spanArray);
Arrays.sort(spanArray, 0, spanArray.length, new SpanObjectComparator());
isList.clear();
for (int i = 0; i < spanArray.length; i++) {
isList.add(spanArray[i]);
}
String str = cs.toString();
for (int i = 0, j = 0; i < cs.length(); ) {
if (j < isList.size()) {
SpanObject is = isList.get(j);
if (i < is.start) {
Integer cp = str.codePointAt(i);
//支持增补字符
if (Character.isSupplementaryCodePoint(cp)) {
i += 2;
} else {
i++;
}
obList.add(new String(Character.toChars(cp)));
} else if (i >= is.start) {
obList.add(is);
j++;
i = is.end;
}
} else {
Integer cp = str.codePointAt(i);
if (Character.isSupplementaryCodePoint(cp)) {
i += 2;
} else {
i++;
}
obList.add(new String(Character.toChars(cp)));
}
}
requestLayout();
}
public void setUseDefault(boolean useDefault) {
this.useDefault = useDefault;
if (useDefault) {
this.setText(text);
this.setTextColor(textColor);
}
}
/**
* 设置行距
*
* @param lineSpacingDP 行距,单位dp
*/
public void setLineSpacingDP(int lineSpacingDP) {
this.lineSpacingDP = lineSpacingDP;
lineSpacing = dip2px(context, lineSpacingDP);
}
public void setParagraphSpacingDP(int paragraphSpacingDP) {
paragraphSpacing = dip2px(context, paragraphSpacingDP);
}
/**
* 获取行距
*
* @return 行距,单位dp
*/
public int getLineSpacingDP() {
return lineSpacingDP;
}
public void setEdtText(List<EditText> edtText) {
mEdtText = edtText;
mEdtTextParams = new ArrayList<>();
if (edtText.size() > 0) {
for (EditText editText : edtText) {
ViewGroup.LayoutParams layoutParams = new ViewGroup.MarginLayoutParams(edtText.get(0).getLayoutParams());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(layoutParams);
mEdtTextParams.add(params);
}
}
}
public void setImageView(List<ImageView> imageView) {
mImageViews = imageView;
mImageViewParams = new ArrayList<>();
if (imageView.size() > 0) {
for (ImageView imageViewa : imageView) {
ViewGroup.LayoutParams layoutParams = new ViewGroup.MarginLayoutParams(imageView.get(0).getLayoutParams());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(layoutParams);
mImageViewParams.add(params);
}
}
}
/**
* @author huangwei
* @功能: 存储Span对象及相关信息
* @2014年5月27日
* @下午5:21:37
*/
class SpanObject {
public Object span;
public int start;
public int end;
public CharSequence source;
}
/**
* @author huangwei
* @功能: 对SpanObject进行排序
* @2017年5月19日16:59:06
*/
class SpanObjectComparator implements Comparator<SpanObject> {
@Override
public int compare(SpanObject lhs, SpanObject rhs) {
return lhs.start - rhs.start;
}
}
/**
* @author huangwei
* @功能: 存储测量好的一行数据
* @2017年5月19日16:59:23
*/
class LINE {
public ArrayList<Object> line = new ArrayList<Object>();
public ArrayList<Integer> widthList = new ArrayList<Integer>();
public float height;
@Override
public String toString() {
StringBuilder sb = new StringBuilder("height:" + height + " ");
for (int i = 0; i < line.size(); i++) {
sb.append(line.get(i) + ":" + widthList.get(i));
}
return sb.toString();
}
}
/**
* @author huangwei
* @功能: 缓存的数据
* @2017年5月19日16:59:31
*/
class MeasuredData {
public int measuredHeight;
public float textSize;
public int width;
public float lineWidthMax;
public int oneLineWidth;
public int hashIndex;
ArrayList<LINE> contentList;
}
}
写完啦,第一次写博文可能会有点乱,有很多技术可能没有很完善,但是看到这个图文输入框混排的资料在网上很少,当初我也是找了很久才找到一点相关的,然后要自己研究出来,所以拿出来给大家参考参考。写的不好的话,大家多多包涵!
可怜的单休狗,快下班了哈哈哈~下次见。

网友评论