Как я могу поместить ListView в ScrollView без его разрушения?

351

Я искал решения этой проблемы, и единственный ответ, который я могу найти, - « не помещайте ListView в ScrollView ». Мне еще предстоит увидеть какое-либо реальное объяснение того, почему . Кажется, единственная причина, по которой я могу найти это, заключается в том, что Google не считает, что вам следует этого делать. Ну, я так и сделал.

Итак, вопрос в том, как вы можете поместить ListView в ScrollView, не сворачивая его до минимальной высоты?

DougW
источник
11
Потому что, когда устройство использует сенсорный экран, нет хорошего способа разграничить два вложенных прокручиваемых контейнера. Он работает на традиционных рабочих столах, где вы используете полосу прокрутки для прокрутки контейнера.
Romain Guy
57
Конечно, есть. Если они касаются внутренней, прокрутите это. Если внутренний слишком большой, это плохой дизайн интерфейса. Это не значит, что это плохая идея в целом.
DougW
5
Просто наткнулся на это для моего приложения для планшета. Хотя на смартфоне это выглядит не так уж плохо, на планшете это ужасно, и вы легко можете решить, хочет ли пользователь прокрутить внешний или внутренний ScrollView. @DougW: Ужасный дизайн, я согласен.
медуза
4
@Romain - это довольно старая публикация, поэтому мне интересно, были ли проблемы уже решены, или все еще нет хорошего способа использования ListView внутри ScrollView (или альтернативы, которая не требует ручного кодирования) все тонкости ListView в LinearLayout)?
Джим
3
@Jim: «или альтернатива, которая не предполагает ручного кодирования всех тонкостей ListView в LinearLayout» - поместите все внутри ListView. ListViewможет иметь несколько стилей строк, а также строки, которые нельзя выбрать.
CommonsWare

Ответы:

196

Использование, ListViewчтобы сделать это не прокрутка очень дорого и идет против всей цели ListView. Вы должны НЕ делать этого. Просто используйте LinearLayoutвместо этого.

Ромэн Гай
источник
69
Источником был бы я, так как я отвечал за ListView в течение последних 2 или 3 лет :) ListView проделывает большую дополнительную работу для оптимизации использования адаптеров. Попытка обойти это все равно заставит ListView выполнять большую работу, которую не нужно выполнять LinearLayout. Я не буду вдаваться в подробности, потому что здесь недостаточно места для объяснения реализации ListView. Это также не решит, как ListView ведет себя в отношении сенсорных событий. Также нет гарантии, что если ваш хак сработает сегодня, он все равно сработает в следующем выпуске. Использование LinearLayout не будет большой работой на самом деле.
Romain Guy
7
Ну, это разумный ответ. Если бы я мог поделиться некоторыми отзывами, у меня гораздо более значительный опыт работы с iPhone, и я могу сказать вам, что документация Apple гораздо лучше написана в отношении характеристик производительности и вариантов использования (или анти-паттернов), подобных этой. В целом, документация по Android гораздо более распространена и менее сфокусирована. Я понимаю, что этому есть несколько причин, но это долгая дискуссия, поэтому, если вы чувствуете необходимость поговорить об этом, дайте мне знать.
DougW
6
Вы можете легко написать статический метод, который создает LinearLayout из Адаптера.
Romain Guy
125
Это НЕ ответ на How can I put a ListView into a ScrollView without it collapsing?... Вы можете сказать, что вы не должны это делать, но также дать ответ о том, как это сделать, это хороший ответ. Вы знаете, что ListView имеет некоторые другие интересные функции, помимо прокрутки, функции, которых у LinearLayout нет.
Хорхе Фуэнтес Гонсалес
44
Что если у вас есть регион, содержащий ListView + другие виды, и вы хотите, чтобы все прокручивалось как единое целое? Это похоже на разумный вариант использования ListView в ScrollView. Замена ListView с LinearLayout не является решением, потому что тогда вы не можете использовать адаптеры.
Барри Фрутман
262

Вот мое решение. Я довольно новичок в платформе Android, и я уверен, что это немного хакерски, особенно в части, касающейся прямого вызова .measure и прямой установки LayoutParams.heightсвойства, но это работает.

Все, что вам нужно сделать, это позвонить, Utility.setListViewHeightBasedOnChildren(yourListView)и он будет изменен, чтобы точно соответствовать высоте его предметов.

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

        int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();

        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            if (listItem instanceof ViewGroup) {
                listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
             }

             listItem.measure(0, 0);
             totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
}
DougW
источник
66
Вы только что воссоздали очень дорогую LinearLayout :)
Romain Guy
82
За исключением того, что a LinearLayoutне обладает всеми присущими ему тонкостями a ListView- у него нет разделителей, представлений верхнего / нижнего колонтитула, селектора списка, который соответствует цветам тем пользовательского интерфейса телефона, и т. Д. Честно говоря, нет причин, по которым вы не можете прокручивать внутренний контейнер, пока он не достигнет конца, а затем перестанет перехватывать события касания, чтобы прокручивать внешний контейнер. Каждый настольный браузер делает это, когда вы используете колесо прокрутки. Сам Android может справиться с вложенной прокруткой, если вы перемещаетесь с помощью трекбола, так почему бы не с помощью касания?
Нил Трафт
29
listItem.measure(0,0)сгенерирует NPE, если listItem является экземпляром ViewGroup. Я добавил следующее listItem.measure:if (listItem instanceof ViewGroup) listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Siklab.ph
4
Исправлено для ListViews с отступом, отличным от 0:int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
Пол
8
К сожалению, этот метод несколько некорректен, когда ListViewможет содержать элементы переменной высоты. Вызов getMeasuredHeight()только возвращает целевую минимальную высоту, а не фактическую высоту. Я попытался использовать getHeight()вместо этого, но это возвращает 0. У кого-нибудь есть понимание того, как подойти к этому?
Мэтт Хаггинс
89

Это определенно сработает ............
Вы должны просто заменить свой <ScrollView ></ScrollView>XML-файл в макете Custom ScrollViewследующим образом <com.tmd.utils.VerticalScrollview > </com.tmd.utils.VerticalScrollview >

package com.tmd.utils;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;

public class VerticalScrollview extends ScrollView{

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

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

        public VerticalScrollview(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: DOWN super false" );
                    super.onTouchEvent(ev);
                    break;

            case MotionEvent.ACTION_MOVE:
                    return false; // redirect MotionEvents to ourself

            case MotionEvent.ACTION_CANCEL:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: CANCEL super false" );
                    super.onTouchEvent(ev);
                    break;

            case MotionEvent.ACTION_UP:
                    Log.i("VerticalScrollview", "onInterceptTouchEvent: UP super false" );
                    return false;

            default: Log.i("VerticalScrollview", "onInterceptTouchEvent: " + action ); break;
        }

        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        super.onTouchEvent(ev);
        Log.i("VerticalScrollview", "onTouchEvent. action: " + ev.getAction() );
         return true;
    }
}
Атул Бхардвадж
источник
4
Это очень хорошо. Это также позволяет поместить фрагмент Google Maps в ScrollView, и он все еще работает с увеличением / уменьшением. Спасибо.
Магнус Йоханссон
Кто-нибудь заметил какие-либо минусы с этим подходом? Это так просто, я боюсь: o
Мария Милошевич
1
Хорошее решение, должен быть принят ответ +1! Я смешал это с ответом ниже (от djunod), и это прекрасно работает!
Tanou
1
Это единственное исправление, при котором прокрутка и щелчок по детям работают одновременно для меня. Большое спасибо
pellyadolfo
25

Помещенный ListViewвнутрь ScrollView, мы можем использовать ListViewкак ScrollView. Вещи, которые должны быть в, ListViewмогут быть помещены внутрь ListView. Другие макеты сверху и снизу ListViewможно поместить, добавив макеты в верхний и нижний колонтитулы ListView. Таким образом, весь ListViewдаст вам опыт прокрутки.

Arun
источник
3
Это самый элегантный и подходящий ответ imho. Для получения дополнительной информации об этом подходе см. Этот ответ: stackoverflow.com/a/20475821/1221537
adamdport
21

Существует множество ситуаций, в которых имеет смысл иметь ListView в ScrollView.

Вот код, основанный на предложении Дуга: он работает фрагментом, занимает меньше памяти.

public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        return;
    }
    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    int totalHeight = 0;
    View view = null;
    for (int i = 0; i < listAdapter.getCount(); i++) {
        view = listAdapter.getView(i, view, listView);
        if (i == 0) {
            view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, LayoutParams.WRAP_CONTENT));
        }
        view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

вызовите setListViewHeightBasedOnChildren (listview) для каждого встроенного списка.

djunod
источник
Это не будет работать, когда элементы списка имеют переменные размеры, то есть некоторые текстовые представления, которые расширяются на несколько строк. Любое решение?
Хобайб
Посмотрите мой комментарий здесь - он дает идеальное решение - stackoverflow.com/a/23315696/1433187
Хобайб,
работал для меня с API 19, но не с API 23: здесь он добавляет много пустого пространства под listView.
Lucker10
17

На самом деле ListView уже способен измерять себя достаточно высоко, чтобы отображать все элементы, но он этого не делает, когда вы просто указываете wrap_content (MeasureSpec.UNSPECIFIED). Это будет сделано, когда задана высота с MeasureSpec.AT_MOST. Обладая этими знаниями, вы можете создать очень простой подкласс для решения этой проблемы, который работает намного лучше, чем любое из решений, опубликованных выше. Вы все еще должны использовать wrap_content с этим подклассом.

public class ListViewForEmbeddingInScrollView extends ListView {
    public ListViewForEmbeddingInScrollView(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 4, MeasureSpec.AT_MOST));
    }
}

Манипулирование heightMeasureSpec для AT_MOST с очень большим размером (Integer.MAX_VALUE >> 4) заставляет ListView измерять все свои дочерние элементы до заданной (очень большой) высоты и соответственно устанавливать его высоту.

Это работает лучше, чем другие решения по нескольким причинам:

  1. Все правильно измеряет (отступы, разделители)
  2. Он измеряет ListView во время прохода меры
  3. Благодаря # 2 он корректно обрабатывает изменения ширины или количества элементов без дополнительного кода

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

Если вы обеспокоены тем, что в будущем поведение может измениться, вам просто нужно скопировать функцию onMeasure и связанные с ней функции из ListView.java в свой собственный подкласс, а затем запустить путь AT_MOST через onMeasure для UNSPECIFIED.

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

Джейсон Y
источник
Также работает с элементами переменной высоты в списке!
Денни
@ Джейсон Y что это, Integer.MAX_VALUE >> 4 означает ?? что там 4 ??
KJEjava48
@ Джейсон Y для меня это сработало только после того, как я изменил Integer.MAX_VALUE >> 2 на Integer.MAX_VALUE >> 21. Почему так? Также он работает с ScrollView, а не с NestedScrollView.
KJEjava48
13

Для этого есть встроенная настройка. На ScrollView:

android:fillViewport="true"

В Java

mScrollView.setFillViewport(true);

Ромэн Гай подробно объясняет это здесь: http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/

TalkLittle
источник
2
Это работает для LinearLayout, как он демонстрирует. Это не работает для ListView. Основываясь на дате этого поста, я предполагаю, что он написал это, чтобы привести пример своей альтернативы, но это не отвечает на вопрос.
DougW
это быстрое решение
10

Вы создаете пользовательский ListView, который не прокручивается

public class NonScrollListView extends ListView {

    public NonScrollListView(Context context) {
        super(context);
    }
    public NonScrollListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
                    Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
            ViewGroup.LayoutParams params = getLayoutParams();
            params.height = getMeasuredHeight();    
    }
}

В вашем файле ресурсов макета

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

    <!-- com.Example Changed with your Package name -->

    <com.Example.NonScrollListView
        android:id="@+id/lv_nonscroll_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </com.Example.NonScrollListView>

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

        <!-- Your another layout in scroll view -->

    </RelativeLayout>
</RelativeLayout>

В файле Java

Создайте объект вашего customListview вместо ListView, например: NonScrollListView non_scroll_list = (NonScrollListView) findViewById (R.id.lv_nonscroll_list);

Дедания ХиренКумар
источник
8

Мы не могли использовать две прокрутки одновременно. У нас будет общая длина ListView и развернутый просмотр списка с общей высотой. Затем мы можем добавить ListView в ScrollView напрямую или с помощью LinearLayout, потому что у ScrollView непосредственно один дочерний элемент. скопируйте метод setListViewHeightBasedOnChildren (lv) в свой код и разверните просмотр списка, после чего вы можете использовать просмотр списка внутри scrollview. \ layout xml file

<?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="match_parent"
android:orientation="vertical" >
 <ScrollView

        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
         android:background="#1D1D1D"
        android:orientation="vertical"
        android:scrollbars="none" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="#1D1D1D"
            android:orientation="vertical" >

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="40dip"
                android:background="#333"
                android:gravity="center_vertical"
                android:paddingLeft="8dip"
                android:text="First ListView"
                android:textColor="#C7C7C7"
                android:textSize="20sp" />

            <ListView
                android:id="@+id/first_listview"
                android:layout_width="260dp"
                android:layout_height="wrap_content"
                android:divider="#00000000"
               android:listSelector="#ff0000"
                android:scrollbars="none" />

               <TextView
                android:layout_width="fill_parent"
                android:layout_height="40dip"
                android:background="#333"
                android:gravity="center_vertical"
                android:paddingLeft="8dip"
                android:text="Second ListView"
                android:textColor="#C7C7C7"
                android:textSize="20sp" />

            <ListView
                android:id="@+id/secondList"
                android:layout_width="260dp"
                android:layout_height="wrap_content"
                android:divider="#00000000"
                android:listSelector="#ffcc00"
                android:scrollbars="none" />
  </LinearLayout>
  </ScrollView>

   </LinearLayout>

Метод onCreate в классе Activity:

 import java.util.ArrayList;
  import android.app.Activity;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 import android.widget.ListAdapter;
  import android.widget.ListView;

   public class MainActivity extends Activity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.listview_inside_scrollview);
    ListView list_first=(ListView) findViewById(R.id.first_listview);
    ListView list_second=(ListView) findViewById(R.id.secondList);
    ArrayList<String> list=new ArrayList<String>();
    for(int x=0;x<30;x++)
    {
        list.add("Item "+x);
    }

       ArrayAdapter<String> adapter=new ArrayAdapter<String>(getApplicationContext(), 
          android.R.layout.simple_list_item_1,list);               
      list_first.setAdapter(adapter);

     setListViewHeightBasedOnChildren(list_first);

      list_second.setAdapter(adapter);

    setListViewHeightBasedOnChildren(list_second);
   }



   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);
      }
Ашиш Сайни
источник
Хорошее исправление! Работает по мере необходимости. Спасибо. В моем случае перед списком у меня есть целая группа представлений, и идеальным решением будет иметь только одну вертикальную прокрутку на представлении.
Пословица
4

Это комбинация ответов Дуга, Гая Грега и Пола. Я обнаружил, что все это было необходимо при попытке использовать это с настраиваемым адаптером списка и нестандартными элементами списка, в противном случае просмотр списка приводил к сбою приложения (также происходил сбой при ответе Nex):

public void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            return;
        }

        int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            if (listItem instanceof ViewGroup)
                listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
Заброшенная корзина
источник
Где вызвать этот метод?
Dhrupal
После создания списка и настройки адаптера.
Заброшенная корзина
Это слишком медленно в длинных списках.
cakan
@cakan Он также был написан 2 года назад как обходной путь, позволяющий использовать вместе определенные компоненты пользовательского интерфейса, которые на самом деле не предназначены для объединения. Вы добавляете ненужное количество накладных расходов в современном Android, поэтому следует ожидать, что список будет замедляться, когда он станет большим.
Заброшенная корзина
3

Вы не должны ставить ListView в ScrollView потому ListView уже есть ScrollView. Так что это все равно что поместить ScrollView в ScrollView.

Что вы пытаетесь достичь?

Шерил Саймон
источник
4
Я пытаюсь добиться наличия ListView внутри ScrollView. Я не хочу, чтобы мой ListView можно было прокручивать, но нет простого свойства для установки myListView.scrollEnabled = false;
DougW
Как сказал Romain Guy, это совсем не цель ListView. ListView - это представление, оптимизированное для отображения длинного списка элементов со множеством сложной логики для ленивой загрузки представлений, повторного использования представлений и т. Д. Ничего из этого не имеет смысла, если вы говорите ListView не выполнять прокрутку. Если вы просто хотите связать элементы в вертикальном столбце, используйте LinearLayout.
Шерил Саймон
9
Проблема в том, что существует несколько решений, которые предоставляет ListView, в том числе упомянутое вами. Большая часть функциональности, которую упрощают адаптеры, касается управления данными и их контроля, а не оптимизации пользовательского интерфейса. IMO должен был быть простой ListView, и более сложный, который делает все элементы списка повторным использованием и прочее.
DougW
2
Абсолютно согласен. Обратите внимание, что легко использовать существующий Адаптер с LinearLayout, но я хочу все приятные вещи, которые предоставляет ListView, такие как делители, и что мне не хочется реализовывать их вручную.
Артем Руссаковский
1
@ArtemRussakovskii Не могли бы вы объяснить свой простой способ заменить ListView на LinearLayout существующим адаптером?
happyhardik
3

Я преобразовал @ DougW's Utilityв C # (используется в Xamarin). Следующее работает отлично для элементов с фиксированной высотой в списке и будет в основном хорошо или, по крайней мере, хорошим началом, если только некоторые из элементов будут немного больше, чем стандартный элемент.

// You will need to put this Utility class into a code file including various
// libraries, I found that I needed at least System, Linq, Android.Views and 
// Android.Widget.
using System;
using System.Linq;
using Android.Views;
using Android.Widget;

namespace UtilityNamespace  // whatever you like, obviously!
{
    public class Utility
    {
        public static void setListViewHeightBasedOnChildren (ListView listView)
        {
            if (listView.Adapter == null) {
                // pre-condition
                return;
            }

            int totalHeight = listView.PaddingTop + listView.PaddingBottom;
            for (int i = 0; i < listView.Count; i++) {
                View listItem = listView.Adapter.GetView (i, null, listView);
                if (listItem.GetType () == typeof(ViewGroup)) {
                    listItem.LayoutParameters = new LinearLayout.LayoutParams (ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
                }
                listItem.Measure (0, 0);
                totalHeight += listItem.MeasuredHeight;
            }

            listView.LayoutParameters.Height = totalHeight + (listView.DividerHeight * (listView.Count - 1));
        }
    }
}

Спасибо @DougW, это вывело меня из трудного положения, когда мне пришлось работать с кодом OtherPeople. :-)

Фил Райан
источник
Один вопрос: когда вы вызывали setListViewHeightBasedOnChildren()метод? Потому что это не всегда работает для меня.
Александр Д.
@AlexandreD вы вызываете Utility.setListViewHeightBasedOnChildren (yourListView), когда вы создали список элементов. т.е. убедитесь, что вы создали свой список первым! Я обнаружил, что составлял список, и я даже отображал свои элементы в Console.WriteLine ... но как-то элементы находились в неправильной области видимости. Как только я понял это и убедился, что у меня есть подходящая область для моего списка, это сработало как шарм.
Фил Райан
Дело в том, что мои списки создаются в моей ViewModel. Как я могу ждать, чтобы мои списки были созданы в моем представлении перед любым вызовом Utility.setListViewHeightBasedOnChildren (yourListView)?
Александр Д.
3

Это единственное, что сработало для меня:

на Lollipop и далее вы можете использовать

yourtListView.setNestedScrollingEnabled(true);

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

Jacka
источник
Это не повлияло на мои представления списка API 22 в DialogFragment в действии AppCompat. Они все еще рушатся. @Phil Райана с ответом работал с немного модификации.
Хакан Челик MK1
3

Раньше это было невозможно. Но с выпуском новых библиотек Appcompat и библиотек дизайна, это может быть достигнуто.

Вам просто нужно использовать NestedScrollView https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

Я не знаю, будет ли он работать с Listview или нет, но работает с RecyclerView.

Фрагмент кода:

<android.support.v4.widget.NestedScrollView 
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</android.support.v4.widget.NestedScrollView>
Шашанк Капсиме
источник
Круто, я не пробовал, поэтому я не могу подтвердить, что внутренний ListView не разрушается, а выглядит так, как будто это и есть цель. Глубоко опечален, что не вижу Ромена Гая в журнале обвинений;)
DougW
1
Это работает с recyclerView, но не listView. Спасибо
IgniteCoders
2

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

db = new dbhelper(this);

 cursor = db.dbCursor();
int count = cursor.getCount();
if (count > 0)
{    
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.layoutId);
startManagingCursor(YOUR_CURSOR);

YOUR_ADAPTER(**or SimpleCursorAdapter **) adapter = new YOUR_ADAPTER(this,
    R.layout.itemLayout, cursor, arrayOrWhatever, R.id.textViewId,
    this.getApplication());

int i;
for (i = 0; i < count; i++){
  View listItem = adapter.getView(i,null,null);
  linearLayout.addView(listItem);
   }
}

Примечание: если вы используете это, notifyDataSetChanged();не будет работать так, как задумано, так как представления не будут перерисованы. Сделайте это, если вам нужно обойти

adapter.registerDataSetObserver(new DataSetObserver() {

            @Override
            public void onChanged() {
                super.onChanged();
                removeAndRedrawViews();

            }

        });
PSchuette
источник
2

Есть две проблемы при использовании ListView внутри ScrollView.

1 - ListView должен полностью расширяться до своих дочерних высот. этот ListView разрешить это:

public class ListViewExpanded extends ListView
{
    public ListViewExpanded(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        setDividerHeight(0);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST));
    }
}

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

2. ListView использует сенсорные события, поэтому ScrollView нельзя прокручивать как обычно. Этот ScrollView решает эту проблему:

public class ScrollViewInterceptor extends ScrollView
{
    float startY;

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e)
    {
        onTouchEvent(e);
        if (e.getAction() == MotionEvent.ACTION_DOWN) startY = e.getY();
        return (e.getAction() == MotionEvent.ACTION_MOVE) && (Math.abs(startY - e.getY()) > 50);
    }
}

Это лучший способ сделать это!

Али
источник
2

Решение, которое я использую, состоит в том, чтобы добавить весь Контент ScrollView (что должно быть выше и ниже listView) как headerView и footerView в ListView.

Таким образом, он работает так же, как и конвертирование возобновляется так, как должно быть.

brokedid
источник
1

благодаря коду Vinay, вот мой код, когда вы не можете иметь просмотр списка внутри прокрутки, но вам нужно что-то подобное

LayoutInflater li = LayoutInflater.from(this);

                RelativeLayout parent = (RelativeLayout) this.findViewById(R.id.relativeLayoutCliente);

                int recent = 0;

                for(Contatto contatto : contatti)
                {
                    View inflated_layout = li.inflate(R.layout.header_listview_contatti, layout, false);


                    inflated_layout.setId(contatto.getId());
                    ((TextView)inflated_layout.findViewById(R.id.textViewDescrizione)).setText(contatto.getDescrizione());
                    ((TextView)inflated_layout.findViewById(R.id.textViewIndirizzo)).setText(contatto.getIndirizzo());
                    ((TextView)inflated_layout.findViewById(R.id.textViewTelefono)).setText(contatto.getTelefono());
                    ((TextView)inflated_layout.findViewById(R.id.textViewMobile)).setText(contatto.getMobile());
                    ((TextView)inflated_layout.findViewById(R.id.textViewFax)).setText(contatto.getFax());
                    ((TextView)inflated_layout.findViewById(R.id.textViewEmail)).setText(contatto.getEmail());



                    RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);

                    if (recent == 0)
                    {
                        relativeParams.addRule(RelativeLayout.BELOW, R.id.headerListViewContatti);
                    }
                    else
                    {
                        relativeParams.addRule(RelativeLayout.BELOW, recent);
                    }
                    recent = inflated_layout.getId();

                    inflated_layout.setLayoutParams(relativeParams);
                    //inflated_layout.setLayoutParams( new RelativeLayout.LayoutParams(source));

                    parent.addView(inflated_layout);
                }

lativeLayout остается внутри ScrollView, поэтому все становится прокручиваемым :)

max4ever
источник
1

Вот небольшая модификация на @djunod «s ответ , что мне нужно , чтобы сделать его работу отлично:

public static void setListViewHeightBasedOnChildren(ListView listView)
{
    ListAdapter listAdapter = listView.getAdapter();
    if(listAdapter == null) return;
    if(listAdapter.getCount() <= 1) return;

    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    int totalHeight = 0;
    View view = null;
    for(int i = 0; i < listAdapter.getCount(); i++)
    {
        view = listAdapter.getView(i, view, listView);
        view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}
Eng.Fouad
источник
Привет, ваш ответ работает большую часть времени, но он не работает, если у представления списка есть вид заголовка и вид ноги. Не могли бы вы проверить это?
hguser
Мне нужно решение, когда элементы списка имеют переменные размеры, то есть некоторые текстовые представления, которые расширяются на несколько строк. Любое предложение?
Хобайб
1

попробуйте это, это работает для меня, я забыл, где я нашел это, где-то в переполнении стека, я не здесь, чтобы объяснить, почему это не работает, но это ответ :).

    final ListView AturIsiPulsaDataIsiPulsa = (ListView) findViewById(R.id.listAturIsiPulsaDataIsiPulsa);
    AturIsiPulsaDataIsiPulsa.setOnTouchListener(new ListView.OnTouchListener() 
    {
        @Override
        public boolean onTouch(View v, MotionEvent event) 
        {
            int action = event.getAction();
            switch (action) 
            {
                case MotionEvent.ACTION_DOWN:
                // Disallow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(true);
                break;

                case MotionEvent.ACTION_UP:
                // Allow ScrollView to intercept touch events.
                v.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }

            // Handle ListView touch events.
            v.onTouchEvent(event);
            return true;
        }
    });
    AturIsiPulsaDataIsiPulsa.setClickable(true);
    AturIsiPulsaDataIsiPulsa.setAdapter(AturIsiPulsaDataIsiPulsaAdapter);

РЕДАКТИРОВАТЬ!, Я наконец узнал, где я получил код. Вот ! : ListView внутри ScrollView не прокручивается на Android

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

Хотя предложенные методы setListViewHeightBasedOnChildren () работают в большинстве случаев, в некоторых случаях, особенно с большим количеством элементов, я заметил, что последние элементы не отображаются. Поэтому я решил имитировать простую версию поведения ListView для повторного использования любого кода адаптера, вот альтернатива ListView:

import android.content.Context;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ListAdapter;

public class StretchedListView extends LinearLayout {

private final DataSetObserver dataSetObserver;
private ListAdapter adapter;
private OnItemClickListener onItemClickListener;

public StretchedListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setOrientation(LinearLayout.VERTICAL);
    this.dataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            syncDataFromAdapter();
            super.onChanged();
        }

        @Override
        public void onInvalidated() {
            syncDataFromAdapter();
            super.onInvalidated();
        }
    };
}

public void setAdapter(ListAdapter adapter) {
    ensureDataSetObserverIsUnregistered();

    this.adapter = adapter;
    if (this.adapter != null) {
        this.adapter.registerDataSetObserver(dataSetObserver);
    }
    syncDataFromAdapter();
}

protected void ensureDataSetObserverIsUnregistered() {
    if (this.adapter != null) {
        this.adapter.unregisterDataSetObserver(dataSetObserver);
    }
}

public Object getItemAtPosition(int position) {
    return adapter != null ? adapter.getItem(position) : null;
}

public void setSelection(int i) {
    getChildAt(i).setSelected(true);
}

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    this.onItemClickListener = onItemClickListener;
}

public ListAdapter getAdapter() {
    return adapter;
}

public int getCount() {
    return adapter != null ? adapter.getCount() : 0;
}

private void syncDataFromAdapter() {
    removeAllViews();
    if (adapter != null) {
        int count = adapter.getCount();
        for (int i = 0; i < count; i++) {
            View view = adapter.getView(i, null, this);
            boolean enabled = adapter.isEnabled(i);
            if (enabled) {
                final int position = i;
                final long id = adapter.getItemId(position);
                view.setOnClickListener(new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        if (onItemClickListener != null) {
                            onItemClickListener.onItemClick(null, v, position, id);
                        }
                    }
                });
            }
            addView(view);

        }
    }
}
}
Альесио Карвалью
источник
1

Все эти ответы неверны !!! Если вы пытаетесь поместить представление списка в представление прокрутки, вам следует пересмотреть свой дизайн. Вы пытаетесь поместить ScrollView в ScrollView. Вмешательство в список повредит производительности списка. Это было разработано, чтобы быть таким как Android.

Если вы действительно хотите, чтобы список находился в той же прокрутке, что и другие элементы, все, что вам нужно сделать, - это добавить другие элементы в верхнюю часть списка с помощью простого оператора switch в вашем адаптере:

class MyAdapter extends ArrayAdapter{

    public MyAdapter(Context context, int resource, List objects) {
        super(context, resource, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
         ViewItem viewType = getItem(position);

        switch(viewType.type){
            case TEXTVIEW:
                convertView = layouteInflater.inflate(R.layout.textView1, parent, false);

                break;
            case LISTITEM:
                convertView = layouteInflater.inflate(R.layout.listItem, parent, false);

                break;            }


        return convertView;
    }


}

Адаптер списка может обрабатывать все, поскольку он отображает только то, что видно.

TacoEater
источник
0

Вся эта проблема исчезла бы, если бы у LinearLayout был метод setAdapter, потому что тогда, когда вы сказали кому-то использовать его, альтернатива была бы тривиальной.

Если вы действительно хотите прокрутить ListView внутри другого прокручиваемого представления, это не поможет, но в противном случае это, по крайней мере, даст вам представление.

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

У меня нет примера кода под рукой, но если вы хотите что-то вроде.

<ListView/>

(other content)

<ListView/>

Затем вам нужно создать адаптер, который представляет весь этот контент. ListView / Adapters достаточно умны, чтобы обрабатывать разные типы, но вам нужно написать адаптер самостоятельно.

API пользовательского интерфейса Android не настолько развит, как почти все остальное, поэтому он не обладает такими же тонкостями, как другие платформы. Кроме того, когда вы делаете что-то на Android, вы должны быть настроены на Android (Unix), где вы ожидаете, что для того, чтобы делать все, что вам, вероятно, придется собирать функциональность из меньших частей и написать кучу вашего собственного кода, чтобы получить его Работа.

majinnaibu
источник
0

Когда мы помещаем ListViewвнутрь, ScrollViewвозникают две проблемы. Один из них ScrollViewизмеряет его дочерние элементы в режиме НЕУТОЧНЕННЫЙ, поэтому ListViewустанавливает собственную высоту для размещения только одного элемента (я не знаю почему), другой - ScrollViewперехватывает событие касания, поэтомуListView не прокручивает.

Но мы можем поместить ListViewвнутрь ScrollViewс некоторым обходным путем. Этот пост , мной, объясняет обходной путь. С помощью этого обходного пути мы также можем сохранить ListViewфункцию утилизации.

Дургадас С
источник
0

Вместо того чтобы помещать представление списка в Scrollview, поместите остальную часть содержимого между просмотром списка и открытием Scrollview в качестве отдельного представления и установите это представление в качестве заголовка представления списка. Таким образом, вы, в конце концов, в конечном итоге получите только представление списка, взяв на себя ответственность за прокрутку.

Saksham
источник
0

Эта библиотека - самое простое и быстрое решение проблемы.

Кашиф Назар
источник
-1

Вот моя версия кода, который вычисляет общую высоту представления списка. Этот работает для меня:

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

    int totalHeight = 0;
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(BCTDApp.getDisplaySize().width, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        if (listItem instanceof ViewGroup) listItem.setLayoutParams(lp);
        listItem.measure(widthMeasureSpec, heightMeasureSpec);
        totalHeight += listItem.getMeasuredHeight();
    }

    totalHeight += listView.getPaddingTop() + listView.getPaddingBottom();
    totalHeight += (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight;
    listView.setLayoutParams(params);
    listView.requestLayout();
}
myforums
источник