Hi, I want to create a slide animation in My Activity like shown in Images.
when I click More
button the Orange layout should come up like slider over the white lay out and when I click Less
button it should go down like slider.
please suggest me any sample code or any tutorial for best way to create such an Approach.
Thanks!
Use this Panel class
this will fulfil your requirement and you can also handle the event.
Reference : http://code.google.com/p/android-misc-widgets/
Panel.java
public class Panel extends LinearLayout {
/**
* Callback invoked when the panel is opened/closed.
*/
public static interface OnPanelListener {
/**
* Invoked when the panel becomes fully closed.
*/
public void onPanelClosed(Panel panel);
/**
* Invoked when the panel becomes fully opened.
*/
public void onPanelOpened(Panel panel);
}
private boolean mIsShrinking;
private int mPosition;
private int mDuration;
private boolean mLinearFlying;
private int mHandleId;
private int mContentId;
private View mHandle;
private View mContent;
private Drawable mOpenedHandle;
private Drawable mClosedHandle;
private float mTrackX;
private float mTrackY;
private float mVelocity;
private OnPanelListener panelListener;
public static final int TOP = 0;
public static final int BOTTOM = 1;
public static final int LEFT = 2;
public static final int RIGHT = 3;
private enum State {
ABOUT_TO_ANIMATE,
ANIMATING,
READY,
TRACKING,
FLYING,
};
private State mState;
private Interpolator mInterpolator;
private GestureDetector mGestureDetector;
private int mContentHeight;
private int mContentWidth;
private int mOrientation;
private float mWeight;
private PanelOnGestureListener mGestureListener;
private boolean mBringToFront;
public Panel(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Panel);
mDuration = a.getInteger(R.styleable.Panel_animationDuration, 750); // duration defaults to 750 ms
mPosition = a.getInteger(R.styleable.Panel_position, BOTTOM); // position defaults to BOTTOM
mLinearFlying = a.getBoolean(R.styleable.Panel_linearFlying, false); // linearFlying defaults to false
mWeight = a.getFraction(R.styleable.Panel_weight, 0, 1, 0.0f); // weight defaults to 0.0
if (mWeight < 0 || mWeight > 1) {
mWeight = 0.0f;
//Log.w(TAG, a.getPositionDescription() + ": weight must be > 0 and <= 1");
}
mOpenedHandle = a.getDrawable(R.styleable.Panel_openedHandle);
mClosedHandle = a.getDrawable(R.styleable.Panel_closedHandle);
RuntimeException e = null;
mHandleId = a.getResourceId(R.styleable.Panel_handle, 0);
if (mHandleId == 0) {
e = new IllegalArgumentException(a.getPositionDescription() +
": The handle attribute is required and must refer to a valid child.");
}
mContentId = a.getResourceId(R.styleable.Panel_content, 0);
if (mContentId == 0) {
e = new IllegalArgumentException(a.getPositionDescription() +
": The content attribute is required and must refer to a valid child.");
}
a.recycle();
if (e != null) {
throw e;
}
mOrientation = (mPosition == TOP || mPosition == BOTTOM)? VERTICAL : HORIZONTAL;
setOrientation(mOrientation);
mState = State.READY;
mGestureListener = new PanelOnGestureListener();
mGestureDetector = new GestureDetector(mGestureListener);
mGestureDetector.setIsLongpressEnabled(false);
// i DON'T really know why i need this...
setBaselineAligned(false);
}
/**
* Sets the listener that receives a notification when the panel becomes open/close.
*
* @param onPanelListener The listener to be notified when the panel is opened/closed.
*/
public void setOnPanelListener(OnPanelListener onPanelListener) {
panelListener = onPanelListener;
}
/**
* Gets Panel's mHandle
*
* @return Panel's mHandle
*/
public View getHandle() {
return mHandle;
}
/**
* Gets Panel's mContent
*
* @return Panel's mContent
*/
public View getContent() {
return mContent;
}
/**
* Sets the acceleration curve for panel's animation.
*
* @param i The interpolator which defines the acceleration curve
*/
public void setInterpolator(Interpolator i) {
mInterpolator = i;
}
/**
* Set the opened state of Panel.
*
* @param open True if Panel is to be opened, false if Panel is to be closed.
* @param animate True if use animation, false otherwise.
*
* @return True if operation was performed, false otherwise.
*
*/
public boolean setOpen(boolean open, boolean animate) {
if (mState == State.READY && isOpen() ^ open) {
mIsShrinking = !open;
if (animate) {
mState = State.ABOUT_TO_ANIMATE;
if (!mIsShrinking) {
// this could make flicker so we test mState in dispatchDraw()
// to see if is equal to ABOUT_TO_ANIMATE
mContent.setVisibility(VISIBLE);
}
post(startAnimation);
} else {
mContent.setVisibility(open? VISIBLE : GONE);
postProcess();
}
return true;
}
return false;
}
/**
* Returns the opened status for Panel.
*
* @return True if Panel is opened, false otherwise.
*/
public boolean isOpen() {
return mContent.getVisibility() == VISIBLE;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mHandle = findViewById(mHandleId);
if (mHandle == null) {
String name = getResources().getResourceEntryName(mHandleId);
throw new RuntimeException("Your Panel must have a child View whose id attribute is 'R.id." + name + "'");
}
mHandle.setOnTouchListener(touchListener);
mHandle.setOnClickListener(clickListener);
mContent = findViewById(mContentId);
if (mContent == null) {
String name = getResources().getResourceEntryName(mHandleId);
throw new RuntimeException("Your Panel must have a child View whose id attribute is 'R.id." + name + "'");
}
// reposition children
removeView(mHandle);
removeView(mContent);
if (mPosition == TOP || mPosition == LEFT) {
addView(mContent);
addView(mHandle);
} else {
addView(mHandle);
addView(mContent);
}
if (mClosedHandle != null) {
mHandle.setBackgroundDrawable(mClosedHandle);
}
mContent.setClickable(true);
mContent.setVisibility(GONE);
if (mWeight > 0) {
ViewGroup.LayoutParams params = mContent.getLayoutParams();
if (mOrientation == VERTICAL) {
params.height = ViewGroup.LayoutParams.FILL_PARENT;
} else {
params.width = ViewGroup.LayoutParams.FILL_PARENT;
}
mContent.setLayoutParams(params);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ViewParent parent = getParent();
if (parent != null && parent instanceof FrameLayout) {
mBringToFront = true;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mWeight > 0 && mContent.getVisibility() == VISIBLE) {
View parent = (View) getParent();
if (parent != null) {
if (mOrientation == VERTICAL) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (parent.getHeight() * mWeight), MeasureSpec.EXACTLY);
} else {
widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (parent.getWidth() * mWeight), MeasureSpec.EXACTLY);
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mContentWidth = mContent.getWidth();
mContentHeight = mContent.getHeight();
}
@Override
protected void dispatchDraw(Canvas canvas) {
// String name = getResources().getResourceEntryName(getId());
// //Log.d(TAG, name + " ispatchDraw " + mState);
// this is why 'mState' was added:
// avoid flicker before animation start
if (mState == State.ABOUT_TO_ANIMATE && !mIsShrinking) {
int delta = mOrientation == VERTICAL? mContentHeight : mContentWidth;
if (mPosition == LEFT || mPosition == TOP) {
delta = -delta;
}
if (mOrientation == VERTICAL) {
canvas.translate(0, delta);
} else {
canvas.translate(delta, 0);
}
}
if (mState == State.TRACKING || mState == State.FLYING) {
canvas.translate(mTrackX, mTrackY);
}
super.dispatchDraw(canvas);
}
private float ensureRange(float v, int min, int max) {
v = Math.max(v, min);
v = Math.min(v, max);
return v;
}
OnTouchListener touchListener = new OnTouchListener() {
int initX;
int initY;
boolean setInitialPosition;
public boolean onTouch(View v, MotionEvent event) {
if (mState == State.ANIMATING) {
// we are animating
return false;
}
// //Log.d(TAG, "state: " + mState + " x: " + event.getX() + " y: " + event.getY());
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
if (mBringToFront) {
bringToFront();
}
initX = 0;
initY = 0;
if (mContent.getVisibility() == GONE) {
// since we may not know content dimensions we use factors here
if (mOrientation == VERTICAL) {
initY = mPosition == TOP? -1 : 1;
} else {
initX = mPosition == LEFT? -1 : 1;
}
}
setInitialPosition = true;
} else {
if (setInitialPosition) {
// now we know content dimensions, so we multiply factors...
initX *= mContentWidth;
initY *= mContentHeight;
// ... and set initial panel's position
mGestureListener.setScroll(initX, initY);
setInitialPosition = false;
// for offsetLocation we have to invert values
initX = -initX;
initY = -initY;
}
// offset every ACTION_MOVE & ACTION_UP event
event.offsetLocation(initX, initY);
}
if (!mGestureDetector.onTouchEvent(event)) {
if (action == MotionEvent.ACTION_UP) {
// tup up after scrolling
post(startAnimation);
}
}
return false;
}
};
OnClickListener clickListener = new OnClickListener() {
public void onClick(View v) {
if (mBringToFront) {
bringToFront();
}
if (initChange()) {
post(startAnimation);
}
}
};
public boolean initChange() {
if (mState != State.READY) {
// we are animating or just about to animate
return false;
}
mState = State.ABOUT_TO_ANIMATE;
mIsShrinking = mContent.getVisibility() == VISIBLE;
if (!mIsShrinking) {
// this could make flicker so we test mState in dispatchDraw()
// to see if is equal to ABOUT_TO_ANIMATE
mContent.setVisibility(VISIBLE);
}
return true;
}
Runnable startAnimation = new Runnable() {
public void run() {
callPanListener();
// this is why we post this Runnable couple of lines above:
// now its save to use mContent.getHeight() && mContent.getWidth()
TranslateAnimation animation;
int fromXDelta = 0, toXDelta = 0, fromYDelta = 0, toYDelta = 0;
if (mState == State.FLYING) {
mIsShrinking = (mPosition == TOP || mPosition == LEFT) ^ (mVelocity > 0);
}
int calculatedDuration;
if (mOrientation == VERTICAL) {
int height = mContentHeight;
if (!mIsShrinking) {
fromYDelta = mPosition == TOP? -height : height;
} else {
toYDelta = mPosition == TOP? -height : height;
}
if (mState == State.TRACKING) {
if (Math.abs(mTrackY - fromYDelta) < Math.abs(mTrackY - toYDelta)) {
mIsShrinking = !mIsShrinking;
toYDelta = fromYDelta;
}
fromYDelta = (int) mTrackY;
} else
if (mState == State.FLYING) {
fromYDelta = (int) mTrackY;
}
// for FLYING events we calculate animation duration based on flying velocity
// also for very high velocity make sure duration >= 20 ms
if (mState == State.FLYING && mLinearFlying) {
calculatedDuration = (int) (1000 * Math.abs((toYDelta - fromYDelta) / mVelocity));
calculatedDuration = Math.max(calculatedDuration, 20);
} else {
calculatedDuration = mDuration * Math.abs(toYDelta - fromYDelta) / mContentHeight;
}
} else {
int width = mContentWidth;
if (!mIsShrinking) {
fromXDelta = mPosition == LEFT? -width : width;
} else {
toXDelta = mPosition == LEFT? -width : width;
}
if (mState == State.TRACKING) {
if (Math.abs(mTrackX - fromXDelta) < Math.abs(mTrackX - toXDelta)) {
mIsShrinking = !mIsShrinking;
toXDelta = fromXDelta;
}
fromXDelta = (int) mTrackX;
} else
if (mState == State.FLYING) {
fromXDelta = (int) mTrackX;
}
// for FLYING events we calculate animation duration based on flying velocity
// also for very high velocity make sure duration >= 20 ms
if (mState == State.FLYING && mLinearFlying) {
calculatedDuration = (int) (1000 * Math.abs((toXDelta - fromXDelta) / mVelocity));
calculatedDuration = Math.max(calculatedDuration, 20);
} else {
calculatedDuration = mDuration * Math.abs(toXDelta - fromXDelta) / mContentWidth;
}
}
mTrackX = mTrackY = 0;
if (calculatedDuration == 0) {
mState = State.READY;
if (mIsShrinking) {
mContent.setVisibility(GONE);
}
postProcess();
return;
}
animation = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
animation.setDuration(calculatedDuration);
if (mState == State.FLYING && mLinearFlying) {
animation.setInterpolator(new LinearInterpolator());
} else
if (mInterpolator != null) {
animation.setInterpolator(mInterpolator);
}
startAnimation(animation);
}
};
@Override
protected void onAnimationEnd() {
super.onAnimationEnd();
mState = State.READY;
if (mIsShrinking) {
mContent.setVisibility(GONE);
}
postProcess();
}
@Override
protected void onAnimationStart() {
super.onAnimationStart();
mState = State.ANIMATING;
}
private void postProcess() {
if (mIsShrinking && mClosedHandle != null) {
mHandle.setBackgroundDrawable(mClosedHandle);
} else
if (!mIsShrinking && mOpenedHandle != null) {
mHandle.setBackgroundDrawable(mOpenedHandle);
}
// invoke listener if any
callPanListener();
}
public void callPanListener()
{
if (panelListener != null) {
if (mIsShrinking) {
panelListener.onPanelClosed(Panel.this);
} else {
panelListener.onPanelOpened(Panel.this);
}
}
}
class PanelOnGestureListener implements OnGestureListener {
float scrollY;
float scrollX;
public void setScroll(int initScrollX, int initScrollY) {
scrollX = initScrollX;
scrollY = initScrollY;
}
public boolean onDown(MotionEvent e) {
scrollX = scrollY = 0;
callPanListener();
initChange();
return true;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mState = State.FLYING;
// velocityX=400;
// velocityY=400;
mVelocity = mOrientation == VERTICAL? velocityY : velocityX;
// mVelocity=400;
post(startAnimation);
return true;
}
public void onLongPress(MotionEvent e) {
// not used
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mState = State.TRACKING;
float tmpY = 0, tmpX = 0;
if (mOrientation == VERTICAL) {
scrollY -= distanceY;
if (mPosition == TOP) {
tmpY = ensureRange(scrollY, -mContentHeight, 0);
} else {
tmpY = ensureRange(scrollY, 0, mContentHeight);
}
} else {
scrollX -= distanceX;
if (mPosition == LEFT) {
tmpX = ensureRange(scrollX, -mContentWidth, 0);
} else {
tmpX = ensureRange(scrollX, 0, mContentWidth);
}
}
if (tmpX != mTrackX || tmpY != mTrackY) {
mTrackX = tmpX;
mTrackY = tmpY;
invalidate();
}
return true;
}
public void onShowPress(MotionEvent e) {
// not used
}
public boolean onSingleTapUp(MotionEvent e) {
// not used
return false;
}
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:panel="http://schemas.android.com/apk/res/packagename"
android:layout_width="fill_parent"
android:layout_height="fill_parent"">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:gravity="center_horizontal" >
<packagename.Panel
android:id="@+id/panel_menu"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
panel:animationDuration="1000"
panel:closedHandle="#0000FF"
panel:content="@+id/panelContent"
panel:handle="@+id/panelHandle"
panel:linearFlying="false"
panel:openedHandle="#0000FF"
android:paddingTop="4dip"
panel:position="bottom" >
<Button
android:id="@+id/panelHandle"
android:layout_width="33dp"
android:layout_height="33dp"
android:layout_gravity="center_horizontal"
android:text="^"
android:textColor="@android:color/white" />
<RelativeLayout
android:id="@+id/panelContent"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:background="#0000FF" >
</RelativeLayout>
</packagename.Panel>
</RelativeLayout>
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Panel">
<!-- Defines panel animation duration in ms. -->
<attr name="animationDuration" format="integer" />
<!-- Defines panel position on the screen. -->
<attr name="position">
<!-- Panel placed at top of the screen. -->
<enum name="top" value="0" />
<!-- Panel placed at bottom of the screen. -->
<enum name="bottom" value="1" />
<!-- Panel placed at left of the screen. -->
<enum name="left" value="2" />
<!-- Panel placed at right of the screen. -->
<enum name="right" value="3" />
</attr>
<!-- Identifier for the child that represents the panel's handle. -->
<attr name="handle" format="reference" />
<!-- Identifier for the child that represents the panel's content. -->
<attr name="content" format="reference" />
<!-- Defines if flying gesture forces linear interpolator in animation. -->
<attr name="linearFlying" format="boolean" />
<!-- Defines size relative to parent (must be in form: nn%p). -->
<attr name="weight" format="fraction" />
<!-- Defines opened handle (drawable/color). -->
<attr name="openedHandle" format="reference|color" />
<!-- Defines closed handle (drawable/color). -->
<attr name="closedHandle" format="reference|color" />
</declare-styleable>
</resources>
To play with this in your Activity you have
SampleActivity.java
public class SampleActivity extends Activity implements OnPanelListener {
public Panel samplePanel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.panel_main)_layout;
samplePanel = (Panel) findViewById(R.id.panel_id);
samplePanel.setOnPanelListener(this);
samplePanel.setInterpolator(new ExpoInterpolator(Type.OUT));
}
public void onPanelClosed(Panel panel) {}//Interface Listener
public void onPanelOpened(Panel panel) {}//Interface Listener
}