Строки Android ListView в ScrollView отображаются не полностью - обрезаны

Я столкнулся с проблемой при встраивании ListView в ScrollView, или, по крайней мере, я думаю, что проблема возникла из-за этого. Элемент ListView довольно прост:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/general_background_list_middle" 
    android:paddingTop="4dp"
    android:paddingBottom="4dp"
    android:paddingRight="0dp"
    android:paddingLeft="0dp">

    <ImageView
        android:id="@+id/chat_friends_avatar"       
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_marginLeft="7dp"
        android:layout_marginRight="8dp"
        android:paddingRight="0dp" 
        android:paddingLeft="0dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:src="@drawable/friends_icon_avatar_default"/>

    <TextView
        android:id="@+id/chat_message_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/chat_friends_avatar"
        android:layout_alignParentTop="true"
        android:layout_marginRight="35dp"
        android:maxLines="10"
        android:textSize="12dp"/>

    <TextView
        android:id="@+id/chat_friend_name"
        android:layout_width="140dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="3dp"
        style="@style/SubText"
        android:layout_toRightOf="@id/chat_friends_avatar"      
        android:layout_below="@id/chat_message_text" />

    <TextView
        android:id="@+id/chat_message_time"
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="8dp"
        android:layout_marginTop="3dp"
        style="@style/SubText"
        android:layout_alignParentRight="true"
        android:layout_below="@id/chat_message_text" />

</RelativeLayout>   

Однако, когда я вставляю список таких элементов в ScrollView, между некоторыми другими элементами строки отображаются не полностью, они обрезаются (см. изображение ниже), если текст переносится. ListView создается в ScrollView следующим образом:

<ListView
android:id="@+id/info_chat_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:cacheColorHint="@color/frame_background_color"
android:clickable="false"
android:divider="@null"
android:footerDividersEnabled="false"
android:focusable="false" >
</ListView> 

Если для высоты ListView установлено значение «wrap_content», отображается только первый элемент. Вот почему я использую метод для вычисления высоты строк списка:

private int getCommentsListHeight() {
        if (mChatAdapter != null && mChatAdapter.getCount() != 0) {
            if (mChatList != null) {// && mCommentsListItemHeight == 0) {
                mCommentsListItemHeight = 0;
                for (int i = 0; i < mChatAdapter.getCount(); i++) {
                    // Get view item height
                    View viewItem = mChatAdapter
                            .getView(i, new View(OnAirActivity.this), mChatList);
                    viewItem.measure(0, 0);
                    Logger.d(LOGTAG, "View " + i + " measured height = " + viewItem.getMeasuredHeight());
                    mCommentsListItemHeight += viewItem.getMeasuredHeight();
                }
            }
            //return mChatAdapter.getCount() * mCommentsListItemHeight;
            return mCommentsListItemHeight;
        } else {
            return 0;
        }
    }

К сожалению, в случае, когда текст внутри TextView переносится даже на несколько строк, высота элемента строки, возвращаемая методом getMeasuredHeight(), остается постоянной. Также метод getLineCount(), вызываемый для TextView внутри элемента строки, возвращает 1, даже если текст перенесен.

С другой стороны, если этот ListView встроен в LinearLayout, все работает нормально, и полный список отображается без обрезки.

Есть ли у вас какие-либо предложения относительно того, что здесь может быть не так? Мне действительно не нравится идея вручную измерять высоту элементов списка, и это, по-видимому, не работает, но почему Android не может красиво растянуть ListView внутри ScrollView, чтобы все это поместилось?

Вырезанный список: см. изображение


person PawelPredki    schedule 22.05.2012    source источник


Ответы (10)


Инкапсуляция ListView в ScrollView является ПЛОХОЙ практикой, поскольку сам ListView содержит возможности прокрутки. Вы должны реализовать решение, которое не содержит такой иерархии представлений, и я надеюсь, что оно сделает волшебство :)

person waqaslam    schedule 22.05.2012
comment
Спасибо за быстрый ответ. Таким образом, ваше предложение состояло бы в том, чтобы, например, программно создавать экземпляры этих строк внутри Scrollview один за другим? - person PawelPredki; 23.05.2012
comment
Нет. Вы должны использовать ListView, но не помещайте его в ScrollView. Вы можете настроить элементы строки, переопределив метод getView из адаптера, используемого для ListView. - person waqaslam; 23.05.2012
comment
Я использую адаптер, и он отлично работает в этом другом действии, о котором я упоминал, где ListView находится в LinearLayout. Здесь я должен использовать ScrollView, так как моя активность содержит много элементов, которые не помещаются на экране, в том числе ListView чата, который является источником всех проблем... - person PawelPredki; 23.05.2012
comment
тогда, возможно, я бы предложил вам использовать заголовки и/или нижние колонтитулы ListView для установки всех других представлений, которые у вас есть на данный момент внутри ScrollView. - person waqaslam; 23.05.2012
comment
Это немного излишне, поскольку большинство элементов в используемом в настоящее время ScrollView НЕ являются списком, который вызывает проблемы... Я понимаю, что нет способа заставить ListView нормально работать внутри ScrollView, поэтому мне нужно подумать о том, какой обходной путь/решение является лучшим в моем случае. Спасибо за помощь! - person PawelPredki; 23.05.2012

Используйте этот метод, созданный https://stackoverflow.com/users/205192/dougw.

 public static void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter(); 
        if (listAdapter == null) {
            // pre-condition
            return;
        }

        int totalHeight = 0;
        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
person Rajesh Wadhwa    schedule 19.04.2013
comment
Это работает для меня, но у меня нет проблемы переноса текста, как это было в исходном вопросе. Спасибо! - person Keith Entzeroth; 07.02.2014
comment
Это отлично работает, но затем, когда я добавил отступы вокруг списка, чтобы убрать текст с края экрана, небольшой бит снова обрезается. Что я должен добавить, чтобы исправить это? - person cnfw; 20.07.2014
comment
params.height - это окончательная высота, которая установлена. Вы можете добавить фиксированный буфер на случай, если вам придется продолжать заполнять - person Rajesh Wadhwa; 21.07.2014
comment
Как реализовать загрузить больше данных? - person Krunal Shah; 17.01.2015
comment
Этот ответ почти дословно скопирован отсюда: stackoverflow.com/a/3495908/1221537 - person adamdport; 10.03.2015
comment
Это плохое решение! Это создаст много бесполезных просмотров. Сама идея хороша, но не так. - person rekire; 10.07.2016
comment
Он может отображать элемент списка, но представление нижнего колонтитула не может отображаться, когда я добавляю его с помощью Listview.addfooter(View). На самом деле макет нижнего колонтитула существует. Как сделать, чтобы можно было видеть нижний колонтитул? - person james; 27.07.2016
comment
@Denny каждый вызов listAdapter.getView(i, null, listView) будет создавать новое представление и только для измерения. Это очень плохо для выступления. - person rekire; 18.09.2018
comment
Это действительно создает новые представления или просто получает то, что уже есть? мне кажется последнее - person Denny; 19.09.2018

Здесь ресурс основного макета с ScrollView:

<ScrollView android:layout_height="fill_parent" android:layout_width="fill_parent">
<LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/parentLayout"/>
</ScrollView>

Вот код для вставки элементов:

parentLayout.removeAllViews();
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = comments.size() - 1; i >= 0; i--) {
CommentInfo comment = comments.get(i);
View view = inflater.inflate(your_resource_id, null, false);

TextView commentsContent =(TextView)view.findViewById(R.id.commentContent);
if (commentsContent != null) {
    String data = String.format("%s (by %s, %s)",  comment.getCommentText(), comment.getUserName(),
        commentsContent.setTextSize(st.getTextSize());
    commentsContent.setText(data);

}

parentLayout.addView(view, 0);

}       
person Layko Andrey    schedule 24.05.2012
comment
Это было бы хорошим решением, если бы единственными элементами в моем ScrollView были представления комментариев. И в этом случае размещение ListView внутри LinearLayout было бы лучшим решением. В моем случае внутри LinearLayout внутри ScrollView есть еще много элементов, поэтому мне сначала нужно знать, где размещать комментарии. Думаю, оба подхода хороши. - person PawelPredki; 24.05.2012
comment
Нет проблем добавить любой элемент в ScrollView после и до LinearLayout. Я просто вырезал код, чтобы показать, что вам нужно. В реальном файле у меня есть много элементов управления до LinearLaout и после включения еще одного LinearLayout, который также используется как ListView. - person Layko Andrey; 24.05.2012
comment
Если у вас есть другие дочерние элементы внутри ScrollView, кроме LinearLayout, то это противоречит тому, что Android говорит вам делать здесь: developer.android.com/reference/android/widget/ScrollView.html ScrollView — это FrameLayout, то есть вы должны поместить в него один дочерний элемент, содержащий все содержимое для прокрутки; этот дочерний элемент может сам быть менеджером компоновки со сложной иерархией объектов - person PawelPredki; 24.05.2012
comment
Добавьте верхний макет, например LinearLayout, в качестве дочернего элемента ScrollView и добавьте к нему дочерний макет любых других элементов управления, включая LinearLayouts, которые будут использоваться в качестве контейнера списка. Делаю так же, работает нормально. - person Layko Andrey; 24.05.2012
comment
что если требуется ExpandableListView вместо ListView? - person CoDe; 07.01.2015

У меня была такая же проблема в моем проекте. Вам нужно создать простой LinearLayout внутри ScrollView. После этого вам нужно создать новый вид с вашим элементом списка в формате xml, используя LayoutInflater. После создания поместите все данные в новый вид и добавьте в LinearLayout как дочерний вид:

linearLayot.addView(newView, position_you_need).

Надеюсь, это поможет вам!

person Layko Andrey    schedule 23.05.2012
comment
Итак, вы предлагаете расположить макеты в следующем порядке: <ScrollView> <LinearLayout> <... my other views ...> <LinearLayout> <ProblematicListView> </LinearLayout> <... more views ...> </LinearLayout> </ScrollView> Разве это не одно и то же? ListView все еще находится внутри ScrollView? Прямо сейчас я пошел по «ручному» пути, выполнив ‹include android:id=@+id/chat_comment_x layout=mylayout›, x=0..5. И у меня есть массив заполнителей для тех пяти макетов, которые я заполняю с помощью тот же адаптер, который я использовал для представления списка. По сути, у меня есть динамический список из 5 элементов, и он отлично работает. - person PawelPredki; 23.05.2012
comment
Нет, я предлагаю вам сделать следующее: ‹ScrollView› ‹LinearLayout› ‹… мои другие виды …› ‹LinearLayout›‹/LinearLayout› ‹/ScrollView›. Основная идея состоит в том, чтобы разместить элементы списка в линейном макете, а не в специальном макете списка. Все элементы будут прокручиваться и отображаться без обрезки. - person Layko Andrey; 24.05.2012

Я принял к сведению рекомендацию не использовать элемент ListView внутри ScrollView и решил использовать метод грубой силы, чтобы добиться того, что мне нужно. Поскольку необходимо отобразить постоянное количество до пяти строк списка, я удалил экземпляр ListView из файла xml и заменил его пятью экземплярами строк:

<include android:id="@+id/info_comment_1" layout="@layout/chat_single_message" />
<include android:id="@+id/info_comment_2" layout="@layout/chat_single_message" />
<include android:id="@+id/info_comment_3" layout="@layout/chat_single_message" />
<include android:id="@+id/info_comment_4" layout="@layout/chat_single_message" />
<include android:id="@+id/info_comment_5" layout="@layout/chat_single_message" />

В классе Activity я объявляю пять заполнителей для этих представлений:

private RelativeLayout mChatMessages[] = new RelativeLayout[COMMENTS_NUMBER];

и инициализируйте их с помощью:

mChatMessages[0] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_1);
mChatMessages[1] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_2);
mChatMessages[2] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_3);
mChatMessages[3] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_4);
mChatMessages[4] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_5);

Затем, всякий раз, когда приходит новое сообщение, я использую ChatAdapter (тот же самый, который я использовал ранее для ListView) и вызываю его метод getView():

protected void updateChatMessages() {
  int msgCount = mChatAdapter.getCount();
  for (int i = 0; i < COMMENTS_NUMBER; i++) {
    if (msgCount <= i) {
      mChatMessages[i].setVisibility(View.GONE);
    } else {
      mChatMessages[i] = (RelativeLayout) mChatAdapter.getView(i, mChatMessages[i], null); 
      mChatMessages[i].setVisibility(View.VISIBLE);
    }   
  }
}

Я больше никогда не раздуваю представления pariculat, поскольку единственное, что меняется, — это содержимое каждой строки, а не макет. Это означает, что здесь нет штрафа за производительность.

По сути, это ручная реализация ListView с ограниченным максимальным количеством элементов. На этот раз, однако, ScrollView может прекрасно вписать их, и ничего не обрезается.

Для динамического количества строк можно использовать подход, предложенный Лайко, когда представления создаются программно и добавляются в LinearLayout внутри ScrollView.

person PawelPredki    schedule 23.05.2012

Я вижу, что ListView находится внутри ViewPager; еще один простой подход к решению этой проблемы — добавить app:layout_behavior="@string/appbar_scrolling_view_behavior" в ViewPager в XML-файл макета, как показано ниже.

<android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

Чтобы предотвратить такое же поведение в нижней части списка, вы также можете добавить android:layout_marginBottom="?attr/actionBarSize" в ViewPager следующим образом.

<android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    android:layout_marginBottom="?attr/actionBarSize"/>

Это поздно, но я надеюсь, что это поможет любому другому человеку.

person Cletus Ajibade    schedule 23.03.2017

попробуйте .. после создания всего представления добавьте строку ниже для местоположения ScrollView на экране (x, y)

  ScrollView scrollView = (ScrollView)findViewById(R.id.scrollView);

  scrollView.smoothScrollTo(0,0);// top location zero index
person Neeraj Singh    schedule 08.05.2017

У меня была аналогичная проблема. у меня есть

RelativeLayout
  listView
  includeLayout  

где я включаю нижнюю навигацию под listView с этим

<include
    android:id="@+id/includeLayout"
    layout="@layout/bottom_nav_bar"

и мой listView был обрезан - не использовалась вся доступная высота между заголовком и нижней навигацией. Я пробовал различные настройки xml, предложенные в этой и других темах, но мне помогло добавить

android:layout_alignBottom="@+id/includeLayout"

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

person John Leasia    schedule 10.05.2017

Это работает для меня

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

    android:padding="@dimen/activity_vertical_margin">

    <TextView
        android:id="@+id/tv_status"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="17sp"
        android:text="@string/text_list_devices" />

    <ListView
        android:id="@+id/lv_paired"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        android:cacheColorHint="#00000000"
        android:layout_above="@+id/signup_t"
        android:layout_below="@id/tv_status"
        />

    <Button

        android:id="@+id/signup_t"
        style="?android:textAppearanceSmall"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textColor="@android:color/white"
        android:layout_alignParentBottom="true"
        android:text="Print All Records"
        android:typeface="sans"
        android:layout_marginLeft="45dp"
        android:layout_marginRight="45dp"
        android:textSize="24sp"
        android:background="@drawable/selector_for_button"
        android:textStyle="bold" />
</RelativeLayout>
person Nouman Ch    schedule 16.11.2017

Это ПЛОХАЯ практика. Но есть некоторые ситуации, когда мы не можем избежать этого. Например, в динамических макетах электронной коммерции мы можем размещать несколько списков или перерабатывать представления, но вы не хотите прокручивать внутри одного элемента высоты (если это случайно нужно!). Я столкнулся с такой проблемой. Исправил простым способом. Я не говорю, что это правильный способ, но это может помочь некоторым. !! Раньше я перерабатывал представление.

(01) Создайте интерфейс для возврата высоты просмотра.

public interface AfterViewLoadListener {

    void onViewHeightMeasured(int height, String mode);
}

(02) внедрить в свою деятельность

public class *Activity extends AppCompatActivity implements AfterViewLoadListener{
  /**  your codes **/
  final SimpleListRecycleAdapter order_adapter = new SimpleListRecycleAdapter(this,"ORDER");
}

@Override
public void onViewHeightMeasured(int height, String mode) {
    if(mode.equals("ORDER") && height > 0){
        recycleView.setMinimumHeight(height);
    }
}

(03) внутри пользовательского адаптера просмотра корзины

AfterViewLoadListener viewLoadListener = null;
public SimpleListRecycleAdapter(AfterViewLoadListener listener, String mode) {
    if(listener instanceof AfterViewLoadListener){
        viewLoadListener = listener;
    }
    this.mode = mode;
}

(04) переопределить метод onViewAttachedToWindow

@Override
public void onViewAttachedToWindow(@NonNull SimpleListViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    View view = holder.itemView;
    view.measure(0, 0);
    this.viewMinHeight = view.getMeasuredHeight();

    if(!firstFlag){
        firstFlag = true;
        viewLoadListener.onViewHeightMeasured(this.viewMinHeight*filtered.length(),mode);
    }
}

(05) Вот так. Это сработало для меня.

person Hariharan Kanakaraja    schedule 18.03.2020