Jignesh Ansodariya Jignesh Ansodariya - 8 days ago 7
Android Question

How to create slide layout animation in Android?

enter image description here

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!

Answer

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
}
Comments