Высота представления вложенного ресайклера не переносит его содержимое

У меня есть приложение для управления коллекциями книг (например, плейлистами).

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

Когда я устанавливаю layout_height внутреннего горизонтального RecyclerView на 300dp, он отображается правильно, но когда я устанавливаю его на wrap_content, он ничего не отображает. Мне нужно использовать wrap_content, потому что я хочу иметь возможность для программного изменения диспетчера компоновки для переключения между вертикальным и горизонтальным отображением.

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

Вы знаете, что я делаю не так?

Макет моего фрагмента:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/white">

    <com.twibit.ui.view.CustomSwipeToRefreshLayout
        android:id="@+id/swipe_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/shelf_collection_listview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingTop="10dp"/>

        </LinearLayout>

    </com.twibit.ui.view.CustomSwipeToRefreshLayout>
</LinearLayout>

Макет элемента коллекции:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="vertical">

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="#FFF">

        <!-- Simple Header -->

    </RelativeLayout>

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

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/empty_collection"
            android:id="@+id/empty_collection_tv"
            android:visibility="gone"
            android:gravity="center"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/collection_book_listview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/> <!-- android:layout_height="300dp" -->

    </FrameLayout>

</LinearLayout>

Элемент списка книг:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="180dp"
              android:layout_height="220dp"
              android:layout_gravity="center">

        <ImageView
            android:id="@+id/shelf_item_cover"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:maxWidth="150dp"
            android:maxHeight="200dp"
            android:src="@drawable/placeholder"
            android:contentDescription="@string/cover"
            android:adjustViewBounds="true"
            android:background="@android:drawable/dialog_holo_light_frame"/>

</FrameLayout>

Вот мой адаптер коллекции:

private class CollectionsListAdapter extends RecyclerView.Adapter<CollectionsListAdapter.ViewHolder> {
    private final String TAG = CollectionsListAdapter.class.getSimpleName();
    private Context mContext;

    // Create the ViewHolder class to keep references to your views
    class ViewHolder extends RecyclerView.ViewHolder {

        private final TextView mHeaderTitleTextView;
        private final TextView mHeaderCountTextView;

        private final RecyclerView mHorizontalListView;
        private final TextView mEmptyTextView;

        public ViewHolder(View view) {
            super(view);

            mHeaderTitleTextView = (TextView) view.findViewById(R.id.collection_header_tv);
            mHeaderCountTextView = (TextView) view.findViewById(R.id.collection_header_count_tv);

            mHorizontalListView = (RecyclerView) view.findViewById(R.id.collection_book_listview);
            mEmptyTextView = (TextView) view.findViewById(R.id.empty_collection_tv);
        }
    }


    public CollectionsListAdapter(Context context) {
        mContext = context;
    }


    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
        Log.d(TAG, "CollectionsListAdapter.onCreateViewHolder(" + parent.getId() + ", " + i + ")");
        // Create a new view by inflating the row item xml.
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.shelf_collection, parent, false);

        // Set the view to the ViewHolder
        ViewHolder holder = new ViewHolder(v);

        holder.mHorizontalListView.setHasFixedSize(false);
        holder.mHorizontalListView.setHorizontalScrollBarEnabled(true);

        // use a linear layout manager
        LinearLayoutManager mLayoutManager = new LinearLayoutManager(mContext);
        mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        holder.mHorizontalListView.setLayoutManager(mLayoutManager);

        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int i) {
        Log.d(TAG, "CollectionsListAdapter.onBindViewHolder(" + holder.getPosition() + ", " + i + ")");

        Collection collection = mCollectionList.get(i);
        Log.d(TAG, "Collection : " + collection.getLabel());

        holder.mHeaderTitleTextView.setText(collection.getLabel());
        holder.mHeaderCountTextView.setText("" + collection.getBooks().size());

        // Create an adapter if none exists
        if (!mBookListAdapterMap.containsKey(collection.getCollectionId())) {
            mBookListAdapterMap.put(collection.getCollectionId(), new BookListAdapter(getActivity(), collection));
        }

        holder.mHorizontalListView.setAdapter(mBookListAdapterMap.get(collection.getCollectionId()));

    }

    @Override
    public int getItemCount() {
        return mCollectionList.size();
    }
}

И, наконец, адаптер Book:

private class BookListAdapter extends RecyclerView.Adapter<BookListAdapter.ViewHolder> implements View.OnClickListener {
    private final String TAG = BookListAdapter.class.getSimpleName();

    // Create the ViewHolder class to keep references to your views
    class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView mCoverImageView;

        public ViewHolder(View view) {
            super(view);
            mCoverImageView = (ImageView) view.findViewById(R.id.shelf_item_cover);
        }
    }

    @Override
    public void onClick(View v) {
        BookListAdapter.ViewHolder holder = (BookListAdapter.ViewHolder) v.getTag();
        int position = holder.getPosition();
        final Book book = mCollection.getBooks().get(position);

        // Click on cover image
        if (v.getId() == holder.mCoverImageView.getId()) {
            downloadOrOpenBook(book);
            return;
        }
    }

    private void downloadOrOpenBook(final Book book) {
        // do stuff
    }

    private Context mContext;
    private Collection mCollection;

    public BookListAdapter(Context context, Collection collection) {
        Log.d(TAG, "BookListAdapter(" + context + ", " + collection + ")");
        mCollection = collection;
        mContext = context;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
        Log.d(TAG, "onCreateViewHolder(" + parent.getId() + ", " + i + ")");
        // Create a new view by inflating the row item xml.
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.shelf_grid_item, parent, false);

        // Set the view to the ViewHolder
        ViewHolder holder = new ViewHolder(v);
        holder.mCoverImageView.setOnClickListener(BookListAdapter.this); // Download or Open

        holder.mCoverImageView.setTag(holder);

        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int i) {
        Log.d(TAG, "onBindViewHolder(" + holder.getPosition() + ", " + i + ")");

        Book book = mCollection.getBooks().get(i);

        ImageView imageView = holder.mCoverImageView;
        ImageLoader.getInstance().displayImage(book.getCoverUrl(), imageView);
    }

    @Override
    public int getItemCount() {
        return mCollection.getBooks().size();
    }
}

comment
Почему вам нужно использовать wrap_content для изменения layoutmanager с вертикального на горизонтальный?   -  person AmaJayJB    schedule 30.10.2014
comment
Если я установил для layout_height определенное значение (скажем, высоту элемента для горизонтального менеджера компоновки), он отобразит только первую строку списка с вертикальным менеджером компоновки.   -  person Twibit    schedule 30.10.2014
comment
Но у recyclerView есть встроенный scrollView, поэтому вы можете просто прокручивать элементы ... или мне здесь не хватает смысла? Извините пытаюсь понять   -  person AmaJayJB    schedule 30.10.2014
comment
Я хочу прокручивать элементы коллекции, но не внутренний список книг (кроме горизонтального). Что-то вроде ExpandableListView.   -  person Twibit    schedule 30.10.2014
comment
Я сталкиваюсь с тем же вопросом, что и вы, вы решили сейчас?   -  person VinceStyling    schedule 02.11.2014
comment
В качестве обходного пути я программно устанавливаю высоту внутреннего представления, но это меня не устраивает.   -  person Twibit    schedule 03.11.2014
comment
Эта ошибка все еще существует с новым API 22.1.1?   -  person Davideas    schedule 26.05.2015
comment
@Twibit, как вы решили проблему, я должен достичь той же функциональности, используя высоту wrap_content с горизонтальным просмотром прокрутки   -  person Erum    schedule 07.09.2015
comment
Привет, из всех решений, которые я смущен, этот MyLinearLayoutManager должен использоваться только для родителя, только для ребенка или для обоих?   -  person CommonSenseCode    schedule 06.11.2015
comment
проверьте stackoverflow.com/a/35623177/2826147 и stackoverflow.com/a/35623132/2826147 ответ   -  person Amit Vaghela    schedule 02.08.2016
comment
Я использовал LruCache вместо mBookListAdapterMap - я считаю, что это более эффективно, чем HashMap.   -  person Mike Mitterer    schedule 10.08.2016


Ответы (15)


Обновить

Многие проблемы, связанные с этой функцией в версии 23.2.0, были исправлены в версии 23.2.1, вместо этого обновите ее.

С выпуском библиотеки поддержки версии 23.2 RecyclerView теперь поддерживает это!

Обновите build.gradle, чтобы:

compile 'com.android.support:recyclerview-v7:23.2.1'

или любая другая версия, кроме этого.

В этом выпуске в LayoutManager API добавлена ​​новая захватывающая функция: автоматическое измерение! Это позволяет RecyclerView изменять свой размер в зависимости от размера его содержимого. Это означает, что теперь возможны ранее недоступные сценарии, такие как использование WRAP_CONTENT для измерения RecyclerView. Вы обнаружите, что все встроенные менеджеры LayoutManager теперь поддерживают автоматическое измерение.

При необходимости это можно отключить с помощью setAutoMeasurementEnabled(). Подробную информацию можно найти здесь.

person razzledazzle    schedule 25.02.2016
comment
Будь осторожен! Вот новые ошибки: stackoverflow.com/questions/35619022/ - person Denis Nek; 25.02.2016
comment
это устраняет ошибку, указанную выше? - person razzledazzle; 07.03.2016
comment
Это не работает должным образом, всегда видно пустое пространство между каждым элементом - person RAHULRSANNIDHI; 08.03.2016
comment
Обратите внимание, что высота родительского макета, содержащего представление дочернего ресайклера, должна быть обернута содержимым. - person RAHULRSANNIDHI; 09.03.2016
comment
Вау ... большое спасибо за эту информацию. Я давно ждал этой функции. - person Swapnil; 16.03.2016
comment
Версия 23.2.1 RecyclerView, похоже, исправила несколько ошибок. У нас очень хорошо работает. Если нет, вы можете попробовать 24.0.0-alpha1 - person Joaquin Iurchuk; 17.03.2016
comment
Меня устраивает! Пустое пространство между каждым элементом было связано с тем, что высота макета элемента также должна быть установлена ​​на wrap_content. - person Roberto Tellez Ibarra; 03.06.2016
comment
24.0 есть некоторые проблемы code.google.com/p/android/ issues / detail? id = 210085 # makechanges, используйте вместо этого 23.2.1 - person Samad; 12.06.2016
comment
В версии 23.2.1 RecyclerView wrap_content работает для layout_height! ура - person anoo_radha; 29.09.2016
comment
для получения дополнительных сведений посетите этот issueetracker.google.com/u/1/issues / 37007605 # comment88 - person Prags; 24.07.2021

Решение @ user2302510 работает не так хорошо, как вы могли ожидать. Полный обходной путь как для ориентации, так и для динамического изменения данных:

public class MyLinearLayoutManager extends LinearLayoutManager {

    public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
        super(context, orientation, reverseLayout);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);
        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }
        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}
person Denis Nek    schedule 23.12.2014
comment
Ваше решение работает довольно хорошо, но, похоже, не учитывает ItemDecoration (например, разделители). - person Twibit; 23.12.2014
comment
Пока не знаю почему, но прокрутка, похоже, не работает. Я пробовал этот код с вертикальной прокруткой макета ... Когда элементы меньше высоты экрана, все работает нормально. Однако, когда элементов больше, чем может быть отображено на экране, прокрутка не работает. - person Justin; 06.01.2015
comment
Я думаю, что если элементов больше, чем размер экрана - вам не нужно изменять исходный LayoutManager. - person Denis Nek; 07.01.2015
comment
@Twibit Я тестировал его прямо сейчас и, похоже, работает с ItemDecoration s. - person Antonio Jose; 22.01.2015
comment
@DenisNek Есть способ заставить его работать, когда элементов больше, чем помещается на экране? - person Antonio Jose; 22.01.2015
comment
Это не работает с анимацией изменения содержимого - размер recyclerview снимается, а не плавно увеличивается / уменьшается. - person zyamys; 21.02.2015
comment
Это работает для установки высоты для обтекания содержимого, но после этого прокрутка представления повторного просмотра не будет smooth, как была. Любая помощь по этому поводу? - person MKJParekh; 05.03.2015
comment
Не удалось заставить это работать - onBindViewHolder вызывается ТОННЫ раз (около 100 раз) для представления ресайклера с 2 представлениями - person Shai; 18.03.2015
comment
используйте measureChildWithMargins(view,widthSpec,heightSpec), тогда вам не нужно принимать во внимание маржу самостоятельно. КСТАТИ. У меня проблема с получением MessuredHeight ребенка независимо от того, что MeasureSpec я сдаю. Вы знаете об этом? Я имею в виду, если я установлю высоту ребенка на wrap_content. - person gone; 28.04.2015
comment
Вот усовершенствованная версия класса, которая, похоже, работает и не имеет проблем, которые есть у других решений: github.com/serso/android-linear-layout-manager/blob/master/lib/ - person se.solovyev; 29.04.2015
comment
У меня это должно работать, но, как сказал @MKJParekh, это уже не так гладко. Если бы кто-то мог это исправить, то это было бы идеально! Если интересно, это то, что у меня было до использования этого класса: stackoverflow.com / questions / 30675006 / - person CularBytes; 06.06.2015
comment
@ se.solovyev Я просто хочу прокомментировать, что это работает отлично. Все посмотрите его ссылку! - person user2968401; 23.06.2015
comment
Спасибо @ se.solovyev. Ваш код работал, и я долго искал способ сделать это. - person Simon; 26.07.2015
comment
Как мы можем добиться того же для gridlayoutmanager и staggeredgridlayoutmanager с учетом подсчета диапазона? - person Amrut Bidri; 18.08.2015
comment
что писать в ориентацию и reverseLayout? - person Jemshit Iskenderov; 25.08.2015
comment
Я использую этот recyclerview внутри scrollview и использую этот код для переноса содержимого recylerview. Но прокрутка прокрутки не гладкая. Решение: stackoverflow.com/a/32283439/3736955 - person Jemshit Iskenderov; 03.09.2015
comment
@ se.solovyev как пользоваться вашим кодом? вместо использования LinarLayoutManager может мне понадобиться использовать ваш класс, определенный в ссылке? github.com/serso/android-linear-layout-manager/blob/master/lib/ - person Erum; 07.09.2015
comment
@Pin, как вы использовали github.com/serso/android-linear-layout-manager/blob/master/lib/ - person Erum; 07.09.2015
comment
Просто используйте его вместо LinearLayoutManager по умолчанию. - person Pin; 07.09.2015
comment
@Pin, но как это будет работать для Horizontal recyclerview, потому что я могу передать ориентацию классу? - person Erum; 07.09.2015
comment
Имеет функцию setOrientation. Вы уверены, что смотрите нужный файл? - person Pin; 07.09.2015
comment
@Pin, пожалуйста, зайдите в Интернет здесь chat.stackoverflow.com/rooms/50272/ - person Erum; 07.09.2015
comment
@ se.solovyev ваша библиотека отлично работала для того, что я искал. Спасибо за помощь! Все, посмотрите его проект на GitHub! - person w3bshark; 14.09.2015
comment
Что, если бы я хотел применить это к StaggeredGridLayoutManager? - person Peter Chappy; 07.10.2015
comment
@DenisNek, какое значение должно быть у reverseLayout? - person Narendra Singh; 22.10.2015
comment
хорошо работал с вертикальным обзором, не работал с горизонтальным ... есть предложения? - person H Raval; 26.10.2015
comment
@ se.solovyev спасибо! кстати, с этим менеджером по расположению последний элемент декора не отрисовывается. Возможная ошибка? - person Jjang; 07.11.2015
comment
@Jjang возможно, сообщите об этом на странице проекта в github - person se.solovyev; 07.11.2015
comment
@ se.solovyev: есть ли подобное решение и для gridLayoutManager. Проблема с GridLayoutManager. - person Amit Kumar Kannaujiya; 25.01.2016

Приведенный выше код не работает, когда вам нужно сделать ваши элементы "wrap_content", потому что он измеряет высоту и ширину элементов с помощью MeasureSpec.UNSPECIFIED. После некоторых проблем я изменил это решение, поэтому теперь предметы могут расширяться. Единственное отличие состоит в том, что он предоставляет родительскую высоту или ширину. MeasureSpec зависит от ориентации макета.

public class MyLinearLayoutManager extends LinearLayoutManager {

public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {


        if (getOrientation() == HORIZONTAL) {

            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    heightSpec,
                    mMeasuredDimension);

            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            measureScrapChild(recycler, i,
                    widthSpec,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }
    switch (widthMode) {
        case View.MeasureSpec.EXACTLY:
            width = widthSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    switch (heightMode) {
        case View.MeasureSpec.EXACTLY:
            height = heightSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    setMeasuredDimension(width, height);
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {
    View view = recycler.getViewForPosition(position);
    recycler.bindViewToPosition(view, position);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                getPaddingLeft() + getPaddingRight(), p.width);
        int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                getPaddingTop() + getPaddingBottom(), p.height);
        view.measure(childWidthSpec, childHeightSpec);
        measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
        measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
        recycler.recycleView(view);
    }
}
}
person Kaerdan    schedule 03.02.2015
comment
Спасибо! Это решение сработало для меня лучше всего. Денис, казалось, недостаточно измерил, и вид все еще можно было прокручивать. Казалось, что Синан вёл себя должным образом только тогда, когда был одинокий ребенок. Примечание. Мои дочерние представления использовали wrap_content - person kassim; 11.03.2015
comment
Чтобы включить прокрутку при достижении границ родительского представления, мне пришлось вставить switch (heightMode) { case View.MeasureSpec.AT_MOST: if (height < heightSize) break; case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.UNSPECIFIED: } - person kassim; 06.04.2015
comment
Отличное решение. Это сработало отлично. Кстати, вместо того, чтобы использовать конструктор, я просто создал переменную частного экземпляра и метод установки / получения с Constant final int ORIENTATION (на всякий случай, если у кого-то еще возникла проблема), а затем использовал это для проверки переданной ориентации . - person PGMacDesign; 29.05.2015
comment
Я столкнулся с проблемой на Lollipop и выше, ScrollView, который обертывает RecyclerView, работает некорректно (нет бросков), когда вы пытались прокрутить выше RecyclerView. Для решения этой проблемы вам нужно сделать NotScrollableRecyclerView (custom) и переопределить методы onInterceptTouchEvent, onTouchEvent, не вызывая их супер-реализации, и вы должны вернуть false в обоих методах. - person ultraon; 17.07.2015
comment
Это работает, но я не понимаю, почему так излишне сложно просто измерить высоту вещи после того, как рассчитан рост ее детей ... - person Joe Maher; 03.11.2015
comment
@Kaerdan большое спасибо, ваше решение работает для меня :) - person BNK; 04.11.2015
comment
Должно ли это применяться к родительскому RecyclerView или только к дочернему или к обоим? - person CommonSenseCode; 06.11.2015
comment
Его следует применить к тем RecyclerViews, которые вы хотите иметь wrap_content. - person Kaerdan; 09.11.2015
comment
Спасибо! Работает как шарм! - person resource8218; 20.11.2015

Существующий менеджер компоновки еще не поддерживает перенос содержимого.

Вы можете создать новый LayoutManager, который расширяет существующий и переопределяет метод onMeasure для измерения содержимого обтекания.

person yigit    schedule 07.11.2014
comment
Я не ожидал этого, но, похоже, это правда. См. RecyclerView.LayoutManager & LinearLayoutManager - person Twibit; 10.11.2014
comment
@ yiğit, будет ли recyclerView поддерживать перенос содержимого по умолчанию? - person Sinan Kozak; 14.02.2015
comment
Это зависит от LayoutManager, а не от RecyclerView. Это есть в нашей дорожной карте. - person yigit; 14.02.2015
comment
@yigit Разве это не делает setHasFixedSize(boolean) бесполезным с менеджерами компоновки по умолчанию? В любом случае, приятно знать, что это на дорожной карте. Надеюсь, это скоро выйдет. - person goncalossilva; 27.02.2015
comment
Да, потому что LM не может иметь фиксированный размер, если его размер зависит от содержимого адаптера. Основное различие заключается в том, обрабатывает ли RV обновления до onMeasure или до onLayout. Кроме того, с изменением размера довольно сложно выполнять прогнозирующую анимацию, поскольку LM должен знать окончательный размер, чтобы правильно измерить себя, тогда как предварительная компоновка будет выполняться после измерения (это похоже на откат времени назад). - person yigit; 28.02.2015

Как упоминалось в @ yiğit, вам нужно переопределить onMeasure (). И @ user2302510, и @DenisNek имеют хорошие ответы, но если вы хотите поддерживать ItemDecoration, вы можете использовать этот настраиваемый менеджер компоновки.

А другие ответы не могут прокручиваться, когда элементов больше, чем может быть отображено на экране. В этом случае используется реализация onMeasure () по умолчанию, когда элементов больше, чем размер экрана.

public class MyLinearLayoutManager extends LinearLayoutManager {

public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {
        measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

        if (getOrientation() == HORIZONTAL) {
            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }

    // If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view.
    if (height < heightSize && width < widthSize) {

        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    } else {
        super.onMeasure(recycler, state, widthSpec, heightSpec);
    }
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {

   View view = recycler.getViewForPosition(position);

   // For adding Item Decor Insets to view
   super.measureChildWithMargins(view, 0, 0);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom() + getPaddingBottom() + getDecoratedBottom(view) , p.height);
            view.measure(childWidthSpec, childHeightSpec);

            // Get decorated measurements
            measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
            measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

И если вы хотите использовать его с GridLayoutManager, просто расширьте его из GridLayoutManager и измените

for (int i = 0; i < getItemCount(); i++)

to

for (int i = 0; i < getItemCount(); i = i + getSpanCount())
person Sinan Kozak    schedule 13.02.2015
comment
Это решение, которое работает правильно, я тестировал всех остальных в этом потоке, и у всех были проблемы. - person wasyl; 17.02.2015
comment
Это решение мне тоже очень понравилось. Единственная проблема заключалась в том, что он не учитывает обернутый текст в дочернем элементе. См. Мой ответ выше, чтобы узнать о решении этой проблемы. - person Bishbulb; 31.03.2015
comment
Это не работает для gridlayout (да, я применил изменения). Сначала вроде работает, потом я меняю ориентацию туда-сюда, и высота меняется по сравнению с исходной высотой. Также представление ведет себя не так, как ожидалось (например, onclicks не работают на большинстве из них, в то время как с простым gridlayoutmanager они работают). - person Csabi; 28.07.2015
comment
Как мы можем добиться того же для gridlayoutmanager и staggeredgridlayoutmanager с учетом подсчета диапазона? - person Amrut Bidri; 18.08.2015
comment
этот MyLinearLayoutManager должен применяться к дочернему представлению ресайклера или к родительскому? или оба? - person CommonSenseCode; 06.11.2015
comment
В определенных обстоятельствах ваш код у меня не работает. Я должен добавить heightSize == 0 || widthSize == 0 || к if (height < heightSize && width < widthSize) - person wrozwad; 15.01.2016
comment
@Sinan kozak Как получить getSpanCount (), если я хочу использовать его в gridlayout? - person GrIsHu; 18.06.2016

ОБНОВЛЕНИЕ март 2016 г.

Автор Библиотека поддержки Android 23.2.1 из версия библиотеки поддержки. Так что все WRAP_CONTENT должны работать правильно.

Обновите версию библиотеки в файле gradle.

compile 'com.android.support:recyclerview-v7:23.2.1'

Это позволяет RecyclerView изменять свой размер в зависимости от размера его содержимого. Это означает, что ранее недоступные сценарии, такие как использование WRAP_CONTENT для измерения RecyclerView, теперь возможны.

вам потребуется вызвать setAutoMeasureEnabled (true)

Исправлены ошибки, связанные с различными методами измерения в обновлении

Проверьте https://developer.android.com/topic/libraries/support-library/features.html

person Amit Vaghela    schedule 25.02.2016

Этот ответ основан на решении, данном Денисом Неком. Это решает проблему невнимания к украшениям, таким как разделители.

public class WrappingRecyclerViewLayoutManager extends LinearLayoutManager {

public WrappingRecyclerViewLayoutManager(Context context)    {
    super(context, VERTICAL, false);
}

public WrappingRecyclerViewLayoutManager(Context context, int orientation, boolean reverseLayout)    {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {
        measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);
        if (getOrientation() == HORIZONTAL) {
            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }
    switch (widthMode) {
        case View.MeasureSpec.EXACTLY:
            width = widthSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    switch (heightMode) {
        case View.MeasureSpec.EXACTLY:
            height = heightSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    setMeasuredDimension(width, height);
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) {
    View view = recycler.getViewForPosition(position);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), p.width);
        int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), p.height);
        view.measure(childWidthSpec, childHeightSpec);
        Rect outRect = new Rect();
        calculateItemDecorationsForChild(view, outRect);
        measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
        measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin + outRect.bottom + outRect.top;
        recycler.recycleView(view);
    }
}

}

person lilienberg    schedule 25.03.2015
comment
Наконец, единственное, что работает правильно. Отличная работа. Спасибо! - person falc0nit3; 24.10.2015
comment
Я получаю java.lang.IndexOutOfBoundsException: недопустимая позиция элемента 0 (0). Количество предметов: 0 исключение. - person RAHULRSANNIDHI; 09.03.2016

Альтернативой расширению LayoutManager может быть просто установка размера представления вручную.

Количество элементов на высоту строки (если все элементы имеют одинаковую высоту и в строке включен разделитель)

LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mListView.getLayoutParams();
params.height = mAdapter.getItemCount() * getResources().getDimensionPixelSize(R.dimen.row_height);
mListView.setLayoutParams(params);

Это все еще обходной путь, но в базовых случаях он работает.

person AntPachon    schedule 02.01.2015

Здесь я нашел решение: https://code.google.com/p/android/issues/detail?id=74772

Это никоим образом не моё решение. Я только что скопировал его оттуда, но надеюсь, что это поможет кому-то так же, как помогло мне при реализации горизонтального RecyclerView и высоты wrap_content (также должно работать для вертикального и ширины wrap_content)

Решение состоит в том, чтобы расширить LayoutManager и переопределить его метод onMeasure, как предлагает @yigit.

Вот код на случай, если ссылка умрет:

public static class MyLinearLayoutManager extends LinearLayoutManager {

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

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        measureScrapChild(recycler, 0,
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

        int width = mMeasuredDimension[0];
        int height = mMeasuredDimension[1];

        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
            case View.MeasureSpec.AT_MOST:
                width = widthSize;
                break;
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
            case View.MeasureSpec.AT_MOST:
                height = heightSize;
                break;
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth();
            measuredDimension[1] = view.getMeasuredHeight();
            recycler.recycleView(view);
        }
    }
}
person user1071762    schedule 16.12.2014
comment
бинго! отлично работает с горизонтальным, но не с вертикальным - person H Raval; 26.10.2015

Использовал решение от @ sinan-kozak, за исключением исправления нескольких ошибок. В частности, мы не должны использовать View.MeasureSpec.UNSPECIFIED для и ширины и высоты при вызове measureScrapChild, поскольку это не будет правильно учитывать обернутый текст в дочернем элементе. Вместо этого мы передадим режимы ширины и высоты от родительского элемента, что позволит работать как для горизонтальных, так и для вертикальных макетов.

public class MyLinearLayoutManager extends LinearLayoutManager {

public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {    
        if (getOrientation() == HORIZONTAL) {
            measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(heightSize, heightMode),
                mMeasuredDimension);

            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(widthSize, widthMode),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }

    // If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view.
    if (height < heightSize && width < widthSize) {

        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    } else {
        super.onMeasure(recycler, state, widthSpec, heightSpec);
    }
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {

   View view = recycler.getViewForPosition(position);

   // For adding Item Decor Insets to view
   super.measureChildWithMargins(view, 0, 0);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom() + getDecoratedTop(view) + getDecoratedBottom(view) , p.height);
            view.measure(childWidthSpec, childHeightSpec);

            // Get decorated measurements
            measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
            measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

`

person Bishbulb    schedule 31.03.2015
comment
Я тестировал, и он работает, но только если RecyclerView вложен в LinearLayout. Не работает с RelativeLayout. - person user2968401; 06.06.2015

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

public class MyLinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

private static boolean canMakeInsetsDirty = true;
private static Field insetsDirtyField = null;

private static final int CHILD_WIDTH = 0;
private static final int CHILD_HEIGHT = 1;
private static final int DEFAULT_CHILD_SIZE = 100;

private final int[] childDimensions = new int[2];
private final RecyclerView view;

private int childSize = DEFAULT_CHILD_SIZE;
private boolean hasChildSize;
private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
private final Rect tmpRect = new Rect();

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(Context context) {
    super(context);
    this.view = null;
}

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
    this.view = null;
}

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(RecyclerView view) {
    super(view.getContext());
    this.view = view;
    this.overScrollMode = ViewCompat.getOverScrollMode(view);
}

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
    super(view.getContext(), orientation, reverseLayout);
    this.view = view;
    this.overScrollMode = ViewCompat.getOverScrollMode(view);
}

public void setOverScrollMode(int overScrollMode) {
    if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER)
        throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
    if (this.view == null) throw new IllegalStateException("view == null");
    this.overScrollMode = overScrollMode;
    ViewCompat.setOverScrollMode(view, overScrollMode);
}

public static int makeUnspecifiedSpec() {
    return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
}

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);

    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);

    final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
    final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;

    final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
    final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;

    final int unspecified = makeUnspecifiedSpec();

    if (exactWidth && exactHeight) {
        // in case of exact calculations for both dimensions let's use default "onMeasure" implementation
        super.onMeasure(recycler, state, widthSpec, heightSpec);
        return;
    }

    final boolean vertical = getOrientation() == VERTICAL;

    initChildDimensions(widthSize, heightSize, vertical);

    int width = 0;
    int height = 0;

    // it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
    // happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
    // recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
    // called whiles scrolling)
    recycler.clear();

    final int stateItemCount = state.getItemCount();
    final int adapterItemCount = getItemCount();
    // adapter always contains actual data while state might contain old data (f.e. data before the animation is
    // done). As we want to measure the view with actual data we must use data from the adapter and not from  the
    // state
    for (int i = 0; i < adapterItemCount; i++) {
        if (vertical) {
            if (!hasChildSize) {
                if (i < stateItemCount) {
                    // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
                    // we will use previously calculated dimensions
                    measureChild(recycler, i, widthSize, unspecified, childDimensions);
                } else {
                    logMeasureWarning(i);
                }
            }
            height += childDimensions[CHILD_HEIGHT];
            if (i == 0) {
                width = childDimensions[CHILD_WIDTH];
            }
            if (hasHeightSize && height >= heightSize) {
                break;
            }
        } else {
            if (!hasChildSize) {
                if (i < stateItemCount) {
                    // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
                    // we will use previously calculated dimensions
                    measureChild(recycler, i, unspecified, heightSize, childDimensions);
                } else {
                    logMeasureWarning(i);
                }
            }
            width += childDimensions[CHILD_WIDTH];
            if (i == 0) {
                height = childDimensions[CHILD_HEIGHT];
            }
            if (hasWidthSize && width >= widthSize) {
                break;
            }
        }
    }

    if (exactWidth) {
        width = widthSize;
    } else {
        width += getPaddingLeft() + getPaddingRight();
        if (hasWidthSize) {
            width = Math.min(width, widthSize);
        }
    }

    if (exactHeight) {
        height = heightSize;
    } else {
        height += getPaddingTop() + getPaddingBottom();
        if (hasHeightSize) {
            height = Math.min(height, heightSize);
        }
    }

    setMeasuredDimension(width, height);

    if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
        final boolean fit = (vertical && (!hasHeightSize || height < heightSize))
                || (!vertical && (!hasWidthSize || width < widthSize));

        ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
    }
}

private void logMeasureWarning(int child) {
    if (BuildConfig.DEBUG) {
        Log.w("MyLinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." +
                "To remove this message either use #setChildSize() method or don't run RecyclerView animations");
    }
}

private void initChildDimensions(int width, int height, boolean vertical) {
    if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
        // already initialized, skipping
        return;
    }
    if (vertical) {
        childDimensions[CHILD_WIDTH] = width;
        childDimensions[CHILD_HEIGHT] = childSize;
    } else {
        childDimensions[CHILD_WIDTH] = childSize;
        childDimensions[CHILD_HEIGHT] = height;
    }
}

@Override
public void setOrientation(int orientation) {
    // might be called before the constructor of this class is called
    //noinspection ConstantConditions
    if (childDimensions != null) {
        if (getOrientation() != orientation) {
            childDimensions[CHILD_WIDTH] = 0;
            childDimensions[CHILD_HEIGHT] = 0;
        }
    }
    super.setOrientation(orientation);
}

public void clearChildSize() {
    hasChildSize = false;
    setChildSize(DEFAULT_CHILD_SIZE);
}

public void setChildSize(int childSize) {
    hasChildSize = true;
    if (this.childSize != childSize) {
        this.childSize = childSize;
        requestLayout();
    }
}

private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) {
    final View child;
    try {
        child = recycler.getViewForPosition(position);
    } catch (IndexOutOfBoundsException e) {
        if (BuildConfig.DEBUG) {
            Log.w("MyLinearLayoutManager", "MyLinearLayoutManager doesn't work well with animations. Consider switching them off", e);
        }
        return;
    }

    final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();

    final int hPadding = getPaddingLeft() + getPaddingRight();
    final int vPadding = getPaddingTop() + getPaddingBottom();

    final int hMargin = p.leftMargin + p.rightMargin;
    final int vMargin = p.topMargin + p.bottomMargin;

    // we must make insets dirty in order calculateItemDecorationsForChild to work
    makeInsetsDirty(p);
    // this method should be called before any getXxxDecorationXxx() methods
    calculateItemDecorationsForChild(child, tmpRect);

    final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
    final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);

    final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
    final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically());

    child.measure(childWidthSpec, childHeightSpec);

    dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
    dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;

    // as view is recycled let's not keep old measured values
    makeInsetsDirty(p);
    recycler.recycleView(child);
}

private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
    if (!canMakeInsetsDirty) {
        return;
    }
    try {
        if (insetsDirtyField == null) {
            insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
            insetsDirtyField.setAccessible(true);
        }
        insetsDirtyField.set(p, true);
    } catch (NoSuchFieldException e) {
        onMakeInsertDirtyFailed();
    } catch (IllegalAccessException e) {
        onMakeInsertDirtyFailed();
    }
}

private static void onMakeInsertDirtyFailed() {
    canMakeInsetsDirty = false;
    if (BuildConfig.DEBUG) {
        Log.w("MyLinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
    }
}
}
person Arun Antoney    schedule 26.11.2015

Библиотека поддержки Android теперь также обрабатывает свойство WRAP_CONTENT. Просто импортируйте это в свой градиент.

compile 'com.android.support:recyclerview-v7:23.2.0'

Готово!

person Suyash Dixit    schedule 26.02.2016

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

Чтобы решить эту проблему, я немного изменил решение, чтобы оно выбирало минимум предоставленного размера и рассчитанного размера. см. ниже:

package com.linkdev.gafi.adapters;

import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

import com.linkdev.gafi.R;

public class MyLinearLayoutManager extends LinearLayoutManager {

    public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
        super(context, orientation, reverseLayout);
        this.c = context;
    }


    private Context c;
    private int[] mMeasuredDimension = new int[2];


    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);



        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }


        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        int widthDesired = Math.min(widthSize,width);
        setMeasuredDimension(widthDesired, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }}
person Mahmoud    schedule 17.02.2015

Я пробовал все решения, они очень полезны, но это работает только для меня.

public class  LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
        super(context, orientation, reverseLayout);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);
        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {


            if (getOrientation() == HORIZONTAL) {

                measureScrapChild(recycler, i,
                        View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                        heightSpec,
                        mMeasuredDimension);

                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                measureScrapChild(recycler, i,
                        widthSpec,
                        View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                        mMeasuredDimension);
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }

        if (height < heightSize || width < widthSize) {

            switch (widthMode) {
                case View.MeasureSpec.EXACTLY:
                    width = widthSize;
                case View.MeasureSpec.AT_MOST:
                case View.MeasureSpec.UNSPECIFIED:
            }

            switch (heightMode) {
                case View.MeasureSpec.EXACTLY:
                    height = heightSize;
                case View.MeasureSpec.AT_MOST:
                case View.MeasureSpec.UNSPECIFIED:
            }

            setMeasuredDimension(width, height);
        } else {
            super.onMeasure(recycler, state, widthSpec, heightSpec);
        }
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        recycler.bindViewToPosition(view, position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}
person Vlad    schedule 24.10.2015

Просто оберните контент с помощью RecyclerView с макетом сетки

Изображение: Recycler как макет GridView

Просто используйте GridLayoutManager следующим образом:

RecyclerView.LayoutManager mRecyclerGrid=new GridLayoutManager(this,3,LinearLayoutManager.VERTICAL,false);
mRecyclerView.setLayoutManager(mRecyclerGrid);

Вы можете установить, сколько элементов должно отображаться в строке (замените 3).

person Ali Ali    schedule 06.01.2019