Android TextView Truncation Priority или сопротивление сжатию

Я выкладываю на экран 3 вида по горизонтали (изображение фиксированного размера и 2 однострочных текстовых вида: leftTextView и rightTextView) и пытаюсь заставить rightTextView обнять leftTextView, но в случае, если ширина обеих меток превысит размер экрана, обрежьте leftTextiew.

Пример желаемой функциональности:

|img|leftText|rightText|                ||(end of screen)
|img|leftTextMedium|rightText|          ||
|img|leftTextTooLongSoTrunc...|rightText||

Что происходит на самом деле:

|img|leftText|rightText|                ||(end of screen)
|img|leftTextPrettyLongButNotHuge|rightT||
|img|leftTextWhichIsIncrediblyLongBlahBl||

В настоящее время, если размер обоих текстовых представлений превышает ширину представления, rightTextView в конечном итоге сжимается, чтобы освободить место, даже несмотря на то, что я установил android:ellipsize="end" на leftTextView.

Есть ли способ заставить leftTextView иметь «приоритет усечения», который говорит, что он должен усекаться, если это приведет к тому, что другие представления не будут соответствовать? Или, с другой стороны, могу ли я настроить rightTextView на «сопротивление сжатию», чтобы он не сжимался, чтобы освободить место для leftTextView? Вот пример моего XML:

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

    <ImageView
        android:id="@+id/fixedWidthImage"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <TextView
        android:id="@+id/leftTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toEndOf="@+id/fixedWidthImage"
        android:layout_toRightOf="@id/fixedWidthImage"
        android:maxLines="1"
        android:textSize="18sp"
        android:ellipsize="end" />

    <TextView
        android:id="@+id/rightTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toEndOf="@+id/leftTextView"
        android:layout_toRightOf="@+id/leftTextView"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:maxLines="1"
        android:textSize="12sp" />

</RelativeLayout>

Что я пробовал:

  1. В leftTextView добавьте toStartOf / toLeftOf rightTextView (вылетает приложение)
  2. Вместо того, чтобы выравнивать rightTextView по концу/справа от leftTextView, переверните его и выровняйте leftTextView по началу/слева от rightTextView. Это приводит к тому, что rightTextView привязывается к концу экрана, а не прямо справа от leftTextView. Пример того, как это выглядит:

    |img|leftText                 |rightText||
    |img|leftTextMedium           |rightText||
    |img|leftTextTooLongSoTrunc...|rightText||
    
  3. Даже если я установлю android:minWidth="25dp" только для того, чтобы обеспечить видимость хотя бы части rightTextView (чего я не хочу делать, потому что длина этого текста является переменной), это все равно приведет к сжатию ширины, чтобы освободить место для leftTextView.

  4. Согласно ответу Эмануэля Моклина, я попытался обернуть LinearLayout и установить вес на leftTextView. Результат был визуально идентичен тому, что я пробовал в № 2.

person Stonz2    schedule 09.08.2016    source источник
comment
В мою защиту двойной маркировки скажу, что на тот другой вопрос не было ответа в то время, когда я задавал свой. Кроме того, его название ужасно, и, вероятно, поэтому я вообще не осознавал, что оно существует.   -  person Stonz2    schedule 05.10.2016


Ответы (4)


Вы можете заархивировать этот макет по атрибуту TableLayout и shrinkColumns.

<?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="1">

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

            <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@mipmap/ic_launcher"/>

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

            <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="1">

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

            <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@mipmap/ic_launcher"/>

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

            <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="1">

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

            <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@mipmap/ic_launcher"/>

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

            <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>

введите описание изображения здесь

person nshmura    schedule 15.08.2016
comment
Можно ли это сделать для вертикальных макетов? Мне нужно, чтобы элементы в TableRow были выровнены по вертикали и установлены android:shrinkColumns=0, чтобы сжать первое представление. - person Vlad; 25.07.2017
comment
Установите android:shrinkColumns="0" нормально. - person woxingxiao; 17.05.2018
comment
Как я могу сделать это с помощью ConstraintLayout? - person jmarkstar; 05.06.2019

Вот что я делаю:

<?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:orientation="horizontal">

    <ImageView
        android:id="@+id/fixedWidthImage"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/ic_launcher_pro" />

    <TextView
        android:id="@+id/leftTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_gravity="center_vertical|left"
        android:ellipsize="marquee"
        android:lines="1"
        android:text="This is a very long text, and now even longer" />

    <TextView
        android:id="@+id/rightTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical|left"
        android:lines="1"
        android:text="This is also quite long" />

</LinearLayout>

android:layout_weight="1" делает здесь "трюк". Документация не совсем ясна по этому поводу:

Например, если есть три текстовых поля и два из них объявляют вес равным 1, а другому не присваивается вес, то третье текстовое поле без веса не будет увеличиваться и будет занимать только область, требуемую его содержимым.

(https://developer.android.com/guide/topics/ui/layout/linear.html#Weight)

В нем не рассматривается случай, когда нет места для заполнения, но недостаточно места для всех представлений. Одной из интерпретаций упомянутого фрагмента документации может быть то, что поле без веса (в данном случае rightTextView) будет занимать область, требуемую его содержимым, в то время как поле с весом (в данном случае leftTextView) будет «расти» (как в случае с отрицательным значением). роста) и это именно то, что происходит.

person Emanuel Moecklin    schedule 12.08.2016
comment
Хотя вес допускает отрицательный рост, о котором вы упомянули, он также расширяет поле, чтобы заполнить ширину, когда в этом нет необходимости. Это приводит к тому, что rightTextView прикрепляется к правому/концу родительского представления. Функционально это выглядит так же, как и в № 2 того, что я пробовал в вопросе. - person Stonz2; 12.08.2016
comment
@ Эмануэль Моклин, что, если я использую RelativeLayout, а не LinearLayout? - person AJW; 27.03.2019
comment
@Stonz2, это то, что обнаружили и мои тесты. - person Bingkongmaster; 06.11.2020

Я думаю, вам нужно будет сделать индивидуальный макет. Это хорошо работает для образца, который я сделал, и он соответствует всем требованиям, которые вы разместили выше, но вам нужно будет добавить немного больше математики в код, чтобы учесть layout_margins и отступы.

Основная идея того, что здесь происходит, заключается в том, что я создал подкласс LinearLayout и на проходе onMeasure проверяю, чтобы правильное представление могло занимать как можно больше места. Обратите внимание, что мне нужно повторно измерить правильный вид, а не просто получить измеренную ширину, потому что linearlayout не даст ему достаточно места. Как только вы узнаете, сколько места занимает правый вид, проверьте, не занимает ли средний вид (называемый leftView... извините) больше места, чем вы хотите. Если это так, просто дайте ему место, которое осталось.

Основываясь на ваших требованиях к тому, чтобы он находился в RecyclerView... Я знаю, вы сказали, что хотите чистое решение xml, но вы получите лучшую производительность, если решите его в коде, поскольку xml 1. может быть невозможным и 2. будет требуют больше вычислений, чем просто сделать это самостоятельно.

public class CustomLinearLayout extends LinearLayout {

private View child0;
private TextView leftView, rightView;
public CustomLinearLayout(Context context) {
    super(context);
}

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

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

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (rightView == null) {
        child0 = getChildAt(0);
        leftView = (TextView)getChildAt(1);
        rightView = (TextView) getChildAt(2);
    }

    int spec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
    rightView.measure(spec, spec);

    int remaining = getMeasuredWidth() - child0.getMeasuredWidth() - rightView.getMeasuredWidth();
    if (leftView.getMeasuredWidth() > remaining) {
        int specW = MeasureSpec.makeMeasureSpec(remaining, MeasureSpec.AT_MOST);
        int specH = MeasureSpec.makeMeasureSpec(leftView.getMeasuredHeight(), MeasureSpec.EXACTLY);
        leftView.measure(specW, specH);
    }
}

XML-макет

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.joshua.myapplication.MainActivity">

<com.example.joshua.myapplication.CustomLinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:background="#FF0000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:maxLines="1"
        android:textSize="18sp"
        android:text="My Left View With Really Really Long Text That should fill the screen"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:maxLines="1"
        android:textSize="18sp"
        android:text="My Right View"
        />
</com.example.joshua.myapplication.CustomLinearLayout>

<com.example.joshua.myapplication.CustomLinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:layout_marginTop="20dp">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:background="#FF0000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:maxLines="1"
        android:textSize="18sp"
        android:text="My Left View with short text"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:maxLines="1"
        android:textSize="18sp"
        android:text="My Right View"
        />
</com.example.joshua.myapplication.CustomLinearLayout>
</LinearLayout>

введите здесь описание изображения

person whizzle    schedule 12.08.2016
comment
Спасибо за ответ - я попробовал, и он отлично работает, но подход nshmura также работает, но он основан исключительно на XML и не полагается на подклассы. - person Stonz2; 16.08.2016

Просто используйте свойство android:maxWidth для вашего левого textView. Таким образом, ваш левый обзор никогда не превысит этот предел. и всегда будет место для вашего правильного взгляда.

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

    <ImageView
        android:id="@+id/fixedWidthImage"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/leftTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_toEndOf="@+id/fixedWidthImage"
        android:layout_toRightOf="@+id/fixedWidthImage"
        android:ellipsize="end"
        android:lines="1"
        android:maxWidth="250dp"
        android:text="This is a Long left Text which never ends and You'll be tired after reading this long stuff.. blah blah... " />

    <TextView
        android:id="@+id/rightTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_toEndOf="@+id/leftTextView"
        android:layout_toRightOf="@+id/leftTextView"
        android:ellipsize="end"
        android:lines="1"
        android:text="Right Text" />

</RelativeLayout>

Предварительный просмотр макета

Однако ограничение решения заключается в том, что вы должны указать фиксированное значение в свойстве maxWidth. Таким образом, он будет отображаться по-разному на разных размерах экрана. Хотя в большинстве случаев вы не столкнетесь с проблемой.

Кроме того, для отображения длинного textView в одной строке android:ellipsize="marquee" является хорошим вариантом. Поскольку он будет прокручивать ваш вид по горизонтали. Но не забудьте написать это textView.setSelected(true) в своем коде, чтобы он работал. Подробности

person Shaishav Jogani    schedule 15.08.2016
comment
С Android, имеющим такое большое несоответствие размеров экрана (плюс портретный и альбомный режим), фиксированная ширина определенно не является желательным подходом. - person Stonz2; 15.08.2016
comment
Я уже говорил, что это будет работать, когда вы знаете некоторый фиксированный размер представления по ширине. В противном случае вы можете сделать это программно, как предложил @whizzle. - person Shaishav Jogani; 16.08.2016