ultrasonic-app-subsonic-and.../core/menudrawer/src/main/java/net/simonvt/menudrawer/MenuDrawer.java

1672 lines
50 KiB
Java

package net.simonvt.menudrawer;
import net.simonvt.menudrawer.compat.ActionBarHelper;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
public abstract class MenuDrawer extends ViewGroup {
/**
* Callback interface for changing state of the drawer.
*/
public interface OnDrawerStateChangeListener {
/**
* Called when the drawer state changes.
*
* @param oldState The old drawer state.
* @param newState The new drawer state.
*/
void onDrawerStateChange(int oldState, int newState);
/**
* Called when the drawer slides.
*
* @param openRatio Ratio for how open the menu is.
* @param offsetPixels Current offset of the menu in pixels.
*/
void onDrawerSlide(float openRatio, int offsetPixels);
}
/**
* Callback that is invoked when the drawer is in the process of deciding whether it should intercept the touch
* event. This lets the listener decide if the pointer is on a view that would disallow dragging of the drawer.
* This is only called when the touch mode is {@link #TOUCH_MODE_FULLSCREEN}.
*/
public interface OnInterceptMoveEventListener {
/**
* Called for each child the pointer i on when the drawer is deciding whether to intercept the touch event.
*
* @param v View to test for draggability
* @param delta Delta drag in pixels
* @param x X coordinate of the active touch point
* @param y Y coordinate of the active touch point
* @return true if view is draggable by delta dx.
*/
boolean isViewDraggable(View v, int delta, int x, int y);
}
public enum Type {
/**
* Positions the drawer behind the content.
*/
BEHIND,
/**
* A static drawer that can not be dragged.
*/
STATIC,
/**
* Positions the drawer on top of the content.
*/
OVERLAY,
}
/**
* Tag used when logging.
*/
private static final String TAG = "MenuDrawer";
/**
* Indicates whether debug code should be enabled.
*/
private static final boolean DEBUG = false;
/**
* The time between each frame when animating the drawer.
*/
protected static final int ANIMATION_DELAY = 1000 / 60;
/**
* The default touch bezel size of the drawer in dp.
*/
private static final int DEFAULT_DRAG_BEZEL_DP = 24;
/**
* The default drop shadow size in dp.
*/
private static final int DEFAULT_DROP_SHADOW_DP = 6;
/**
* Drag mode for sliding only the content view.
*/
public static final int MENU_DRAG_CONTENT = 0;
/**
* Drag mode for sliding the entire window.
*/
public static final int MENU_DRAG_WINDOW = 1;
/**
* Disallow opening the drawer by dragging the screen.
*/
public static final int TOUCH_MODE_NONE = 0;
/**
* Allow opening drawer only by dragging on the edge of the screen.
*/
public static final int TOUCH_MODE_BEZEL = 1;
/**
* Allow opening drawer by dragging anywhere on the screen.
*/
public static final int TOUCH_MODE_FULLSCREEN = 2;
/**
* Indicates that the drawer is currently closed.
*/
public static final int STATE_CLOSED = 0;
/**
* Indicates that the drawer is currently closing.
*/
public static final int STATE_CLOSING = 1;
/**
* Indicates that the drawer is currently being dragged by the user.
*/
public static final int STATE_DRAGGING = 2;
/**
* Indicates that the drawer is currently opening.
*/
public static final int STATE_OPENING = 4;
/**
* Indicates that the drawer is currently open.
*/
public static final int STATE_OPEN = 8;
/**
* Indicates whether to use {@link View#setTranslationX(float)} when positioning views.
*/
static final boolean USE_TRANSLATIONS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
/**
* Time to animate the indicator to the new active view.
*/
static final int INDICATOR_ANIM_DURATION = 800;
/**
* The maximum animation duration.
*/
private static final int DEFAULT_ANIMATION_DURATION = 600;
/**
* Interpolator used when animating the drawer open/closed.
*/
protected static final Interpolator SMOOTH_INTERPOLATOR = new SmoothInterpolator();
/**
* Interpolator used for stretching/retracting the active indicator.
*/
protected static final Interpolator INDICATOR_INTERPOLATOR = new AccelerateInterpolator();
/**
* Drawable used as menu overlay.
*/
protected Drawable mMenuOverlay;
/**
* Defines whether the drop shadow is enabled.
*/
protected boolean mDropShadowEnabled;
/**
* The color of the drop shadow.
*/
protected int mDropShadowColor;
/**
* Drawable used as content drop shadow onto the menu.
*/
protected Drawable mDropShadowDrawable;
private boolean mCustomDropShadow;
/**
* The size of the content drop shadow.
*/
protected int mDropShadowSize;
/**
* Bitmap used to indicate the active view.
*/
protected Bitmap mActiveIndicator;
/**
* The currently active view.
*/
protected View mActiveView;
/**
* Position of the active view. This is compared to View#getTag(R.id.mdActiveViewPosition) when drawing the
* indicator.
*/
protected int mActivePosition;
/**
* Whether the indicator should be animated between positions.
*/
private boolean mAllowIndicatorAnimation;
/**
* Used when reading the position of the active view.
*/
protected final Rect mActiveRect = new Rect();
/**
* Temporary {@link Rect} used for deciding whether the view should be invalidated so the indicator can be redrawn.
*/
private final Rect mTempRect = new Rect();
/**
* The custom menu view set by the user.
*/
private View mMenuView;
/**
* The parent of the menu view.
*/
protected BuildLayerFrameLayout mMenuContainer;
/**
* The parent of the content view.
*/
protected BuildLayerFrameLayout mContentContainer;
/**
* The size of the menu (width or height depending on the gravity).
*/
protected int mMenuSize;
/**
* Indicates whether the menu is currently visible.
*/
protected boolean mMenuVisible;
/**
* The drag mode of the drawer. Can be either {@link #MENU_DRAG_CONTENT} or {@link #MENU_DRAG_WINDOW}.
*/
private int mDragMode = MENU_DRAG_CONTENT;
/**
* The current drawer state.
*
* @see #STATE_CLOSED
* @see #STATE_CLOSING
* @see #STATE_DRAGGING
* @see #STATE_OPENING
* @see #STATE_OPEN
*/
protected int mDrawerState = STATE_CLOSED;
/**
* The touch bezel size of the drawer in px.
*/
protected int mTouchBezelSize;
/**
* The touch area size of the drawer in px.
*/
protected int mTouchSize;
/**
* Listener used to dispatch state change events.
*/
private OnDrawerStateChangeListener mOnDrawerStateChangeListener;
/**
* Touch mode for the Drawer.
* Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or {@link #TOUCH_MODE_FULLSCREEN}
* Default: {@link #TOUCH_MODE_BEZEL}
*/
protected int mTouchMode = TOUCH_MODE_BEZEL;
/**
* Indicates whether to use {@link View#LAYER_TYPE_HARDWARE} when animating the drawer.
*/
protected boolean mHardwareLayersEnabled = true;
/**
* The Activity the drawer is attached to.
*/
private Activity mActivity;
/**
* Scroller used when animating the indicator to a new position.
*/
private FloatScroller mIndicatorScroller;
/**
* Runnable used when animating the indicator to a new position.
*/
private Runnable mIndicatorRunnable = new Runnable() {
@Override
public void run() {
animateIndicatorInvalidate();
}
};
/**
* The start position of the indicator when animating it to a new position.
*/
protected int mIndicatorStartPos;
/**
* [0..1] value indicating the current progress of the animation.
*/
protected float mIndicatorOffset;
/**
* Whether the indicator is currently animating.
*/
protected boolean mIndicatorAnimating;
/**
* Bundle used to hold the drawers state.
*/
protected Bundle mState;
/**
* The maximum duration of open/close animations.
*/
protected int mMaxAnimationDuration = DEFAULT_ANIMATION_DURATION;
/**
* Callback that lets the listener override intercepting of touch events.
*/
protected OnInterceptMoveEventListener mOnInterceptMoveEventListener;
protected SlideDrawable mSlideDrawable;
protected Drawable mThemeUpIndicator;
protected boolean mDrawerIndicatorEnabled;
private ActionBarHelper mActionBarHelper;
private int mCurrentUpContentDesc;
private int mDrawerOpenContentDesc;
private int mDrawerClosedContentDesc;
/**
* The position of the drawer.
*/
private Position mPosition;
private Position mResolvedPosition;
private final Rect mIndicatorClipRect = new Rect();
protected boolean mIsStatic;
protected final Rect mDropShadowRect = new Rect();
/**
* Current offset.
*/
protected float mOffsetPixels;
/**
* Whether an overlay should be drawn as the drawer is opened and closed.
*/
protected boolean mDrawOverlay;
/**
* Attaches the MenuDrawer to the Activity.
*
* @param activity The activity that the MenuDrawer will be attached to.
* @return The created MenuDrawer instance.
*/
public static MenuDrawer attach(Activity activity) {
return attach(activity, Type.BEHIND);
}
/**
* Attaches the MenuDrawer to the Activity.
*
* @param activity The activity the menu drawer will be attached to.
* @param type The {@link Type} of the drawer.
* @return The created MenuDrawer instance.
*/
public static MenuDrawer attach(Activity activity, Type type) {
return attach(activity, type, Position.START);
}
/**
* Attaches the MenuDrawer to the Activity.
*
* @param activity The activity the menu drawer will be attached to.
* @param position Where to position the menu.
* @return The created MenuDrawer instance.
*/
public static MenuDrawer attach(Activity activity, Position position) {
return attach(activity, Type.BEHIND, position);
}
/**
* Attaches the MenuDrawer to the Activity.
*
* @param activity The activity the menu drawer will be attached to.
* @param type The {@link Type} of the drawer.
* @param position Where to position the menu.
* @return The created MenuDrawer instance.
*/
public static MenuDrawer attach(Activity activity, Type type, Position position) {
return attach(activity, type, position, MENU_DRAG_CONTENT);
}
/**
* Attaches the MenuDrawer to the Activity.
*
* @param activity The activity the menu drawer will be attached to.
* @param type The {@link Type} of the drawer.
* @param position Where to position the menu.
* @param dragMode The drag mode of the drawer. Can be either {@link MenuDrawer#MENU_DRAG_CONTENT}
* or {@link MenuDrawer#MENU_DRAG_WINDOW}.
* @return The created MenuDrawer instance.
*/
public static MenuDrawer attach(Activity activity, Type type, Position position, int dragMode) {
MenuDrawer menuDrawer = createMenuDrawer(activity, dragMode, position, type);
menuDrawer.setId(R.id.md__drawer);
switch (dragMode) {
case MenuDrawer.MENU_DRAG_CONTENT:
attachToContent(activity, menuDrawer);
break;
case MenuDrawer.MENU_DRAG_WINDOW:
attachToDecor(activity, menuDrawer);
break;
default:
throw new RuntimeException("Unknown menu mode: " + dragMode);
}
return menuDrawer;
}
/**
* Constructs the appropriate MenuDrawer based on the position.
*/
private static MenuDrawer createMenuDrawer(Activity activity, int dragMode, Position position, Type type) {
MenuDrawer drawer;
if (type == Type.STATIC) {
drawer = new StaticDrawer(activity);
} else if (type == Type.OVERLAY) {
drawer = new OverlayDrawer(activity, dragMode);
if (position == Position.LEFT || position == Position.START) {
drawer.setupUpIndicator(activity);
}
} else {
drawer = new SlidingDrawer(activity, dragMode);
if (position == Position.LEFT || position == Position.START) {
drawer.setupUpIndicator(activity);
}
}
drawer.mDragMode = dragMode;
drawer.setPosition(position);
return drawer;
}
/**
* Attaches the menu drawer to the content view.
*/
private static void attachToContent(Activity activity, MenuDrawer menuDrawer) {
/**
* Do not call mActivity#setContentView.
* E.g. if using with a ListActivity, Activity#setContentView is overridden and dispatched to
* MenuDrawer#setContentView, which then again would call Activity#setContentView.
*/
ViewGroup content = (ViewGroup) activity.findViewById(android.R.id.content);
content.removeAllViews();
content.addView(menuDrawer, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
/**
* Attaches the menu drawer to the window.
*/
private static void attachToDecor(Activity activity, MenuDrawer menuDrawer) {
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
ViewGroup decorChild = (ViewGroup) decorView.getChildAt(0);
decorView.removeAllViews();
decorView.addView(menuDrawer, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
menuDrawer.mContentContainer.addView(decorChild, decorChild.getLayoutParams());
}
MenuDrawer(Activity activity, int dragMode) {
this(activity);
mActivity = activity;
mDragMode = dragMode;
}
public MenuDrawer(Context context) {
this(context, null);
}
public MenuDrawer(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.menuDrawerStyle);
}
public MenuDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initDrawer(context, attrs, defStyle);
}
public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
Drawable drawable = AppCompatResources.getDrawable(context, drawableId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = (DrawableCompat.wrap(drawable)).mutate();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
setWillNotDraw(false);
setFocusable(false);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MenuDrawer, R.attr.menuDrawerStyle,
R.style.Widget_MenuDrawer);
final Drawable contentBackground = a.getDrawable(R.styleable.MenuDrawer_mdContentBackground);
final Drawable menuBackground = a.getDrawable(R.styleable.MenuDrawer_mdMenuBackground);
mMenuSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdMenuSize, dpToPx(240));
final int indicatorResId = a.getResourceId(R.styleable.MenuDrawer_mdActiveIndicator, 0);
if (indicatorResId != 0) {
//mActiveIndicator = BitmapFactory.decodeResource(getResources(), indicatorResId);
mActiveIndicator = getBitmapFromVectorDrawable(context, indicatorResId);
}
mDropShadowEnabled = a.getBoolean(R.styleable.MenuDrawer_mdDropShadowEnabled, true);
mDropShadowDrawable = a.getDrawable(R.styleable.MenuDrawer_mdDropShadow);
if (mDropShadowDrawable == null) {
mDropShadowColor = a.getColor(R.styleable.MenuDrawer_mdDropShadowColor, 0xFF000000);
} else {
mCustomDropShadow = true;
}
mDropShadowSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdDropShadowSize,
dpToPx(DEFAULT_DROP_SHADOW_DP));
mTouchBezelSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdTouchBezelSize,
dpToPx(DEFAULT_DRAG_BEZEL_DP));
mAllowIndicatorAnimation = a.getBoolean(R.styleable.MenuDrawer_mdAllowIndicatorAnimation, false);
mMaxAnimationDuration = a.getInt(R.styleable.MenuDrawer_mdMaxAnimationDuration, DEFAULT_ANIMATION_DURATION);
final int slideDrawableResId = a.getResourceId(R.styleable.MenuDrawer_mdSlideDrawable, -1);
if (slideDrawableResId != -1) {
setSlideDrawable(slideDrawableResId);
}
mDrawerOpenContentDesc = a.getResourceId(R.styleable.MenuDrawer_mdDrawerOpenUpContentDescription, 0);
mDrawerClosedContentDesc = a.getResourceId(R.styleable.MenuDrawer_mdDrawerClosedUpContentDescription, 0);
mDrawOverlay = a.getBoolean(R.styleable.MenuDrawer_mdDrawOverlay, true);
final int position = a.getInt(R.styleable.MenuDrawer_mdPosition, 0);
setPosition(Position.fromValue(position));
a.recycle();
mMenuContainer = new NoClickThroughFrameLayout(context);
mMenuContainer.setId(R.id.md__menu);
mMenuContainer.setBackgroundDrawable(menuBackground);
mContentContainer = new NoClickThroughFrameLayout(context);
mContentContainer.setId(R.id.md__content);
mContentContainer.setBackgroundDrawable(contentBackground);
mMenuOverlay = new ColorDrawable(0xFF000000);
mIndicatorScroller = new FloatScroller(SMOOTH_INTERPOLATOR);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
View menu = findViewById(R.id.mdMenu);
if (menu != null) {
removeView(menu);
setMenuView(menu);
}
View content = findViewById(R.id.mdContent);
if (content != null) {
removeView(content);
setContentView(content);
}
if (getChildCount() > 2) {
throw new IllegalStateException(
"Menu and content view added in xml must have id's @id/mdMenu and @id/mdContent");
}
}
protected int dpToPx(int dp) {
return (int) (getResources().getDisplayMetrics().density * dp + 0.5f);
}
protected boolean isViewDescendant(View v) {
ViewParent parent = v.getParent();
while (parent != null) {
if (parent == this) {
return true;
}
parent = parent.getParent();
}
return false;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnScrollChangedListener(mScrollListener);
}
@Override
protected void onDetachedFromWindow() {
getViewTreeObserver().removeOnScrollChangedListener(mScrollListener);
super.onDetachedFromWindow();
}
private boolean shouldDrawIndicator() {
return mActiveView != null && mActiveIndicator != null && isViewDescendant(mActiveView);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
final int offsetPixels = (int) mOffsetPixels;
if (mDrawOverlay && offsetPixels != 0) {
drawOverlay(canvas);
}
if (mDropShadowEnabled && (offsetPixels != 0 || mIsStatic)) {
drawDropShadow(canvas);
}
if (shouldDrawIndicator() && (offsetPixels != 0 || mIsStatic)) {
drawIndicator(canvas);
}
}
protected abstract void drawOverlay(Canvas canvas);
private void drawDropShadow(Canvas canvas) {
// Can't pass the position to the constructor, so wait with loading the drawable until the drop shadow is
// actually drawn.
if (mDropShadowDrawable == null) {
setDropShadowColor(mDropShadowColor);
}
updateDropShadowRect();
mDropShadowDrawable.setBounds(mDropShadowRect);
mDropShadowDrawable.draw(canvas);
}
protected void updateDropShadowRect() {
// This updates the rect for the static and sliding drawer. The overlay drawer has its own implementation.
switch (getPosition()) {
case LEFT:
mDropShadowRect.top = 0;
mDropShadowRect.bottom = getHeight();
mDropShadowRect.right = ViewHelper.getLeft(mContentContainer);
mDropShadowRect.left = mDropShadowRect.right - mDropShadowSize;
break;
case TOP:
mDropShadowRect.left = 0;
mDropShadowRect.right = getWidth();
mDropShadowRect.bottom = ViewHelper.getTop(mContentContainer);
mDropShadowRect.top = mDropShadowRect.bottom - mDropShadowSize;
break;
case RIGHT:
mDropShadowRect.top = 0;
mDropShadowRect.bottom = getHeight();
mDropShadowRect.left = ViewHelper.getRight(mContentContainer);
mDropShadowRect.right = mDropShadowRect.left + mDropShadowSize;
break;
case BOTTOM:
mDropShadowRect.left = 0;
mDropShadowRect.right = getWidth();
mDropShadowRect.top = ViewHelper.getBottom(mContentContainer);
mDropShadowRect.bottom = mDropShadowRect.top + mDropShadowSize;
break;
}
}
private void drawIndicator(Canvas canvas) {
Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
final int pos = position == null ? 0 : position;
if (pos == mActivePosition) {
updateIndicatorClipRect();
canvas.save();
canvas.clipRect(mIndicatorClipRect);
int drawLeft = 0;
int drawTop = 0;
switch (getPosition()) {
case LEFT:
case TOP:
drawLeft = mIndicatorClipRect.left;
drawTop = mIndicatorClipRect.top;
break;
case RIGHT:
drawLeft = mIndicatorClipRect.right - mActiveIndicator.getWidth();
drawTop = mIndicatorClipRect.top;
break;
case BOTTOM:
drawLeft = mIndicatorClipRect.left;
drawTop = mIndicatorClipRect.bottom - mActiveIndicator.getHeight();
}
canvas.drawBitmap(mActiveIndicator, drawLeft, drawTop, null);
canvas.restore();
}
}
/**
* Update the {@link Rect} where the indicator is drawn.
*/
protected void updateIndicatorClipRect() {
mActiveView.getDrawingRect(mActiveRect);
offsetDescendantRectToMyCoords(mActiveView, mActiveRect);
final float openRatio = mIsStatic ? 1.0f : Math.abs(mOffsetPixels) / mMenuSize;
final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio));
final int indicatorWidth = mActiveIndicator.getWidth();
final int indicatorHeight = mActiveIndicator.getHeight();
final int interpolatedWidth = (int) (indicatorWidth * interpolatedRatio);
final int interpolatedHeight = (int) (indicatorHeight * interpolatedRatio);
final int startPos = mIndicatorStartPos;
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
switch (getPosition()) {
case LEFT:
case RIGHT:
final int finalTop = mActiveRect.top + ((mActiveRect.height() - indicatorHeight) / 2);
if (mIndicatorAnimating) {
top = (int) (startPos + ((finalTop - startPos) * mIndicatorOffset));
} else {
top = finalTop;
}
bottom = top + indicatorHeight;
break;
case TOP:
case BOTTOM:
final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
if (mIndicatorAnimating) {
left = (int) (startPos + ((finalLeft - startPos) * mIndicatorOffset));
} else {
left = finalLeft;
}
right = left + indicatorWidth;
break;
}
switch (getPosition()) {
case LEFT: {
right = ViewHelper.getLeft(mContentContainer);
left = right - interpolatedWidth;
break;
}
case TOP: {
bottom = ViewHelper.getTop(mContentContainer);
top = bottom - interpolatedHeight;
break;
}
case RIGHT: {
left = ViewHelper.getRight(mContentContainer);
right = left + interpolatedWidth;
break;
}
case BOTTOM: {
top = ViewHelper.getBottom(mContentContainer);
bottom = top + interpolatedHeight;
break;
}
}
mIndicatorClipRect.left = left;
mIndicatorClipRect.top = top;
mIndicatorClipRect.right = right;
mIndicatorClipRect.bottom = bottom;
}
private void setPosition(Position position) {
mPosition = position;
mResolvedPosition = getPosition();
}
protected Position getPosition() {
final int layoutDirection = ViewHelper.getLayoutDirection(this);
switch (mPosition) {
case START:
if (layoutDirection == LAYOUT_DIRECTION_RTL) {
return Position.RIGHT;
} else {
return Position.LEFT;
}
case END:
if (layoutDirection == LAYOUT_DIRECTION_RTL) {
return Position.LEFT;
} else {
return Position.RIGHT;
}
}
return mPosition;
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
if (!mCustomDropShadow) setDropShadowColor(mDropShadowColor);
if (getPosition() != mResolvedPosition) {
mResolvedPosition = getPosition();
setOffsetPixels(mOffsetPixels * -1);
}
if (mSlideDrawable != null) mSlideDrawable.setIsRtl(layoutDirection == LAYOUT_DIRECTION_RTL);
requestLayout();
invalidate();
}
/**
* Sets the number of pixels the content should be offset.
*
* @param offsetPixels The number of pixels to offset the content by.
*/
protected void setOffsetPixels(float offsetPixels) {
final int oldOffset = (int) mOffsetPixels;
final int newOffset = (int) offsetPixels;
mOffsetPixels = offsetPixels;
if (mSlideDrawable != null) {
final float offset = Math.abs(mOffsetPixels) / mMenuSize;
mSlideDrawable.setOffset(offset);
updateUpContentDescription();
}
if (newOffset != oldOffset) {
onOffsetPixelsChanged(newOffset);
mMenuVisible = newOffset != 0;
// Notify any attached listeners of the current open ratio
final float openRatio = ((float) Math.abs(newOffset)) / mMenuSize;
dispatchOnDrawerSlide(openRatio, newOffset);
}
}
/**
* Called when the number of pixels the content should be offset by has changed.
*
* @param offsetPixels The number of pixels to offset the content by.
*/
protected abstract void onOffsetPixelsChanged(int offsetPixels);
/**
* Toggles the menu open and close with animation.
*/
public void toggleMenu() {
toggleMenu(true);
}
/**
* Toggles the menu open and close.
*
* @param animate Whether open/close should be animated.
*/
public abstract void toggleMenu(boolean animate);
/**
* Animates the menu open.
*/
public void openMenu() {
openMenu(true);
}
/**
* Opens the menu.
*
* @param animate Whether open/close should be animated.
*/
public abstract void openMenu(boolean animate);
/**
* Animates the menu closed.
*/
public void closeMenu() {
closeMenu(true);
}
/**
* Closes the menu.
*
* @param animate Whether open/close should be animated.
*/
public abstract void closeMenu(boolean animate);
/**
* Indicates whether the menu is currently visible.
*
* @return True if the menu is open, false otherwise.
*/
public abstract boolean isMenuVisible();
/**
* Set the size of the menu drawer when open.
*
* @param size The size of the menu.
*/
public abstract void setMenuSize(int size);
/**
* Returns the size of the menu.
*
* @return The size of the menu.
*/
public int getMenuSize() {
return mMenuSize;
}
/**
* Set the active view.
* If the mdActiveIndicator attribute is set, this View will have the indicator drawn next to it.
*
* @param v The active view.
*/
public void setActiveView(View v) {
setActiveView(v, 0);
}
/**
* Set the active view.
* If the mdActiveIndicator attribute is set, this View will have the indicator drawn next to it.
*
* @param v The active view.
* @param position Optional position, usually used with ListView. v.setTag(R.id.mdActiveViewPosition, position)
* must be called first.
*/
public void setActiveView(View v, int position) {
final View oldView = mActiveView;
mActiveView = v;
mActivePosition = position;
if (mAllowIndicatorAnimation && oldView != null) {
startAnimatingIndicator();
}
invalidate();
}
/**
* Sets whether the indicator should be animated between active views.
*
* @param animate Whether the indicator should be animated between active views.
*/
public void setAllowIndicatorAnimation(boolean animate) {
if (animate != mAllowIndicatorAnimation) {
mAllowIndicatorAnimation = animate;
completeAnimatingIndicator();
}
}
/**
* Indicates whether the indicator should be animated between active views.
*
* @return Whether the indicator should be animated between active views.
*/
public boolean getAllowIndicatorAnimation() {
return mAllowIndicatorAnimation;
}
/**
* Scroll listener that checks whether the active view has moved before the drawer is invalidated.
*/
private ViewTreeObserver.OnScrollChangedListener mScrollListener = new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
if (mActiveView != null && isViewDescendant(mActiveView)) {
mActiveView.getDrawingRect(mTempRect);
offsetDescendantRectToMyCoords(mActiveView, mTempRect);
if (mTempRect.left != mActiveRect.left || mTempRect.top != mActiveRect.top
|| mTempRect.right != mActiveRect.right || mTempRect.bottom != mActiveRect.bottom) {
invalidate();
}
}
}
};
/**
* Starts animating the indicator to a new position.
*/
private void startAnimatingIndicator() {
mIndicatorStartPos = getIndicatorStartPos();
mIndicatorAnimating = true;
mIndicatorScroller.startScroll(0.0f, 1.0f, INDICATOR_ANIM_DURATION);
animateIndicatorInvalidate();
}
/**
* Returns the start position of the indicator.
*
* @return The start position of the indicator.
*/
private int getIndicatorStartPos() {
switch (getPosition()) {
case TOP:
return mIndicatorClipRect.left;
case RIGHT:
return mIndicatorClipRect.top;
case BOTTOM:
return mIndicatorClipRect.left;
default:
return mIndicatorClipRect.top;
}
}
/**
* Compute the touch area based on the touch mode.
*/
protected void updateTouchAreaSize() {
if (mTouchMode == TOUCH_MODE_BEZEL) {
mTouchSize = mTouchBezelSize;
} else if (mTouchMode == TOUCH_MODE_FULLSCREEN) {
mTouchSize = getMeasuredWidth();
} else {
mTouchSize = 0;
}
}
/**
* Callback when each frame in the indicator animation should be drawn.
*/
private void animateIndicatorInvalidate() {
if (mIndicatorScroller.computeScrollOffset()) {
mIndicatorOffset = mIndicatorScroller.getCurr();
invalidate();
if (!mIndicatorScroller.isFinished()) {
postOnAnimation(mIndicatorRunnable);
return;
}
}
completeAnimatingIndicator();
}
/**
* Called when the indicator animation has completed.
*/
private void completeAnimatingIndicator() {
mIndicatorOffset = 1.0f;
mIndicatorAnimating = false;
invalidate();
}
/**
* Enables or disables offsetting the menu when dragging the drawer.
*
* @param offsetMenu True to offset the menu, false otherwise.
*/
public abstract void setOffsetMenuEnabled(boolean offsetMenu);
/**
* Indicates whether the menu is being offset when dragging the drawer.
*
* @return True if the menu is being offset, false otherwise.
*/
public abstract boolean getOffsetMenuEnabled();
/**
* Get the current state of the drawer.
*
* @return The state of the drawer.
*/
public int getDrawerState() {
return mDrawerState;
}
/**
* Register a callback to be invoked when the drawer state changes.
*
* @param listener The callback that will run.
*/
public void setOnDrawerStateChangeListener(OnDrawerStateChangeListener listener) {
mOnDrawerStateChangeListener = listener;
}
/**
* Register a callback that will be invoked when the drawer is about to intercept touch events.
*
* @param listener The callback that will be invoked.
*/
public void setOnInterceptMoveEventListener(OnInterceptMoveEventListener listener) {
mOnInterceptMoveEventListener = listener;
}
/**
* Defines whether the drop shadow is enabled.
*
* @param enabled Whether the drop shadow is enabled.
*/
public void setDropShadowEnabled(boolean enabled) {
mDropShadowEnabled = enabled;
invalidate();
}
protected GradientDrawable.Orientation getDropShadowOrientation() {
// Gets the orientation for the static and sliding drawer. The overlay drawer provides its own implementation.
switch (getPosition()) {
case TOP:
return GradientDrawable.Orientation.BOTTOM_TOP;
case RIGHT:
return GradientDrawable.Orientation.LEFT_RIGHT;
case BOTTOM:
return GradientDrawable.Orientation.TOP_BOTTOM;
default:
return GradientDrawable.Orientation.RIGHT_LEFT;
}
}
/**
* Sets the color of the drop shadow.
*
* @param color The color of the drop shadow.
*/
public void setDropShadowColor(int color) {
GradientDrawable.Orientation orientation = getDropShadowOrientation();
final int endColor = color & 0x00FFFFFF;
mDropShadowDrawable = new GradientDrawable(orientation,
new int[] {
color,
endColor,
});
invalidate();
}
/**
* Sets the drawable of the drop shadow.
*
* @param drawable The drawable of the drop shadow.
*/
public void setDropShadow(Drawable drawable) {
mDropShadowDrawable = drawable;
mCustomDropShadow = drawable != null;
invalidate();
}
/**
* Sets the drawable of the drop shadow.
*
* @param resId The resource identifier of the the drawable.
*/
public void setDropShadow(int resId) {
setDropShadow(getResources().getDrawable(resId));
}
/**
* Returns the drawable of the drop shadow.
*/
public Drawable getDropShadow() {
return mDropShadowDrawable;
}
/**
* Sets the size of the drop shadow.
*
* @param size The size of the drop shadow in px.
*/
public void setDropShadowSize(int size) {
mDropShadowSize = size;
invalidate();
}
/**
* Animates the drawer slightly open until the user opens the drawer.
*/
public abstract void peekDrawer();
/**
* Animates the drawer slightly open. If delay is larger than 0, this happens until the user opens the drawer.
*
* @param delay The delay (in milliseconds) between each run of the animation. If 0, this animation is only run
* once.
*/
public abstract void peekDrawer(long delay);
/**
* Animates the drawer slightly open. If delay is larger than 0, this happens until the user opens the drawer.
*
* @param startDelay The delay (in milliseconds) until the animation is first run.
* @param delay The delay (in milliseconds) between each run of the animation. If 0, this animation is only run
* once.
*/
public abstract void peekDrawer(long startDelay, long delay);
/**
* Enables or disables the user of {@link View#LAYER_TYPE_HARDWARE} when animations views.
*
* @param enabled Whether hardware layers are enabled.
*/
public abstract void setHardwareLayerEnabled(boolean enabled);
/**
* Sets the maximum duration of open/close animations.
*
* @param duration The maximum duration in milliseconds.
*/
public void setMaxAnimationDuration(int duration) {
mMaxAnimationDuration = duration;
}
/**
* Sets whether an overlay should be drawn when sliding the drawer.
*
* @param drawOverlay Whether an overlay should be drawn when sliding the drawer.
*/
public void setDrawOverlay(boolean drawOverlay) {
mDrawOverlay = drawOverlay;
}
/**
* Gets whether an overlay is drawn when sliding the drawer.
*
* @return Whether an overlay is drawn when sliding the drawer.
*/
public boolean getDrawOverlay() {
return mDrawOverlay;
}
protected void updateUpContentDescription() {
final int upContentDesc = isMenuVisible() ? mDrawerOpenContentDesc : mDrawerClosedContentDesc;
if (mDrawerIndicatorEnabled && mActionBarHelper != null && upContentDesc != mCurrentUpContentDesc) {
mCurrentUpContentDesc = upContentDesc;
mActionBarHelper.setActionBarDescription(upContentDesc);
}
}
/**
* Sets the drawable used as the drawer indicator.
*
* @param drawable The drawable used as the drawer indicator.
*/
public void setSlideDrawable(int drawableRes) {
setSlideDrawable(getResources().getDrawable(drawableRes));
}
/**
* Sets the drawable used as the drawer indicator.
*
* @param drawable The drawable used as the drawer indicator.
*/
public void setSlideDrawable(Drawable drawable) {
mSlideDrawable = new SlideDrawable(drawable);
mSlideDrawable.setIsRtl(ViewHelper.getLayoutDirection(this) == LAYOUT_DIRECTION_RTL);
if (mActionBarHelper != null) {
mActionBarHelper.setDisplayShowHomeAsUpEnabled(true);
if (mDrawerIndicatorEnabled) {
mActionBarHelper.setActionBarUpIndicator(mSlideDrawable,
isMenuVisible() ? mDrawerOpenContentDesc : mDrawerClosedContentDesc);
}
}
}
/**
* Sets up the drawer indicator. It cna then be shown with {@link #setDrawerIndicatorEnabled(boolean)}.
*
* @param activity The activity the drawer is attached to.
*/
public void setupUpIndicator(Activity activity) {
if (mActionBarHelper == null) {
mActionBarHelper = new ActionBarHelper(activity);
mThemeUpIndicator = mActionBarHelper.getThemeUpIndicator();
if (mDrawerIndicatorEnabled) {
mActionBarHelper.setActionBarUpIndicator(mSlideDrawable,
isMenuVisible() ? mDrawerOpenContentDesc : mDrawerClosedContentDesc);
}
}
}
/**
* Sets whether the drawer indicator should be enabled. {@link #setupUpIndicator(android.app.Activity)} must be
* called first.
*
* @param enabled Whether the drawer indicator should enabled.
*/
public void setDrawerIndicatorEnabled(boolean enabled) {
if (mActionBarHelper == null) {
throw new IllegalStateException("setupUpIndicator(Activity) has not been called");
}
mDrawerIndicatorEnabled = enabled;
if (enabled) {
mActionBarHelper.setActionBarUpIndicator(mSlideDrawable,
isMenuVisible() ? mDrawerOpenContentDesc : mDrawerClosedContentDesc);
} else {
mActionBarHelper.setActionBarUpIndicator(mThemeUpIndicator, 0);
}
}
/**
* Indicates whether the drawer indicator is currently enabled.
*
* @return Whether the drawer indicator is enabled.
*/
public boolean isDrawerIndicatorEnabled() {
return mDrawerIndicatorEnabled;
}
/**
* Returns the ViewGroup used as a parent for the menu view.
*
* @return The menu view's parent.
*/
public ViewGroup getMenuContainer() {
return mMenuContainer;
}
/**
* Returns the ViewGroup used as a parent for the content view.
*
* @return The content view's parent.
*/
public ViewGroup getContentContainer() {
if (mDragMode == MENU_DRAG_CONTENT) {
return mContentContainer;
} else {
return (ViewGroup) findViewById(android.R.id.content);
}
}
/**
* Set the menu view from a layout resource.
*
* @param layoutResId Resource ID to be inflated.
*/
public void setMenuView(int layoutResId) {
mMenuContainer.removeAllViews();
mMenuView = LayoutInflater.from(getContext()).inflate(layoutResId, mMenuContainer, false);
mMenuContainer.addView(mMenuView);
}
/**
* Set the menu view to an explicit view.
*
* @param view The menu view.
*/
public void setMenuView(View view) {
setMenuView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
/**
* Set the menu view to an explicit view.
*
* @param view The menu view.
* @param params Layout parameters for the view.
*/
public void setMenuView(View view, LayoutParams params) {
mMenuView = view;
mMenuContainer.removeAllViews();
mMenuContainer.addView(view, params);
}
/**
* Returns the menu view.
*
* @return The menu view.
*/
public View getMenuView() {
return mMenuView;
}
/**
* Set the content from a layout resource.
*
* @param layoutResId Resource ID to be inflated.
*/
public void setContentView(int layoutResId) {
switch (mDragMode) {
case MenuDrawer.MENU_DRAG_CONTENT:
mContentContainer.removeAllViews();
LayoutInflater.from(getContext()).inflate(layoutResId, mContentContainer, true);
break;
case MenuDrawer.MENU_DRAG_WINDOW:
mActivity.setContentView(layoutResId);
break;
}
}
/**
* Set the content to an explicit view.
*
* @param view The desired content to display.
*/
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
/**
* Set the content to an explicit view.
*
* @param view The desired content to display.
* @param params Layout parameters for the view.
*/
public void setContentView(View view, LayoutParams params) {
switch (mDragMode) {
case MenuDrawer.MENU_DRAG_CONTENT:
mContentContainer.removeAllViews();
mContentContainer.addView(view, params);
break;
case MenuDrawer.MENU_DRAG_WINDOW:
mActivity.setContentView(view, params);
break;
}
}
protected void setDrawerState(int state) {
if (state != mDrawerState) {
final int oldState = mDrawerState;
mDrawerState = state;
if (mOnDrawerStateChangeListener != null) mOnDrawerStateChangeListener.onDrawerStateChange(oldState, state);
if (DEBUG) logDrawerState(state);
}
}
protected void logDrawerState(int state) {
switch (state) {
case STATE_CLOSED:
Log.d(TAG, "[DrawerState] STATE_CLOSED");
break;
case STATE_CLOSING:
Log.d(TAG, "[DrawerState] STATE_CLOSING");
break;
case STATE_DRAGGING:
Log.d(TAG, "[DrawerState] STATE_DRAGGING");
break;
case STATE_OPENING:
Log.d(TAG, "[DrawerState] STATE_OPENING");
break;
case STATE_OPEN:
Log.d(TAG, "[DrawerState] STATE_OPEN");
break;
default:
Log.d(TAG, "[DrawerState] Unknown: " + state);
}
}
/**
* Returns the touch mode.
*/
public abstract int getTouchMode();
/**
* Sets the drawer touch mode. Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or
* {@link #TOUCH_MODE_FULLSCREEN}.
*
* @param mode The touch mode.
*/
public abstract void setTouchMode(int mode);
/**
* Sets the size of the touch bezel.
*
* @param size The touch bezel size in px.
*/
public abstract void setTouchBezelSize(int size);
/**
* Returns the size of the touch bezel in px.
*/
public abstract int getTouchBezelSize();
@Override
public void postOnAnimation(Runnable action) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
super.postOnAnimation(action);
} else {
postDelayed(action, ANIMATION_DELAY);
}
}
@Override
protected boolean fitSystemWindows(Rect insets) {
if (mDragMode == MENU_DRAG_WINDOW && mPosition != Position.BOTTOM) {
mMenuContainer.setPadding(0, insets.top, 0, 0);
}
return super.fitSystemWindows(insets);
}
protected void dispatchOnDrawerSlide(float openRatio, int offsetPixels) {
if (mOnDrawerStateChangeListener != null) {
mOnDrawerStateChangeListener.onDrawerSlide(openRatio, offsetPixels);
}
}
/**
* Saves the state of the drawer.
*
* @return Returns a Parcelable containing the drawer state.
*/
public final Parcelable saveState() {
if (mState == null) mState = new Bundle();
saveState(mState);
return mState;
}
void saveState(Bundle state) {
// State saving isn't required for subclasses.
}
/**
* Restores the state of the drawer.
*
* @param in A parcelable containing the drawer state.
*/
public void restoreState(Parcelable in) {
mState = (Bundle) in;
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState state = new SavedState(superState);
if (mState == null) mState = new Bundle();
saveState(mState);
state.mState = mState;
return state;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
restoreState(savedState.mState);
}
static class SavedState extends BaseSavedState {
Bundle mState;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel in) {
super(in);
mState = in.readBundle();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeBundle(mState);
}
@SuppressWarnings("UnusedDeclaration")
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}