Android TextView证明文本

你如何得到一个TextView的文本是正当的(与文本在左侧和右侧平齐)?

我找到了一个可能的解决方案在这里,但它不起作用(即使你改变垂直中心为center_vertical等)。

552769 次浏览

我不认为Android支持完全的理由。

更新2018-01-01: Android 8.0+支持使用TextView进行辩护模式

@CommonsWare的答案是正确的。Android 8.0+确实支持“完全论证”(或简单的“论证”,因为它有时被含糊地提到)。

Android还支持“左/右文本对齐”。有关区别,请参阅wikipedia关于的理由的文章。许多人认为“对齐”的概念包括完全对齐以及左/右文本对齐,这是他们在想要进行左/右文本对齐时最终搜索的内容。这个答案解释了如何实现左/右文本对齐。

可以实现左/右文本对齐(而不是问题所询问的完全对齐)。为了演示,我将使用一个基本的2列表单(标签在左列,文本字段在右列)作为示例。在本例中,左列标签中的文本将右对齐,因此它们将与右列中的文本字段齐平。

在XML布局中,你可以得到的TextView元素本身(左列)对齐到右边,通过添加以下属性在所有的TextView:

<TextView
...
android:layout_gravity="center_vertical|end">
...
</TextView>

但是,如果文本换行到多行,文本仍将在TextView内保持左对齐。添加以下属性使实际文本在TextView内右对齐(不规则左对齐):

<TextView
...
android:gravity="end">
...
</TextView>

因此,重力属性指定如何对齐TextView内部的文本layout_gravity指定如何对齐/布局TextView元素本身。

为了在android中证明文本,我使用了WebView

    setContentView(R.layout.main);


WebView view = new WebView(this);
view.setVerticalScrollBarEnabled(false);


((LinearLayout)findViewById(R.id.inset_web_view)).addView(view);


view.loadData(getString(R.string.hello), "text/html; charset=utf-8", "utf-8");

和html。

<string name="hello">
<![CDATA[
<html>
<head></head>
<body style="text-align:justify;color:gray;background-color:black;">
Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Nunc pellentesque, urna
nec hendrerit pellentesque, risus massa
</body>
</html>
]]>
</string>

我还不能上传图片来证明它,但“它对我有用”。

我认为有两种选择:

  • 使用像Pango这样的东西,通过NDK专门处理这一点,并将文本渲染到OpenGL或其他表面。

  • 使用Paint.measureText()和朋友获得单词的长度,并在自定义视图中手动在画布上布局它们。

我是这么做的,我认为是我能做到的最优雅的方式。有了这个解决方案,你只需要在你的布局中做的事情是:

  • 添加一个额外的xmlns声明
  • 将你的TextViews源文本命名空间从android改为你的新命名空间
  • x.y.z.JustifiedTextView替换你的__abc0

这是代码。在我的手机上运行得非常好(Galaxy Nexus Android 4.0.2, Galaxy Teos Android 2.1)。当然,您可以随意用您的包名替换我的包名。

/资产/ justified_textview.css:

body {
font-size: 1.0em;
color: rgb(180,180,180);
text-align: justify;
}


@media screen and (-webkit-device-pixel-ratio: 1.5) {
/* CSS for high-density screens */
body {
font-size: 1.05em;
}
}


@media screen and (-webkit-device-pixel-ratio: 2.0) {
/* CSS for extra high-density screens */
body {
font-size: 1.1em;
}
}

/ / attrs.xml res /值:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="JustifiedTextView">
<attr name="text" format="reference" />
</declare-styleable>
</resources>

/ res / layout / test.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myapp="http://schemas.android.com/apk/res/net.bicou.myapp"
android:layout_width="match_parent"
android:layout_height="match_parent">


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >


<net.bicou.myapp.widget.JustifiedTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
myapp:text="@string/surv1_1" />


</LinearLayout>
</ScrollView>

/ src / net/bicou/myapp/widget/JustifiedTextView.java:

package net.bicou.myapp.widget;


import net.bicou.myapp.R;


import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.webkit.WebView;


public class JustifiedTextView extends WebView {
public JustifiedTextView(final Context context) {
this(context, null, 0);
}


public JustifiedTextView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}


public JustifiedTextView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);


if (attrs != null) {
final TypedValue tv = new TypedValue();
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.JustifiedTextView, defStyle, 0);
if (ta != null) {
ta.getValue(R.styleable.JustifiedTextView_text, tv);


if (tv.resourceId > 0) {
final String text = context.getString(tv.resourceId).replace("\n", "<br />");
loadDataWithBaseURL("file:///android_asset/",
"<html><head>" +
"<link rel=\"stylesheet\" type=\"text/css\" href=\"justified_textview.css\" />" +
"</head><body>" + text + "</body></html>",


"text/html", "UTF8", null);
setTransparentBackground();
}
}
}
}


public void setTransparentBackground() {
try {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
} catch (final NoSuchMethodError e) {
}


setBackgroundColor(Color.TRANSPARENT);
setBackgroundDrawable(null);
setBackgroundResource(0);
}
}

我们需要将渲染设置为软件,以便在Android 3+上获得透明的背景。因此,老版本的Android会出现“试接”现象。

希望这能有所帮助!

PS:请不要认为这可能是有用的添加到您的整个活动在Android 3+上,以获得预期的行为:
android:hardwareAccelerated="false" < / p >

FILL_HORIZONTAL等价于CENTER_HORIZONTAL。 你可以在textview的源代码中看到这个代码片段:

case Gravity.CENTER_HORIZONTAL:
case Gravity.FILL_HORIZONTAL:
return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
getCompoundPaddingLeft() - getCompoundPaddingRight())) /
getHorizontalFadingEdgeLength();

Android目前还不支持完全的理由。我们可以使用Webview和证明HTML而不是使用textview。它工作得很好。如果你们不清楚,可以问我:)

我写了我自己的类来解决这个问题,这就是 你只需要调用带有两个参数

的static justify函数
  1. 文本视图对象
  2. 内容宽度(文本视图的总宽度)

/ / MainActivity

package com.fawad.textjustification;
import android.app.Activity;
import android.database.Cursor;
import android.graphics.Point;
import android.graphics.Typeface;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Gravity;
import android.view.Menu;
import android.widget.TextView;


public class MainActivity extends Activity {
static Point size;
static float density;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


Display display = getWindowManager().getDefaultDisplay();
size=new Point();
DisplayMetrics dm=new DisplayMetrics();
display.getMetrics(dm);
density=dm.density;
display.getSize(size);




TextView tv=(TextView)findViewById(R.id.textView1);
Typeface typeface=Typeface.createFromAsset(this.getAssets(), "Roboto-Medium.ttf");
tv.setTypeface(typeface);
tv.setLineSpacing(0f, 1.2f);
tv.setTextSize(10*MainActivity.density);


//some random long text
String myText=getResources().getString(R.string.my_text);


tv.setText(myText);
TextJustification.justify(tv,size.x);




}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}


}

/ / TextJustificationClass

package com.fawad.textjustification;


import java.util.ArrayList;


import android.graphics.Paint;
import android.text.TextUtils;
import android.widget.TextView;


public class TextJustification {


public static void justify(TextView textView,float contentWidth) {
String text=textView.getText().toString();
Paint paint=textView.getPaint();


ArrayList<String> lineList=lineBreak(text,paint,contentWidth);


textView.setText(TextUtils.join(" ", lineList).replaceFirst("\\s", ""));
}




private static ArrayList<String> lineBreak(String text,Paint paint,float contentWidth){
String [] wordArray=text.split("\\s");
ArrayList<String> lineList = new ArrayList<String>();
String myText="";


for(String word:wordArray){
if(paint.measureText(myText+" "+word)<=contentWidth)
myText=myText+" "+word;
else{
int totalSpacesToInsert=(int)((contentWidth-paint.measureText(myText))/paint.measureText(" "));
lineList.add(justifyLine(myText,totalSpacesToInsert));
myText=word;
}
}
lineList.add(myText);
return lineList;
}


private static String justifyLine(String text,int totalSpacesToInsert){
String[] wordArray=text.split("\\s");
String toAppend=" ";


while((totalSpacesToInsert)>=(wordArray.length-1)){
toAppend=toAppend+" ";
totalSpacesToInsert=totalSpacesToInsert-(wordArray.length-1);
}
int i=0;
String justifiedText="";
for(String word:wordArray){
if(i<totalSpacesToInsert)
justifiedText=justifiedText+word+" "+toAppend;


else
justifiedText=justifiedText+word+toAppend;


i++;
}


return justifiedText;
}


}

/ / XML

 <RelativeLayout 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"


tools:context=".MainActivity"
>






<ScrollView
android:id="@+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>


<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"


>
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
</LinearLayout>
</ScrollView>


</RelativeLayout>

有一个自定义视图来解决这个问题,这个自定义文本视图支持有理文本视图。

战利品在这个:JustifiedTextView

import java.util.ArrayList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.view.View;


public class JustifiedTextView extends View {
String text;
ArrayList<Line> linesCollection = new ArrayList<Line>();
TextPaint textPaint;
Typeface font;
int textColor;
float textSize = 42f, lineHeight = 57f, wordSpacing = 15f, lineSpacing = 15f;
float onBirim, w, h;
float leftPadding, rightPadding;


public JustifiedTextView(Context context, String text) {
super(context);
this.text = text;
init();
}


private void init() {
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textColor = Color.BLACK;
}


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);


if (font != null) {
font = Typeface.createFromAsset(getContext().getAssets(), "font/Trykker-Regular.ttf");
textPaint.setTypeface(font);
}
textPaint.setColor(textColor);


int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
w = resolveSizeAndState(minw, widthMeasureSpec, 1);
h = MeasureSpec.getSize(widthMeasureSpec);


onBirim = 0.009259259f * w;
lineHeight = textSize + lineSpacing;
leftPadding = 3 * onBirim + getPaddingLeft();
rightPadding = 3 * onBirim + getPaddingRight();


textPaint.setTextSize(textSize);


wordSpacing = 15f;
Line lineBuffer = new Line();
this.linesCollection.clear();
String[] lines = text.split("\n");
for (String line : lines) {
String[] words = line.split(" ");
lineBuffer = new Line();
float lineWidth = leftPadding + rightPadding;
float totalWordWidth = 0;
for (String word : words) {
float ww = textPaint.measureText(word) + wordSpacing;
if (lineWidth + ww + (lineBuffer.getWords().size() * wordSpacing) > w) {// is
lineBuffer.addWord(word);
totalWordWidth += textPaint.measureText(word);
lineBuffer.setSpacing((w - totalWordWidth - leftPadding - rightPadding) / (lineBuffer.getWords().size() - 1));
this.linesCollection.add(lineBuffer);
lineBuffer = new Line();
totalWordWidth = 0;
lineWidth = leftPadding + rightPadding;
} else {
lineBuffer.setSpacing(wordSpacing);
lineBuffer.addWord(word);
totalWordWidth += textPaint.measureText(word);
lineWidth += ww;
}
}
this.linesCollection.add(lineBuffer);
}
setMeasuredDimension((int) w, (int) ((this.linesCollection.size() + 1) * lineHeight + (10 * onBirim)));
}


@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawLine(0f, 10f, getMeasuredWidth(), 10f, textPaint);
float x, y = lineHeight + onBirim;
for (Line line : linesCollection) {
x = leftPadding;
for (String s : line.getWords()) {
canvas.drawText(s, x, y, textPaint);
x += textPaint.measureText(s) + line.spacing;
}
y += lineHeight;
}
}


public String getText() {
return text;
}


public void setText(String text) {
this.text = text;
}


public Typeface getFont() {
return font;
}


public void setFont(Typeface font) {
this.font = font;
}


public float getLineHeight() {
return lineHeight;
}


public void setLineHeight(float lineHeight) {
this.lineHeight = lineHeight;
}


public float getLeftPadding() {
return leftPadding;
}


public void setLeftPadding(float leftPadding) {
this.leftPadding = leftPadding;
}


public float getRightPadding() {
return rightPadding;
}


public void setRightPadding(float rightPadding) {
this.rightPadding = rightPadding;
}


public void setWordSpacing(float wordSpacing) {
this.wordSpacing = wordSpacing;
}


public float getWordSpacing() {
return wordSpacing;
}


public float getLineSpacing() {
return lineSpacing;
}


public void setLineSpacing(float lineSpacing) {
this.lineSpacing = lineSpacing;
}


class Line {
ArrayList<String> words = new ArrayList<String>();
float spacing = 15f;


public Line() {
}


public Line(ArrayList<String> words, float spacing) {
this.words = words;
this.spacing = spacing;
}


public void setSpacing(float spacing) {
this.spacing = spacing;
}


public float getSpacing() {
return spacing;
}


public void addWord(String s) {
words.add(s);
}


public ArrayList<String> getWords() {
return words;
}
}
}

将上面的类添加到你的src文件夹中,并使用这个示例代码添加到你的布局中:

JustifiedTextView jtv= new JustifiedTextView(getApplicationContext(), "Lorem ipsum dolor sit amet... ");
LinearLayout place = (LinearLayout) findViewById(R.id.book_profile_content);
place.addView(jtv);

更新

我们为此创建了一个简单的类。目前有两种方法可以达到你想要的效果。两者都需要没有WEBVIEW支持SPANNABLES

图书馆: https://github.com/bluejamesbond/TextJustify-Android

支持: Android 2.0到5。X

设置

// Please visit Github for latest setup instructions.

截图

 compare .png

XML布局:声明WebView而不是TextView

<WebView
android:id="@+id/textContent"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />

Java代码:设置文本数据到WebView

WebView view = (WebView) findViewById(R.id.textContent);
String text;
text = "<html><body><p align=\"justify\">";
text+= "This is the text will be justified when displayed!!!";
text+= "</p></body></html>";
view.loadData(text, "text/html", "utf-8");
这可能会解决你的问题。

我写了一个基于本地textview的小部件来做到这一点。

< a href = " https://github.com/ufo22940268/android-justifiedtextview " > github < / >

.

你可以在github中使用jusfiedtextview for Android项目。这是一个自定义视图,为您模拟正确的文本。它支持Android 2.0+和从右向左的语言。 enter image description here

虽然仍然不是完整的对齐文本,但从API 23开始,您现在可以使用android:breakStrategy="balanced"来平衡行长度

http://developer.android.com/reference/android/widget/TextView.html#attr_android:breakStrategy

TextViewAndroid O提供了完整的理由(新的排版对齐)本身。
你只需要这样做:

芬兰湾的科特林

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
textView.justificationMode = JUSTIFICATION_MODE_INTER_WORD
}

Java

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
textView.setJustificationMode(JUSTIFICATION_MODE_INTER_WORD);
}

XML

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:justificationMode="inter_word" />

默认值是JUSTIFICATION_MODE_NONE (xml中的none)。

我找到了一个办法来解决这个问题,但是这个办法可能不是很优雅,但是效果还不错。

其原理是将每一行的空格替换为固定宽度的ImageSpan(颜色为透明)。

public static void justify(final TextView textView) {


final AtomicBoolean isJustify = new AtomicBoolean(false);


final String textString = textView.getText().toString();


final TextPaint textPaint = textView.getPaint();


final SpannableStringBuilder builder = new SpannableStringBuilder();


textView.post(new Runnable() {
@Override
public void run() {


if (!isJustify.get()) {


final int lineCount = textView.getLineCount();
final int textViewWidth = textView.getWidth();


for (int i = 0; i < lineCount; i++) {


int lineStart = textView.getLayout().getLineStart(i);
int lineEnd = textView.getLayout().getLineEnd(i);


String lineString = textString.substring(lineStart, lineEnd);


if (i == lineCount - 1) {
builder.append(new SpannableString(lineString));
break;
}


String trimSpaceText = lineString.trim();
String removeSpaceText = lineString.replaceAll(" ", "");


float removeSpaceWidth = textPaint.measureText(removeSpaceText);
float spaceCount = trimSpaceText.length() - removeSpaceText.length();


float eachSpaceWidth = (textViewWidth - removeSpaceWidth) / spaceCount;


SpannableString spannableString = new SpannableString(lineString);
for (int j = 0; j < trimSpaceText.length(); j++) {
char c = trimSpaceText.charAt(j);
if (c == ' ') {
Drawable drawable = new ColorDrawable(0x00ffffff);
drawable.setBounds(0, 0, (int) eachSpaceWidth, 0);
ImageSpan span = new ImageSpan(drawable);
spannableString.setSpan(span, j, j + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}


builder.append(spannableString);
}


textView.setText(builder);
isJustify.set(true);
}
}
});
}
我把代码放在GitHub上: https://github.com/twiceyuan/TextJustification < / p >

概述:

Overview

参见这里在github

只需导入两个文件“textjustify yutils .java”和“TextViewEx.java”在你的项目。

public class TextJustifyUtils {
// Please use run(...) instead
public static void justify(TextView textView) {
Paint paint = new Paint();


String[] blocks;
float spaceOffset = 0;
float textWrapWidth = 0;


int spacesToSpread;
float wrappedEdgeSpace;
String block;
String[] lineAsWords;
String wrappedLine;
String smb = "";
Object[] wrappedObj;


// Pull widget properties
paint.setColor(textView.getCurrentTextColor());
paint.setTypeface(textView.getTypeface());
paint.setTextSize(textView.getTextSize());


textWrapWidth = textView.getWidth();
spaceOffset = paint.measureText(" ");
blocks = textView.getText().toString().split("((?<=\n)|(?=\n))");


if (textWrapWidth < 20) {
return;
}


for (int i = 0; i < blocks.length; i++) {
block = blocks[i];


if (block.length() == 0) {
continue;
} else if (block.equals("\n")) {
smb += block;
continue;
}


block = block.trim();


if (block.length() == 0)
continue;


wrappedObj = TextJustifyUtils.createWrappedLine(block, paint,
spaceOffset, textWrapWidth);
wrappedLine = ((String) wrappedObj[0]);
wrappedEdgeSpace = (Float) wrappedObj[1];
lineAsWords = wrappedLine.split(" ");
spacesToSpread = (int) (wrappedEdgeSpace != Float.MIN_VALUE ? wrappedEdgeSpace
/ spaceOffset
: 0);


for (String word : lineAsWords) {
smb += word + " ";


if (--spacesToSpread > 0) {
smb += " ";
}
}


smb = smb.trim();


if (blocks[i].length() > 0) {
blocks[i] = blocks[i].substring(wrappedLine.length());


if (blocks[i].length() > 0) {
smb += "\n";
}


i--;
}
}


textView.setGravity(Gravity.LEFT);
textView.setText(smb);
}


protected static Object[] createWrappedLine(String block, Paint paint,
float spaceOffset, float maxWidth) {
float cacheWidth = maxWidth;
float origMaxWidth = maxWidth;


String line = "";


for (String word : block.split("\\s")) {
cacheWidth = paint.measureText(word);
maxWidth -= cacheWidth;


if (maxWidth <= 0) {
return new Object[] { line, maxWidth + cacheWidth + spaceOffset };
}


line += word + " ";
maxWidth -= spaceOffset;


}


if (paint.measureText(block) <= origMaxWidth) {
return new Object[] { block, Float.MIN_VALUE };
}


return new Object[] { line, maxWidth };
}


final static String SYSTEM_NEWLINE = "\n";
final static float COMPLEXITY = 5.12f; // Reducing this will increase
// efficiency but will decrease
// effectiveness
final static Paint p = new Paint();


public static void run(final TextView tv, float origWidth) {
String s = tv.getText().toString();
p.setTypeface(tv.getTypeface());
String[] splits = s.split(SYSTEM_NEWLINE);
float width = origWidth - 5;
for (int x = 0; x < splits.length; x++)
if (p.measureText(splits[x]) > width) {
splits[x] = wrap(splits[x], width, p);
String[] microSplits = splits[x].split(SYSTEM_NEWLINE);
for (int y = 0; y < microSplits.length - 1; y++)
microSplits[y] = justify(removeLast(microSplits[y], " "),
width, p);
StringBuilder smb_internal = new StringBuilder();
for (int z = 0; z < microSplits.length; z++)
smb_internal.append(microSplits[z]
+ ((z + 1 < microSplits.length) ? SYSTEM_NEWLINE
: ""));
splits[x] = smb_internal.toString();
}
final StringBuilder smb = new StringBuilder();
for (String cleaned : splits)
smb.append(cleaned + SYSTEM_NEWLINE);
tv.setGravity(Gravity.LEFT);
tv.setText(smb);
}


private static String wrap(String s, float width, Paint p) {
String[] str = s.split("\\s"); // regex
StringBuilder smb = new StringBuilder(); // save memory
smb.append(SYSTEM_NEWLINE);
for (int x = 0; x < str.length; x++) {
float length = p.measureText(str[x]);
String[] pieces = smb.toString().split(SYSTEM_NEWLINE);
try {
if (p.measureText(pieces[pieces.length - 1]) + length > width)
smb.append(SYSTEM_NEWLINE);
} catch (Exception e) {
}
smb.append(str[x] + " ");
}
return smb.toString().replaceFirst(SYSTEM_NEWLINE, "");
}


private static String removeLast(String s, String g) {
if (s.contains(g)) {
int index = s.lastIndexOf(g);
int indexEnd = index + g.length();
if (index == 0)
return s.substring(1);
else if (index == s.length() - 1)
return s.substring(0, index);
else
return s.substring(0, index) + s.substring(indexEnd);
}
return s;
}


private static String justifyOperation(String s, float width, Paint p) {
float holder = (float) (COMPLEXITY * Math.random());
while (s.contains(Float.toString(holder)))
holder = (float) (COMPLEXITY * Math.random());
String holder_string = Float.toString(holder);
float lessThan = width;
int timeOut = 100;
int current = 0;
while (p.measureText(s) < lessThan && current < timeOut) {
s = s.replaceFirst(" ([^" + holder_string + "])", " "
+ holder_string + "$1");
lessThan = p.measureText(holder_string) + lessThan
- p.measureText(" ");
current++;
}
String cleaned = s.replaceAll(holder_string, " ");
return cleaned;
}


private static String justify(String s, float width, Paint p) {
while (p.measureText(s) < width) {
s = justifyOperation(s, width, p);
}
return s;
}
}

而且

public class TextViewEx extends TextView {
private Paint paint = new Paint();


private String[] blocks;
private float spaceOffset = 0;
private float horizontalOffset = 0;
private float verticalOffset = 0;
private float horizontalFontOffset = 0;
private float dirtyRegionWidth = 0;
private boolean wrapEnabled = false;
int left, top, right, bottom = 0;
private Align _align = Align.LEFT;
private float strecthOffset;
private float wrappedEdgeSpace;
private String block;
private String wrappedLine;
private String[] lineAsWords;
private Object[] wrappedObj;


private Bitmap cache = null;
private boolean cacheEnabled = false;


public TextViewEx(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// set a minimum of left and right padding so that the texts are not too
// close to the side screen
// this.setPadding(10, 0, 10, 0);
}


public TextViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
// this.setPadding(10, 0, 10, 0);
}


public TextViewEx(Context context) {
super(context);
// this.setPadding(10, 0, 10, 0);
}


@Override
public void setPadding(int left, int top, int right, int bottom) {
// TODO Auto-generated method stub
super.setPadding(left + 10, top, right + 10, bottom);
}


@Override
public void setDrawingCacheEnabled(boolean cacheEnabled) {
this.cacheEnabled = cacheEnabled;
}


public void setText(String st, boolean wrap) {
wrapEnabled = wrap;
super.setText(st);
}


public void setTextAlign(Align align) {
_align = align;
}


@SuppressLint("NewApi")
@Override
protected void onDraw(Canvas canvas) {
// If wrap is disabled then,
// request original onDraw
if (!wrapEnabled) {
super.onDraw(canvas);
return;
}


// Active canas needs to be set
// based on cacheEnabled
Canvas activeCanvas = null;


// Set the active canvas based on
// whether cache is enabled
if (cacheEnabled) {


if (cache != null) {
// Draw to the OS provided canvas
// if the cache is not empty
canvas.drawBitmap(cache, 0, 0, paint);
return;
} else {
// Create a bitmap and set the activeCanvas
// to the one derived from the bitmap
cache = Bitmap.createBitmap(getWidth(), getHeight(),
Config.ARGB_4444);
activeCanvas = new Canvas(cache);
}
} else {
// Active canvas is the OS
// provided canvas
activeCanvas = canvas;
}


// Pull widget properties
paint.setColor(getCurrentTextColor());
paint.setTypeface(getTypeface());
paint.setTextSize(getTextSize());
paint.setTextAlign(_align);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);


// minus out the paddings pixel
dirtyRegionWidth = getWidth() - getPaddingLeft() - getPaddingRight();
int maxLines = Integer.MAX_VALUE;
int currentapiVersion = android.os.Build.VERSION.SDK_INT;
if (currentapiVersion >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
maxLines = getMaxLines();
}
int lines = 1;
blocks = getText().toString().split("((?<=\n)|(?=\n))");
verticalOffset = horizontalFontOffset = getLineHeight() - 0.5f; // Temp
// fix
spaceOffset = paint.measureText(" ");


for (int i = 0; i < blocks.length && lines <= maxLines; i++) {
block = blocks[i];
horizontalOffset = 0;


if (block.length() == 0) {
continue;
} else if (block.equals("\n")) {
verticalOffset += horizontalFontOffset;
continue;
}


block = block.trim();


if (block.length() == 0) {
continue;
}


wrappedObj = TextJustifyUtils.createWrappedLine(block, paint,
spaceOffset, dirtyRegionWidth);


wrappedLine = ((String) wrappedObj[0]);
wrappedEdgeSpace = (Float) wrappedObj[1];
lineAsWords = wrappedLine.split(" ");
strecthOffset = wrappedEdgeSpace != Float.MIN_VALUE ? wrappedEdgeSpace
/ (lineAsWords.length - 1)
: 0;


for (int j = 0; j < lineAsWords.length; j++) {
String word = lineAsWords[j];
if (lines == maxLines && j == lineAsWords.length - 1) {
activeCanvas.drawText("...", horizontalOffset,
verticalOffset, paint);


} else if (j == 0) {
// if it is the first word of the line, text will be drawn
// starting from right edge of textview
if (_align == Align.RIGHT) {
activeCanvas.drawText(word, getWidth()
- (getPaddingRight()), verticalOffset, paint);
// add in the paddings to the horizontalOffset
horizontalOffset += getWidth() - (getPaddingRight());
} else {
activeCanvas.drawText(word, getPaddingLeft(),
verticalOffset, paint);
horizontalOffset += getPaddingLeft();
}


} else {
activeCanvas.drawText(word, horizontalOffset,
verticalOffset, paint);
}
if (_align == Align.RIGHT)
horizontalOffset -= paint.measureText(word) + spaceOffset
+ strecthOffset;
else
horizontalOffset += paint.measureText(word) + spaceOffset
+ strecthOffset;
}


lines++;


if (blocks[i].length() > 0) {
blocks[i] = blocks[i].substring(wrappedLine.length());
verticalOffset += blocks[i].length() > 0 ? horizontalFontOffset
: 0;
i--;
}
}


if (cacheEnabled) {
// Draw the cache onto the OS provided
// canvas.
canvas.drawBitmap(cache, 0, 0, paint);
}
}
}

现在,如果你使用普通的textView,比如:

<TextView
android:id="@+id/original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lorum_ipsum" />

简单地使用

<yourpackagename.TextViewEx
android:id="@+id/changed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lorum_ipsum" />

定义一个变量并将justify设为true,

TextViewEx changed = (TextViewEx) findViewById(R.id.changed);
changed.setText(getResources().getString(R.string.lorum_ipsum),true);
< p >非常简单 我们可以在xml文件

中完成
<TextView
android:justificationMode="inter_word"
/>

你可以在xml中使用rationationmode作为inter_word。您必须记住,此属性可用于api级别26及更高级别。为此,你可以将targetApi赋值为o。完整的代码如下所示

<com.google.android.material.textview.MaterialTextView
...
android:justificationMode="inter_word"
tools:targetApi="o" />

请在8.0以下尝试此代码

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center|start"/>

在xml文件中使用此属性即可

android:justificationMode="inter_word"

Simplay我们可以使用android: justificationMode =“inter_word"

 <TextView
android:justificationMode="inter_word"
android:id="@+id/messageTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:paddingTop="15sp"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium" />