title: How to implement expandable panels in Android?
body: <p>Is there an easy way to create expandable/collapsible blocks like seen in official market app?</p>
  
  <p>Screenshot of Market app, when you click on "More" button, the description section expands with animation:</p>
  
  <p><img src="https://i.stack.imgur.com/0hqno.jpg" alt="enter image description here"></p>
  
  <p>I know of <a href="http://developer.android.com/reference/android/widget/SlidingDrawer.html" rel="noreferrer">SlidingDrawer</a> but it doesn't seem to be suited for stuff like this--it's supposed to be put in overlay, and doesn't support half-open states.</p>
  
  <p><strong>Update:</strong></p>
  
  <p>Here's my half-working solution. It's a custom widget that extends <code>LinearLayout</code>. It kind-of works, but doesn't handle edge cases well, like content height smaller than <code>collapsedHeight</code> parameter. I'm sure with enough staring, digging in code and experimenting the quirks could be fixed. Was hoping to avoid doing that, and save some time by using a ready-made official or 3rd party solution. Anyway, here it is, code:</p>
  
  <pre><code>package com.example.androidapp.widgets;
  
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.util.AttributeSet;
  import android.view.View;
  import android.view.animation.Animation;
  import android.view.animation.Transformation;
  import android.widget.LinearLayout;
  
  import com.example.androidapp.R;
  
  public class ExpandablePanel extends LinearLayout {
  
      private final int mHandleId;
      private final int mContentId;
  
      private View mHandle;
      private View mContent;
  
      private boolean mExpanded = true;
      private int mCollapsedHeight = 0;
      private int mContentHeight = 0;
  
      public ExpandablePanel(Context context) {
          this(context, null);
      }
  
      public ExpandablePanel(Context context, AttributeSet attrs) {
          super(context, attrs);
  
          TypedArray a = context.obtainStyledAttributes(attrs,
              R.styleable.ExpandablePanel, 0, 0);
  
          // How high the content should be in "collapsed" state
          mCollapsedHeight = (int) a.getDimension(
              R.styleable.ExpandablePanel_collapsedHeight, 0.0f);
  
          int handleId = a.getResourceId(R.styleable.ExpandablePanel_handle, 0);
          if (handleId == 0) {
              throw new IllegalArgumentException(
                  "The handle attribute is required and must refer "
                      + "to a valid child.");
          }
  
          int contentId = a.getResourceId(R.styleable.ExpandablePanel_content, 0);
          if (contentId == 0) {
              throw new IllegalArgumentException(
                  "The content attribute is required and must refer "
                      + "to a valid child.");
          }
  
          mHandleId = handleId;
          mContentId = contentId;
  
          a.recycle();
      }
  
      @Override
      protected void onFinishInflate() {
          super.onFinishInflate();
  
          mHandle = findViewById(mHandleId);
          if (mHandle == null) {
              throw new IllegalArgumentException(
                  "The handle attribute is must refer to an"
                      + " existing child.");
          }
  
          mContent = findViewById(mContentId);
          if (mContent == null) {
              throw new IllegalArgumentException(
                  "The content attribute is must refer to an"
                      + " existing child.");
          }
  
          mHandle.setOnClickListener(new PanelToggler());
      }
  
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          if (mContentHeight == 0) {
              // First, measure how high content wants to be
              mContent.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
              mContentHeight = mContent.getMeasuredHeight();
          }
  
          // Then let the usual thing happen
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      }
  
      private class PanelToggler implements OnClickListener {
          public void onClick(View v) {
              Animation a;
              if (mExpanded) {
                  a = new ExpandAnimation(mContentHeight, mCollapsedHeight);
              } else {
                  a = new ExpandAnimation(mCollapsedHeight, mContentHeight);
              }
              a.setDuration(500);
              mContent.startAnimation(a);
              mExpanded = !mExpanded;
          }
      }
  
      private class ExpandAnimation extends Animation {
          private final int mStartHeight;
          private final int mDeltaHeight;
  
          public ExpandAnimation(int startHeight, int endHeight) {
              mStartHeight = startHeight;
              mDeltaHeight = endHeight - startHeight;
          }
  
          @Override
          protected void applyTransformation(float interpolatedTime,
              Transformation t) {
              android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
              lp.height = (int) (mStartHeight + mDeltaHeight * interpolatedTime);
              mContent.setLayoutParams(lp);
          }
  
          @Override
          public boolean willChangeBounds() {
              // TODO Auto-generated method stub
              return true;
          }
      }
  }
  </code></pre>
  
  <p>Here's <code>res/values/attrs.xml</code>:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;resources&gt;
    &lt;declare-styleable name="ExpandablePanel"&gt;
      &lt;attr name="handle" format="reference" /&gt;
      &lt;attr name="content" format="reference" /&gt;
      &lt;attr name="collapsedHeight" format="dimension" /&gt;
    &lt;/declare-styleable&gt;
  &lt;/resources&gt;
  </code></pre>
  
  <p>And here's how I use it in layout:</p>
  
  <pre><code>&lt;com.example.androidapp.widgets.ExpandablePanel
      android:orientation="vertical"
      android:layout_height="wrap_content"
      android:layout_width="fill_parent"
      example:handle="@+id/expand"
      example:content="@+id/value"
      example:collapsedHeight="50dip"&gt;
      &lt;TextView
          android:id="@id/value"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:maxHeight="50dip"
          /&gt;
      &lt;Button
          android:id="@id/expand"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="More" /&gt;
  &lt;/com.example.androidapp.widgets.ExpandablePanel&gt;
  </code></pre>