关于TextView多行排版不整齐的方法:
android开发中的textview可以自动换行,但是对于显示纯英文文字来说很好用,如果夹杂了中文字符后,全角字符和半角字符混在一块儿,就会出现文字排版参差不齐,超级难看,这就需要重写textview来实现我们需要的显示方式。
TextView在显示中文的时候遵循以下规则:
1.标点符号不能显示在一行的行首和行尾,如果一个标点符号刚好在一行的行尾,该标点符号就会连同前一个字符跳到下一行显示。
2.如果遇到双符号,例如:“《》”,“()”等符号,如果一行显示不完全就会在下一行显示。
3.英文字符串和数字如果在一行显示不全,则会自动换到下一行显示。
解决方法:在标点符号后加一个空格。
一、重写TextView算法的原理
(1)、如何绘制:
对此TextView中的所有字符串使用canvas.drawText(str, x,y,mPaint)方法进行一个字、一个字的绘制。
(2)、目标:
主要的目标是解决标点带来的换行参差不齐的问题。
(3)、标点的说明:
这里要说明自己命名的几种标点名称:
单独出现的标点:“,” “。” “.”等
左侧标点:“《”“<”“{”等
右侧标点:“》”“>”“}”等
(4)、最初思考要处理的情况和解决方式:
对一行结尾的字符或者下一行开始的字符进行判断。根据标点的不同,分别进行字体间距的拉伸和压缩。
字间距压缩的情况:
正常绘制时(没有字间距的变化),如果一行绘制结束,但是在下一行的开始时,“单独出现的标点符号”被绘制到了该行的开头。即,“,”或者“。”出现在了行首的位置。
这种情况下,将上一行每两个字符之间的字间距进行压缩,使其刚好可以将本行首的标点放在上一行的行首。
字间距的拉伸的情况:
这种情况下即,“《”、“<”等标点符号出现在一行的行尾位置。那么将该行字符的字间距进行拉伸,使这些标点出现在下一行的行首。
(5)、最终的解决方案:
后来发现,只是简单的对“行尾字符”、“下一行的开始字符”进行标点的判断还不够。有的时候,几个标点会连续同时出现。
因此在进行标点判断时,要对结尾的三个字符进行判断。即:
1.一行的结尾字符;
2.下一行的开始字符;
3.下一行的第二个字符。
二、相关代码解释
因为要解决的问题为文本的绘制问题,所以这里只重写了onDraw方法。onDraw方法中:
1.首先,通过this.getText().toString()方法,获取要绘制的文本字符串;
2.其次,对字符串进行第一次循环判断,第一次判断是将字符串分行(即获取整个文本中,每一行文本开始的startIndex和结束endIndex、该行字体间距拉伸了多少和压缩了多少;
3.再次,对字符串进行第二次循环,这次循环要做的工作就是根据第一次循环获取的信息进行一个字符一个字符的绘制了。
public void drawText(ArrayList tempLineArray, String mTextStr,
Canvas canvas) {
if (tempLineArray == null || canvas == null || mTextStr == null
|| mTextStr.equals("") == true) {
return;
}
//第一次循环判断
for (int lineNum = 0; lineNum < tempLineArray.size(); lineNum++) {
LinePar linePar = tempLineArray.get(lineNum);
int start = linePar.getStart();
int end = linePar.getEnd();
float width = linePar.getWordSpaceOffset();
int lineCount = linePar.getLineCount();// 得到当前文档行数
if (lineNum > 0 && lineNum == tempLineArray.size() - 1) {
/** 之前获取到实际文本长度后,无法将其值设置为TEXTVIEW高度 **/
mBaikeTextHeight = ((lineCount) * (mLineSpace + mTextSize));
}
float lineWidth = 0;
//第二次循环判断
for (int strNum = start; strNum <= end; strNum++) {
char ch = mTextStr.charAt(strNum);
String str = null;
if (ch == '\n') {
str = "";
} else {
str = String.valueOf(ch);
}
if (str == null || str.equals("") == true) {
continue;
}
if (strNum >= start && strNum <= end && lineCount >= 1) {
canvas.drawText(str, mPaddingLeft + lineWidth, lineCount
* (mFontHeight - mOffset) + (lineCount - 1)
* (mLineSpace - 2), mPaint);
lineWidth += BaikeConstant.getWidthofString(str, mPaint);
lineWidth = lineWidth - width;
}
}
}
}
4.因为自定义TEXTVIEW是设定高度之后绘制,所以每次都需要获取到文本字符串的高度,不然则会出现两种情况:
①文字没显示完全,剩下的文字看不见了。
②文字显示完全了,可是TextView的高度太高,导致空白了一块地方。
所以,这里利用创建一个同步线程来获取到当前需要显示文本字符串的高度,然后将其高度赋值给自定义的TEXTVIEW,赋值结束后,关闭线程。
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg != null) {
b = false;
setHeight((int) mBaikeTextHeight);
System.out.println("-----2");
paint_end = false;
Log.d("textView: ", "" + getHeight());
}
}
};
class height implements Runnable {
public void run() {
while (b) {
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
// 当绘制TEXTVIEW结束并且文本不等于0
if (paint_end == true && mBaikeTextHeight != 0) {
Log.d("height", "" + mBaikeTextHeight);
// if (mBaikeTextHeight > mScreenHeight) {
Message msg = handler.obtainMessage();
msg.what = 0;
handler.sendMessage(msg);
}
} catch (Exception e) {
Log.d("height", "error:" + e.getMessage());
}
}
}
}
5.如果需要将其工具化,可以在XML文件里设置textview的行距,然后在ondraw()方法里面获取到设定的高度,通过行距和文字的高度来计算当前文本字符串的高度。
if (lineNum > 0 && lineNum == tempLineArray.size() - 1) {
/** 之前获取到实际文本长度后,没有将其值设置为TEXTVIEW高度**/
mBaikeTextHeight = ((lineCount) * (mLineSpace + mTextSize));
}
2014年12月9日,转载时,请注明转载地址!
以下有2个Demo,一个是加入线程同步来操作,一个是手动获取文本高度的。
朋友们可以根据自己需求下载使用。
Demo下载地址:
点击打开链接
点击打开链接