/* * Copyright (C) 2013 Masahiko Adachi(http://www.adamrocker.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.navdrawer; import android.app.Activity; import android.content.Context; import android.graphics.Rect; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.Scroller; /** * <p>This class enables to add a NavDrawer simply.</p> * <p>How to use:</p> * <p>After calling setContentView method in onCreate method, call 2 methods bellow.</p> * <ul> * <li>- New SimpleNavDrawer instance</li> * <li>- Set a layout file which will be set in the NavDrawer.</li> * </ul> * <pre class="prettyprint"> * public void onCreate(Bundle data) { * super.onCreate(data); * setContentView(R.layout.main); * SimpleNavDrawer nav = new SimpleNavDrawer(this); * nav.setLeftBehindContentView(R.layout.manu); * } * </pre> * @author Masahiko Adachi */ public class SimpleSideDrawer extends FrameLayout { private final Window mWindow; private final ViewGroup mAboveView; private final BehindLinearLayout mBehindView; private final LinearLayout mLeftBehindBase; private final LinearLayout mRightBehindBase; private final View mOverlay; private Scroller mScroller; private View mLeftBehindView;//menu of left-behind will be set private View mRightBehindView;//menu of right-behind will be set private Rect mLeftPaddingRect; private Rect mRightPaddingRect; private int mDurationLeft; private int mDurationRight; private int mLeftBehindViewWidth; private int mRightBehindViewWidth; private abstract class DragAction { private float mLastMotionX = 0f; private boolean mOpening = false; private boolean mDraggable = false; abstract public boolean onTouchEvent(MotionEvent event); } private DragAction mLeftDragAction = new DragAction() { @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction() & MotionEvent.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: { float x = ev.getX(); mLeftDragAction.mLastMotionX = x; mLeftDragAction.mDraggable = mAboveView.getScrollX() != 0; break; } case MotionEvent.ACTION_UP: { if (mLeftDragAction.mDraggable) { int currentX = mAboveView.getScrollX(); int diffX; if (mLeftDragAction.mOpening) { diffX = -(mLeftBehindViewWidth + currentX); } else { diffX = -currentX; } mScroller.startScroll(currentX, 0, diffX, 0, mDurationLeft); invalidate(); } break; } case MotionEvent.ACTION_MOVE: if (!mLeftDragAction.mDraggable) { return false; } float newX = ev.getX(); float diffX = -(newX - mLeftDragAction.mLastMotionX); int x = mAboveView.getScrollX(); mLeftDragAction.mOpening = mLeftDragAction.mLastMotionX < newX; mLeftDragAction.mLastMotionX = newX; float nextX = x + diffX; if (0 < nextX) { mAboveView.scrollTo(0, 0); } else { if (nextX < -mLeftBehindViewWidth) { mAboveView.scrollTo(-mLeftBehindViewWidth, 0); } else { mAboveView.scrollBy((int) diffX, 0); } } break; } return false; } }; private DragAction mRightDragAction = new DragAction() { @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction() & MotionEvent.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: { float x = ev.getX(); mRightDragAction.mLastMotionX = x; mRightDragAction.mDraggable = mAboveView.getScrollX() != 0; break; } case MotionEvent.ACTION_UP: { if (mRightDragAction.mDraggable) { int currentX = mAboveView.getScrollX(); int diffX; if (mRightDragAction.mOpening) { diffX = mRightBehindViewWidth - currentX; } else { diffX = -currentX; } mScroller.startScroll(currentX, 0, diffX, 0, mDurationRight); invalidate(); } break; } case MotionEvent.ACTION_MOVE: if (!mRightDragAction.mDraggable) { return false; } float newX = ev.getX(); float diffX = -(newX - mRightDragAction.mLastMotionX); int x = mAboveView.getScrollX(); mRightDragAction.mOpening = newX < mRightDragAction.mLastMotionX; mRightDragAction.mLastMotionX = newX; float nextX = x + diffX; if (nextX < 0) { mAboveView.scrollTo(0, 0); } else { if (nextX < mRightBehindViewWidth) { mAboveView.scrollBy((int) diffX, 0); } else { mAboveView.scrollTo(mRightBehindViewWidth, 0); } } break; } return false; } }; /** * <p>The default Interpolator of drawer animation is DecelerateInterpolator(9.9).</p> * <p>The default animation duration is 230msec.</p> * @see SimpleSideDrawer#SimpleSideDrawer(Activity, Interpolator, int); * @param act */ public SimpleSideDrawer(Activity act) { this(act, new DecelerateInterpolator(0.9f), 180); } public SimpleSideDrawer(Activity act, Interpolator ip, int duration) { super(act.getApplicationContext()); final Context context = act.getApplicationContext(); mDurationLeft = duration; mDurationRight = duration; mWindow = act.getWindow(); mScroller = new Scroller(context, ip); final int fp = ViewGroup.LayoutParams.MATCH_PARENT; final int wp = ViewGroup.LayoutParams.WRAP_CONTENT; //behind mBehindView = new BehindLinearLayout(context); mBehindView.setLayoutParams(new LinearLayout.LayoutParams(fp, fp)); mBehindView.setOrientation(LinearLayout.HORIZONTAL); //left-behind base mLeftBehindBase = new BehindLinearLayout(context); mBehindView.addView(mLeftBehindBase, new LinearLayout.LayoutParams(wp, fp)); //behind adjusting view mBehindView.addView(new View(context), new LinearLayout.LayoutParams(0, fp, 1)); //right-behind base mRightBehindBase = new BehindLinearLayout(context); mBehindView.addView(mRightBehindBase, new LinearLayout.LayoutParams(wp, fp)); addView(mBehindView); //above mAboveView = new FrameLayout(context); mAboveView.setLayoutParams(new FrameLayout.LayoutParams(fp, fp)); //overlay is used for controlling drag action, slid to close/open. mOverlay = new OverlayView(getContext()); mOverlay.setLayoutParams(new FrameLayout.LayoutParams(fp, fp, Gravity.BOTTOM)); mOverlay.setEnabled(true); mOverlay.setVisibility(View.GONE); mOverlay.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if ( mLeftBehindBase.getVisibility() != View.GONE ) { closeLeftSide(); } else if ( mRightBehindBase.getVisibility() != View.GONE ){ closeRightSide(); } } }); ViewGroup decor = (ViewGroup) mWindow.getDecorView(); ViewGroup above = (ViewGroup) decor.getChildAt(0);//including actionbar decor.removeView(above); //noinspection deprecation(API16から非推奨になった無視) above.setBackgroundDrawable(decor.getBackground()); mAboveView.addView(above); mAboveView.addView(mOverlay); decor.addView(this); addView(mAboveView); } /** * <p>Get the left behind view</p> * @return The view which you set. Return null if you did not set the view. */ public View getLeftBehindView() { return mLeftBehindBase.getChildAt(0); } /** * <p>Get the right behind view</p> * @return The view which you set. Return null if you did not set the view. */ public View getRightBehindView() { return mRightBehindBase.getChildAt(0); } /** * <p>Set the behind view layout.</p> * <p>Call this method after setting the main content view by calling setContentView().</p> * @param leftBehindLayout <p><b>leftBehindLayout</b>: The layout id, under the res/layout directory, which is displayed left side.</p> * @return The view which will be created from the layout id. * @deprecated You should use setLeftBehindContentView() */ public View setBehindContentView(int leftBehindLayout) { return setLeftBehindContentView(leftBehindLayout); } /** * <p>Set the left behind view layout.</p> * <p>Call this method after setting the main content view by calling setContentView().</p> * @param leftBehindLayout <p><b>leftBehindLayout</b>: The layout id, under the res/layout directory, which is displayed left side.</p> * @return The view which will be created from the layout id. */ public View setLeftBehindContentView(int leftBehindLayout) { final View content = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(leftBehindLayout, mLeftBehindBase); mLeftPaddingRect = new Rect(content.getPaddingLeft(), content.getPaddingTop(), content.getPaddingRight(), content.getPaddingBottom()); mLeftBehindView = content; return content; } /** * <p>Set the right behind view layout.</p> * <p>Call this method after setting the main content view by calling setContentView().</p> * @param rightBehindLayout <p><b>leftBehindLayout</b>: The layout id, under the res/layout directory, which is displayed left side.</p> * @return The view which will be created from the layout id. */ public View setRightBehindContentView(int rightBehindLayout) { final View content = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(rightBehindLayout, mRightBehindBase); mRightPaddingRect = new Rect(content.getPaddingLeft(), content.getPaddingTop(), content.getPaddingRight(), content.getPaddingBottom()); mRightBehindView = content; return content; } /** * Change the side scroll interpolator * @param ip Interpolator object */ public void setScrollInterpolator(Interpolator ip) { mScroller = new Scroller(getContext(), ip); } /** * Change the duration time of scrolling * @param msec The duration time should be milli-second * @deprecated You should use setAnimationDurationLeft() */ public void setAnimationDuration(int msec) { setAnimationDurationLeft(msec); } /** * Set the duration time of left sliding animation. * @param msec The duration time should be milli-second ( default = 180 ) */ public void setAnimationDurationLeft(int msec) { mDurationLeft = msec; } /** * Set the duration time of ringht sliding animation. * @param msec The duration time should be milli-second ( default = 180 ) */ public void setAnimationDurationRight(int msec) { mDurationRight = msec; } /** * Close the behind view by swiping left the front view. * @deprecated You should use closeLeftSide() */ public void close() { closeLeftSide(); } /** * Close the left-side behind view */ public void closeLeftSide() { int curX = -mLeftBehindViewWidth;//mAboveView.getScrollX(); mScroller.startScroll(curX, 0, -curX, 0, mDurationLeft); invalidate(); } /** * Close the right-side behide view */ public void closeRightSide() { int curX = mRightBehindViewWidth;//mAboveView.getScrollX(); mScroller.startScroll(curX, 0, -curX, 0, mDurationRight); invalidate(); } /** * Open the behind view by swiping the front view right * @deprecated You should use openLeftSide() */ public void open() { openLeftSide(); } /** * Open the left behind view by swiping the front view right */ public void openLeftSide() { mLeftBehindBase.setVisibility( View.VISIBLE ); mRightBehindBase.setVisibility( View.GONE ); int curX = mAboveView.getScrollX(); mScroller.startScroll(curX, 0, -mLeftBehindViewWidth, 0, mDurationLeft); invalidate(); } public void openRightSide() { mRightBehindBase.setVisibility( View.VISIBLE ); mLeftBehindBase.setVisibility( View.GONE ); int curX = mAboveView.getScrollX(); mScroller.startScroll(curX, 0, mRightBehindViewWidth, 0, mDurationRight); invalidate(); } /** * If the behind view is opened, close it. If the behind view is closed, open it. * @deprecated You should use toggleLeftDrawer() */ public void toggleDrawer() { toggleLeftDrawer(); } /** * If the left behind view is opened, close it. If the left behind view is closed, open it. */ public void toggleLeftDrawer() { if (isClosed()) { openLeftSide(); } else { closeLeftSide(); } } /** * If the right behind view is opened, close it. If the left behind view is closed, open it. */ public void toggleRightDrawer() { if (isClosed()) { openRightSide(); } else { closeRightSide(); } } /** * Check the current status of the behind view * @return */ public boolean isClosed() { return mAboveView != null && mAboveView.getScrollX() == 0; } private boolean isLeftSideOpened() { return mLeftBehindBase.getVisibility() == View.VISIBLE && mRightBehindBase.getVisibility() == View.GONE; } private boolean isRightSideOpened() { return mRightBehindBase.getVisibility() == View.VISIBLE && mLeftBehindBase.getVisibility() == View.GONE; } /** * Need to adjust the behind view height * {@hide} */ @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mLeftBehindViewWidth = mLeftBehindBase.getMeasuredWidth(); mRightBehindViewWidth = mRightBehindBase.getMeasuredWidth(); //adjust the behind display area ViewGroup decor = (ViewGroup) mWindow.getDecorView(); Rect rect = new Rect(); decor.getWindowVisibleDisplayFrame(rect); mBehindView.fitDisplay(rect); } /** * Side scroll animation * {@hide} */ @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { mAboveView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate(); } else { if (mAboveView.getScrollX() == 0) { mOverlay.setVisibility(View.GONE); mLeftBehindBase.setVisibility(View.GONE); mRightBehindBase.setVisibility(View.GONE); } else { mOverlay.setVisibility(View.VISIBLE); } } } /** * {@hide} */ @Override public boolean onTouchEvent(MotionEvent ev) { if (isLeftSideOpened()) { return mLeftDragAction.onTouchEvent(ev); } else if (isRightSideOpened()) { return mRightDragAction.onTouchEvent(ev); } else { return true; } } private class BehindLinearLayout extends LinearLayout { public BehindLinearLayout(Context context) { super(context); } /** * Adjust the behind view * @param rect The display area */ public void fitDisplay(Rect rect) { mBehindView.setPadding(rect.left, rect.top, 0, 0); requestLayout(); } } /** * Overlay view only when the behind menu is appeared. * This view control scrolling the above view * @author Masahiko Adachi */ private class OverlayView extends View { private static final float CLICK_RANGE = 3; private float mDownX; private float mDownY; private OnClickListener mClickListener; public OverlayView(Context context) { super(context); } @Override public void setOnClickListener(OnClickListener listener) { mClickListener = listener; super.setOnClickListener(listener); } @Override public boolean onTouchEvent(MotionEvent ev) { ev.setLocation(ev.getX() - mAboveView.getScrollX(), 0); SimpleSideDrawer.this.onTouchEvent(ev); int action = ev.getAction() & MotionEvent.ACTION_MASK; float x = ev.getX(); float y = ev.getY(); if (action == MotionEvent.ACTION_DOWN) { mDownX = x; mDownY = y; } else if (action == MotionEvent.ACTION_UP) { if (mClickListener != null) { if (Math.abs(mDownX - x) < CLICK_RANGE && Math.abs(mDownY - y) < CLICK_RANGE) { mClickListener.onClick(this); } } } return true; } } }