Android Listview измеряет высоту

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

public 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);

        if(listItem != null){
            listItem.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));                
            listItem.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            //listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

Теперь я вызываю указанную выше функцию в виде списка следующим образом:

public void displayReviews(ArrayList<Reviews> resultReviews){
    // Hide the loading progress
    hideReviewsLoading();

    if(resultReviews != null && resultReviews.size() > 0){
        mCurrentReviewList.onFetchFinished(resultReviews);
        setListViewHeightBasedOnChildren(mCurrentReviewList.getListView());
    }
    else{
        // Display a generic text to indicate no reviews are in yet
        displayEmptyText();
    }
}

Здесь выше mCurrentreviewList - это ListFragment, который в основном имеет адаптер для установки элементов в макете.

Проблема, с которой я столкнулась, заключается в том, что высота каждого элемента, который он измеряет, неточна. И поэтому в конце, когда все элементы списка (обзоры) заполняются общим списком, содержащим его, никогда полностью не отображают все элементы списка. Он срезается где-то внизу - Как показывает только 7,5 обзоров из 10 баллов.

Я не уверен, что я делаю неправильно. Любая помощь и руководство будут очень благодарны!

Ответ 1

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

listItem.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

Для ListItem с переменной/динамической высотой это не сработает. Измерение должно знать как минимум одну переменную, и в этом случае она будет постоянной шириной. Итак, сначала вычислите желаемую ширину списка, который, как вы знаете, будет постоянным, используя:

int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);

а затем передать его в метку listitem. Полный код приведен ниже:

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

    int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
    int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);

        if(listItem != null){
            // This next line is needed before you call measure or else you won't get measured height at all. The listitem needs to be drawn first to know the height.
            listItem.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
            listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
            totalHeight += listItem.getMeasuredHeight();

        }
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

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

Ответ 2

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

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

Ответ 3

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

Это из-за первой строки View listItem = listAdapter.getView(i, null, listView); цикла for.

Передача null в качестве параметра convertView приведет к тому, что адаптер раздувает каждую строку в вашем списке. Это не идеально, поэтому вместо этого в уже завышенную строку, чтобы getView мог заменить содержимое внутри этой строки (например, надуть первую строку списка самостоятельно и продолжать использовать это). См. Раздел "Просмотр утилизации" в следующей ссылке http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/

Ответ 4

falc0nit3 ответ (принятый) работает для меня, однако я предлагаю добавить следующий код сразу после цикла for, если вы используете разделители списков

totalHeight += listView.getDividerHeight() * i;

Итак, окончательный фрагмент кода будет выглядеть так:

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

    int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.UNSPECIFIED);
    int totalHeight = 0;
    View view = null;
    int i;
    for (i = 0; i < listAdapter.getCount(); i++) {
        view = listAdapter.getView(i, view, listView);
        if (i == 0)
            view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT));

        view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
        totalHeight += view.getMeasuredHeight();

    }

    //add divider height to total height as many items as there are in listview
    totalHeight += listView.getDividerHeight()*i;
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
}