title: CardView wrap its content dynamically
body: <p>I have the following card in layout:</p>
  
  <pre><code>&lt;android.support.v7.widget.CardView
      android:id="@+id/card1"
      android:layout_width="match_parent"
      android:layout_marginBottom="6dp"&gt;
  
      &lt;RelativeLayout
          android:id="@+id/content"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"&gt;
  
          &lt;TextView
              android:id="@+id/label"
              android:layout_width="match_parent"
              android:layout_height="wrap_content" /&gt;
  
          &lt;!-- [a RecyclerView as list with LinearLayout as header] --&gt;
          &lt;include
              android:id="@+id/table"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_above="@+id/total_label"
              android:layout_below="@id/label"
              android:layout_marginBottom="4dp"
              android:layout_marginTop="4dp" /&gt;
  
          &lt;!-- [a LinearLayout with 2 TextViews] --&gt;
          &lt;include
              android:id="@+id/total_label"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_alignParentBottom="true" /&gt;
  
      &lt;/RelativeLayout&gt;
  &lt;/android.support.v7.widget.CardView&gt;
  </code></pre>
  
  <p>What I try to achieve is to click on label <code>TextView</code> and hide the other content from the card (let visible only its label component). Again, by clicking further the label expand the hidden content. I can do that if I set explicitly the height of the card i.e. <code>400dp</code>, BUT that way if the containing <code>RecyclerView</code> used as list have not many items (which all are the same height and fixed) there is much empty space. How can the card wrap its content? (When I use <code>wrap_content</code> in <code>layout_height</code> of the card only the label is visible).</p>
  
  <p>This is the click handler for the card:</p>
  
  <pre><code>card.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  Resources r = getResources();
  
                  if (toggle) {
                      tableContainer.setVisibility(View.GONE);
                      totalLabel.setVisibility(View.GONE);
  
                      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
  
                      float pxTopMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, getResources().getDimension(R.dimen.card_list_margin_top), r.getDisplayMetrics());
  
                      params.topMargin = (int) pxTopMargin;
  
                      card.setLayoutParams(params);
                      card.setVisibility(View.GONE);
                      card.setVisibility(View.VISIBLE);
                  } else {
                      tableContainer.setVisibility(View.VISIBLE);
                      totalLabel.setVisibility(View.VISIBLE);
  
  
                      float pxHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 400, r.getDisplayMetrics());
  
                      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) pxHeight);
  
                      float pxTopMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, getResources().getDimension(R.dimen.card_list_margin_top), r.getDisplayMetrics());
  
                      params.topMargin = (int) pxTopMargin;
                      params.bottomMargin = (int) pxTopMargin;
  
                      card.setLayoutParams(params);
                      card.setVisibility(View.GONE);
                      card.setVisibility(View.VISIBLE);
                  }
  
                  toggle = !toggle;
              }
          });
  </code></pre>
  
  <h2>SOLUTION</h2>
  
  <p>ok the problem actually was to create list using <code>RecyclerView</code> that has the property:<br/></p>
  
  <p>A specific number of rows to be visible (i.e. 15) and if lower then wrap its content</p>
  
  <p>I found from <a href="https://stackoverflow.com/questions/26649406/nested-recycler-view-height-doesnt-wrap-its-content">other SO thread</a> (comment by <strong>se.solovyev</strong>) a custom linear layout and edited a little. The result was satisfying (although I don't actually know if did something really bad in there, but works).</p>
  
  <pre><code>package com.trafficbroker.trafficbroker.views;
  
  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.
   * &lt;p/&gt;
   * 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 WrappingLinearLayoutManager 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 WrappingLinearLayoutManager(Context context) {
      super(context);
      this.view = null;
  }
  
  @SuppressWarnings("UnusedDeclaration")
  public WrappingLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
      super(context, orientation, reverseLayout);
      this.view = null;
  }
  
  @SuppressWarnings("UnusedDeclaration")
  public WrappingLinearLayoutManager(RecyclerView view) {
      super(view.getContext());
      this.view = view;
      this.overScrollMode = ViewCompat.getOverScrollMode(view);
  }
  
  @SuppressWarnings("UnusedDeclaration")
  public WrappingLinearLayoutManager(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 &lt; ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode &gt; 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 &amp;&amp; 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() &lt; 15 ? getItemCount() : 15);//getItemCount();   // MAX VISIBLE ROWS
      // 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 &lt; adapterItemCount; i++) {
          if (vertical) {
              if (!hasChildSize) {
                  if (i &lt; 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 &amp;&amp; height &gt;= heightSize) {
                  break;
              }
          } else {
              if (!hasChildSize) {
                  if (i &lt; 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 &amp;&amp; width &gt;= 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 &amp;&amp; overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
          final boolean fit = (vertical &amp;&amp; (!hasHeightSize || height &lt; heightSize))
                  || (!vertical &amp;&amp; (!hasWidthSize || width &lt; 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");
      }
  }
  }
  </code></pre>