Textview with long text pushes out other views in

2019-01-31 17:19发布

My problem is very similar to How to get a layout where one text can grow and ellipsize, but not gobble up the other elements on the layout, but read on below why I can't use TableLayouts as proposed there.

I'm trying to create a listview row that basically looks like this:

| TextView | View 1 | View 2 |

All views contain variable width elements. The TextView has ellipsize="end" set. View 1 should align left of the TextView, while View 2 should align to the right of the screen. So, normally, there would be whitespace between View 1 and View 2. As the text in the TextView grows longer, the TextView should grow, pushing View 1 to the right until there is no more whitespace left. Then, ellipsize should kick in, cutting of the text in TextView and appending an ellipsis ("...") at the end.

So, the result should look something like this:

+----------------------------------------+
| short text [view1]             [view2] |
+----------------------------------------+
| long text with ell ... [view1] [view2] |
+----------------------------------------+

I've tried:

  • TableLayouts, but they seem to make scrolling extremely slow on some devices.
  • RelativeLayouts, but I either had overlapping views, or view1 or view2 disappeared completely.
  • GridLayouts, but the TextView always grows until it takes up the whole width of the screen, thus pushing view1 and view2 out of the screen.

This is the GridLayout I tried:

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

    <TextView
        android:layout_gravity="left|fill_horizontal"
        android:ellipsize="end"
        android:singleLine="true"
        android:text="Long text to demonstrate problem with TextView in GridLayout taking up too much space despite ellipsis" />

    <TextView
        android:layout_gravity="left"
        android:text="(view1)" />

    <TextView
        android:layout_gravity="right"
        android:text="(view2)" />
</GridLayout>

View 1 and View 2 are not really TextViews, I just used them in the example to simplify things.

Is there any way to achieve this without using TableLayouts?

EDIT: As requested, here is my attempt at solving this with a RelativeLayout. The TextView takes up the full width of the screen in this case, so neither view1 nor view2 are visible.

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

    <TextView
        android:id="@+id/rl0"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:ellipsize="end"
        android:singleLine="true"
        android:text="Long text to demonstrate problem with TextView in GridLayout taking up too much space despite ellipsis" />

    <TextView
        android:id="@+id/rl1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/rl0"
        android:layout_marginLeft="10dp"
        android:text="(view1)" />

    <TextView
        android:id="@+id/rl2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/rl1"
        android:layout_alignParentRight="true"
        android:layout_marginLeft="10dp"
        android:text="(view2)" />
</RelativeLayout>

14条回答
家丑人穷心不美
2楼-- · 2019-01-31 17:45

Try this

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center">
       <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:singleLine="true"
            android:text="Long text to demonstrate problem with TextView in GridLayout taking up too much space despite ellipsis"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center">
        <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="left"
             android:text="(view1)"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center">
        <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="right"
             android:text="(view2)"/>
    </LinearLayout>

</LinearLayout>

Currently, all views are centered. You can change android:gravity property to meet your needs. For example, you may want to align view1 right and view2 left in which case last two LinearLayouts would look something like (with 5dp margin on the right and left respectively):

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:gravity="center|right">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginRight="5dp"
        android:text="(view1)"/>
</LinearLayout>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:gravity="center|left">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:layout_marginLeft="5dp"
        android:text="(view2)"/>
</LinearLayout>
查看更多
SAY GOODBYE
3楼-- · 2019-01-31 17:48

GridLayout is like the other things on Android : flawed by design.

You will need a custom Layout, the following example will allow you to layout things like:

    [      label | short text     | very long label | short text     ]

    [ long label | very very very |           label | very long text ] 
    [            | long text      |                 |                ]



import android.content.Context;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;


public class TwoColumsGridLayout extends ViewGroup {
    private final List<List<View>> rows;
    private int rowCount = 0;
    private int firstColumWidth;
    private int secondColumWidth;
    private int thirdColumWidth;
    private int fourthColumnWidth;
    private final List<Integer> rowHeights = new ArrayList<>();
    private final List<List<Integer>> cellHeights = new ArrayList<>();
    private final List<Integer> firstCellsWidths = new ArrayList<>(4);
    private final List<Integer> thirdCellsWidths = new ArrayList<>(4);

    public TwoColumsGridLayout(Context context, int rowCount) {
        super(context);
        rows = new ArrayList<>(rowCount);
    }


    public void add(Context ctx, TextView l1, View t1, TextView l2, View t2) {
        final List<View> row = new ArrayList<>(4);
        row.add(l1);
        row.add(t1);
        row.add(l2);
        row.add(t2);
        rows.add(row);
        this.addView(l1);
        this.addView(t1);
        if (l2 != null)
            this.addView(l2);
        if (t2 != null)
            this.addView(t2);
        this.rowCount++;
    }

    public int getRowCount() {
        return rowCount;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int curLeft = 0;
        int curBottom;
        int curRight;
        int curTop = 0;
        int i = 0;
        for (List<View> row : rows) {
            final int rowHeight = this.rowHeights.get(i);
            final List<Integer> rowCellHeights = this.cellHeights.get(i);
            final View v0 = row.get(0);
            curLeft = 0;
            curRight = curLeft + this.firstColumWidth;
            if (v0 != null) {
                curBottom = curTop + rowCellHeights.get(0);
                // Right align
                v0.layout(curLeft + this.firstColumWidth - this.firstCellsWidths.get(i), curTop + 7, curRight, curBottom + 7);
            }
            //
            final View v1 = row.get(1);
            curLeft += this.firstColumWidth;

            curRight = curLeft + this.secondColumWidth;
            if (v1 != null) {
                curBottom = curTop + rowCellHeights.get(1);
                v1.layout(curLeft, curTop, curRight, curBottom);
            }
            //
            final View v2 = row.get(2);
            curLeft += this.secondColumWidth;
            curRight = curLeft + this.thirdColumWidth;
            if (v2 != null) {
                curBottom = curTop + rowCellHeights.get(2);
                // Right align
                v2.layout(curLeft + this.thirdColumWidth - this.thirdCellsWidths.get(i), curTop + 7, curRight, curBottom + 7);
            }
            //
            final View v3 = row.get(3);
            curLeft += this.thirdColumWidth;
            curRight = curLeft + this.fourthColumnWidth;
            if (v3 != null) {
                curBottom = curTop + rowCellHeights.get(3);
                v3.layout(curLeft, curTop, curRight, curBottom);
            }
            curTop += rowHeight;
            i++;
        }

    } 

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        final int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        // Compute first column width
        firstColumWidth = 0;
        for (List<View> row : rows) {
            final View v = row.get(0);
            if (v != null) {
                v.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
                measureChild(v, widthMeasureSpec, heightMeasureSpec);
                final int w = v.getMeasuredWidth();
                if (firstColumWidth < w) {
                    firstColumWidth = w;
                }
            }
        }
        // Compute third column width
        thirdColumWidth = 0;
        for (List<View> row : rows) {
            final View v = row.get(2);
            if (v != null) {
                v.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
                measureChild(v, widthMeasureSpec, heightMeasureSpec);
                final int w = v.getMeasuredWidth();
                if (thirdColumWidth < w) {
                    thirdColumWidth = w;
                }
            }
        }
        secondColumWidth = (parentWidth - firstColumWidth - thirdColumWidth) / 2;
        fourthColumnWidth = parentWidth - firstColumWidth - secondColumWidth - thirdColumWidth;
        // Clear
        this.rowHeights.clear();
        this.cellHeights.clear();
        this.firstCellsWidths.clear();
        this.thirdCellsWidths.clear();
        // Compute heights
        int height = 0;
        for (List<View> row : rows) {
            final ArrayList<Integer> rowCellHeights = new ArrayList<>(4);
            cellHeights.add(rowCellHeights);
            int rowHeight = 0;
            // First column
            final View v0 = row.get(0);
            if (v0 != null) {
                int h = v0.getMeasuredHeight();
                this.firstCellsWidths.add(v0.getMeasuredWidth());
                rowCellHeights.add(h);
                if (rowHeight < h) {
                    rowHeight = h;
                }
            } else {
                this.firstCellsWidths.add(0);
            }
            // Second column
            final View v1 = row.get(1);
            if (v1 != null) {
                v1.setLayoutParams(new ViewGroup.LayoutParams(secondColumWidth, LayoutParams.WRAP_CONTENT));
                measureChild(v1, widthMeasureSpec, heightMeasureSpec);
                int h = v1.getMeasuredHeight();
                rowCellHeights.add(h);
                if (rowHeight < h) {
                    rowHeight = h;
                }
            }
            // Third column
            final View v2 = row.get(2);
            if (v2 != null) {
                int h = v2.getMeasuredHeight();
                this.thirdCellsWidths.add(v2.getMeasuredWidth());
                rowCellHeights.add(h);
                if (rowHeight < h) {
                    rowHeight = h;
                }
            } else {
                this.thirdCellsWidths.add(0);
            }
            // Fourth column
            final View v3 = row.get(3);
            if (v3 != null) {
                v3.setLayoutParams(new ViewGroup.LayoutParams(fourthColumnWidth, LayoutParams.WRAP_CONTENT));
                measureChild(v3, widthMeasureSpec, heightMeasureSpec);
                int h = v3.getMeasuredHeight();
                rowCellHeights.add(h);
                if (rowHeight < h) {
                    rowHeight = h;
                }
            }
            height += rowHeight;
            this.rowHeights.add(rowHeight);

        }
        setMeasuredDimension(parentWidth, height);
    }
}

Have fun.

查看更多
劫难
4楼-- · 2019-01-31 17:50

I seem to have found a potential solution to prevent a TextView in GridLayout from growing unboundedly and pushing out other views. Not sure if this has been documented before.

You need to use fill layout_gravity and set an arbitrary layout_width or width on the long TextView in need of ellipsizing.

android:layout_gravity="fill"
android:layout_width="1dp"

Works for both GridLayout and android.support.v7.widget.GridLayout

查看更多
干净又极端
5楼-- · 2019-01-31 17:51

I think you should create custom layout for your purpose. I don't know how to do this using only default layouts/view and make it work for all cases.

查看更多
不美不萌又怎样
6楼-- · 2019-01-31 17:51

I had the same problem with the grid layout. what i did is given a fixed width for the text view and also given layout_columnWeight property for each text view then the issue was fixed ,hope it helps ...

           <TextView
                android:id="@+id/txtName"
                style="@style/MyDetailTitle"
                android:layout_width="@dimen/detail_length"
                android:layout_height="wrap_content"
                android:text="@string/app_name"
                app:layout_column="3"
                app:layout_columnWeight="1"
                app:layout_gravity="start"
                app:layout_row="1" />
查看更多
欢心
7楼-- · 2019-01-31 17:53

If the views on the right get pushed over by the text by design, you might as well use a ListView instead of a GridView.

You would just need to make the base of the list item layout a RelativeLayout, and set rules like this:

  1. You can set the two views on the right to alignParentRight (using android:layout_alignParentLeft="true"), but make sure the first view stays to the left of the second so it will push itself to the left as the views stretch out.

  2. You can make the TextView on the left align to the left, but stay to the left of the first view (using android:layout_toLeftOf="@+id/viewId") so it won't overlap with the views.

查看更多
登录 后发表回答