que - recyclerview android studio ejemplo 2018
¿Cómo puedo hacer que WRAP_CONTENT funcione en un RecyclerView? (17)
Tengo un DialogFragment
que contiene un RecyclerView
(una lista de tarjetas).
Dentro de este RecyclerView
hay uno o más CardViews
que pueden tener cualquier altura.
Quiero darle a este DialogFragment
la altura correcta en función de las CardViews
que están dentro.
Normalmente esto sería simple, establecería wrap_content
en RecyclerView
así.
<android.support.v7.widget.RecyclerView ...
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:scrollbars="vertical" >
</android.support.v7.widget.RecyclerView>
Debido a que estoy usando un RecyclerView
esto no funciona, mira:
https://issuetracker.google.com/issues/37001674
y
La altura de la vista del reciclador anidado no envuelve su contenido
En ambas páginas, la gente sugiere extender LinearLayoutManager
y anular onMeasure()
Primero utilicé el LayoutManager que alguien proporcionó en el primer enlace:
public static class WrappingLayoutManager extends LinearLayoutManager {
public WrappingLayoutManager(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);
}
}
}
Sin embargo, esto no funcionó porque
heightSize = View.MeasureSpec.getSize(heightSpec);
devuelve un valor muy grande que parece estar relacionado con match_parent
.
Comentando height = heightSize;
(en la segunda caja del conmutador) logré que la altura funcionara, pero solo si un elemento TextView
dentro de CardView
no CardView
su propio texto (una frase larga).
Tan pronto como TextView
su propio texto, la altura DEBERÍA aumentar, pero no es así. Se calculó la altura para esa oración larga como una sola línea, no como una línea envuelta (2 o más).
¿Algún consejo sobre cómo debería mejorar este LayoutManager
para que mi RecyclerView
funcione con WRAP_CONTENT
?
Editar: este administrador de diseño podría funcionar para la mayoría de las personas, pero todavía tiene problemas para desplazarse y calcular las alturas de ajuste de las textviews
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);
}
}
}
Aquí está la versión c # para mono android
/*
* Ported by Jagadeesh Govindaraj (@jaganjan)
*Copyright 2015 serso aka se.solovyev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Contact details
*
* Email: se.solovyev @gmail.com
* Site: http://se.solovyev.org
*/
using Android.Content;
using Android.Graphics;
using Android.Support.V4.View;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Views;
using Java.Lang;
using Java.Lang.Reflect;
using System;
using Math = Java.Lang.Math;
namespace Droid.Helper
{
public class WrapLayoutManager : LinearLayoutManager
{
private const int DefaultChildSize = 100;
private static readonly Rect TmpRect = new Rect();
private int _childSize = DefaultChildSize;
private static bool _canMakeInsetsDirty = true;
private static readonly int[] ChildDimensions = new int[2];
private const int ChildHeight = 1;
private const int ChildWidth = 0;
private static bool _hasChildSize;
private static Field InsetsDirtyField = null;
private static int _overScrollMode = ViewCompat.OverScrollAlways;
private static RecyclerView _view;
public WrapLayoutManager(Context context, int orientation, bool reverseLayout)
: base(context, orientation, reverseLayout)
{
_view = null;
}
public WrapLayoutManager(Context context) : base(context)
{
_view = null;
}
public WrapLayoutManager(RecyclerView view) : base(view.Context)
{
_view = view;
_overScrollMode = ViewCompat.GetOverScrollMode(view);
}
public WrapLayoutManager(RecyclerView view, int orientation, bool reverseLayout)
: base(view.Context, orientation, reverseLayout)
{
_view = view;
_overScrollMode = ViewCompat.GetOverScrollMode(view);
}
public void SetOverScrollMode(int overScrollMode)
{
if (overScrollMode < ViewCompat.OverScrollAlways || overScrollMode > ViewCompat.OverScrollNever)
throw new ArgumentException("Unknown overscroll mode: " + overScrollMode);
if (_view == null) throw new ArgumentNullException(nameof(_view));
_overScrollMode = overScrollMode;
ViewCompat.SetOverScrollMode(_view, overScrollMode);
}
public static int MakeUnspecifiedSpec()
{
return View.MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified);
}
public override void OnMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,
int heightSpec)
{
var widthMode = View.MeasureSpec.GetMode(widthSpec);
var heightMode = View.MeasureSpec.GetMode(heightSpec);
var widthSize = View.MeasureSpec.GetSize(widthSpec);
var heightSize = View.MeasureSpec.GetSize(heightSpec);
var hasWidthSize = widthMode != MeasureSpecMode.Unspecified;
var hasHeightSize = heightMode != MeasureSpecMode.Unspecified;
var exactWidth = widthMode == MeasureSpecMode.Exactly;
var exactHeight = heightMode == MeasureSpecMode.Exactly;
var unspecified = MakeUnspecifiedSpec();
if (exactWidth && exactHeight)
{
// in case of exact calculations for both dimensions let''s use default "onMeasure" implementation
base.OnMeasure(recycler, state, widthSpec, heightSpec);
return;
}
var vertical = Orientation == Vertical;
InitChildDimensions(widthSize, heightSize, vertical);
var width = 0;
var 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();
var stateItemCount = state.ItemCount;
var adapterItemCount = ItemCount;
// 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 (var 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[ChildHeight];
if (i == 0)
{
width = ChildDimensions[ChildWidth];
}
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[ChildWidth];
if (i == 0)
{
height = ChildDimensions[ChildHeight];
}
if (hasWidthSize && width >= widthSize)
{
break;
}
}
}
if (exactWidth)
{
width = widthSize;
}
else
{
width += PaddingLeft + PaddingRight;
if (hasWidthSize)
{
width = Math.Min(width, widthSize);
}
}
if (exactHeight)
{
height = heightSize;
}
else
{
height += PaddingTop + PaddingBottom;
if (hasHeightSize)
{
height = Math.Min(height, heightSize);
}
}
SetMeasuredDimension(width, height);
if (_view == null || _overScrollMode != ViewCompat.OverScrollIfContentScrolls) return;
var fit = (vertical && (!hasHeightSize || height < heightSize))
|| (!vertical && (!hasWidthSize || width < widthSize));
ViewCompat.SetOverScrollMode(_view, fit ? ViewCompat.OverScrollNever : ViewCompat.OverScrollAlways);
}
private void LogMeasureWarning(int child)
{
#if DEBUG
Log.WriteLine(LogPriority.Warn, "LinearLayoutManager",
"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");
#endif
}
private void InitChildDimensions(int width, int height, bool vertical)
{
if (ChildDimensions[ChildWidth] != 0 || ChildDimensions[ChildHeight] != 0)
{
// already initialized, skipping
return;
}
if (vertical)
{
ChildDimensions[ChildWidth] = width;
ChildDimensions[ChildHeight] = _childSize;
}
else
{
ChildDimensions[ChildWidth] = _childSize;
ChildDimensions[ChildHeight] = height;
}
}
public void ClearChildSize()
{
_hasChildSize = false;
SetChildSize(DefaultChildSize);
}
public void SetChildSize(int size)
{
_hasChildSize = true;
if (_childSize == size) return;
_childSize = size;
RequestLayout();
}
private void MeasureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize,
int[] dimensions)
{
View child = null;
try
{
child = recycler.GetViewForPosition(position);
}
catch (IndexOutOfRangeException e)
{
Log.WriteLine(LogPriority.Warn, "LinearLayoutManager",
"LinearLayoutManager doesn''t work well with animations. Consider switching them off", e);
}
if (child != null)
{
var p = child.LayoutParameters.JavaCast<RecyclerView.LayoutParams>()
var hPadding = PaddingLeft + PaddingRight;
var vPadding = PaddingTop + PaddingBottom;
var hMargin = p.LeftMargin + p.RightMargin;
var 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);
var hDecoration = GetRightDecorationWidth(child) + GetLeftDecorationWidth(child);
var vDecoration = GetTopDecorationHeight(child) + GetBottomDecorationHeight(child);
var childWidthSpec = GetChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.Width,
CanScrollHorizontally());
var childHeightSpec = GetChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.Height,
CanScrollVertically());
child.Measure(childWidthSpec, childHeightSpec);
dimensions[ChildWidth] = GetDecoratedMeasuredWidth(child) + p.LeftMargin + p.RightMargin;
dimensions[ChildHeight] = 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)
{
var klass = Java.Lang.Class.FromType (typeof (RecyclerView.LayoutParams));
InsetsDirtyField = klass.GetDeclaredField("mInsetsDirty");
InsetsDirtyField.Accessible = true;
}
InsetsDirtyField.Set(p, true);
}
catch (NoSuchFieldException e)
{
OnMakeInsertDirtyFailed();
}
catch (IllegalAccessException e)
{
OnMakeInsertDirtyFailed();
}
}
private static void OnMakeInsertDirtyFailed()
{
_canMakeInsetsDirty = false;
#if DEBUG
Log.Warn("LinearLayoutManager",
"Can''t make LayoutParams insets dirty, decorations measurements might be incorrect");
#endif
}
}
}
Aquí está la versión refinada de la clase que parece funcionar y carece de los problemas que otras soluciones tienen:
package org.solovyev.android.views.llm;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
/**
* {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
* wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters.
*
* Now it''s impossible to run add/remove animations with child views which have arbitrary dimensions (height for
* VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
* {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
* If animations are not used at all then a normal measuring procedure will run and child views will be measured during
* the measure pass.
*/
public class LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {
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 int childSize = DEFAULT_CHILD_SIZE;
private boolean hasChildSize;
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(Context context) {
super(context);
}
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
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 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, widthSpec, unspecified, childDimensions);
} else {
logMeasureWarning(i);
}
}
height += childDimensions[CHILD_HEIGHT];
if (i == 0) {
width = childDimensions[CHILD_WIDTH];
}
if (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, heightSpec, childDimensions);
} else {
logMeasureWarning(i);
}
}
width += childDimensions[CHILD_WIDTH];
if (i == 0) {
height = childDimensions[CHILD_HEIGHT];
}
if (width >= widthSize) {
break;
}
}
}
if ((vertical && height < heightSize) || (!vertical && width < widthSize)) {
// we really should wrap the contents of the view, let''s do it
if (exactWidth) {
width = widthSize;
} else {
width += getPaddingLeft() + getPaddingRight();
}
if (exactHeight) {
height = heightSize;
} else {
height += getPaddingTop() + getPaddingBottom();
}
setMeasuredDimension(width, height);
} else {
// if calculated height/width exceeds requested height/width let''s use default "onMeasure" implementation
super.onMeasure(recycler, state, widthSpec, heightSpec);
}
}
private void logMeasureWarning(int child) {
if (BuildConfig.DEBUG) {
Log.w("LinearLayoutManager", "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 widthSpec, int heightSpec, int[] dimensions) {
final View child = recycler.getViewForPosition(position);
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;
final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);
final int childWidthSpec = getChildMeasureSpec(widthSpec, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
final int childHeightSpec = getChildMeasureSpec(heightSpec, 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;
recycler.recycleView(child);
}
}
Esto también está disponible como una library . Enlace a la clase relevante .
Desde la actualización de la Biblioteca de Soporte de Android 23.2.1 , todo WRAP_CONTENT debería funcionar correctamente.
gradle
versión de una biblioteca en gradle
archivo gradle
O para avanzar:
compile ''com.android.support:recyclerview-v7:23.2.1''
resuelto algún problema como errores corregidos relacionados con varios métodos de medida y especificación
Consulte http://developer.android.com/tools/support-library/features.html#v7-recyclerview
puede consultar el historial de revisión de la Biblioteca de soporte
El problema con el desplazamiento y el ajuste del texto es que este código asume que tanto el ancho como el alto se establecen en wrap_content
. Sin embargo, LayoutManager
necesita saber que el ancho horizontal está restringido. Entonces, en lugar de crear su propia widthSpec
para cada vista secundaria, simplemente use la widthSpec
original:
@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,
widthSpec,
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 childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(widthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
}
Pruebe esto (es una solución desagradable, pero puede funcionar): en el método onCreate
de su Activity
o en el método onViewCreated
de su fragmento. Establezca una devolución de llamada lista para ser activada cuando el RecyclerView
primera vez, así:
vRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
calculeRecyclerViewFullHeight();
}
});
En calculeRecyclerViewFullHeight
calcule la altura completa de RecyclerView
en función de la altura de sus elementos calculeRecyclerViewFullHeight
.
protected void calculateSwipeRefreshFullHeight() {
int height = 0;
for (int idx = 0; idx < getRecyclerView().getChildCount(); idx++ ) {
View v = getRecyclerView().getChildAt(idx);
height += v.getHeight();
}
SwipeRefreshLayout.LayoutParams params = getSwipeRefresh().getLayoutParams();
params.height = height;
getSwipeRefresh().setLayoutParams(params);
}
In my case my RecyclerView
is contain in a SwipeRefreshLayout
for that reason I''m setting the height to the SwipeRefreshView
and not to the RecyclerView
but if you don''t have any SwipeRefreshView
then you can set the height to the RecyclerView
instead.
Let me know if this helped you or not.
This comment helped me. Put the recyclerview in any other layout (Relative layout is preferable). Then change recyclerview''s height/width as match parent to that layout and set the parent layout''s height/width as wrap content.
RecyclerView
agregó soporte para wrap_content
en 23.2.0
que tenía errores, 23.2.1 era simplemente estable, por lo que puede usar:
compile ''com.android.support:recyclerview-v7:24.2.0''
Puedes ver el historial de revisión aquí:
https://developer.android.com/topic/libraries/support-library/revisions.html
Nota:
También tenga en cuenta que después de actualizar la biblioteca de soporte, RecyclerView
respetará wrap_content
y match_parent
por lo que si tiene una vista de elemento de un RecyclerView
configurada como match_parent
la vista única llenará toda la pantalla.
ACTUALIZAR
Con la actualización de Android Support Library 23.2, todo WRAP_CONTENT debería funcionar correctamente.
Actualice la versión de una biblioteca en un archivo gradle.
compile ''com.android.support:recyclerview-v7:23.2.0''
Respuesta original
Como se respondió en otra pregunta, debe usar el método original onMeasure () cuando la altura de la vista de su reciclador sea mayor que la altura de la pantalla. Este administrador de diseño puede calcular ItemDecoration y puede desplazarse con más.
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);
}
}
}
respuesta original: https://.com/a/28510031/1577792
Finally found the solution for this problem.
All you need to do is wrapping the RecyclerView
in a RelativeLayout. Maybe there are other Views which may also work.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
I had used some of the above solutions but it was working for width
but height
.
- If your specified
compileSdkVersion
greater than 23 , you can directly use RecyclerView provided in their respective support libraries of recycler view, like for 23 it will be''com.android.support:recyclerview-v7:23.2.1''
. These support libraries support attributes ofwrap_content
for both width and height.
You have to add it to your dependencies
compile ''com.android.support:recyclerview-v7:23.2.1''
- If your
compileSdkVersion
less than 23 , you can use below-mentioned solution.
I found this Google thread regarding this issue. In this thread, there is one contribution which leads to the implementation of LinearLayoutManager .
I have tested it for both height and width and it worked fine for me in both cases.
/*
* Copyright 2015 serso aka se.solovyev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Contact details
*
* Email: [email protected]
* Site: http://se.solovyev.org
*/
package org.solovyev.android.views.llm;
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import java.lang.reflect.Field;
/**
* {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
* wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters.
* <p/>
* Now it''s impossible to run add/remove animations with child views which have arbitrary dimensions (height for
* VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
* {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
* If animations are not used at all then a normal measuring procedure will run and child views will be measured during
* the measure pass.
*/
public class LinearLayoutManager 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 LinearLayoutManager(Context context) {
super(context);
this.view = null;
}
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
this.view = null;
}
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(RecyclerView view) {
super(view.getContext());
this.view = view;
this.overScrollMode = ViewCompat.getOverScrollMode(view);
}
@SuppressWarnings("UnusedDeclaration")
public LinearLayoutManager(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("LinearLayoutManager", "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("LinearLayoutManager", "LinearLayoutManager 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("LinearLayoutManager", "Can''t make LayoutParams insets dirty, decorations measurements might be incorrect");
}
}
}
Instead of using any library, easiest solution till the new version comes out is to just open b.android.com/74772 . You''ll easily find the best solution known to date there.
PS: b.android.com/74772 worked for me
Replace measureScrapChild
to follow code:
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension)
{
View view = recycler.GetViewForPosition(position);
if (view != null)
{
MeasureChildWithMargins(view, widthSpec, heightSpec);
measuredDimension[0] = view.MeasuredWidth;
measuredDimension[1] = view.MeasuredHeight;
recycler.RecycleView(view);
}
}
I use xamarin, so this is c# code. I think this can be easily "translated" to Java.
This now works as they''ve made a release in version 23.2, as stated in this post . Quoting the official blogpost
This release brings an exciting new feature to the LayoutManager API: auto-measurement! This allows a RecyclerView to size itself based on the size of its contents. This means that previously unavailable scenarios, such as using WRAP_CONTENT for a dimension of the RecyclerView, are now possible. You''ll find all built in LayoutManagers now support auto-measurement.
Update your view with null value instead of parent viewgroup in Adapter viewholder onCreateViewHolder method.
@Override
public AdapterItemSku.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflator.inflate(R.layout.layout_item, null, false);
return new MyViewHolder(view);
}
You must put a FrameLayout as Main view then put inside a RelativeLayout with ScrollView and at least your RecyclerView, it works for me.
The real trick here is the RelativeLayout...
Happy to help.
i suggest you to put the recyclerview in any other layout (Relative layout is preferable). Then change recyclerview''s height/width as match parent to that layout and set the parent layout''s height/width as wrap content. esto funciona para mi
No he trabajado en mi respuesta, pero la forma en que lo sé StaggridLayoutManager con no. de la grilla 1 puede resolver su problema ya que StaggridLayout ajustará automáticamente su altura y ancho en el tamaño del contenido. si funciona, no olvides comprobarlo como una respuesta correcta.