43

I need to achieve the following layout:

enter image description here

I have two TextViews in a relative layout: the green one is fixed text with wrap_content, the black one has dynamic text with wrap_content. The black text can change and become very long. I want the black TextView to expand with the text until the green view reaches the end of the parent. If that happens, the black TextView should stop expanding and ellipsize the end.

How can I achieve that?

What I tried:

 <RelativeLayout
    android:id="@+id/parent"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >


    <TextView
        android:id="@+id/leftTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:maxLines="1"
        android:layout_alignParentLeft="true"
        android:textSize="18sp" />

    <TextView
        android:id="@+id/rightTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/leftTextView"
        android:textSize="18sp" />

</RelativeLayout>

But when the black text gets bigger and bigger it pushes the green one out of the view

Onik
  • 19,396
  • 14
  • 68
  • 91
Y2theZ
  • 10,162
  • 38
  • 131
  • 200

7 Answers7

32

You can archive this layout by TableLayout and shrinkColumns attribute.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <!-- Row 1 -->
    <TableLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:shrinkColumns="0">

        <TableRow
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center_vertical">

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="4dp"
                    android:maxLines="1"
                    android:ellipsize="end"
                    android:text="abcdefghijklmnopqrstuvwxyz"/>

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="4dp"
                    android:maxLines="1"
                    android:ellipsize="none"
                    android:text="rightText"/>
        </TableRow>

    </TableLayout>


    <!-- Row 2 -->
    <TableLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:shrinkColumns="0">

        <TableRow
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center_vertical">

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="4dp"
                    android:maxLines="1"
                    android:ellipsize="end"
                    android:text="Longer Text view abcdefghijklmnopqrstuvwxyz"/>

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="4dp"
                    android:maxLines="1"
                    android:ellipsize="none"
                    android:text="rightText"/>
        </TableRow>

    </TableLayout>

    <!-- Row 3 -->
    <TableLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:shrinkColumns="0">

        <TableRow
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center_vertical">

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="4dp"
                    android:maxLines="1"
                    android:ellipsize="end"
                    android:text="Text view that is very logn and will not fit the parent width abcdefghijklmnopqrstuvwxyz"/>

            <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="4dp"
                    android:maxLines="1"
                    android:ellipsize="none"
                    android:text="rightText"/>
        </TableRow>

    </TableLayout>

</LinearLayout>

enter image description here

Here is same question ;)

Community
  • 1
  • 1
nshmura
  • 5,940
  • 3
  • 27
  • 46
23

Here comes a ConstraintLayout solution.

The magic trick is Chains.

<?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="20dp">

    <TextView
        android:id="@+id/leftTv"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:maxLines="1"
        android:textColor="#333333"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/rightTv"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_default="wrap"
        tools:text="Text view that is very long and will not fit the" />

    <TextView
        android:id="@+id/rightTv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:textColor="#21b38a"
        android:textSize="16sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/leftTv"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Text View" />

</androidx.constraintlayout.widget.ConstraintLayout>
Nich
  • 230
  • 2
  • 8
  • When your `rightTv` is too long, `leftTv` is gone and show nothing – mtrakal Jul 07 '20 at 10:28
  • Thanks. This is what I'm looking for. I think this should be the right answer. For the concern of `rightTv` is too long, I think in that case, it's impossible, we need to change the design instead. In my case, the right text (right view) width is determined. – Tuan Chau Sep 10 '20 at 21:57
  • 3
    Thanks a lot. `app:layout_constraintWidth_default="wrap"` is what did the trick for me. I even tried chains but didn't know something as constraint width exist. – Sparkzz Oct 17 '20 at 16:28
  • Hello, after 5hr I found your answer. – new Nov 06 '20 at 05:11
  • Hello, when the leftTv is long, its string doesn't show from the beginning. Why is there align issue? Thanks – new Nov 06 '20 at 07:57
  • After looking that this for a while, the difference between which views that is collapsed is the wrap_content or 0dp width. – Morten Holmgaard Feb 15 '21 at 08:01
  • Best solution I've seen for this case. Thank you! – serkancay Nov 05 '21 at 13:22
9

I suggest wrapping two TextView's into LinearLayout. Then apply android:weightSum="1" to this LinearLayout and apply android:layout_weight="1" to the child TextView that must extend.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:weightSum="1">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:ellipsize="end"
        android:maxLines="1"
        android:padding="8dp"
        android:text="blabla bla bla bla bla bla bla bla bla bla bla bla bla "/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:text="static text"/>

</LinearLayout>

Remember to set the two atributes this it to work perfectly:

android:ellipsize="end"
android:maxLines="1"

and LinearLayout must have android:layout_width="wrap_content"

If you would like this ViewGroup to take whole space, wrap it with another ViewGroup:

<RelativeLayout
   android:layout_height="wrap_content"
   android:layout_width="match_parent">

   <!--The previous view group-->

</RelativeLayout>
R. Zagórski
  • 20,020
  • 5
  • 65
  • 90
1

Here is a layout that will do what you'd like it to:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="left">

    <TextView
    android:id="@+id/leftTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_toLefOf="@+id/rightTextView"
    android:maxLines="1"
    android:textSize="18sp" />

    <TextView
    android:id="@+id/rightTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:textSize="18sp" />

</RelativeLayout>

The key parts here are the gravity of the relative layout, and aligning the right textview as alignParentRight with the left textview attached to that on the left.

John Gallagher
  • 525
  • 4
  • 15
0

The Layout you are expected can be created with help of layout_weight attirbute which comes with LinearLayout. So you may need to use LinearLayout with weightSumand layout_weight (passing to its childview) attributes.

Here's a layout which divides your width of layout in four parts which divides in to 3:1 between both Textview. Intacting right TextView making left TextView displays text as much as possible it can show without disurbing right TextView.

Please feel free to try out with variant weightSum and layout_weight combination as per your need.

Layout:

<LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:weightSum="4" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:singleLine="true"
        android:text="StackOverflow StackOverflow StackOverflow "
        />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:layout_weight="3"
        android:maxLines="1"
        android:text="TextView"
        android:textColor="@android:color/white" />
</LinearLayout>
Vikalp Patel
  • 10,669
  • 6
  • 61
  • 96
  • 1
    I tried your code but your second textview is taking some unwanted space too. like if i put one A as text then it is occupying space of 10 A – Illegal Argument Jun 08 '14 at 14:37
  • @IllegalArgument : As you said your second textview is fixed. You can tried with more `weightSum` and variant distribution of `layout_weight` in `TextView` – Vikalp Patel Jun 08 '14 at 14:43
  • I am also planning to answer this question but I havent found a correct answer yet using variants works only if the content is satic – Illegal Argument Jun 08 '14 at 14:46
  • @IllegalArgument I just calculate the width and set the maxwidth of left text programmly. Do you think it is a correct way? – Weibo Jun 08 '14 at 14:50
-1

I set the maxwidth of left textview base of the width of right textview, take a looking my code. hope this will help.

TextView leftText ;
TextView rightText;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    leftText = (TextView) findViewById(R.id.leftTextView);
    rightText = (TextView) findViewById(R.id.rightTextView);


    measureView(leftText);
    measureView(rightText);

    leftText.setMaxWidth(getWidth() - rightText.getMeasuredWidth());

}

private void measureView(View view) {
    ViewGroup.LayoutParams params = view.getLayoutParams();
    int childWidth = ViewGroup.getChildMeasureSpec(0, view.getPaddingLeft() + view.getPaddingRight(), params.width);
    int childHeight = ViewGroup.getChildMeasureSpec(0, view.getPaddingBottom() + view.getPaddingTop(), params.height);
    view.measure(childWidth, childHeight);

}

private int getWidth() {
    WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    return display.getWidth();
}

and the xml is:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

<TextView
    android:id="@+id/leftTextView"
    android:layout_width="wrap_content"
    android:layout_height="40dp"
    android:singleLine="true"
    android:text="abadabadabadabadabadabadabadabadabadabaasdfasdfasdfadabadabadabadabadabadabadabadabadabadabaasdfasdfasdfadabadabadabadabadabadabadabadabadabadabaasdfasdfasdfad"/>

<TextView
    android:id="@+id/rightTextView"
    android:layout_width="wrap_content"
    android:layout_height="40dp"
    android:textColor="#adc391"
    android:singleLine="true"
    android:text="adsfasd"/>

</LinearLayout>

this is the screenshot after running: enter image description here

Weibo
  • 1,042
  • 8
  • 18
  • I am not sure this is the correct way but can you measure the view in oncreate itself I tried to get the textveiw number of lines but I had to use viewtreeobserver becaure requestlayout and measurelayout functions of textview was not called so make of your answer – Illegal Argument Jun 08 '14 at 15:36
  • I can get the measured width after call ```measureView()``` in ```onCreate()``` – Weibo Jun 09 '14 at 04:28
-1

I have faced this kind of usecase in our app. In our app,there are 3 Views. ImageView | TextView | ImageView. The two ImageViews must be visible in the layout.The TextView should be ellipsized at the end. And I came up with a custom ViewGroup.

public class PriorityLayoutHorizontal extends ViewGroup
{

    private ArrayList<Integer> mPriorityHighChildren;

    public PriorityLayoutHorizontal(Context context)
    {
        this(context, null);
    }

    public PriorityLayoutHorizontal(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

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

        TypedArray typedArray = null;
        try
        {
            typedArray = context.obtainStyledAttributes(attrs, R.styleable.PriorityLayoutHorizontal, defStyleAttr, 0);
            final int id = typedArray.getResourceId(R.styleable.PriorityLayoutHorizontal_priorityHigh, -1);
            if(id != -1)
            {
                final int[] highPriorityChildren = getResources().getIntArray(id);
                setPriorityHigh(highPriorityChildren);
            }
        }
        finally
        {
            if(typedArray != null)
            {
                typedArray.recycle();
            }
        }
    }

    public void setPriorityHigh(int... priorityHigh)
    {
        Integer[] priorityHighInteger = new Integer[priorityHigh.length];
        int i = 0;
        for(int value : priorityHigh)
        {
            priorityHighInteger[i++] = Integer.valueOf(value);
        }
        mPriorityHighChildren = new ArrayList<Integer>(Arrays.asList(priorityHighInteger));
    }

    int mMaxHeight = 0;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthUsed = 0;
        int heightUsed = 0;
        final int childCount = getChildCount();

        for(int childPosition : mPriorityHighChildren)
        {
            final View childView = getChildAt(childPosition);
            if(childView.getVisibility() != View.GONE)
            {
                measureChildWithMargins(childView, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);
                widthUsed += getMeasuredWidthWithMargins(childView);
                heightUsed = Math.max(getMeasuredHeightWithMargins(childView), heightUsed);
            }
        }

        for(int childPosition = 0; childPosition < childCount; childPosition++)
        {
            if(! mPriorityHighChildren.contains(Integer.valueOf(childPosition)))
            {
                final View childView = getChildAt(childPosition);
                if(childView.getVisibility() != View.GONE)
                {
                    measureChildWithMargins(childView, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);
                    widthUsed += getMeasuredWidthWithMargins(childView);
                    heightUsed = Math.max(getMeasuredHeightWithMargins(childView), heightUsed);
                }
            }
        }

        mMaxHeight = heightUsed;

        int heightSize = heightUsed + getPaddingTop() + getPaddingBottom();
        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();

        int spaceUsed = paddingLeft;
        for (int childPosition = 0; childPosition < getChildCount(); childPosition++)
        {
            final View childView = getChildAt(childPosition);
            if(childView.getVisibility() != View.GONE)
            {
                final int top = (mMaxHeight / 2) - (childView.getMeasuredHeight() / 2);
                layoutView(childView, spaceUsed, paddingTop + top, childView.getMeasuredWidth(), childView.getMeasuredHeight());
                spaceUsed += getWidthWithMargins(childView);
            }
        }
    }

    private int getWidthWithMargins(View child)
    {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        return child.getWidth() + lp.leftMargin + lp.rightMargin;
    }

    private int getHeightWithMargins(View child)
    {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        return child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
    }

    private int getMeasuredWidthWithMargins(View child)
    {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        return child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
    }

    private int getMeasuredHeightWithMargins(View child)
    {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        return child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
    }

    private void layoutView(View view, int left, int top, int width, int height)
    {
        MarginLayoutParams margins = (MarginLayoutParams) view.getLayoutParams();
        final int leftWithMargins = left + margins.leftMargin;
        final int topWithMargins = top + margins.topMargin;

        view.layout(leftWithMargins, topWithMargins, leftWithMargins + width, topWithMargins + height);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs)
    {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams()
    {
        return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }
}

Declare a custom attribute, in values/attrs.xml

<declare-styleable name="PriorityLayoutHorizontal">
        <attr name="priorityHigh" format="reference"/>
</declare-styleable>

Using this layout, you can give high priority to the Views that should be visible in the layout definitely. Remaining one TextView will be drawn based on the remaining width in the screen.

To use this layout:

    <com.example.component.PriorityLayoutHorizontal
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    app:priorityHigh="@array/priority_array"
                    >

                    <TextView
                        android:id="@+id/contact_name"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:singleLine="true"
                        />

                    <ImageView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:src="@drawable/your_drawable"
                        />

</com.example.component.PriorityLayoutHorizontal>

And define the priority_array in values/arrays.xml

<integer-array name="priority_array">
        <item>1</item>
</integer-array>

Here, we have mentioned the priority for the View "1". That is the second TextView in your layout. So the second TextView will be visible always, and the first TextView will be ellipsized.

P.S: this is a generic solution. i.e. this can be used with different Views, and more than 2 Views in a line. You can use this layout, or a custom ViewGroup specific to your usecase could be written.

Bob
  • 13,447
  • 7
  • 35
  • 45