Two TextViews side by side, only one to ellipsize?

AndroidAndroid Layout

Android Problem Overview


I want to have two TextView elements appear side by side (in a list item), one aligned to the left, one to the right. Something like:

|<TextView>               <TextView>|

(the | represent the screen's extremities)

However, the TextView on the left can have content that is too long to fit on the screen. In this case, I want to have it ellipsize but still show the entire right TextView. Something like:

|This is a lot of conte...<TextView>|

I have had numerous attempts at this, using both LinearLayout and RelativeLayout, and the only solution I have come up with is to use a RelativeLayout and put a marginRight on the left TextView big enough to clear the right TextView. As you can imagine, though, this is not optimal.

Are there any other solutions?

Final, LinearLayout solution:

<LinearLayout
	android:layout_height="wrap_content"
	android:layout_width="fill_parent"
	android:orientation="horizontal"
	>
	<TextView
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:layout_weight="1"
		android:ellipsize="end"
		android:inputType="text"
		/>
	<TextView
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:layout_weight="0"
		android:layout_gravity="right"
		android:inputType="text"
		/>
</LinearLayout>

Old, TableLayout solution:

<TableLayout
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:stretchColumns="1"
	android:shrinkColumns="0"
	>
	<TableRow>
		<TextView android:id="@+id/title"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:ellipsize="end"
			android:singleLine="true"
			/>
		<TextView android:id="@+id/date"
			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:singleLine="true"
			android:ellipsize="none"
			android:gravity="right"
			/>
	</TableRow>
</TableLayout>

Android Solutions


Solution 1 - Android

Just an idea, why don't you declare first in the xml layout the textview on the right and set its width as wrap content, android:layout_alignParentRight="true" and android:gravity="right". Then declare the textview on the left, set its width as fill parent, android:layout__toLeftOf={the id of the textview on the right} having RelativeView as the root view.

By declaring first the right textview, its required width will be computed first and occupy the view while the textview on the left will occupy the remaining space of the view.

I still have not tried this though it might give you some idea.

[Update]

I tried creating an xml resource layout... and it somehow works...

<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content">
  <TextView 
  	android:id="@+id/right"
  	android:layout_alignParentRight="true"
  	android:layout_width="wrap_content"
  	android:layout_height="wrap_content"
  	android:gravity="right"
  	android:text="right"
  	>
  </TextView>
  <TextView 
  	android:id="@+id/left"
  	android:layout_alignParentLeft="true"
  	android:layout_toLeftOf="@id/right"
  	android:layout_width="fill_parent"
  	android:layout_height="wrap_content"
  	android:ellipsize="end"
  	android:lines="1"
  	android:singleLine="true"
  	android:maxLines="1"
  	android:text="too looooooooooong ofskgjo sdogj sdkogjdfgds dskjgdsko jgleft"
  	>
  </TextView>
</RelativeLayout>

Solution 2 - Android

The LinearLayout answer worked for me with this same problem. Posted as a separate answer because it wasn't clear what did and didn't work for the asker.

One difference. TableLayout was less ideal for me because I had two rows of data, and I wanted the bottom row to behave as this question describes, and the top row to span the area. That question's been answered in another SO question: Colspan in TableLayout, but LinearLayout was simpler.

Though getting the widths right took me a bit. I included the android lint tweak of using 0dp width on the scaling item for performance.

<LinearLayout
    android:layout_height="wrap_content"
    android:layout_width="fill_parent"
    android:orientation="horizontal"
    >
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:ellipsize="end"
        android:inputType="text"
        />
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_weight="0"
        android:layout_gravity="right"
        android:inputType="text"
        />
</LinearLayout>

Solution 3 - Android

Use TableLayout and put both TextView in table row, have a try. I haven't tried

Solution 4 - Android

There are many answers to this and practically equivalent, duplicate questions on SO. The suggested approaches usually work, sort of. Putting it into a LinearLayout, wrap the whole in an extra RelativeLayout, use a TableLayout; all these seem to solve it for a simpler layout but if you need these two TextViews inside something more complicated, or the same layout will be reused, for instance, by a RecyclerView, things get broken very quickly.

The only solution I found that really works all the time, regardless of what bigger layout you put it into, is a custom layout. It's very simple to implement, and being as lean as it possibly gets, it will keep the layout reasonably flat, it's easy to maintain—so in the long run, I consider this the best solution to the problem.

public class TwoTextLayout extends ViewGroup {

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

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

  public TwoTextLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = getChildCount();
    if (count != 2)
      throw new IllegalStateException("TwoTextLayout needs exactly two children");

    int childLeft = this.getPaddingLeft();
    int childTop = this.getPaddingTop();
    int childRight = this.getMeasuredWidth() - this.getPaddingRight();
    int childBottom = this.getMeasuredHeight() - this.getPaddingBottom();
    int childWidth = childRight - childLeft;
    int childHeight = childBottom - childTop;

    View text1View = getChildAt(0);
    text1View.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
    int text1Width = text1View.getMeasuredWidth();
    int text1Height = text1View.getMeasuredHeight();

    View text2View = getChildAt(1);
    text2View.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
    int text2Width = text2View.getMeasuredWidth();
    int text2Height = text2View.getMeasuredHeight();

    if (text1Width + text2Width > childRight)
      text1Width = childRight - text2Width;

    text1View.layout(childLeft, childTop, childLeft + text1Width, childTop + text1Height);
    text2View.layout(childLeft + text1Width, childTop, childLeft + text1Width + text2Width, childTop + text2Height);
  }
}

The implementation couldn't be simpler, it just measures the two texts (or any other child views, actually) and if their combined width exceeds the layout width, reduces the width of the first view.

And if you need modifications, eg. to align the second text to the baseline of the first, you can solve that easily, too:

text2View.layout(childLeft + text1Width, childTop + text1Height - text2Height, childLeft + text1Width + text2Width, childTop + text1Height);

Or any other solution, like shrinking the second view in relation to the first, aligning to the right, etc.

Solution 5 - Android

Why don't you put a left margin on the right TextView? I'm using this approach for a

|<TextView>       <ImageButton>|

and it works.

Solution 6 - Android

Solution with ConstraintLayout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp">

    <TextView
        android:id="@+id/leftText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:maxLines="1"
        app:layout_constraintEnd_toStartOf="@id/rightText"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="This is a lot of content that should be cut" />

    <TextView
        android:id="@+id/rightText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Right text" />

</androidx.constraintlayout.widget.ConstraintLayout>

enter image description here

Solution 7 - Android

When I faced with the sililar issue, I did following: I needed:

|<TextView, may be long> <TextViewFixedSize>      |
|<TextView, may be longer ...> <TextViewFixedSize>|
|<TextViewLong> <TextViewFixedSize>               |

You may use a solution like this:

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/layoutRecommendedServiceDescription"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="@+id/textViewRecommendedServiceTitle"
    app:layout_constraintTop_toBottomOf="@id/textViewRecommendedServiceTitle">
    <TextView
        android:id="@+id/textViewRecommendedService1"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:ellipsize="end"
        android:lines="1"
        android:maxLines="1"
        app:layout_constrainedWidth="true"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintEnd_toStartOf="@+id/textViewRecommendedServicePopular"
        app:layout_constraintStart_toStartOf="parent"
        tools:text="Long text"
        tools:visibility="visible" />
    <TextView
        android:id="@+id/textViewRecommendedServicePopular"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginStart="8dp"
        android:lines="1"
        android:text="@string/services_popular"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textViewRecommendedService1"
        app:layout_goneMarginStart="0dp"
        tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFelixView Question on Stackoverflow
Solution 1 - AndroidcapecrawlerView Answer on Stackoverflow
Solution 2 - AndroiddavenpcjView Answer on Stackoverflow
Solution 3 - Androidud_anView Answer on Stackoverflow
Solution 4 - AndroidGáborView Answer on Stackoverflow
Solution 5 - AndroidMaraguesView Answer on Stackoverflow
Solution 6 - AndroidDeni ErdyneevView Answer on Stackoverflow
Solution 7 - AndroidAleksey KislyakovView Answer on Stackoverflow