share
Stack OverflowAndroid TextView Justify Text
[+475] [23] user130076
[2009-08-18 08:32:09]
[ android text format textview justify ]
[ https://stackoverflow.com/questions/1292575/android-textview-justify-text ]

How do you get the text of a TextView to be Justified (with text flush on the left- and right- hand sides)?

I found a possible solution here [1], but it does not work (even if you change vertical-center to center_vertical, etc).

@Jimbo answer is correct definetly working for my case on inputtext and textview for language arabic from right to left input and display but for input text i had to add also gravity="right" - shareef
you can use github.com/pouriaHemmati/JustifiedTextView - Pouria Hemi
github.com/amilcar-sr/JustifiedTextView can be used - Reejesh PK
[+295] [2009-08-18 12:33:37] CommonsWare [ACCEPTED]

I do not believe Android supports full justification.

UPDATE 2018-01-01: Android 8.0+ supports justification modes with TextView [1].

[1] https://stackoverflow.com/a/42991773/115145

(5) Upon further analysis, you could give android:gravity="fill_horizontal" a shot. That is described as "Grow the horizontal size of the object if needed so it completely fills its container", but I do not know how they "grow" the text. - CommonsWare
(8) android:gravity="fill_horizontal" didn't work either. It looks like android doesn't support justification after all, oh well :) - user130076
(6) No, you can't set the property like gravity. But still you can set the justification to your text by taking webview instead of text view. You may refer to seal.io/2010/12/only-way-how-to-align-text-in-block-in.html. (Stole from stackoverflow.com/questions/5976627/…) - jcaruso
(3) @CommonsWare Now any proper way to justify text? - John R
But if we use some algorithm like in github spacing in words is horrible and in case of we view text appears after 2 seconds. So really confusing. By the way I m ur really big fan. - John R
this is possible. see answers below. - Sagar Nayak
(1) Man , I am living with heavy webview to achieve this, and believe me, my UI cries for some new stuff yet to be added to the API, coz it is damn damn slow for components like chat in a listview. - nobalG
Oreo added support for this. - natario
(1) @FarhanFarooqui: Android 8.0+ supports justification modes. - CommonsWare
(1) setJustificationMode(JUSTIFICATION_MODE_INTER_WORD); do not give perfect view :( it's shame - Viks
this works fine in most cases, but it doesnt work if you are using url (links) in your text view - qkx
@JohnR Imagine, still there is no any proper way to do it versions below 26 excpet for crooked 3rd-party libraries. Android has always proved itself crappy in such things and this is yet another example - Farid
1
[+185] [2017-03-24 05:04:13] Jaydipsinh Zala

TextView in Android O offers full justification (new typographic alignment) itself.
You just need to do this:

Kotlin

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

Default is JUSTIFICATION_MODE_NONE (none in xml).


(4) Let's hope it gets ported back to the support library then O:) - Stefan Haustein
(2) pls add library here !! - Kunal Dharaiya
(1) I couldn't find the docs for .setJustify but I found this instead, developer.android.com/reference/android/widget/… Am I missing something or did the Android team change the implementation from setJustify to setJustificationMode? - MikeOscarEcho
(1) Yes you're right @MikeOscarEcho. It(setJustify) was there earlier for Android O DP1. Thanks for pointing it out. - Jaydipsinh Zala
(2) It's still not good enough! imgur.com/a/BR8sV The right side is not straight line but jagged! :( - WindRider
(5) how to justify using XML? - Viks
(28) You can use android:justificationMode="inter_word" in xml. - Christian D
(9) API 26 required for android:justificationMode. - Bink
(1) Must be one of: LineBreaker.JUSTIFICATION_MODE_NONE, LineBreaker.JUSTIFICATION_MODE_INTER_WORD Min API 29 - Iman Marashi
It's a mess. less or small words in a line and it creates spaces between them which looks funky and bad design. - Lalit Fauzdar
I have the same warning as stated by @iman-marashi but the code does compile and a device running Android 9 did justify the text. - Mackovich
Adding the android:justificationMode="inter_word" with the center align text in layout causing the layout text cuts from the sides - Gopal Awasthi
This is specially messed up. JustificationMode is added in API 26 and the value it can accept is added in API 29 - amar
Import statement -> import android.graphics.text.LineBreaker - Dipin P J
2
[+168] [2010-05-24 17:58:36] plainjimbo

The @CommonsWare answer is correct. Android 8.0+ does support "Full Justification" (or simply "Justification", as it is sometimes ambiguously referred to).

Android also supports "Flush Left/Right Text Alignment". See the wikipedia article on Justification [1] for the distinction. Many people consider the concept of 'justification' to encompass full-justification as well as left/right text alignment, which is what they end up searching for when they want to do left/right text alignment. This answer explains how to achieve the left/right text alignment.

It is possible to achieve Flush Left/Right Text Alignment (as opposed to Full Justification, as the question is asking about). To demonstrate I will be using a basic 2-column form (labels in the left column and text fields in the right column) as an example. In this example the text in the labels in the left column will be right-aligned so they appear flush up against their text fields in the right column.

In the XML layout you can get the TextView elements themselves (the left column) to align to the right by adding the following attribute inside all of the TextViews:

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

However, if the text wraps to multiple lines, the text would still be flush left aligned inside the TextView. Adding the following attribute makes the actual text flush right aligned (ragged left) inside the TextView:

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

So the gravity attribute specifies how to align the text inside the TextView layout_gravity specifies how to align/layout the TextView element itself.

[1] http://en.wikipedia.org/wiki/Justification_%28typesetting%29

(13) If I understand correctly, and given the results of testing this, all this does is align text left or right. This doesn't justify the text, does it? - Paul Lammertsma
(15) Excellent. Just to add, if you want center justification, you can do android:layout_gravity="center_horizontal|center" android:gravity="center". - Luis A. Florit
definetly working for my case on inputtext and textview for language arabic from right to left input and display - shareef
(3) This is just alignment, not justification. Read that Wikipedia link carefully. The difference between the different types of justification only affects the last line of a paragraph. There is no left/right/center justification for paragraphs that only have one line. - Karu
(2) then why even answering it here if it's not about justify - user924
(1) This is all about left or right alignment, not about justify (as in MS word). Formal terminology is not helpful here, you understand what is meant in the question. - Shuvo Sarker
3
[+141] [2010-11-30 14:23:53] Konrad Nowicki

To justify text in android I used 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");

and 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>

I can't yet upload images to prove it but "it works for me".


(3) Nice solution here. FWIW you don't need most of the extra html. The body tag with text align is enough. - gnac
(5) This works well. Note that you can make the background transparent by following view.loadData() with view.setBackgroundColor("#00000000"). - Paul Lammertsma
I've not been successful in getting it to load a custom font/typeface, however. I've tried this and this suggestion, without any luck. - Paul Lammertsma
(2) As I mentioned in those threads, I found a resolution: if you create an HTML file and place it in the assets, loading it via view.loadUrl() works, whereas view.loadData() does not. I have no clue why the latter doesn't. - Paul Lammertsma
i wish to set a background image how can i modify this line <body style="text-align:justify;color:gray;background-color:black;‌​"> - Aerrow
WebView does not have method to set background image. You can try this w3schools.com/cssref/pr_background-image.asp - Konrad Nowicki
(1) @PaulLammertsma , setBackgroundColor(0x00000000) would rather be the correct format for setting the transparent background. - richey
webview.loadDataWithBaseURL(null, displayString, "text/html", "utf-8", ""); - Kirit Vaghela
4
[+101] [2013-12-14 00:02:05] Mathew Kurian

UPDATED

We have created a simple class for this. There are currently two methods to achieve what you are looking for. Both require NO WEBVIEW and SUPPORTS SPANNABLES.

LIBRARY: https://github.com/bluejamesbond/TextJustify-Android

SUPPORTS: Android 2.0 to 5.X

SETUP

// Please visit Github for latest setup instructions.

SCREENSHOT

Comparison.png


Is a really help, but using it, my TextViews doesn't keep the original format, I refeer: margins, text style, and I think that text size neither, Plese continue working in it, should be a really great help - Leonardo Sapuy
Well I can not establish those classes. one of them didnt have any packagename, the other gives some yellow error. Actually I can not trust. - mehmet
Nice lib, but I still don't know how to add any formating to Text using this library. - Semanticer
How can we detect line breaks in end of the lines? because lines with hard breaks should not be justified. - Mbt925
@Mbt925 I believe this does that. Can you post an image? - Mathew Kurian
I tested this library and documentation is deprecated, and the library is not worked for me. - Adam Varhegyi
@AdamVarhegyi Support has grown now and the documentation has improved. - Mathew Kurian
(5) Thanks for this great shared library, but it cannot support persian or arabic text. When I set direction, my word draw from last to start, instead of start to last. I means this: my Word is : "سلام" and its draw like this: "مالس" . (if you dont understand persian see this example: let me "1234" -> "4321" ) - Naruto Uzumaki
(1) Based on scrollView... Nice solution however cant find yet any answer that make it posible with textview. :( - superUser
Works perfectly on 6.0.1. - Andrey Luiz
@superUser out of curiosity, what is the purpose of inheriting from textview? - Mathew Kurian
I don't see anything on android 7.0. - Makalele
This library doesn't support RTL languages - Hossein Shahdoost
what about the license? - ahmed_khan_89
GIT repository shows "Library no longer maintained." :-( - richey
5
[+45] [2015-02-03 13:51:03] Saeed Zarinfam

You can use JustifiedTextView for Android [1] project in github. this is a custom view that simulate justified text for you. It support Android 2.0+ and right to left languages. enter image description here

[1] https://github.com/navabi/JustifiedTextView

it does not support spannable string - MSepehr
how can we add our own text? - Karan
Please see the sample on github. - Saeed Zarinfam
hi Saeed, tnx for your help, is there any way to support spannable textviews?! - Hamid Reza
@SaeedZarinfam I tried to use "JustifiedTextView for Android" but i got error on the xml tag ir.noghteh.JustifiedTextView would u plz to help me on this question stackoverflow.com/questions/37911376/… - Jumong
6
[+30] [2014-07-27 23:46:29] Frank Cheng

I write a widget base on native textview to do it.

github [1]

[1] https://github.com/ufo22940268/android-justifiedtextview

i'd recommed this one, mostly because its based in the original textview from the android official sdk, which in my personal opinion it is a way more lighter than the webview's technique many people is posting regarding this common topic. If you building a app that needs to be memory wise, using listview objects for example, you might consider using something like this. Ï allready try it out, and works as expected. If you people know another one better o like this 1, please you might share you experience with me. - superUser
Nice job btw. What i was looking for. - superUser
(5) doesn't support RTL languages like persian - fire in the hole
Other than RTL language support this is great. One file, minimal code, minimal fuss. - jjxtra
(1) @Frank Cheng Very Useful Library. I get lot of spaces at the end of paragraph. How can i fix it? - iSrinivasan27
(1) worked for me, but the last line of the textview got cut off. I had to keep padding down 5 for the textview. - TharakaNirmana
Having a error java.lang.ClassCastException: android.text.SpannableString cannot be cast to java.lang.String Unfortunately the project is dead, there's other records with this errors in its issue tracker on GitHub which don't solved yet. - Acuna
7
[+28] [2017-05-06 09:43:08] twiceYuan

I found a way to solve this problem, but this may not be very grace, but the effect is not bad.

Its principle is to replace the spaces of each line to the fixed-width ImageSpan (the color is transparent).

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);
            }
        }
    });
}

I put the code on GitHub: https://github.com/twiceyuan/TextJustification

Overview:

Overview


(3) Not working in XML preview but working great with a real device :) - pgreze
(1) only working if the TextView doesn't contain any formatting unfortunately. - richey
Working Fine in Android 11, using Kotlin. - tanrobles
8
[+18] [2018-11-21 05:55:01] Machhindra Neupane

Very Simple We can do that in the xml file

<TextView 
android:justificationMode="inter_word"
/>

(5) This requires api level 26. How would I support for lesser? - viper
9
[+17] [2014-04-29 16:01:10] user2599233

XML Layout: declare WebView instead of TextView

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

Java code: set text data to 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");

This may Solve your problem. Its Fully worked for me.


10
[+9] [2012-03-11 13:12:17] Topper Harley

Here's how I did it, I think the most elegant way I could. With this solution, the only things you need to do in your layouts are:

  • add an additional xmlns declaration
  • change your TextViews source text namespace from android to your new namespace
  • replace your TextViews with x.y.z.JustifiedTextView

Here's the code. Works perfectly fine on my phones (Galaxy Nexus Android 4.0.2, Galaxy Teos Android 2.1). Feel free, of course, to replace my package name with yours.

/assets/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;
    }
}

/res/values/attrs.xml:

<?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);
    }
}

We need to set the rendering to software in order to get transparent background on Android 3+. Hence the try-catch for older versions of Android.

Hope this helps!

PS: please not that it might be useful to add this to your whole activity on Android 3+ in order to get the expected behavior:
android:hardwareAccelerated="false"


(1) This is webView based solution. Any one have found yet textview based, considering textview is lighter than webview and scrollview. - superUser
11
[+9] [2016-01-13 20:52:07] kassim

While still not complete justified text, you can now balance line lengths using android:breakStrategy="balanced" from API 23 onwards

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


12
[+7] [2013-07-23 10:04:53] Fawad Badar

I write my own class to solve this problem, Here it is Just you have to call the static justify function that takes two arguments

  1. Text View object
  2. Content Width (Total width of your text view)

//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>

pleas complete this example at least for "\n" or System.getProperty("line.separator") to respect :) - ceph3us
This do not support spannable. - Saeed Arianmanesh
Worked for me. Android native support, doesn't. Thanks! - moyo
13
[+5] [2012-08-06 02:17:24] jiashie

FILL_HORIZONTAL is equivalent to CENTER_HORIZONTAL. You can see this code snippet in textview's source code:

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

14
[+5] [2013-12-06 23:31:04] Merter

There is a CustomView for this problem, this custom text view is support Justified Text View.

Loot at this: JustifiedTextView [1]

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;
                }
        }
}

Add above class to your src folder and use this sample code to add to your layout:

JustifiedTextView jtv= new JustifiedTextView(getApplicationContext(), "Lorem ipsum dolor sit amet... ");
LinearLayout place = (LinearLayout) findViewById(R.id.book_profile_content);
place.addView(jtv);
[1] https://github.com/merterhk/JustifiedTextView

works nicely! Unfortunately, it can't deal with contained html tags - richey
15
[+5] [2018-06-04 07:35:56] Mr_Moradi

see here in the github [1]

Just import the two files "TextJustifyUtils.java" and "TextViewEx.java" in your project.

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;
    }
}

and

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);
        }
    }
}

Now, if you use normal textView like:

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

Simply use

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

Define a variable and set justify to be true,

TextViewEx changed = (TextViewEx) findViewById(R.id.changed);
changed.setText(getResources().getString(R.string.lorum_ipsum),true);
[1] https://github.com/nikoo28/justify-textview-android

bold text not working, please help if you have any fix for it? - praveenb
16
[+2] [2011-03-09 03:32:13] Matthew

I think there are two options:

  • Use something like Pango that specializes in this via the NDK and render text to an OpenGL or other surface.

  • Use Paint.measureText() [1] and friends to get the lengths of words and lay them out manually on a Canvas in a custom view.

[1] http://developer.android.com/reference/android/graphics/Paint.html#measureText%28java.lang.String%29

17
[+2] [2013-01-14 06:20:43] kypiseth

Android does not yet support full justification. We can use Webview and justify HTML instead of using textview. It works so fine. If you guys not clear, feel free to ask me :)


That Can be done. But Can we set background of WebView transparent. I have a background image . - Mr.India
I dont think this may be memory wise. - superUser
18
[+2] [2022-10-07 07:25:13] Surajkaran Meghwanshi

Just use this property in xml file

android:justificationMode="inter_word"

it works, but only for API level 26 and higher. - The MJ
19
[+1] [2022-11-16 11:21:22] Amal

Simplay we can use 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" />

20
[0] [2020-10-27 06:46:14] Aminul Haque Aome

You can use justificationMode as inter_word in xml. You have to remember that this attribute is available for api level 26 and higher. For that you can assign targetApi as o. The full code is given bellow

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

21
[0] [2021-06-30 20:36:36] Absar Alam

Please try this code for below 8.0

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

22
[0] [2023-10-21 18:35:26] ΓDΛ

For Compose

usage : TextAlign.Justify

Text(
            text = body2,
            textAlign = TextAlign.Justify,
            modifier = Modifier.padding(16.dp, 8.dp, 16.dp, 16.dp)
        )
    }

23