
Twibit
Ich habe eine Anwendung, die Sammlungen von Büchern (wie Wiedergabelisten) verwaltet.
Ich möchte eine Sammlungsliste mit einer vertikalen RecyclerView und in jeder Zeile eine Liste mit Büchern in einer horizontalen RecyclerView anzeigen.
Wenn ich die layout_height der inneren horizontalen RecyclerView auf 300dp setze, wird sie aber korrekt angezeigt Wenn ich es auf wrap_content setze, wird nichts angezeigt.
Ich muss wrap_content verwenden, weil ich in der Lage sein möchte, den Layout-Manager programmgesteuert zu ändern, um zwischen vertikaler und horizontaler Anzeige zu wechseln.

Weißt du was ich falsch mache?
Mein Fragment-Layout:
<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>
Sammlungselement-Layout:
<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>
Artikel der Buchliste:
<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="https://stackoverflow.com/questions/26649406/@drawable/placeholder"
android:contentDescription="@string/cover"
android:adjustViewBounds="true"
android:background="@android:drawable/dialog_holo_light_frame"/>
</FrameLayout>
Hier ist mein Sammeladapter:
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();
}
}
Und schließlich der Book-Adapter :
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();
}
}

Denis Neke
@ user2302510 Lösung funktioniert nicht so gut wie erwartet. Vollständige Problemumgehung für beide Ausrichtungen und dynamische Datenänderungen ist:
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);
}
}
}

verblüfft
Aktualisieren
Viele Probleme im Zusammenhang mit dieser Funktion in Version 23.2.0 wurden in 23.2.1 behoben, aktualisieren Sie stattdessen darauf.
Mit der Veröffentlichung der Support-Bibliothek Version 23.2 RecyclerView
jetzt unterstützt das!
Aktualisieren build.gradle
zu:
compile 'com.android.support:recyclerview-v7:23.2.1'
oder jede Version darüber hinaus.
Diese Version bringt eine aufregende neue Funktion in die LayoutManager-API: automatische Messung! Dadurch kann eine RecyclerView ihre Größe basierend auf der Größe ihres Inhalts anpassen. Dies bedeutet, dass zuvor nicht verfügbare Szenarien, wie z. B. die Verwendung von WRAP_CONTENT für eine Dimension der RecyclerView, jetzt möglich sind. Sie werden feststellen, dass alle eingebauten LayoutManager jetzt die automatische Messung unterstützen.
Dies kann über deaktiviert werden setAutoMeasurementEnabled()
wenn nötig. Prüfen Sie im Detail Hier.

Kaerdan
Der obige Code funktioniert nicht gut, wenn Sie Ihre Artikel zu “wrap_content” machen müssen, da er sowohl die Höhe als auch die Breite der Artikel mit MeasureSpec.UNSPECIFIED misst. Nach einigen Problemen habe ich diese Lösung geändert, sodass Elemente jetzt erweitert werden können. Der einzige Unterschied besteht darin, dass die Höhe oder Breite der Eltern angegeben wird. MeasureSpec hängt von der Layoutausrichtung ab.
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);
}
}
}
Vorhandene Layout-Manager unterstützen noch keine Wrap-Inhalte.
Sie können einen neuen LayoutManager erstellen, der den vorhandenen erweitert und die onMeasure-Methode überschreibt, um den Umbruchinhalt zu messen.

Sinan Kosak
Wie @yiğit erwähnt hat, müssen Sie onMeasure() überschreiben. Sowohl @user2302510 als auch @DenisNek haben gute Antworten, aber wenn Sie ItemDecoration unterstützen möchten, können Sie diesen benutzerdefinierten Layout-Manager verwenden.
Und andere Antworten können nicht gescrollt werden, wenn mehr Elemente vorhanden sind, als auf dem Bildschirm angezeigt werden können. Dieser verwendet die Standardimplementierung von onMeasure(), wenn mehr Elemente als die Bildschirmgröße vorhanden sind.
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);
}
}
}
Und wenn Sie es mit GridLayoutManager verwenden möchten, erweitern Sie es einfach von GridLayoutManager und ändern Sie es
for (int i = 0; i < getItemCount(); i++)
zu
for (int i = 0; i < getItemCount(); i = i + getSpanCount())

Wadim Kotow
Update März 2016
Durch Android-Unterstützungsbibliothek 23.2.1 einer unterstützenden Bibliotheksversion. So alles WRAP_CONTENT sollte korrekt funktionieren.
Bitte aktualisieren Sie die Version einer Bibliothek in der Gradle-Datei.
compile 'com.android.support:recyclerview-v7:23.2.1'
Dadurch kann eine RecyclerView ihre Größe basierend auf der Größe ihres Inhalts anpassen. Das bedeutet, dass bisher nicht verfügbare Szenarien, wie z die Verwendung von WRAP_CONTENT für eine Dimension der RecyclerView, sind jetzt möglich.
Sie müssen anrufen setAutoMeasureEnabled(true)
Fehler im Zusammenhang mit verschiedenen Messspezifikationsmethoden im Update behoben
Überprüfen https://developer.android.com/topic/libraries/support-library/features.html

Lilienberg
Diese Antwort basiert auf der Lösung von Denis Nek. Es löst das Problem, Dekorationen wie Trennwände nicht zu berücksichtigen.
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);
}
}
}
9143300cookie-checkDie Ansichtshöhe des verschachtelten Recyclers umschließt seinen Inhalt nichtyes
Warum müssen Sie wrap_content verwenden, um den Layoutmanager zwischen vertikal und horizontal zu ändern?
– AmaJayJB
30. Oktober 2014 um 9:47 Uhr
Wenn ich die layout_height auf einen bestimmten Wert setze (sagen wir die Höhe eines Elements für den horizontalen Layout-Manager), wird nur die erste Zeile der Liste mit dem vertikalen Layout-Manager angezeigt.
– Twibit
30. Oktober 2014 um 9:52 Uhr
Aber recyclerView hat eine eingebaute ScrollView, sodass Sie einfach durch die Elemente scrollen können … oder verfehle ich hier den Punkt? Entschuldigung, ich versuche es zu verstehen
– AmaJayJB
30. Oktober 2014 um 10:02 Uhr
Ich möchte Sammlungsinhalte durchblättern, aber nicht die innere Buchliste (außer horizontal). So etwas wie ExpandableListView.
– Twibit
30. Oktober 2014 um 10:12 Uhr
Ich stehe vor der gleichen Frage mit dir, hast du sie jetzt gelöst?
– VinceStyling
2. November 2014 um 9:30 Uhr