先看下效果图:
上面是MTextView,下面是默认的TextView。
一、原因
用最简单的全英文句子为例,如果有一个很长的单词,这一行剩余的空间显示不下了,那么规则就是不打断单词,而是把整个单词丢到下一行开始显示。这样本来没有错。一是咱们中国人都是方块字,怎么都放得下,不存在英文的这个问题。所以不习惯那个排版。二是如果TextView里面有图片,如图,
二、解决方法
最简单的就是表情之间加空格,如果不想这么做,就只有自己来画啦。
先给初学的朋友解释一下View绘制的流程,首先是onMeasure(int widthMeasureSpec, int heightMeasureSpec),onMeasure执行的时候,就是父View在问你,小朋友,你要占多大的地儿呀?当然,问你的时候,会给你个限制条件,就是那两参数,以widthMeasureSpec为例,这参数不能直接用,得先拆开,用int widthMode = MeasureSpec.getMode(widthMeasureSpec) 和 int widthSize =
MeasureSpec.getSize(widthMeasureSpec);widthMode就三种情况:
MeasureSpec.EXACTLY:你就widthSize那么宽就行了。
MeasureSpec.AT_MOST:你最多只能widthSize那么宽。
MeasureSpec.UNSPECIFIED:未指定,你爱多宽多宽。
当然,其实这只父View给你的建议,遵不遵守你自己看着办,但是自己乱来导致显示不全就不是父View的错了。
最终你听取了建议,思量了一番,觉得自己应该有width那么宽,height那么高,最后就得用setMeasuredDimension(width, height)这个函数真正确定自己的高宽。然后onMeasure()的工作就完了。
然后就是onDraw(Canvas canvas),这个就简单了,canvas就是父View给的一块画布,爱在上面画啥都行,比如写个字drawText(String text,float
x, float y,
Paint paint),
text是要写的字,paint是写字的笔,值得注意的是x,y坐标是相对于你自己这一小块画布的左上角的。最左上就是0,0右下是width,height
上代码
/**
* @功能 图文混排TextView,请使用{@link #setMText(CharSequence)}
* @author huangwei
* @2014年5月27日
* @下午5:29:27
*/
public class MTextView extends TextView
{
private Context context;
/**
* 用于测量字符宽度
*/
private Paint paint = new Paint();
private int textColor = Color.BLACK;
//行距
private float lineSpacing;
private int lineSpacingDP = 2;
// private float lineSpacingMult = 0.5f;
/**
* 最大宽度
*/
private int maxWidth;
/**
* 只有一行时的宽度
*/
private int oneLineWidth = -1;
/**
* 已绘的行中最宽的一行的宽度
*/
private float lineWidthMax = -1;
/**
* 存储当前文本内容,每个item为一个字符或者一个ImageSpan
*/
private ArrayList obList = new ArrayList();
/**
* 是否使用默认{@link #onMeasure(int, int)}和{@link #onDraw(Canvas)}
*/
private boolean useDefault = false;
/**
* 存储当前文本内容,每个item为一行
*/
ArrayList contentList = new ArrayList();
/**
* 缓存测量过的数据
*/
private static HashMap> measuredData = new HashMap>();
private static int hashIndex = 0;
private CharSequence text = "";
/**
* 最小高度
*/
private int minHeight;
/**
* 用以获取屏幕高宽
*/
private DisplayMetrics displayMetrics;
public MTextView(Context context)
{
super(context);
this.context = context;
paint.setAntiAlias(true);
lineSpacing = dip2px(context, lineSpacingDP);
minHeight = dip2px(context, 30);
displayMetrics = new DisplayMetrics();
}
public MTextView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.context = context;
paint.setAntiAlias(true);
lineSpacing = dip2px(context, lineSpacingDP);
minHeight = dip2px(context, 30);
displayMetrics = new DisplayMetrics();
}
@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:
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
width = displayMetrics.widthPixels;
break;
default:
break;
}
if (maxWidth > 0)
width = Math.min(width, maxWidth);
paint.setTextSize(this.getTextSize());
paint.setColor(textColor);
int realHeight = measureContentHeight((int) 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);
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas)
{
if (useDefault)
{
super.onDraw(canvas);
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 (int i = 0; i0)
{
return cachedHeight;
}
// 已绘的宽度
float obWidth = 0;
float obHeight = 0;
float textSize = this.getTextSize();
//行高
float lineHeight = textSize;
//计算出的所需高度
float height = lineSpacing;
int leftPadding = getCompoundPaddingLeft();
int rightPadding = getCompoundPaddingRight();
float drawedWidth = 0;
width = width - leftPadding - rightPadding;
oneLineWidth = -1;
contentList.clear();
StringBuilder sb;
LINE line = new LINE();
for (int i = 0; ilineHeight)
lineHeight = obHeight;
}
//这一行满了,存入contentList,新起一行
if (width - drawedWidthlineWidthMax)
{
lineWidthMax = drawedWidth;
}
drawedWidth = 0;
height += line.height + lineSpacing;
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 (line != null && line.line.size() > 0)
{
contentList.add(line);
height += lineHeight + lineSpacing;
}
if (contentList.size()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) md.contentList.clone();
oneLineWidth = md.oneLineWidth;
StringBuilder sb = new StringBuilder();
for(int i=0;i) 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 cache = new SoftReference(md);
measuredData.put(text.toString(),cache);
}
/**
* 用本函数代替{@link #setText(CharSequence)}
* @param cs
*/
public void setMText(CharSequence cs)
{
text = cs;
obList.clear();
// contentList.clear();
ArrayList isList = new ArrayList();
useDefault = false;
if (cs instanceof SpannableString)
{
SpannableString ss = (SpannableString) cs;
ImageSpan[] imageSpans = ss.getSpans(0, ss.length(), ImageSpan.class);
for (int i = 0; i = is.start)
{
obList.add(is.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);
}
}
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);
}
/**
* @功能: 存储ImageSpan及其开始结束位置
* @author huangwei
* @2014年5月27日
* @下午5:21:37
*/
class IS
{
public ImageSpan is;
public int start;
public int end;
}
/**
* @功能: 存储测量好的一行数据
* @author huangwei
* @2014年5月27日
* @下午5:22:12
*/
class LINE
{
public ArrayList line = new ArrayList();
public ArrayList widthList = new ArrayList();
public int height;
@Override
public String toString()
{
StringBuilder sb = new StringBuilder("height:"+height+"");
for(int i=0;i contentList;
public int oneLineWidth;
public int hashIndex;
}
}
为方便在ListView中使用(ListView反复上下滑动会多次重新onMeasure),加了缓存,相同的情况下可以不用重复在测量一次。
对于SpannableString,只支持了ImageSpan,有其它需要者可自行扩展
Demo:http://download.csdn.net/detail/yellowcath/7421147
或https://github.com/yellowcath/MTextView.git