一、问题在哪里?
textview显示长文字时会进行自动折行,如果遇到一些特殊情况,自动折行会杯具成这个样子:

上述特殊情况包括:
1)全角/半角符号混排(一般是数字、字母、汉字混排)
2)全角/半角标点符号出现在行首时,该标点符号会连同其前一个字符跳到下一行
3)英文单词不能被折成两行
4)……
二、怎么搞?
通常有两类解决方案:
1)修改文本内容,将所有符号全角化、在标点符号前面加空格等等……
2)保持文本内容不变,在合适的位置将文本手动分成多行
本文采用第二种方案,更加通用,也最大限度的保留了原文本。
三、开始干活
3.1 “在合适的位置将文本手动分成多行”需要知道textview的实际宽度、字体大小等信息,框架如下:
1 public class TestCActivity extends Activity {
2 private TextView mText;
3
4 @Override
5 protected void onCreate(Bundle savedInstanceState) {
6 super.onCreate(savedInstanceState);
7
8 setContentView(R.layout.testc);
9
10 mText = (TextView)findViewById(R.id.txt);
11 mText.setText("本文地址http://www.cnblogs.com/goagent/p/5159125.html本文地址啊本文。地址。啊http://www.cnblogs.com/goagent/p/5159125.html");
12 mText.getViewTreeObserver().addOnGlobalLayoutListener(new OnTvGlobalLayoutListener());
13 }
14
15 private class OnTvGlobalLayoutListener implements OnGlobalLayoutListener {
16 @Override
17 public void onGlobalLayout() {
18 mText.getViewTreeObserver().removeOnGlobalLayoutListener(this);
19 final String newText = autoSplitText(mText);
20 if (!TextUtils.isEmpty(newText)) {
21 mText.setText(newText);
22 }
23 }
24 }
25
26 private String autoSplitText(final TextView tv) {
27 final String rawText = tv.getText().toString();
28 final Paint tvPaint = tv.getPaint();
29 final int tvWidth = tv.getWidth() - tv.getPaddingLeft() - tv.getPaddingRight();
30
31 //autoSplitText begin....
32 String newText = rawText;
33 //autoSplitText end....
34
35 return newText;
36 }
37 }
3.2 实现自动分割文本,简单来说就是用textview的paint逐字符测量,如果发现当前行绘制不下了,就手动加入一个换行符:
1 private String autoSplitText(final TextView tv) {
2 final String rawText = tv.getText().toString(); //原始文本
3 final Paint tvPaint = tv.getPaint(); //paint,包含字体等信息
4 final float tvWidth = tv.getWidth() - tv.getPaddingLeft() - tv.getPaddingRight(); //控件可用宽度
5
6 //将原始文本按行拆分
7 String [] rawTextLines = rawText.replaceAll("\r", "").split("\n");
8 StringBuilder sbNewText = new StringBuilder();
9 for (String rawTextLine : rawTextLines) {
10 if (tvPaint.measureText(rawTextLine) <= tvWidth) {
11 //如果整行宽度在控件可用宽度之内,就不处理了
12 sbNewText.append(rawTextLine);
13 } else {
14 //如果整行宽度超过控件可用宽度,则按字符测量,在超过可用宽度的前一个字符处手动换行
15 float lineWidth = 0;
16 for (int cnt = 0; cnt != rawTextLine.length(); ++cnt) {
17 char ch = rawTextLine.charAt(cnt);
18 lineWidth += tvPaint.measureText(String.valueOf(ch));
19 if (lineWidth <= tvWidth) {
20 sbNewText.append(ch);
21 } else {
22 sbNewText.append("\n");
23 lineWidth = 0;
24 --cnt;
25 }
26 }
27 }
28 sbNewText.append("\n");
29 }
30
31 //把结尾多余的\n去掉
32 if (!rawText.endsWith("\n")) {
33 sbNewText.deleteCharAt(sbNewText.length() - 1);
34 }
35
36 return sbNewText.toString();
37 }
3.3 话不多说,效果如下:

四、更多玩法
4.1 可以封装一个自定义的textview,直接包含自动排版换行的功能:

```
1 package cc.snser.test;
2
3 import android.content.Context;
4 import android.graphics.Paint;
5 import android.text.TextUtils;
6 import android.util.AttributeSet;
7 import android.widget.TextView;
8
9 public class AutoSplitTextView extends TextView {
10 private boolean mEnabled = true;
11
12 public AutoSplitTextView(Context context) {
13 super(context);
14 }
15
16 public AutoSplitTextView(Context context, AttributeSet attrs) {
17 super(context, attrs);
18 }
19
20 public AutoSplitTextView(Context context, AttributeSet attrs, int defStyle) {
21 super(context, attrs, defStyle);
22 }
23
24 public void setAutoSplitEnabled(boolean enabled) {
25 mEnabled = enabled;
26 }
27
28 @Override
29 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
30 if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
31 && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
32 && getWidth() > 0
33 && getHeight() > 0
34 && mEnabled) {
35 String newText = autoSplitText(this);
36 if (!TextUtils.isEmpty(newText)) {
37 setText(newText);
38 }
39 }
40 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
41 }
42
43 private String autoSplitText(final TextView tv) {
44 final String rawText = tv.getText().toString(); //原始文本
45 final Paint tvPaint = tv.getPaint(); //paint,包含字体等信息
46 final float tvWidth = tv.getWidth() - tv.getPaddingLeft() - tv.getPaddingRight(); //控件可用宽度
47
48 //将原始文本按行拆分
49 String [] rawTextLines = rawText.replaceAll("\r", “”).split("\n");
50 StringBuilder sbNewText = new StringBuilder();
51 for (String rawTextLine : rawTextLines) {
52 if (tvPaint.measureText(rawTextLine) <= tvWidth) {
53 //如果整行宽度在控件可用宽度之内,就不处理了
54 sbNewText.append(rawTextLine);
55 } else {
56 //如果整行宽度超过控件可用宽度,则按字符测量,在超过可用宽度的前一个字符处手动换行
57 float lineWidth = 0;
58 for (int cnt = 0; cnt != rawTextLine.length(); ++cnt) {
59 char ch = rawTextLine.charAt(cnt);
60 lineWidth += tvPaint.measureText(String.valueOf(ch));
61 if (lineWidth <= tvWidth) {
62 sbNewText.append(ch);
63 } else {
64 sbNewText.append("\n");
65 lineWidth = 0;
66 –cnt;
67 }
68 }
69 }
70 sbNewText.append("\n");
71 }
72
73 //把结尾多余的\n去掉
74 if (!rawText.endsWith("\n")) {
75 sbNewText.deleteCharAt(sbNewText.length() - 1);
76 }
77
78 return sbNewText.toString();
79 }
80 }
<div class="cnblogs_code_toolbar">
<span class="cnblogs_code_copy"><a title="复制代码"></a></span>
</div>
</div>
</div>
<div class="cnblogs_code">

<div id="cnblogs_code_open_8e2944ef-bf7a-4b98-bd80-4ca691a8c9cf" class="cnblogs_code_hide">
<div class="cnblogs_code_toolbar">
<span class="cnblogs_code_copy"><a title="复制代码"></a></span>
</div>
```
1 package cc.snser.test;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5
6 public class TestCActivity extends Activity {
7 private AutoSplitTextView mText;
8
9 @Override
10 protected void onCreate(Bundle savedInstanceState) {
11 super.onCreate(savedInstanceState);
12
13 setContentView(R.layout.testc);
14
15 mText = (AutoSplitTextView)findViewById(R.id.txt);
16 mText.setText("本文地址http://www.cnblogs.com/goagent/p/5159125.html本文地址啊本文。地址。啊http://www.cnblogs.com/goagent/p/5159125.html");
17 }
18 }
<div class="cnblogs_code_toolbar">
<span class="cnblogs_code_copy"><a title="复制代码"></a></span>
</div>
4.2 实现悬挂缩进

```
1 private String autoSplitText(final TextView tv, final String indent) {
2 final String rawText = tv.getText().toString(); //原始文本
3 final Paint tvPaint = tv.getPaint(); //paint,包含字体等信息
4 final float tvWidth = tv.getWidth() - tv.getPaddingLeft() - tv.getPaddingRight(); //控件可用宽度
5
6 //将缩进处理成空格
7 String indentSpace = “”;
8 float indentWidth = 0;
9 if (!TextUtils.isEmpty(indent)) {
10 float rawIndentWidth = tvPaint.measureText(indent);
11 if (rawIndentWidth < tvWidth) {
12 while ((indentWidth = tvPaint.measureText(indentSpace)) < rawIndentWidth) {
13 indentSpace += " “;
14 }
15 }
16 }
17
18 //将原始文本按行拆分
19 String [] rawTextLines = rawText.replaceAll("\r”, “”).split("\n");
20 StringBuilder sbNewText = new StringBuilder();
21 for (String rawTextLine : rawTextLines) {
22 if (tvPaint.measureText(rawTextLine) <= tvWidth) {
23 //如果整行宽度在控件可用宽度之内,就不处理了
24 sbNewText.append(rawTextLine);
25 } else {
26 //如果整行宽度超过控件可用宽度,则按字符测量,在超过可用宽度的前一个字符处手动换行
27 float lineWidth = 0;
28 for (int cnt = 0; cnt != rawTextLine.length(); ++cnt) {
29 char ch = rawTextLine.charAt(cnt);
30 //从手动换行的第二行开始,加上悬挂缩进
31 if (lineWidth < 0.1f && cnt != 0) {
32 sbNewText.append(indentSpace);
33 lineWidth += indentWidth;
34 }
35 lineWidth += tvPaint.measureText(String.valueOf(ch));
36 if (lineWidth <= tvWidth) {
37 sbNewText.append(ch);
38 } else {
39 sbNewText.append("\n");
40 lineWidth = 0;
41 –cnt;
42 }
43 }
44 }
45 sbNewText.append("\n");
46 }
47
48 //把结尾多余的\n去掉
49 if (!rawText.endsWith("\n")) {
50 sbNewText.deleteCharAt(sbNewText.length() - 1);
51 }
52
53 return sbNewText.toString();
54 }
<div class="cnblogs_code_toolbar">
<span class="cnblogs_code_copy"><a title="复制代码"></a></span>
</div>
</div>
</div>
调用方式:
<span class="cnblogs_code">autoSplitText(tv, “1、”);</span>
悬挂缩进效果:

转自:_http://www.cnblogs.com/snser/p/5159125.html]_
<div class="title">
## Android中 TextView 加载 混合字符 自动换行解决方案
</div>
<div class="content">
昨天测试提看一个bug,如下:
【3.1.0】当实勘员点评由中文和数字组成时,第一个中文后会自动换行
最终解决办法为加入这个方法:
<div class="cnblogs_code">

<div id="cnblogs_code_open_b676ddb2-cf37-4729-81b1-6fbaa6f0745c" class="cnblogs_code_hide">
<div class="top-box hide">
</div>
```
private String autoSplitText(final TextView tv) {
final String rawText = tv.getText().toString(); //原始文本
final Paint tvPaint = tv.getPaint(); //paint,包含字体等信息
final float tvWidth = tv.getWidth() - tv.getPaddingLeft() - tv.getPaddingRight(); //控件可用宽度
//将原始文本按行拆分
String[] rawTextLines = rawText.replaceAll("\r", "").split("\n");
StringBuilder sbNewText = new StringBuilder();
for (String rawTextLine : rawTextLines) {
if (tvPaint.measureText(rawTextLine) <= tvWidth) {
//如果整行宽度在控件可用宽度之内,就不处理了
sbNewText.append(rawTextLine);
} else {
//如果整行宽度超过控件可用宽度,则按字符测量,在超过可用宽度的前一个字符处手动换行
float lineWidth = 0;
for (int cnt = 0; cnt != rawTextLine.length(); ++cnt) {
char ch = rawTextLine.charAt(cnt);
lineWidth += tvPaint.measureText(String.valueOf(ch));
if (lineWidth <= tvWidth) {
sbNewText.append(ch);
} else {
sbNewText.append("\n");
lineWidth = 0;
--cnt;
}
}
}
sbNewText.append("\n");
}
//把结尾多余的\n去掉
if (!rawText.endsWith("\n")) {
sbNewText.deleteCharAt(sbNewText.length() - 1);
}
return sbNewText.toString();
}
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
加载完调用:
<div class="cnblogs_code">

<div id="cnblogs_code_open_f0bb6e5e-2c9c-43fb-97bd-a567fd48c148" class="cnblogs_code_hide">
<div class="top-box hide">
</div>
```
tvDes.setText(desc.getSurvey_conclusion()); tvDes.setText(autoSplitText(tvDes));
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
如果出现不正常的问题,再换种方式调用,:
<div class="cnblogs_code">

<div id="cnblogs_code_open_f9e0301e-16f5-43d7-b89a-7fc4725cbb4a" class="cnblogs_code_hide">
<div class="top-box hide">
</div>
```
ViewTreeObserver observer = tvDes.getViewTreeObserver();
observer.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
tvDes.setText(autoSplitText(tvDes));
}
});
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
就可以了。
💬 评论