public class SlidingLayout extends ViewGroup implements GestureDetector.OnGestureListener { private static final int HORIZONTAL_SCROLL_TRIGGER_DISTANCE = 150; private static final int VERTICAL_SCROLL_TRIGGER_DISTANCE = 60; private View contentView; private View menuView; private boolean isMenuOpened; private boolean isScrolling; private Scroller scroller; private int distance; private GestureDetector gestureDetector; private int screenWidth; private int screenHeight; private int duration; private boolean isHorizontalScroll; private float touchDownX; private float touchDownY; public SlidingLayout(Context context) { super(context); init(context); } public SlidingLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public SlidingLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { scroller = new Scroller(context); gestureDetector = new GestureDetector(context, this); isMenuOpened = false; isScrolling = false; duration = 300; distance = 700; } public void setDistance(int distance) { this.distance = distance; } public void setScreenWidth(int screenWidth) { this.screenWidth = screenWidth; } public void setScreenHeight(int screenHeight) { this.screenHeight = screenHeight; } @Override public void computeScroll() { if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); } super.computeScroll(); } /** * When the scroll distance more than HORIZONTAL_SCROLL_TRIGGER_DISTANCE in * the horizontal direction, the menu view start to scroll. */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { Logger.i("sliding layout", "dispatchTouchEvent"); if (ev.getAction() == MotionEvent.ACTION_DOWN) { touchDownX = ev.getX(); touchDownY = ev.getY(); } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { if (Math.abs(touchDownX - ev.getX()) > HORIZONTAL_SCROLL_TRIGGER_DISTANCE && Math.abs(touchDownY - ev.getY()) <= VERTICAL_SCROLL_TRIGGER_DISTANCE) { isHorizontalScroll = true; } } else if (ev.getAction() == MotionEvent.ACTION_UP && isScrolling) { slideMenu(); isHorizontalScroll = false; } gestureDetector.onTouchEvent(ev); return super.dispatchTouchEvent(ev); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) { Logger.i("sliding layout", "onLayout"); contentView = getChildAt(0); menuView = getChildAt(1); // Set content view full screen. contentView.measure(0, 0); contentView.layout(0, 0, screenWidth, screenHeight); } } void closeMenu() { isMenuOpened = false; scroller.startScroll(getScrollX(), 0, -distance, 0, duration); invalidate(); } /** * Check whether the menu view is opened. * * @return true if opened, or false. */ public boolean isOpen() { return isMenuOpened; } private void slideMenu() { if (getScrollX() < distance / 2) { scroller.startScroll(getScrollX(), 0, -getScrollX(), 0, duration); isMenuOpened = false; } else if (getScrollX() > distance / 2) { scroller.startScroll(getScrollX(), 0, distance - getScrollX(), 0, duration); isMenuOpened = true; menuView.setClickable(true); } invalidate(); } /** * Init menu view. */ @Override public boolean onDown(MotionEvent e) { menuView.measure(0, 0); menuView.layout(screenWidth, 0, screenWidth + distance, screenHeight); return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Logger.i("sliding layout", "onScroll: " + getScrollX() + "," + distanceX + "," + isMenuOpened); if (((getScrollX() > 0 && getScrollX() < distance) || (getScrollX() == 0 && distanceX >= 0) || (getScrollX() >= distance && distanceX < 0)) && isHorizontalScroll) { isScrolling = true; scrollBy((int) distanceX, 0); } else if (getScrollX() > distance && isMenuOpened && distanceX > 0) { isScrolling = false; } return true; } @Override public void onLongPress(MotionEvent e) { } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } }