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

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

Я хочу отобразить список коллекции с вертикальным 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(); } } 

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

 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(); } } 

Solutions Collecting From Web of "Высота вложенного ресайклера не ограничивает его содержимое"

Обновить

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

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

Обновить build.gradle до:

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

Или любой другой версии.

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

Это может быть отключено с помощью setAutoMeasurementEnabled() если это необходимо. Подробнее здесь .

@ 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); } } } 

Приведенный выше код не работает, когда вам нужно сделать ваши элементы «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); } } } 

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

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

Как упоминалось в @ 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++) 

в

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

ОБНОВЛЕНИЕ Март 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.

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

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

}

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

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

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

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

Используемое решение от @ 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); } } } 

`

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

Это не мое решение. Я только что скопировал его оттуда, но я надеюсь, что это поможет кому-то так же сильно, как это помогло мне при реализации горизонтальной высоты RecyclerView и height_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); } } } 

Android Support Library теперь также поддерживает свойство WRAP_CONTENT. Просто импортируйте это в свой град.

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

И сделано!

Основываясь на работе Дениса Нек, он хорошо работает, если сумма ширины элемента меньше размера контейнера. Кроме этого, это сделает 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); } }} 

I have tried all solutions, they are very useful but this only works fine for me

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

Yes the workaround shown in all answer is correct , that is we need to customize the linear layout manager to calculate the height of its child items dynamically at run time. But all answers not working as expected .Please the below answer for custom layout manger with all orientation support.

 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 (fe 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"); } } }