Skip to content

Instantly share code, notes, and snippets.

@atermenji
Created November 8, 2012 11:06
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save atermenji/4038192 to your computer and use it in GitHub Desktop.
Save atermenji/4038192 to your computer and use it in GitHub Desktop.
A layout that expands/collapses its content by pressing on some view
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpandablePanel">
<attr name="handle" format="reference" />
<attr name="content" format="reference" />
<attr name="contentContainer" format="reference" />
<attr name="animationDuration" format="integer"/>
</declare-styleable>
</resources>
package some.awesome.package;
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.Animation.AnimationListener;
import android.view.animation.Transformation;
import android.widget.RelativeLayout;
public class ExpandablePanel extends RelativeLayout {
private static final int DEFAULT_ANIM_DURATION = 500;
private final int mHandleId;
private final int mContentContainerId;
private final int mContentId;
private View mHandle;
private View mContentContainer;
private View mContent;
private boolean mExpanded = false;
private boolean mFirstOpen = true;
private int mCollapsedHeight;
private int mContentHeight;
private int mContentWidth;
private int mAnimationDuration = 0;
private OnExpandListener mListener;
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);
mAnimationDuration = a.getInteger(R.styleable.ExpandablePanel_animationDuration, DEFAULT_ANIM_DURATION);
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 contentContainerId = a.getResourceId(R.styleable.ExpandablePanel_contentContainer, 0);
if (contentContainerId == 0) {
throw new IllegalArgumentException("The content 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;
mContentContainerId = contentContainerId;
mContentId = contentId;
a.recycle();
}
public void setOnExpandListener(OnExpandListener listener) {
mListener = listener;
}
public void setAnimationDuration(int animationDuration) {
mAnimationDuration = animationDuration;
}
@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.");
}
mContentContainer = findViewById(mContentContainerId);
if (mContentContainer == null) {
throw new IllegalArgumentException("The content container attribute must refer to an existing child.");
}
mContent = findViewById(mContentId);
if (mContentContainer == null) {
throw new IllegalArgumentException("The content attribute must refer to an existing child.");
}
mContent.setVisibility(View.INVISIBLE);
mHandle.setOnClickListener(new PanelToggler());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
mHandle.measure(MeasureSpec.UNSPECIFIED, heightMeasureSpec);
mCollapsedHeight = mHandle.getMeasuredHeight();
mContentWidth = mContentContainer.getMeasuredWidth();
mContentHeight = mContentContainer.getMeasuredHeight();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mFirstOpen) {
mContentContainer.getLayoutParams().width = 0;
mContentContainer.getLayoutParams().height = mCollapsedHeight;
mFirstOpen = false;
}
int width = mHandle.getMeasuredWidth()
+ mContentContainer.getMeasuredWidth()
+ mContentContainer.getPaddingRight();
int height = mContentContainer.getMeasuredHeight() + mContentContainer.getPaddingBottom();
setMeasuredDimension(width, height);
}
private class PanelToggler implements OnClickListener {
@Override
public void onClick(View v) {
Animation animation;
if (mExpanded) {
mContent.setVisibility(View.INVISIBLE);
animation = new ExpandAnimation(mContentWidth, 0, mContentHeight, mCollapsedHeight);
if (mListener != null) {
mListener.onCollapse(mHandle, mContentContainer);
}
} else {
ExpandablePanel.this.invalidate();
animation = new ExpandAnimation(0, mContentWidth, mCollapsedHeight, mContentHeight);
if (mListener != null) {
mListener.onExpand(mHandle, mContentContainer);
}
}
animation.setDuration(mAnimationDuration);
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mExpanded = !mExpanded;
if (mExpanded) {
mContent.setVisibility(View.VISIBLE);
}
}
});
mContentContainer.startAnimation(animation);
}
}
private class ExpandAnimation extends Animation {
private final int mStartWidth;
private final int mDeltaWidth;
private final int mStartHeight;
private final int mDeltaHeight;
public ExpandAnimation(int startWidth, int endWidth, int startHeight, int endHeight) {
mStartWidth = startWidth;
mDeltaWidth = endWidth - startWidth;
mStartHeight = startHeight;
mDeltaHeight = endHeight - startHeight;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
android.view.ViewGroup.LayoutParams lp = mContentContainer.getLayoutParams();
lp.width = (int) (mStartWidth + mDeltaWidth * interpolatedTime);
lp.height = (int) (mStartHeight + mDeltaHeight * interpolatedTime);
mContentContainer.setLayoutParams(lp);
}
@Override
public boolean willChangeBounds() {
return true;
}
}
public interface OnExpandListener {
public void onExpand(View handle, View content);
public void onCollapse(View handle, View content);
}
}
<?xml version="1.0" encoding="utf-8"?>
<some.awesome.package.ExpandablePanel
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:panel="http://schemas.android.com/apk/res/com.digitalfootsteps.android"
android:id="@+id/expandable_panel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp"
android:background="@drawable/some_awesome_background"
panel:content="@+id/value"
panel:contentContainer="@+id/content_container"
panel:handle="@+id/expand" >
<FrameLayout
android:id="@id/content_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/expand"
android:paddingBottom="5dp"
android:paddingRight="15dp" >
<some.awesome.package.SomeAwesomeContentView
android:id="@id/value"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<Button
android:id="@id/expand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true" />
</some.awesome.package.ExpandablePanel>
@AlexDenisov
Copy link

Awesome, thank you very much!!1

@ingyesid
Copy link

ingyesid commented Apr 3, 2013

i need expand de view from bottom to top , please help me !

@JeppeLeth
Copy link

I cannot get this to work. Can you make small a sample app?

@ahmadfarrokh
Copy link

how can i use this code in my app? do you have any sample app that use this code?

@ElQuintoViento
Copy link

mContentContainer = findViewById(mContentContainerId);
if (mContentContainer == null) {
throw new IllegalArgumentException("The content container attribute must refer to an existing child.");
}

	mContent = findViewById(mContentId);
	**if (mContentContainer == null) {**
		throw new IllegalArgumentException("The content attribute must refer to an existing child.");
	}

Assuming the second mContentContainer should be mContent

@mustafabayram
Copy link

It's not useful to see instantly how code is work 👎 where the fuck is SomeAwesomeContentView class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment