Home > android, ui > Simple animated panels

Simple animated panels

April 26, 2011

A common UI element in Android applications is an animated panel that provides access to certain actions. A panel like this should only be shown temporarily, perhaps triggered by other UI state changes initiated by the user.

You can see an example of this in GMail application: when you select one or more messages, a panel slides up from the bottom with buttons to act on those selected messages.

While using a PopupWindow is cetainly an option for this, it can be quite convenent to define the panel as a view group within the activity’s layout.

With a unified layout, the activity can obtain a reference to the panel’s view group with findViewById, and change its visibility state with View.setVisibility, either to View.VISIBLE or View.GONE.

Just changing the panel’s visibility isn’t by itself animated, especially when going from VISBLE to GONE: view animations are only shown if the view is visible. When hiding the panel, it’s necessary to first run the “slide out” animation, and only then set the visibility to GONE.

So – here is a simple class that makes it very easy to implement animated panels. It is constructed with a reference to the panel’s parent ViewGroup, and the type of animation (from right / from bottom), and has methods to animate the panel to slide in / out.

Here is a simple example of how this might look:

The class keeps track of the panel’s state, including currently running animations. When requested to hide the panel, it runs a “hide” animation first, and when the animation completes, but not sooner, changes the panel’s visibility to View.GONE.

public class PanelAnimation {

	public static final int WHERE_RIGHT = 0;
	public static final int WHERE_BOTTOM = 1;

	public PanelAnimation(ViewGroup viewGroup, int where) {
		mViewGroup = viewGroup;
		mContext = viewGroup.getContext();
		mWhere = where;
		mState = STATE_HIDDEN;
	}

	public boolean isShownOrShowing() {
		return mState == STATE_SHOWING || mState == STATE_SHOWN;
	}

	public void toggle() {
		if (mState == STATE_SHOWN) {
			hide();
		} else if (mState == STATE_HIDDEN) {
			show();
		}
	}

	public void showNow() {
		mState = STATE_SHOWN;
		mViewGroup.setVisibility(View.VISIBLE);
	}

	public void show() {
		if (mState != STATE_SHOWN) {
			mState = STATE_SHOWING;

			switch (mWhere) {
			default:
			case WHERE_RIGHT:
				mShowAnimation = AnimationUtils.makeInAnimation(mContext, false);
				break;
			case WHERE_BOTTOM:
				mShowAnimation = AnimationUtils.loadAnimation(mContext, R.anim.slide_in_up);
				break;
			}

			mShowAnimation.setAnimationListener(new AnimationListener() {
				@Override
				public void onAnimationStart(Animation animation) {
				}

				@Override
				public void onAnimationRepeat(Animation animation) {
				}

				@Override
				public void onAnimationEnd(Animation animation) {
					mState = STATE_SHOWN;
				}
			});
			mViewGroup.startAnimation(mShowAnimation);
			mViewGroup.setVisibility(View.VISIBLE);
		}
	}

	public void hide() {
		if (mState != STATE_HIDDEN) {
			mState = STATE_HIDING;

			switch (mWhere) {
			default:
			case WHERE_RIGHT:
				mHideAnimation = AnimationUtils.makeOutAnimation(mContext, true);
				break;
			case WHERE_BOTTOM:
				mHideAnimation = AnimationUtils.loadAnimation(mContext, R.anim.slide_out_down);
				break;
			}

			mHideAnimation.setAnimationListener(new AnimationListener() {
				@Override
				public void onAnimationStart(Animation animation) {
				}

				@Override
				public void onAnimationRepeat(Animation animation) {
				}

				@Override
				public void onAnimationEnd(Animation animation) {
					mState = STATE_HIDDEN;
					mViewGroup.setVisibility(View.GONE);
				}
			});
			mViewGroup.startAnimation(mHideAnimation);
		}
	}

	/**
	 * Attachment panel animation
	 */
	private static final int STATE_HIDDEN = 0;
	private static final int STATE_SHOWING = 1;
	private static final int STATE_SHOWN = 2;
	private static final int STATE_HIDING = 3;

	private int mState = STATE_HIDDEN;

	private ViewGroup mViewGroup;
	private Context mContext;
	private int mWhere;

	private Animation mShowAnimation;
	private Animation mHideAnimation;
}

This class uses two animations defined as resources, used when the panel is positioned at the bottom.

<?xml version="1.0" encoding="utf-8"?>
<!-- res/anim/slide_in_up.xml -->
<translate
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:interpolator="@android:anim/accelerate_interpolator"
	android:fromYDelta="100%"
	android:toYDelta="0%"
	android:duration="250" />
<?xml version="1.0" encoding="utf-8"?>
<!-- res/anim/slide_out_down.xml -->
<translate
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:interpolator="@android:anim/accelerate_interpolator"
	android:fromYDelta="0%"
	android:toYDelta="100%"
	android:duration="250" />

When the panel is on the right, this class uses standard Android animations (also used for activity animations).

To use this class, first add panel views to your activity’s layout .xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">

	<!-- Normal content -->

	<ListView
		android:id="@+id/message_list"
		android:layout_width="fill_parent"
		android:layout_height="0dp"
		android:layout_weight="1" />

	<!-- Button panel -->

	<LinearLayout
		style="@style/ButtonPanel"
		android:id="@+id/message_list_ops_panel"
		android:visibility="gone">

		<TextView
			style="@style/ButtonLikeText"
			android:id="@+id/message_list_op_delete"
			android:text="@string/message_list_op_delete" />

		<TextView
			style="@style/ButtonLikeText"
			android:id="@+id/message_list_op_as_read"
			android:layout_marginLeft="4dp"
			android:text="@string/message_list_op_as_read" />

		<TextView
			style="@style/ButtonLikeText"
			android:id="@+id/message_list_op_as_unread"
			android:layout_marginLeft="4dp"
			android:text="@string/message_list_op_as_unread" />

	</LinearLayout>

</LinearLayout>

Now in Java, get a refrence to the ButtonPanel ViewGroup and construct PanelAnimation for it:

class SomeActivity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		setContentView(R.layout.some_layout_here);

		mMessageOpsPanel = (ViewGroup) findViewById(R.id.message_list_ops_panel);
		mMessageOpsPanelAnimation = new PanelAnimation(mMessageOpsPanel,
				PanelAnimation.WHERE_BOTTOM);

	}

	private ViewGroup mMessageOpsPanel;
	private PanelAnimation mMessageOpsPanelAnimation;
}

Finally, showing and hiding the panel can be done like this:

	private void onMessageOpsChange(boolean visible) {
		if (visible) {
			mMessageOpsPanelAnimation.show();
		} else {
			mMessageOpsPanelAnimation.hide();
		}
	}

And that’s really it.

Advertisements
Categories: android, ui