Android TextView Justify Text
Asked Answered
M

23

473

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, but it does not work (even if you change vertical-center to center_vertical, etc).

Mantelet answered 18/8, 2009 at 8:32 Comment(3)
@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"Joubert
you can use github.com/pouriaHemmati/JustifiedTextViewPrecedential
github.com/amilcar-sr/JustifiedTextView can be usedVandervelde
E
293

I do not believe Android supports full justification.

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

Eric answered 18/8, 2009 at 12:33 Comment(12)
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.Eric
android:gravity="fill_horizontal" didn't work either. It looks like android doesn't support justification after all, oh well :)Mantelet
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 #5977127)Tiphani
@Eric Now any proper way to justify text?Canst
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.Canst
this is possible. see answers below.Onia
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.Oversubtlety
Oreo added support for this.Tremor
@FarhanFarooqui: Android 8.0+ supports justification modes.Eric
setJustificationMode(JUSTIFICATION_MODE_INTER_WORD); do not give perfect view :( it's shameStrawflower
this works fine in most cases, but it doesnt work if you are using url (links) in your text viewWelldefined
@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 exampleRoundhead
M
182

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).

Messenia answered 24/3, 2017 at 5:4 Comment(14)
Let's hope it gets ported back to the support library then O:)Quinquereme
pls add library here !!Cowshed
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?Iamb
Yes you're right @MikeOscarEcho. It(setJustify) was there earlier for Android O DP1. Thanks for pointing it out.Messenia
It's still not good enough! imgur.com/a/BR8sV The right side is not straight line but jagged! :(Springhouse
how to justify using XML?Strawflower
You can use android:justificationMode="inter_word" in xml.Levator
API 26 required for android:justificationMode.Dougald
Must be one of: LineBreaker.JUSTIFICATION_MODE_NONE, LineBreaker.JUSTIFICATION_MODE_INTER_WORD Min API 29Rank
It's a mess. less or small words in a line and it creates spaces between them which looks funky and bad design.Vickyvico
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.Lananna
Adding the android:justificationMode="inter_word" with the center align text in layout causing the layout text cuts from the sidesRestrictive
This is specially messed up. JustificationMode is added in API 26 and the value it can accept is added in API 29Nuke
Import statement -> import android.graphics.text.LineBreakerJell
L
168

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

Lund answered 24/5, 2010 at 17:58 Comment(6)
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?Carboy
Excellent. Just to add, if you want center justification, you can do android:layout_gravity="center_horizontal|center" android:gravity="center".Harlanharland
definetly working for my case on inputtext and textview for language arabic from right to left input and displayJoubert
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.Disrobe
then why even answering it here if it's not about justifyCroton
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.Comparison
F
141

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

Fingertip answered 30/11, 2010 at 14:23 Comment(8)
Nice solution here. FWIW you don't need most of the extra html. The body tag with text align is enough.Chaschase
This works well. Note that you can make the background transparent by following view.loadData() with view.setBackgroundColor("#00000000").Carboy
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.Carboy
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.Carboy
i wish to set a background image how can i modify this line <body style="text-align:justify;color:gray;background-color:black;">Calipash
WebView does not have method to set background image. You can try this w3schools.com/cssref/pr_background-image.aspFingertip
@PaulLammertsma , setBackgroundColor(0x00000000) would rather be the correct format for setting the transparent background.Sommersommers
webview.loadDataWithBaseURL(null, displayString, "text/html", "utf-8", "");Trilateral
C
101

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

Conscionable answered 14/12, 2013 at 0:2 Comment(15)
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 helpDubbin
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.Oldest
Nice lib, but I still don't know how to add any formating to Text using this library.Angelinaangeline
How can we detect line breaks in end of the lines? because lines with hard breaks should not be justified.Allergic
@Allergic I believe this does that. Can you post an image?Conscionable
I tested this library and documentation is deprecated, and the library is not worked for me.Kraul
@AdamVarhegyi Support has grown now and the documentation has improved.Conscionable
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" )Propylene
Based on scrollView... Nice solution however cant find yet any answer that make it posible with textview. :(Chromatology
Works perfectly on 6.0.1.Vanquish
@Chromatology out of curiosity, what is the purpose of inheriting from textview?Conscionable
I don't see anything on android 7.0.Decretal
This library doesn't support RTL languagesBadinage
what about the license?Featherston
GIT repository shows "Library no longer maintained." :-(Sommersommers
M
45

You can use JustifiedTextView for Android 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

Muzzleloader answered 3/2, 2015 at 13:51 Comment(5)
it does not support spannable stringBradstreet
how can we add our own text?Rawden
Please see the sample on github.Muzzleloader
hi Saeed, tnx for your help, is there any way to support spannable textviews?!Isocracy
@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 #37911876Deepsix
C
30

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

github

Cabezon answered 27/7, 2014 at 23:46 Comment(7)
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.Chromatology
Nice job btw. What i was looking for.Chromatology
doesn't support RTL languages like persianWedekind
Other than RTL language support this is great. One file, minimal code, minimal fuss.Immutable
@Frank Cheng Very Useful Library. I get lot of spaces at the end of paragraph. How can i fix it?Showiness
worked for me, but the last line of the textview got cut off. I had to keep padding down 5 for the textview.Westmorland
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.Gondolier
P
27

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

Paucker answered 6/5, 2017 at 9:43 Comment(3)
Not working in XML preview but working great with a real device :)Pack
only working if the TextView doesn't contain any formatting unfortunately.Sommersommers
Working Fine in Android 11, using Kotlin.Columbus
M
18

Very Simple We can do that in the xml file

<TextView 
android:justificationMode="inter_word"
/>
Milling answered 21/11, 2018 at 5:55 Comment(1)
This requires api level 26. How would I support for lesser?Glosso
B
17

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.

Bevan answered 29/4, 2014 at 16:1 Comment(0)
A
9

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"

Arium answered 11/3, 2012 at 13:12 Comment(1)
This is webView based solution. Any one have found yet textview based, considering textview is lighter than webview and scrollview.Chromatology
S
9

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

Solatium answered 13/1, 2016 at 20:52 Comment(0)
P
7

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>
Peeress answered 23/7, 2013 at 10:4 Comment(3)
pleas complete this example at least for "\n" or System.getProperty("line.separator") to respect :)Term
This do not support spannable.Vidrine
Worked for me. Android native support, doesn't. Thanks!Tarragona
T
5

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();
Tadeo answered 6/8, 2012 at 2:17 Comment(0)
P
5

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

Loot at this: 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;
                }
        }
}

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);
Precipitous answered 6/12, 2013 at 23:31 Comment(1)
works nicely! Unfortunately, it can't deal with contained html tagsSommersommers
A
5

see here in the github

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);
Armindaarming answered 4/6, 2018 at 7:35 Comment(1)
bold text not working, please help if you have any fix for it?Saleable
R
2

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() and friends to get the lengths of words and lay them out manually on a Canvas in a custom view.

Raab answered 9/3, 2011 at 3:32 Comment(0)
C
2

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 :)

Coagulum answered 14/1, 2013 at 6:20 Comment(2)
That Can be done. But Can we set background of WebView transparent. I have a background image .Kucik
I dont think this may be memory wise.Chromatology
M
2

Just use this property in xml file

android:justificationMode="inter_word"
Mort answered 7/10, 2022 at 7:25 Comment(1)
it works, but only for API level 26 and higher.Curculio
C
1

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" />
Chicane answered 16/11, 2022 at 11:21 Comment(0)
D
0

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" />
Decorative answered 27/10, 2020 at 6:46 Comment(0)
A
0

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"/>
Amador answered 30/6, 2021 at 20:36 Comment(0)
H
0

For Compose

usage : TextAlign.Justify

Text(
            text = body2,
            textAlign = TextAlign.Justify,
            modifier = Modifier.padding(16.dp, 8.dp, 16.dp, 16.dp)
        )
    }
Hydrastine answered 21/10, 2023 at 18:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.