android textview 自動換行 整齊排版
阿新 • • 發佈:2019-02-05
在網上找了很久的程式碼終於找到了,經過測試,可以使用,先記錄下來,以便以後使用。先上實驗的效果圖
圖上有兩個textview,不同之處請看下文。
以下是轉載的原文:
一、問題在哪裡?
textview顯示長文字時會進行自動折行,如果遇到一些特殊情況,自動折行會杯具成這個樣子:
上述特殊情況包括:
1)全形/半形符號混排(一般是數字、字母、漢字混排)
2)全形/半形標點符號出現在行首時,該標點符號會連同其前一個字元跳到下一行
3)英文單詞不能被折成兩行
4)......
二、怎麼搞?
通常有兩類解決方案:
1)修改文字內容,將所有符號全形化、在標點符號前面加空格等等……
2)保持文字內容不變,在合適的位置將文字手動分成多行
本文采用第二種方案,更加通用,也最大限度的保留了原文字。
三、開始幹活
3.1 “在合適的位置將文字手動分成多行”需要知道textview的實際寬度、字型大小等資訊,框架如下:
3.2 實現自動分割文字,簡單來說就是用textview的paint逐字元測量,如果發現當前行繪製不下了,就手動加入一個換行符:public class TestCActivity extends Activity { private TextView mText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.testc); mText = (TextView)findViewById(R.id.txt); mText.setText("本文地址http://www.cnblogs.com/goagent/p/5159125.html本文地址啊本文。地址。啊http://www.cnblogs.com/goagent/p/5159125.html"); mText.getViewTreeObserver().addOnGlobalLayoutListener(new OnTvGlobalLayoutListener()); } private class OnTvGlobalLayoutListener implements OnGlobalLayoutListener { @Override public void onGlobalLayout() { mText.getViewTreeObserver().removeOnGlobalLayoutListener(this); final String newText = autoSplitText(mText); if (!TextUtils.isEmpty(newText)) { mText.setText(newText); } } } private String autoSplitText(final TextView tv) { final String rawText = tv.getText().toString(); final Paint tvPaint = tv.getPaint(); final int tvWidth = tv.getWidth() - tv.getPaddingLeft() - tv.getPaddingRight(); //autoSplitText begin.... String newText = rawText; //autoSplitText end.... return newText; } }
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(); }
3.3 話不多說,效果如下:
四、更多玩法
4.1 可以封裝一個自定義的textview,直接包含自動排版換行的功能:
package cc.snser.test;
import android.content.Context;
import android.graphics.Paint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.TextView;
public class AutoSplitTextView extends TextView {
private boolean mEnabled = true;
public AutoSplitTextView(Context context) {
super(context);
}
public AutoSplitTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AutoSplitTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setAutoSplitEnabled(boolean enabled) {
mEnabled = enabled;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
&& getWidth() > 0
&& getHeight() > 0
&& mEnabled) {
String newText = autoSplitText(this);
if (!TextUtils.isEmpty(newText)) {
setText(newText);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
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();
}
}
View AutoSplitTextView.java
TestActivity程式碼:
package cc.snser.test;
import android.app.Activity;
import android.os.Bundle;
public class TestCActivity extends Activity {
private AutoSplitTextView mText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.testc);
mText = (AutoSplitTextView)findViewById(R.id.txt);
mText.setText("本文地址http://www.cnblogs.com/goagent/p/5159125.html本文地址啊本文。地址。啊http://www.cnblogs.com/goagent/p/5159125.html");
}
}
xml程式碼:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical" >
<cc.snser.test.AutoSplitTextView
android:id="@+id/txt"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="11dp"
android:layout_marginLeft="11dp"
android:layout_marginRight="11dp"
android:background="@android:color/holo_blue_light"
android:textSize="20sp"
android:textColor="@android:color/black" />
</LinearLayout>
4.2 實現懸掛縮排
private String autoSplitText(final TextView tv, final String indent) {
final String rawText = tv.getText().toString(); //原始文字
final Paint tvPaint = tv.getPaint(); //paint,包含字型等資訊
final float tvWidth = tv.getWidth() - tv.getPaddingLeft() - tv.getPaddingRight(); //控制元件可用寬度
//將縮排處理成空格
String indentSpace = "";
float indentWidth = 0;
if (!TextUtils.isEmpty(indent)) {
float rawIndentWidth = tvPaint.measureText(indent);
if (rawIndentWidth < tvWidth) {
while ((indentWidth = tvPaint.measureText(indentSpace)) < rawIndentWidth) {
indentSpace += " ";
}
}
}
//將原始文字按行拆分
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);
//從手動換行的第二行開始,加上懸掛縮排
if (lineWidth < 0.1f && cnt != 0) {
sbNewText.append(indentSpace);
lineWidth += indentWidth;
}
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();
}
呼叫方式:
autoSplitText(tv, "1、");
懸掛縮排效果:
注意:得在layout的xml裡面用AutoSplitTextView,不要用原生的TextView了,否則就會是文章開始時那張效果圖顯示的那樣。