title: Justifying text inside a TextView in android
body: <p>So, as most of you know, there is no text justifying inside a TextView in Android. So, I built a custom TextView to get around the problem. However, for some reason, sometimes punctuation marks break the line for some reason in some devices. I tested on an LG G3 and emulator (Nexus 4 running latest version) and a comma "," for instance breaks the justification on the LG G3 but not on the emulator.</p>
  
  <p>If I add a Padding start and end (or left and right) of at least 2, the problem is solved. This looks very arbitrary to me.</p>
  
  <p>Basically, my logic was that in order to justify the text, I would need to know the width of the TextView itself, construct the text into lines that are at maximum that length. Then, by finding the number of spaces in the line and the remaining empty space, stretch the " " (space) characters to be scaled according to remaining pixels (or, space in the view).</p>
  
  <p>It works almost perfectly, and most of the time it supports RTL text as well.</p>
  
  <p>here're some pictures of the text (a simple lorem impsum) with and without the offending marks (first one is on emulator nexus 4 running 7.1.1, second one is on LG G3 running v5.0)
  <a href="https://i.stack.imgur.com/NErtV.png" rel="noreferrer"><img src="https://i.stack.imgur.com/NErtV.png" alt="Text on emulator running nexus 4 running"></a>
  <a href="https://i.stack.imgur.com/SrRna.png" rel="noreferrer"><img src="https://i.stack.imgur.com/SrRna.png" alt="Text on LG G3 running android 5.0"></a></p>
  
  <p>Here's the code:</p>
  
  <pre><code>public class DTextView extends AppCompatTextView {
  
      private boolean justify;
  
      public DTextView(Context context) {
          super(context);
      }
  
      public DTextView(Context context, @Nullable AttributeSet attrs) {
          super(context, attrs);
          init(attrs);
      }
  
      public DTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          init(attrs);
      }
  
      private void setJustify(boolean justify) {
          this.justify = justify;
          if (justify) {
              justify();
          }
      }
  
      private void init(@Nullable AttributeSet attrs) {
          TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.DTextView, 0, 0);
          justify = ta.getBoolean(R.styleable.DTextView_justify, false);
  
          ta.recycle();
      }
  
      private SpannableStringBuilder justifyText() {
  
          String[] words = getText().toString().split(" ");
          setText("");
  
          int maxLineWidth = getWidth() - getPaddingLeft() - getPaddingRight();
  
          SpannableStringBuilder justifiedTextSpannable = new SpannableStringBuilder();
  
          //This will build the new text with the lines rearranged so that they will have a width
          //bigger than the View's own width
          ArrayList&lt;String&gt; lines = new ArrayList&lt;&gt;(0);
          String line = "";
          for (String word : words) {
              if (getWordWidth(line + word) &lt; maxLineWidth) {
                  line += word + " ";
              } else {
                  line = line.substring(0, line.length() - 1);
                  lines.add(line);
                  line = word + " ";
              }
          }
          //Add the last line
          lines.add(line);
  
          for (int i = 0; i &lt; lines.size() - 1; i++) {
              justifiedTextSpannable.append(justifyLine(lines.get(i), maxLineWidth));
              justifiedTextSpannable.append("\n");
          }
  
          justifiedTextSpannable.append(lines.get(lines.size() - 1));
  
  
          return justifiedTextSpannable;
      }
  
      private SpannableString justifyLine(String line, float maxWidth) {
  
          SpannableString sLine = new SpannableString(line);
          float spaces = line.split(" ").length - 1;
  
          float spaceCharSize = getWordWidth(" ");
          float emptySpace = maxWidth - getWordWidth(line);
          float newSpaceSize = (emptySpace / spaces) + spaceCharSize;
          float scaleX = newSpaceSize / spaceCharSize;
  
          for (int i = 0; i &lt; line.length(); i++) {
              if (line.charAt(i) == ' ') {
                  sLine.setSpan(new ScaleXSpan(scaleX), i, i + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
              }
          }
  
          return sLine;
      }
  
      private void justify() {
          justify = false;
          setText(justifyText());
          invalidate();
      }
  
      private float getWordWidth(String word) {
          return getPaint().measureText(word);
      }
  
      @Override
      protected void onDraw(Canvas canvas) {
          if (!justify)
              super.onDraw(canvas);
          else
              justify();
      }
  }
  </code></pre>
  
  <p>I would very much appreciate anyone that can shed some light on this.</p>