20

I have this TextView. Some parts of it is supposed to be aligned to the left and some parts to the right. How would I do this in Java?

Basicly I want the first line to align to the left, and the next line to the right and so on.

Anyone got a clue?

EDIT

I have tried to use HTML and then when that did not work I tried spans.

html attempt

textView.setText(Html.fromHtml("<p align=\"right\">THIS IS TO THE RIGHT</p>"));

And heres the span attempt

    String LeftText = "LEFT";
    String RightText = "RIGHT";

    final String resultText = LeftText + "  " + RightText;
    final SpannableString styledResultText = new SpannableString(resultText);
    styledResultText.setSpan(new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE), LeftText.length() + 1, LeftText.length() + 2 +RightText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    textView.setText(styledResultText);

But none of them seems to work.

twlkyao
  • 14,302
  • 7
  • 27
  • 44
user1991905
  • 325
  • 1
  • 2
  • 8

7 Answers7

28
TextView resultTextView = new TextView(this);
final String resultText = LeftText + "  " + RightText;
final SpannableString styledResultText = new SpannableString(resultText);
styledResultText.setSpan(new AlignmentSpan.Standard(Alignment.ALIGN_OPPOSITE)
    , LeftText.length() + 2
    , LeftText.length() + 2 + RightText.length()
    , Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
resultTextView.setText(styledResultText);

Alignment.ALIGN_OPPOSITE is the equivalent for right side.

Alignment.ALIGN_NORMAL is the equivalent for left side.

atiruz
  • 2,782
  • 27
  • 36
Vikalp Patel
  • 10,669
  • 6
  • 61
  • 96
  • 2
    Hmm. It does´nt align to the right. Does the gravity in the textview matter? – user1991905 Jan 25 '13 at 11:46
  • 1
    @user1991905 gravity of `TextView` do matters here. Try not to give `android:gravity` inside `TextView` in your xml. – Vikalp Patel Jan 25 '13 at 11:49
  • 1
    Hmm. I did´nt have gravity set to anyhthing in the xml before.. – user1991905 Jan 25 '13 at 11:50
  • 4
    This only works for me if i use the whole line with the span, but not for a part. :( – WonderCsabo Mar 29 '14 at 16:49
  • 3
    @WonderCsabo ```AlignmentSpan.Standard``` is a paragraph style so you need to add a new line character to ```LeftText``` (i.e. \n ) to make ```RightText```right aligned. (I've not tested the particular example above) – alexbirkett Feb 18 '15 at 10:30
  • 6
    For me, I could only get it to work with a linefeed "\n " instead of two spaces, and setting the second parameter of setSpan to LeftText.length() without the "+2". I've tried many combinations trying to get rid of the line feed that I don't want, but haven't come up with a satisfactory solution. – Frank Apr 07 '16 at 21:16
  • 2
    Why it is presented as an answer while it does not work and can't work? its a paragraph-based span so it will work if we;ll make it TWO strings (with \n) but it could be trivially solved without spans then And with a single string it is useless :( – sergeych Oct 09 '20 at 20:11
8

Here is a solution that works with Spannable, with the cavate that if the right and left are too wide, they will overlap on the same line. Since to do the Left/Right trick with a spannable requires a line feed between Left and Right, my fix is to add a spannable that reduces the line height to zero (i.e. overlapped lines) for the one linefeed and then restore normal line height after that.

    String fullText = leftText + "\n " + rightText;     // only works if  linefeed between them! "\n ";

    int fullTextLength = fullText.length();
    int leftEnd = leftText.length();
    int rightTextLength = rightText.length();

    final SpannableString s = new SpannableString(fullText);
    AlignmentSpan alignmentSpan = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE);
    s.setSpan(alignmentSpan, leftEnd, fullTextLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    s.setSpan(new SetLineOverlap(true), 1, fullTextLength-2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    s.setSpan(new SetLineOverlap(false), fullTextLength-1, fullTextLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

And we have the small routine to handle the overlapping lines:

    private static class SetLineOverlap implements LineHeightSpan {
    private int originalBottom = 15;        // init value ignored
    private int originalDescent = 13;       // init value ignored
    private Boolean overlap;                // saved state
    private Boolean overlapSaved = false;   // ensure saved values only happen once

    SetLineOverlap(Boolean overlap) {
        this.overlap = overlap;
    }

    @Override
    public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v,
                             Paint.FontMetricsInt fm) {
        if (overlap) {
            if (!overlapSaved) {
                originalBottom = fm.bottom;
                originalDescent = fm.descent;
                overlapSaved = true;
            }
            fm.bottom += fm.top;
            fm.descent += fm.top;
        } else {
            // restore saved values
            fm.bottom = originalBottom;
            fm.descent = originalDescent;
            overlapSaved = false;
        }
    }
}
Frank
  • 605
  • 1
  • 8
  • 14
  • 1
    Nice trick, but doesn't work if the leftText is longer than 1 line in textview (without linefeed) – tsig Jan 02 '19 at 16:52
5

Thanks for the tip, @Frank, but this was just enough:

public class LineOverlapSpan implements LineHeightSpan {
    @Override
    public void chooseHeight(final CharSequence text, final int start, final int end, final int spanstartv, final int v, final Paint.FontMetricsInt fm) {
        fm.bottom += fm.top;
        fm.descent += fm.top;
    }
}

Used like this:

CharSequence text = return new Truss()
        .append("LEFT")
        .pushSpan(LineOverlapSpan())
        .append("\n")
        .popSpan()
        .pushSpan(AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE))
        .append("RIGHT")
        .build()

where Truss is

A SpannableStringBuilder wrapper whose API doesn't make me want to stab my eyes out.

Community
  • 1
  • 1
Eugen Pechanec
  • 37,669
  • 7
  • 103
  • 124
2

Two solution :

1) have a different text view for each line and set its gravity.

2) Use Html while loading data setting the alignment in html for it REF:how-to-display-html-in-textview This will not work align is not a supported tags Edited:

Also

3: will be to use a span.

Community
  • 1
  • 1
Nimish Choudhary
  • 2,048
  • 18
  • 17
  • Hmm I tought that the second solution with HTML would be a great way to do it but it did´nt work.. textView.setText(Html.fromHtml("

    This should be to the right

    "));
    – user1991905 Jan 25 '13 at 11:34
  • textView.setText(Html.fromHtml("

    This should be to the right

    ")); Are you sure that the alignment from html works on the textview too?
    – user1991905 Jan 25 '13 at 11:49
  • I just tried some samples and researched that HTML will not support alignment http://stackoverflow.com/questions/3874999/alignment-in-html-fromhtml – Nimish Choudhary Jan 25 '13 at 12:12
  • Yep I just read that too.. But why is´nt the span solution working.. That should work, should´nt it? – user1991905 Jan 25 '13 at 12:17
0

Simple.Adjust xml part in textview. Use layouts.If you want the textview to be in left

android:alignParentLeft="true" For more align details look this.

http://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.html

And this is also an example for different layouts.

http://www.androidhive.info/2011/07/android-layouts-linear-layout-relative-layout-and-table-layout/

Shadow
  • 6,864
  • 6
  • 44
  • 93
0

Inspired other solutions, but resolving multiline text issues and text overlap issue.

class LineOverlapSpan() : LineHeightSpan {
    var originalBottom: Int = 0
    var originalDescent: Int = 0
    var overlapSaved = false

    override fun chooseHeight(
        text: CharSequence?,
        start: Int,
        end: Int,
        spanstartv: Int,
        v: Int,
        fm: Paint.FontMetricsInt?
    ) {
        if (fm == null) {
            return
        }

        if (!overlapSaved) {
            originalBottom = fm.bottom
            originalDescent = fm.descent
            overlapSaved = true
        }

        if (text?.subSequence(start, end)?.endsWith("\n") == true) {
            fm.bottom += fm.top
            fm.descent += fm.top
        } else {
            fm.bottom = originalBottom
            fm.descent = originalDescent
        }
    }

}

Usage:

fun keyValueSpan(key: CharSequence, value: CharSequence) = span {
    span {
        alignment = "normal"
        +key
        span {
            textColor = Color.TRANSPARENT
            +value
        }
        span {
            +"\n"
            addSpan(LineOverlapSpan())
        }
    }
    span {
        alignment = "opposite"
        +value
    }
}

Kotlin solution is using Kpan library https://github.com/2dxgujun/Kpan

frogggias
  • 53
  • 1
  • 6
  • But it does not solve when you are using a BIG (lets say 24dp) text on left and then add in the same line a 10dp text aligned to the right... It cuts the big text on the bottom ? Any idea how to solve that – Lonko Jun 28 '21 at 07:16
0

I didn't want to create a custom view and I wasn't able to use the span answers here because I wanted to achieve this in a dialog with list items.

So, I just calculated how many spaces I would need to add to the middle of my string to get my text to fill the entire parent width and align part of it to the left.

Formula: ((width of parent - width of text) / width of single space) = number of spaces

For calculating text width, I used the answers from How to get string width on Android?

private fun calculateTextWidthPixels(context: Context, text: String, textSizeSp: Float): Int {
    val bounds = Rect()
    val textPaint = TextPaint().apply {
        typeface = Typeface.create("sans-serif", Typeface.NORMAL)
        textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSizeSp, context.resources.displayMetrics)
        getTextBounds(text, 0, text.length, bounds)
    }
    return StaticLayout.getDesiredWidth(text, textPaint).toAccurateInt()
}

private fun padMiddleOfTextWithSpaces(leftText: String, rightText: String, desiredWidthPixels: Int, textSizeSp: Float): String {
    val originalText = "$leftText $rightText"
    val originalTextWidth = calculateTextWidthPixels(context, originalText, textSizeSp)
    val singleSpaceTextWidth = calculateTextWidthPixels(context, " ", textSizeSp)
    val availableWidthForSpaces = desiredWidthPixels - originalTextWidth
    val spacesRequired = " ".repeat((availableWidthForSpaces / singleSpaceTextWidth))
    return "$leftText $spacesRequired$rightText"
}
Lifes
  • 1,226
  • 2
  • 25
  • 45