Eu tenho um aplicativo que gerencia coleções de livros (como listas de reprodução).
Quero exibir uma lista de coleções com um RecyclerView vertical e, dentro de cada linha, uma lista de livros em um RecyclerView horizontal.
Quando defino o layout_height do RecyclerView horizontal horizontal como 300dp, ele é exibido corretamente, mas quando o defino como wrap_content, ele não exibe nada. Preciso usar o wrap_content porque quero poder alterar o gerenciador de layout programaticamente para alternar entre a exibição vertical e a horizontal.
Você sabe o que estou fazendo de errado?
Meu layout de fragmento:
<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>
Layout do elemento de coleção:
<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>
Item da lista de livros:
<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>
Aqui está o meu adaptador de coleção:
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();
}
}
E, finalmente, o adaptador 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();
}
}
Respostas:
Atualizar
Muitos problemas relacionados a esse recurso na versão 23.2.0 foram corrigidos no 23.2.1, atualizando para isso.
Com o lançamento da Biblioteca de Suporte versão 23.2,
RecyclerView
agora é possível!Atualize
build.gradle
para:ou qualquer versão além disso.
Isso pode ser desativado via,
setAutoMeasurementEnabled()
se necessário. Confira em detalhes aqui .fonte
23.2.1
do RecyclerView parece ter corrigido vários bugs. Funciona muito bem para nós. Se não, você pode até tentar com24.0.0-alpha1
A solução @ user2302510 não funciona tão bem quanto o esperado. A solução completa para orientações e alterações de dados dinamicamente é:
fonte
O código acima não funciona bem quando você precisa tornar seus itens "wrap_content", porque mede a altura e a largura dos itens com o MeasureSpec.UNSPECIFIED. Depois de alguns problemas, modifiquei a solução e agora os itens podem se expandir. A única diferença é que ele fornece a altura ou largura dos pais. MeasureSpec depende da orientação do layout.
fonte
wrap_content
switch (heightMode) { case View.MeasureSpec.AT_MOST: if (height < heightSize) break; case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.UNSPECIFIED: }
O gerenciador de layout existente ainda não suporta conteúdo de quebra automática.
Você pode criar um novo LayoutManager que estenda o existente e substitua o método onMeasure para medir o conteúdo de quebra automática.
fonte
setHasFixedSize(boolean)
serve para os gerenciadores de layout padrão? Enfim, é bom saber que está no roteiro. Espero que saia logo.Como @ yiğit mencionou, você precisa substituir onMeasure (). @ User2302510 e @DenisNek têm boas respostas, mas se você deseja dar suporte ao ItemDecoration, pode usar esse gerenciador de layout personalizado.
E outras respostas não podem rolar quando há mais itens do que podem ser exibidos na tela. Este está usando a implementação padrão de onMeasure () quando há mais itens que o tamanho da tela.
E se você quiser usá-lo com o GridLayoutManager, apenas o estenda do GridLayoutManager e altere
para
fonte
gridlayoutmanager
estaggeredgridlayoutmanager
considerar a contagem de span em mente?ATUALIZAÇÃO março 2016
Pela Android Support Library 23.2.1 de uma versão da biblioteca de suporte. Portanto, todos os WRAP_CONTENT devem funcionar corretamente.
Atualize a versão de uma biblioteca no arquivo gradle.
Isso permite que um RecyclerView se dimensione com base no tamanho de seu conteúdo. Isso significa que agora os cenários indisponíveis anteriormente, como o uso de WRAP_CONTENT para uma dimensão do RecyclerView, agora são possíveis .
você precisará chamar setAutoMeasureEnabled (true)
Correção de bugs relacionados a vários métodos de especificação de medidas na atualização
Verifique https://developer.android.com/topic/libraries/support-library/features.html
fonte
Esta resposta é baseada na solução dada por Denis Nek. Resolve o problema de não levar em consideração as decorações como divisórias.
}
fonte
Uma alternativa para estender o LayoutManager pode ser apenas definir o tamanho da vista manualmente.
Número de itens por altura da linha (se todos os itens tiverem a mesma altura e o separador estiver incluído na linha)
Ainda é uma solução alternativa, mas para casos básicos funciona.
fonte
Aqui encontrei uma solução: https://code.google.com/p/android/issues/detail?id=74772
Não é de forma alguma a minha solução. Acabei de copiá-lo a partir daí, mas espero que ajude alguém tanto quanto me ajudou na implementação do RecyclerView horizontal e da altura wrap_content (deve funcionar também na vertical e na largura wrap_content)
A solução é estender o LayoutManager e substituir seu método onMeasure como sugerido por @yigit.
Aqui está o código caso o link morra:
fonte
A solução usada do @ sinan-kozak, exceto corrigiu alguns bugs. Especificamente, não devemos usar
View.MeasureSpec.UNSPECIFIED
para tanto a largura e altura ao chamarmeasureScrapChild
como que não vai contabilizar corretamente o texto envolvido na criança. Em vez disso, passaremos pelos modos de largura e altura do pai, o que permitirá que as coisas funcionem para layouts horizontais e verticais.`
fonte
RecyclerView
estiver aninhado em umLinearLayout
. Não funciona comRelativeLayout
.Sim, a solução alternativa mostrada em todas as respostas está correta, ou seja, precisamos personalizar o gerenciador de layout linear para calcular a altura de seus itens filhos dinamicamente no tempo de execução. Mas todas as respostas não estão funcionando como o esperado. Por favor, responda a seguir para o gerente de layout personalizado com todo o suporte de orientação.
fonte
A Biblioteca de suporte do Android também lida com a propriedade WRAP_CONTENT. Basta importar isso no seu gradle.
E feito!
fonte
Com base no trabalho de Denis Nek, funciona bem se a soma das larguras dos itens for menor que o tamanho do contêiner. fora isso, tornará a reciclerview não rolável e mostrará apenas um subconjunto dos dados.
Para resolver esse problema, modifiquei um pouco a solução para escolher o mínimo do tamanho fornecido e do tamanho calculado. ver abaixo:
fonte
Eu tentei todas as soluções, elas são muito úteis, mas isso só funciona bem para mim
fonte
Basta agrupar o conteúdo usando o RecyclerView com o layout de grade
Imagem: Reciclador como layout do GridView
Basta usar o GridLayoutManager assim:
Você pode definir quantos itens devem aparecer em uma linha (substitua os 3).
fonte