android textview 自動換行 整齊排版,androidtextview
一、問題在哪裡?
textview顯示長文字時會進行自動折行,如果遇到一些特殊情況,自動折行會杯具成這個樣子:

上述特殊情況包括:
1)全角/半角符號混排(一般是數字、字母、漢字混排)
2)全角/半角標點符號出現在行首時,該標點符號會連同其前一個字符跳到下一行
3)英文單詞不能被折成兩行
4)......
[轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5159125.html]
二、怎麼搞?
通常有兩類解決方案:
1)修改文本內容,將所有符號全角化、在標點符號前面加空格等等……
2)保持文本內容不變,在合適的位置將文本手動分成多行
本文采用第二種方案,更加通用,也最大限度的保留了原文本。
[轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5159125.html]
三、開始干活
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 話不多說,效果如下:

[轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5159125.html]
四、更多玩法
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 }
View AutoSplitTextView.java

![]()
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 }
View TestCActivity.java

![]()
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:background="@android:color/white"
6 android:orientation="vertical" >
7
8 <cc.snser.test.AutoSplitTextView
9 android:id="@+id/txt"
10 android:layout_width="match_parent"
11 android:layout_height="200dp"
12 android:layout_marginTop="11dp"
13 android:layout_marginLeft="11dp"
14 android:layout_marginRight="11dp"
15 android:background="@android:color/holo_blue_light"
16 android:textSize="20sp"
17 android:textColor="@android:color/black" />
18
19 </LinearLayout>
View testc.xml
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 }
View Code
調用方式:
autoSplitText(tv, "1、");
懸掛縮進效果:

[轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5159125.html]