Mam aplikację, która zarządza zbiorami książek (np. Playlistami).
Chcę wyświetlić listę kolekcji z pionowym RecyclerView i wewnątrz każdego wiersza, listę książek w poziomym RecyclerView.
Kiedy ustawię layout_height wewnętrznego poziomego RecyclerView na 300dp, jest on wyświetlany poprawnie, ale kiedy ustawię go na wrap_content, nic nie wyświetla. Muszę użyć wrap_content, ponieważ chcę mieć możliwość programowej zmiany menedżera układu, aby przełączać się między wyświetlaniem pionowym i poziomym.
Czy wiesz, co robię źle?
Układ My Fragment:
<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>
Układ elementu kolekcji:
<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>
Pozycja listy książek:
<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>
Oto mój adapter do kolekcji:
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();
}
}
I wreszcie adapter 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();
}
}
Odpowiedzi:
Aktualizacja
Wiele problemów związanych z tą funkcją w wersji 23.2.0 zostało naprawionych w 23.2.1, zamiast tego zaktualizuj ją.
Wraz z wydaniem wersji 23.2 Support Library,
RecyclerView
teraz to obsługuje!Zaktualizuj
build.gradle
do:lub jakiejkolwiek innej wersji.
W
setAutoMeasurementEnabled()
razie potrzeby można to wyłączyć . Sprawdź szczegółowo tutaj .źródło
23.2.1
Wydaje się, że wersja RecyclerView naprawiła kilka błędów. U nas działa bardzo dobrze. Jeśli nie, możesz nawet spróbować24.0.0-alpha1
Rozwiązanie @ user2302510 nie działa tak dobrze, jak można się było spodziewać. Pełne obejście dla obu orientacji i dynamicznych zmian danych to:
źródło
Powyższy kod nie działa dobrze, gdy musisz ustawić swoje elementy jako „wrap_content”, ponieważ mierzy zarówno wysokość, jak i szerokość elementów za pomocą MeasureSpec.UNSPECIFIED. Po kilku problemach zmodyfikowałem to rozwiązanie, aby teraz elementy mogły się rozszerzać. Jedyną różnicą jest to, że zapewnia rodzicom wysokość lub szerokość MeasureSpec w zależności od orientacji układu.
źródło
wrap_content
switch (heightMode) { case View.MeasureSpec.AT_MOST: if (height < heightSize) break; case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.UNSPECIFIED: }
Istniejący menedżer układu nie obsługuje jeszcze zawijania zawartości.
Możesz utworzyć nowy LayoutManager, który rozszerzy istniejący i nadpisuje metodę onMeasure w celu pomiaru zawartości zawijania.
źródło
setHasFixedSize(boolean)
bezużytecznym z domyślnymi menedżerami układu? W każdym razie świetnie wiedzieć, że jest to zgodne z planem. Mam nadzieję, że wkrótce wyjdzie.Jak wspomniał @ yiğit, musisz zastąpić onMeasure (). Zarówno @ user2302510, jak i @DenisNek mają dobre odpowiedzi, ale jeśli chcesz wesprzeć ItemDecoration, możesz użyć tego niestandardowego menedżera układu.
Inne odpowiedzi nie mogą być przewijane, gdy jest więcej elementów, niż można wyświetlić na ekranie. Ten używa domyślnej implementacji onMeasure (), gdy jest więcej elementów niż rozmiar ekranu.
A jeśli chcesz go używać z GridLayoutManager, po prostu rozszerza go z GridLayoutManager i zmień
do
źródło
gridlayoutmanager
istaggeredgridlayoutmanager
biorąc pod uwagę liczbę rozpiętości w umyśle?UPDATE marzec 2016
Według biblioteki obsługi systemu Android 23.2.1 wersji biblioteki obsługi. Więc wszystko WRAP_CONTENT powinno działać poprawnie.
Proszę zaktualizować wersję biblioteki w pliku gradle.
Dzięki temu RecyclerView może sam dopasować swój rozmiar na podstawie rozmiaru jego zawartości. Oznacza to, że wcześniej niedostępne scenariusze, takie jak użycie WRAP_CONTENT dla wymiaru RecyclerView, są teraz możliwe .
będziesz musiał wywołać setAutoMeasureEnabled (true)
Naprawiono błędy związane z różnymi metodami pomiaru specyfikacji w aktualizacji
Sprawdź https://developer.android.com/topic/libraries/support-library/features.html
źródło
Ta odpowiedź jest oparta na rozwiązaniu podanym przez Denisa Neka. Rozwiązuje problem nieuwzględniania dekoracji, takich jak przekładki.
}
źródło
Alternatywą dla rozszerzenia LayoutManager może być ręczne ustawienie rozmiaru widoku.
Liczba elementów na wysokość wiersza (jeśli wszystkie elementy mają tę samą wysokość i separator znajduje się w wierszu)
Nadal jest obejściem, ale w podstawowych przypadkach działa.
źródło
Tutaj znalazłem rozwiązanie: https://code.google.com/p/android/issues/detail?id=74772
W żaden sposób nie jest to moje rozwiązanie. Właśnie skopiowałem to stamtąd, ale mam nadzieję, że pomoże to komuś tak samo, jak pomogło mi przy implementacji poziomego RecyclerView i wrap_content height (powinno działać również dla pionowej i wrap_content width)
Rozwiązaniem jest rozszerzenie LayoutManager i zastąpienie jego metody onMeasure, zgodnie z sugestią @yigit.
Oto kod na wypadek śmierci linku:
źródło
Użyte rozwiązanie z @ sinan-kozak, z wyjątkiem naprawienia kilku błędów. W szczególności, nie powinno się używać
View.MeasureSpec.UNSPECIFIED
dla obu szerokości i wysokości podczas wywoływaniameasureScrapChild
jako że nie będzie prawidłowo stanowią zawinięty tekst w dziecko. Zamiast tego przejdziemy przez tryby szerokości i wysokości od rodzica, co pozwoli rzeczy działać zarówno dla układów poziomych, jak i pionowych.`
źródło
RecyclerView
jest zagnieżdżony wLinearLayout
. Nie działa zRelativeLayout
.Tak, obejście pokazane we wszystkich odpowiedziach jest poprawne, to znaczy musimy dostosować menedżera układu liniowego, aby dynamicznie obliczał wysokość elementów podrzędnych w czasie wykonywania. Ale wszystkie odpowiedzi nie działają zgodnie z oczekiwaniami. Proszę o odpowiedź poniżej dla niestandardowego menedżera układu z obsługą wszystkich orientacji.
źródło
Biblioteka obsługi systemu Android obsługuje teraz również właściwość WRAP_CONTENT. Po prostu zaimportuj to do swojego gradle.
I zrobione!
źródło
Bazując na pracy Denisa Neka, sprawdza się, gdy suma szerokości przedmiotu jest mniejsza niż rozmiar pojemnika. poza tym sprawi, że podgląd recyklingu nie będzie przewijalny i pokaże tylko podzbiór danych.
Aby rozwiązać ten problem, nieznacznie zmodyfikowałem rozwiązanie tak, aby wybierało min z podanego rozmiaru i obliczonego rozmiaru. patrz poniżej:
źródło
Wypróbowałem wszystkie rozwiązania, są bardzo przydatne, ale to działa dobrze tylko dla mnie
źródło
Po prostu zawiń zawartość przy użyciu RecyclerView z układem siatki
Obraz: Recykler jako układ GridView
Po prostu użyj GridLayoutManager w następujący sposób:
Możesz ustawić, ile elementów ma pojawić się w rzędzie (zastąp 3).
źródło