I found on GitHub a code by Nyakokishi a link that allows to justify the text in a TextView through a custom class, works perfectly for the initial purpose, but creates another problem, when the text is too large is not shown all.
Custom TextView class (All the gratitude to Droidroid for creating and Nyakokishi for great demo):
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatTextView;
import mz.ac.ucm.fgti.newsfgti.android_client.R;
/**
* Created by Droidroid on 2016/3/24.
*/
public class JustifyTextView extends AppCompatTextView {
private TextPaint mPaint;
private Layout mLayout;
// Row height
private int mTextHeight;
// Y-axis
private float mLineY;
// View content width
private int mContentWidth;
// Character spacing
private float mCharacterSpace;
// Half of the character spacing
private float mGapWidth;
// Line spacing
private int mLineSpace;
// Full-width character width
private float mSBCwith;
// Aligned
private boolean isToAlignChars;
public JustifyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.JustifyTextView);
mCharacterSpace = ta.getDimensionPixelSize(ta.getIndex(R.styleable.JustifyTextView_character_space)
, 0);
mLineSpace = ta.getDimensionPixelSize(ta.getIndex(R.styleable.JustifyTextView_line_space), 1);
isToAlignChars = ta.getBoolean(ta.getIndex(R.styleable.JustifyTextView_align_chars), true);
ta.recycle();
mGapWidth = mCharacterSpace / 2;
String text = getText().toString();
if (text.charAt(text.length() - 1) == 10) {
text = text.substring(0, text.length() - 1);
setText(text);
}
}
@Override
protected void onDraw(Canvas canvas) {
mPaint = getPaint();
mPaint.setColor(getCurrentTextColor());
mPaint.drawableState = getDrawableState();
String text = getText().toString();
mLayout = getLayout();
// layout.getLayout() has a NullPointerException in 4.4.3
if (mLayout == null) {
return;
}
Paint.FontMetrics fm = mPaint.getFontMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mTextHeight = (int) (Math.ceil(fm.descent - fm.ascent) + getLineSpacingExtra());
} else {
mTextHeight = (int) (Math.ceil(fm.descent - fm.ascent) + mLineSpace);
}
mTextHeight = (int) (mTextHeight * mLayout.getSpacingMultiplier() + mLayout.getSpacingAdd());
// The width of the view content
mContentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
// Full-width character width
mSBCwith = getSBCwidth(text);
if (mSBCwith > 0) {
if (isToAlignChars) {
drawAligmentLines(canvas, text);
} else {
drawSBCLines(canvas, text);
}
} else {
drawDBCLines(canvas, text);
}
}
private void drawSBCLines(Canvas canvas, String text) {
mLineY = getTextSize() + getPaddingTop();
float x = getPaddingLeft();
for (int i = 0; i < text.length(); i++) {
String c = String.valueOf(text.charAt(i));
if (c.equals(" ")) {
continue;
}
float cw = StaticLayout.getDesiredWidth(c, getPaint());
canvas.drawText(c, x, mLineY, getPaint());
x += cw + mCharacterSpace;
if (x + cw - mCharacterSpace > mContentWidth + getPaddingLeft() || c.equals("\n")) {
mLineY += mTextHeight;
x = getPaddingLeft();
}
// The last character in a line can squeeze
else if (x + cw > mContentWidth + getPaddingLeft()
&& x - mCharacterSpace + cw <= mContentWidth + getPaddingLeft()) {
x -= mCharacterSpace;
}
}
}
private void drawAligmentLines(Canvas canvas, String text) {
mLineY = getTextSize() + getPaddingTop();
float x = getPaddingLeft();
for (int i = 0; i < text.length(); i++) {
String c = String.valueOf(text.charAt(i));
if (c.equals(" ")) {
continue;
}
float cw = StaticLayout.getDesiredWidth(c, getPaint());
float currentGapWidth = getGapWidth(text.charAt(i));
canvas.drawText(c, x + currentGapWidth, mLineY, getPaint());
if (i < text.length() - 1) {
x += cw + 2 * currentGapWidth;
float nextCw = StaticLayout.getDesiredWidth(String.valueOf(text.charAt(i + 1)), getPaint());
float nextGapWidth = getGapWidth(text.charAt(i + 1));
if (x + nextCw + 2 * nextGapWidth > mContentWidth + getPaddingLeft() || c.equals("\n")) {
mLineY += mTextHeight;
x = getPaddingLeft();
}
}
}
}
private void drawDBCLines(Canvas canvas, String text) {
mLineY = getTextSize() + getPaddingTop();
float x = getPaddingLeft();
int lineCount = mLayout.getLineCount();
if (lineCount > 1) {
for (int i = 0; i < lineCount; i++) {
int lineStart = mLayout.getLineStart(i);
int lineEnd = mLayout.getLineEnd(i);
float lineWidth = mLayout.getLineWidth(i);
String line = text.substring(lineStart, lineEnd);
float wordSpace = (mContentWidth - lineWidth) / (getBlankCount(line) - 1);
for (int j = 0; j < line.length(); j++) {
String c = String.valueOf(line.charAt(j));
float cw = StaticLayout.getDesiredWidth(c, getPaint());
canvas.drawText(c, x, mLineY, getPaint());
if (i < lineCount - 1 && line.charAt(j) == 32) {
x += cw + wordSpace;
} else {
x += cw;
}
}
mLineY += mTextHeight;
x = getPaddingLeft();
}
} else {
int lineStart = mLayout.getLineStart(0);
int lineEnd = mLayout.getLineEnd(0);
String line = text.substring(lineStart, lineEnd);
for (int j = 0; j < line.length(); j++) {
String c = String.valueOf(line.charAt(j));
float cw = StaticLayout.getDesiredWidth(c, getPaint());
canvas.drawText(c, x, mLineY, getPaint());
x += cw;
}
}
}
private int getBlankCount(String line) {
int count = 0;
for (char c : line.toCharArray()) {
if (c == 32) {
count++;
}
}
return count;
}
private float getSBCwidth(String text) {
for (int i = 0; i < text.length(); i++) {
String c = String.valueOf(text.charAt(i));
if (Character.UnicodeBlock.of(text.charAt(i)) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {
return StaticLayout.getDesiredWidth(c, getPaint());
}
}
return -1;
}
private float getGapWidth(char c) {
float gapWidth = (mSBCwith - StaticLayout.getDesiredWidth(String.valueOf(c)
, getPaint())) / 2 + mGapWidth;
return gapWidth;
}
}
The XML file where I use the JustifyTextView (I use only on TextViews were I want it justified):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:clickable="true"
android:paddingLeft="10sp"
android:paddingRight="10sp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10sp"
android:scrollbarAlwaysDrawHorizontalTrack="false"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5sp"
android:orientation="vertical"
android:overScrollMode="never">
<mz.ac.ucm.fgti.newsfgti.android_client.view.adapters.JustifyTextView
android:id="@+id/col_titulo_detalhe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:editable="false"
android:enabled="false"
android:fontFamily="@font/ubuntu_bold"
android:gravity="center|top"
android:inputType="textMultiLine"
android:justificationMode="inter_word"
android:lineSpacingExtra="8sp"
android:text="texto para nao dar erro com JustifiedTextViee, sera substituido"
android:textColor="@color/colorPrimary"
android:textSize="24sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5sp"
android:orientation="horizontal"
android:overScrollMode="never">
<TextView
android:id="@+id/col_categoria_detalhe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5.5sp"
android:layout_weight="1"
android:editable="false"
android:enabled="false"
android:fontFamily="@font/ubuntu_light"
android:gravity="right"
android:textColor="@color/colorBlack"
android:textSize="12sp" />
<TextView
android:id="@+id/col_privilegio_detalhe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:editable="false"
android:enabled="false"
android:fontFamily="@font/ubuntu_light"
android:textColor="@color/colorBlack"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/col_data_detalhe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20sp"
android:editable="false"
android:enabled="false"
android:fontFamily="@font/ubuntu_light_italic"
android:gravity="center"
android:textColor="@color/colorBlack"
android:textSize="12sp"
android:textStyle="italic" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:overScrollMode="never">
<mz.ac.ucm.fgti.newsfgti.android_client.view.adapters.JustifyTextView
android:id="@+id/col_conteudo_detalhe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:editable="false"
android:ems="10"
android:enabled="false"
android:fontFamily="@font/ubuntu_light"
android:inputType="textMultiLine"
android:justificationMode="inter_word"
android:lineSpacingExtra="8sp"
android:paddingBottom="10sp"
android:text="texto para nao dar erro com JustifiedTextViee, sera substituido"
android:textColor="@color/colorBlack"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</RelativeLayout>
Fragment for inflate the previous XML:
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
import mz.ac.ucm.fgti.newsfgti.android_client.R;
import mz.ac.ucm.fgti.newsfgti.android_client.view.adapters.JustifyTextView;
public class TbInformacaoDetalheFragment extends Fragment {
public JustifyTextView col_titulo_detalhe;
public TextView col_privilegio_detalhe;
public TextView col_categoria_detalhe;
public TextView col_data_detalhe;
public JustifyTextView col_conteudo_detalhe;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View itemView = LayoutInflater.from(container.getContext())
.inflate(R.layout.fragment_tb_informacao_detalhe, container, false);
Bundle bundle = getActivity().getIntent().getExtras();
col_titulo_detalhe = (JustifyTextView) itemView.findViewById(R.id.col_titulo_detalhe);
col_privilegio_detalhe = (TextView) itemView.findViewById(R.id.col_privilegio_detalhe);
col_categoria_detalhe = (TextView) itemView.findViewById(R.id.col_categoria_detalhe);
col_data_detalhe = (TextView) itemView.findViewById(R.id.col_data_detalhe);
col_conteudo_detalhe = (JustifyTextView) itemView.findViewById(R.id.col_conteudo_detalhe);
col_titulo_detalhe.setText(getArguments().getString("titulo"));
//A BARRA ANTES DO TEXTO SERVE PARA DEFINIR UMA SEPARACAO VISUAL ENTRE A CATEGORIA E O PRIVILEGIO
col_privilegio_detalhe.setText("| " + getArguments().getString("privilegio"));
col_categoria_detalhe.setText(getArguments().getString("categoria"));
col_data_detalhe.setText(getArguments().getString("data"));
col_conteudo_detalhe.setText(getArguments().getString("conteudo"));
return itemView;
}
}
Note that when I use a embedded TextView the whole text is shown.
Thank you in advance.
Try this solution
Create a custom class and use this
JustifyTextView.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.widget.TextView;
public class JustifyTextView extends TextView {
private int mLineY;
private int mViewWidth;
public static final String TWO_CHINESE_BLANK = " ";
public JustifyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
TextPaint paint = getPaint();
paint.setColor(getCurrentTextColor());
paint.drawableState = getDrawableState();
mViewWidth = getMeasuredWidth();
String text = getText().toString();
mLineY = 0;
mLineY += getTextSize();
Layout layout = getLayout();
// layout.getLayout()在4.4.3出现NullPointerException
if (layout == null) {
return;
}
Paint.FontMetrics fm = paint.getFontMetrics();
int textHeight = (int) (Math.ceil(fm.descent - fm.ascent));
textHeight = (int) (textHeight * layout.getSpacingMultiplier() + layout.getSpacingAdd());
for (int i = 0; i < layout.getLineCount(); i++) {
int lineStart = layout.getLineStart(i);
int lineEnd = layout.getLineEnd(i);
float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, getPaint());
String line = text.substring(lineStart, lineEnd);
if (needScale(line) && i < layout.getLineCount() -1) {
drawScaledText(canvas, lineStart, line, width);
} else {
canvas.drawText(line, 0, mLineY, paint);
}
mLineY += textHeight;
}
}
private void drawScaledText(Canvas canvas, int lineStart, String line, float lineWidth) {
float x = 0;
if (isFirstLineOfParagraph(lineStart, line)) {
String blanks = " ";
canvas.drawText(blanks, x, mLineY, getPaint());
float bw = StaticLayout.getDesiredWidth(blanks, getPaint());
x += bw;
line = line.substring(3);
}
int gapCount = line.length() - 1;
int i = 0;
if (line.length() > 2 && line.charAt(0) == 12288 && line.charAt(1) == 12288) {
String substring = line.substring(0, 2);
float cw = StaticLayout.getDesiredWidth(substring, getPaint());
canvas.drawText(substring, x, mLineY, getPaint());
x += cw;
i += 2;
}
float d = (mViewWidth - lineWidth) / gapCount;
for (; i < line.length(); i++) {
String c = String.valueOf(line.charAt(i));
float cw = StaticLayout.getDesiredWidth(c, getPaint());
canvas.drawText(c, x, mLineY, getPaint());
x += cw + d;
}
}
private boolean isFirstLineOfParagraph(int lineStart, String line) {
return line.length() > 3 && line.charAt(0) == ' ' && line.charAt(1) == ' ';
}
private boolean needScale(String line) {
if (line == null || line.length() == 0) {
return false;
} else {
return line.charAt(line.length() - 1) != '\n';
}
}
}