2

I need to justify some text (RTL), which is a string (S1) from the server. But a TextView can't justify text, so I have to use a WebView, now I have to create a HTML file in which will display S1. And then I store the address of that html file in the database and then I display that html file. I've seen this question asked before on SO and many have recommended to use a 3rd party library, I've tried all of those approaches to no avail (they work in 90% of scenarios but are no fully reliable).

I feel that this approach seems convoluted, I was wondering if there is a better approach?

Shayan Pourvatan
  • 11,898
  • 4
  • 42
  • 63
  • can you give some sample. How do you want to justify?.. check this [answer](http://stackoverflow.com/a/2899082/2219600). – amalBit Nov 09 '13 at 08:14
  • i want justify Persian text, and if I cant justify with some class or library i should get String and Create HTML file and load into WebView. – Shayan Pourvatan Nov 09 '13 at 08:17
  • Here is exactly what you are looking for. http://stackoverflow.com/a/4314724/2219600 – amalBit Nov 09 '13 at 08:20
  • @amalBit your first link does is align text left or right. This doesn't justify the text and second link is what i must do, but this is static and i must create many html file into database – Shayan Pourvatan Nov 09 '13 at 08:21
  • 1
    Well thats the best android can do for justification. – amalBit Nov 09 '13 at 08:24
  • I have updated https://github.com/bluejamesbond/TextJustify-Android with the latest technique. This should be loads better now. @amalBit There is a work around! – Mathew Kurian Dec 13 '13 at 16:46
  • Possible duplicate of [Android TextView Justify Text](http://stackoverflow.com/questions/1292575/android-textview-justify-text) – blahdiblah Sep 09 '14 at 22:39
  • @blahdiblah really? did you read my question? i needed one approach for that solution, I mentioned in my question that this question asked before, any way, you have right to close question and down vote, but after that read questions with more time. Thanks – Shayan Pourvatan Sep 10 '14 at 04:44

6 Answers6

5

I use the following code that answer with very people that need this subject and i create formula that support in every display.

    public class TextJustify {

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

/* @author Mathew Kurian */

public static void run(final TextView tv, float origWidth, int paddingLeft, int paddingRight, int marginLeft, int marginRight) {


    origWidth-= paddingRight+marginRight+paddingLeft+marginLeft;
    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.RIGHT);
    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 for calling this you mus use following code, I tested for Persian language and in every display and device worked fine.

     public static final int FinallwidthDp  = 320 ;
     public static final int widthJustify  = 223 ;

     DisplayMetrics metrics = new DisplayMetrics();
     getWindowManager().getDefaultDisplay().getMetrics(metrics);
     int widthPixels = metrics.widthPixels;

     float scaleFactor = metrics.density;
     float widthDp = widthPixels / scaleFactor;

     TextView tv = (TextView) findViewById(R.id.textView1);
     ViewGroup.MarginLayoutParams lp1 = (ViewGroup.MarginLayoutParams) tv.getLayoutParams();

     tv.setText(text);
     TextJustify.run(tv,widthDp / FinallwidthDp * widthJustify , tv.getPaddingLeft(),tv.getPaddingRight() , lp1.leftMargin, lp1.rightMargin);

this algorithm tested on various device and worked fine in normal activity (not dialog) and wrap-content width for TextView, and worked with every padding and margin.if not good for you, you can change widthJustify until look good to you, I hope this useful. for newly update see This

Shayan Pourvatan
  • 11,898
  • 4
  • 42
  • 63
  • 3
    Hello, I am the one who had written this library. I would be obliged if you can add support for all calculating the `widthDp` for all devices. I don't have an Android device so it would be hard for me to code and test. To add this support, please fork this https://github.com/bluejamesbond/TextJustify-Android, make your changes and then do a pull request and I will merge in the code. I will also add you as a contributor as well. Thank you very much. – Mathew Kurian Dec 11 '13 at 20:23
  • @mk1 I edited your library but i don't know that I confirm edit or not because i never do like this, please tell me result of My Edit, I edit two file,read me and your class, in read me i add 10 - 15 line and in your class add 2 line, – Shayan Pourvatan Dec 12 '13 at 07:33
  • I saw your edits I have merged them. I have reopened one issue there, hopefully you can look into it. – Mathew Kurian Dec 12 '13 at 08:08
  • @mk1 In some lines spacing between words is so much and not looking so good. How to solve this problem? – John R Feb 11 '14 at 06:20
  • @Shayanpourvatan same question if you know please help me. – John R Feb 11 '14 at 06:21
  • @JohnR did you see the link? did you used second way because that is much better performance, ( i think ). i really don't know i just brought one formula for this, all algorithm is for mk1. change value of `widthJustify` and see result, may be look better – Shayan Pourvatan Feb 11 '14 at 08:47
  • second option is not working. So author of that project told me to use first option. – John R Feb 11 '14 at 09:01
2

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

SUPPORTS: Android 2.0 to 5.X; String/Spannables; RTL language support! NO WEBVIEW :)

SCREENSHOT

Comparison.png

Mathew Kurian
  • 5,949
  • 5
  • 46
  • 73
0

Try this:

Add a TextViewJustify.java file in src folder.

TextViewJustify.java wil be like this

import android.graphics.Paint;
import android.view.Gravity;
import android.widget.TextView;

public class TextViewJustify {

    /*
     * PLEASE DO NOT REMOVE Coded by Mathew Kurian I wrote this code for a
     * Google Interview for Internship. Unfortunately, I got too nervous during
     * the interview that I messed, but anyhow that doesn't matter. I have
     * resent my work in hopes that I might still get a position there. Thank
     * you :DD
     */

    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 justifyText(final TextView tv, final 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 use this class like this:

TextViewJustify.justifyText(your_text_view, 225f);

In my case it was 225f. change it according to your need.

Avijit
  • 3,834
  • 4
  • 33
  • 45
  • i use this class but this work in 90% of scenarios but are no fully reliable – Shayan Pourvatan Nov 09 '13 at 08:25
  • This is because you cant justify text from XML. – Avijit Nov 09 '13 at 08:29
  • and for your number (225f) you must set width of screen that on every screen work good, if you set final number in other dp screen is not work good – Shayan Pourvatan Nov 09 '13 at 08:29
  • No I didn't set any width. And I used it in a dialog screen. It was working like a charm in every device. @Shayan – Avijit Nov 09 '13 at 08:31
  • @Andru I didn't think people would actually use this. So I will optimize this and increase its reliability. If you are user of this, please follow its repository on github so you can stay on top of the latest releases. – Mathew Kurian Dec 11 '13 at 20:27
0

You can justify Text using WebView Simply

LinearLayout lv=(LinearLayout)dialog.findViewById(R.id.**yourId**);


                     String text1 = "<html><body>"
                                + "<p align=\"justify\">"                
                                   +**your text**
                                + "</p> "
                                + "</body></html>";



                     WebView wv=new WebView(getApplicationContext());
                     wv.loadData(text1,"text/html","utf-8");



                    lv.removeAllViews();

                    lv.addView(wv);
Arunkumar
  • 224
  • 2
  • 6
  • yes you right but in that project i didn't want to use html file because i had many many text in database, but thanks for your solution. – Shayan Pourvatan Aug 12 '14 at 15:01
  • if you can split your text and print it in different text views, You can use html tags for text color, text size, text style etc – Arunkumar Aug 13 '14 at 05:15
  • I need that for law book, 100 pages and many lines, this solution is not worked for that situation. any way, problem has been solved with my answer on this post – Shayan Pourvatan Aug 13 '14 at 05:19
0

i made simple class.
this can be used just like TextView

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * text justifying
 * you can just use like TextView
 * @author hyunsikkim
 *
 */
public class JustifiedTextView extends TextView {

public JustifiedTextView(Context context) {
    super(context);
}

public JustifiedTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

private void setBreakText(String text) {
    if(text == null) return;

    String breakText = breakText(getPaint(), text, 
            getWidth()-this.getPaddingLeft()-this.getPaddingRight());

    if(breakText.equals(getText()) == false) {
        setText(breakText);
    }
}

public String breakText(Paint textPaint, String strText, int breakWidth) { 
    StringBuilder sb = new StringBuilder(); 
    int endValue = 0; 
    final String NEW_LINE = "\n";
    do{ 
        endValue = textPaint.breakText(strText, true, breakWidth, null); 
        if(endValue > 0) {
            /**
             * handle if text contains NEW_LINE
             */
            final int index = strText.indexOf(NEW_LINE);
            if(0<=index && index <= endValue) {
                endValue = index + NEW_LINE.length();
            }
            final String sub = strText.substring(0, endValue);
            sb.append(sub);
            /**
             * handle breaked text endWidth NEW_LINE
             */
            if(sub.endsWith(NEW_LINE) == false) {
                if(strText.length() != endValue) {
                    sb.append(NEW_LINE);
                }
            }

            strText = strText.substring(endValue); 
        } 
    } while(endValue > 0);
    return sb.toString();
} 

public String breakText(Paint textPaint, int id, int breakWidth) {
    String strText = getResources().getString(id);
    return breakText(textPaint, strText, breakWidth);
}

@Override
protected void onTextChanged(CharSequence text, int start,
        int lengthBefore, int lengthAfter) {
    super.onTextChanged(text, start, lengthBefore, lengthAfter);
    /**
     * this control changes from setText(Charsequence text)
     */
    if(getWidth() != 0) {
        setBreakText(text.toString());
    }
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    /**
     * this help to break initial text.
     */
    if(w != oldw) {
        setBreakText(getText().toString());
    }
}


}
0

Use web view

  WebView tv = (WebView) findViewById(R.id.aboutme); 
  String youtContentStr = String.valueOf(Html
                        .fromHtml("<![CDATA[<body style=\"text-align:justify;background-color:#00222222;\">"
                                    + text
                                    + "</body>]]>"));

  tv.setBackgroundColor(Color.TRANSPARENT);
  tv.loadData(youtContentStr, "text/html", "utf-8");`
Shayan Pourvatan
  • 11,898
  • 4
  • 42
  • 63
sujith s
  • 864
  • 11
  • 20