Justificando o texto dentro de um TextView no Android

Portanto, como muitos de vocês sabem, não há texto justificando dentro de um TextView no Android. Então, criei um TextView personalizado para solucionar o problema. No entanto, por algum motivo, algumas vezes os sinais de pontuação quebram a linha por algum motivo em alguns dispositivos. Testei em um LG G3 e emulador (Nexus 4 executando a versão mais recente) e uma vírgula "," por exemplo quebra a justificativa no LG G3, mas não no emulador.

Se eu adicionar um começo e um final de preenchimento (ou esquerda e direita) de pelo menos 2, o problema será resolvido. Isso parece muito arbitrário para mim.

Basicamente, minha lógica era que, para justificar o texto, eu precisaria conhecer a largura do próprio TextView, construir o texto em linhas que tenham no máximo esse comprimento. Em seguida, localizando o número de espaços na linha e o espaço vazio restante, estique os caracteres "" (espaço) a serem redimensionados de acordo com os pixels restantes (ou espaço na exibição).

Funciona quase perfeitamente e, na maioria das vezes, também suporta texto RTL.

aqui estão algumas fotos do texto (um simples lorem impsum) com e sem as marcas ofensivas (a primeira é no emulador nexus 4 executando 7.1.1, a segunda é no LG G3 executando a versão 5.0)

Aqui está o código:

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<String> lines = new ArrayList<>(0);
        String line = "";
        for (String word : words) {
            if (getWordWidth(line + word) < 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 < 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 < 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();
    }
}

Eu apreciaria muito qualquer um que possa lançar alguma luz sobre isso.

questionAnswers(3)

yourAnswerToTheQuestion