/* * Copyright (C) 2014 Hippo Seven * * 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.hippo.ehviewer.widget; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.v4.content.ContextCompat; import android.support.v4.view.GravityCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import com.hippo.ehviewer.util.MathUtils; import com.hippo.ehviewer.util.Ui; import com.hippo.ehviewer.util.ViewUtils; @SuppressLint("RtlHardcoded") public class SlidingDrawerLayout extends ViewGroup implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { private static final String TAG = SlidingDrawerLayout.class.getSimpleName(); private static final int[] LAYOUT_ATTRS = new int[] { android.R.attr.layout_gravity }; private static final int STATE_CLOSED = 0x0; private static final int STATE_SLIDING = 0x1; private static final int STATE_OPENED = 0x2; private static final float OPEN_SENSITIVITY = 0.1f; private static final float CLOSE_SENSITIVITY = 0.9f; private int mMinDrawerMargin; private boolean mInLayout; private View mContentView; private View mLeftDrawer; private View mRightDrawer; private ViewDragHelper mDragHelper; private float mInitialMotionX; private float mInitialMotionY; private boolean mLeftOpened; private boolean mRightOpened; private int mLeftState; private int mRightState; private float mLeftPercent; private float mRightPercent; private ValueAnimator mAnimator; private View mTargetView; private int mStartLeft; private int mEndLeft; private boolean mToOpen; private boolean mCancelAnimation; private View mOpenTask; private DrawerListener mListener; private Drawable mStatusBarBackground; private boolean mIntercepted; private int mInterceptPointNum; private Object mLastInsets; private boolean mDrawStatusBarBackground; private static int sDefaultMinDrawerMargin = 56; private static final SlidingDrawerLayoutCompatImpl IMPL; static { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { IMPL = new SlidingDrawerLayoutCompatImplApi21(); } else { IMPL = new SlidingDrawerLayoutCompatImplBase(); } } interface SlidingDrawerLayoutCompatImpl { void configureApplyInsets(View drawerLayout); void dispatchChildInsets(View child, Object insets, int drawerGravity); void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity); int getTopInset(Object lastInsets); } static class SlidingDrawerLayoutCompatImplBase implements SlidingDrawerLayoutCompatImpl { @Override public void configureApplyInsets(View drawerLayout) { // This space for rent } @Override public void dispatchChildInsets(View child, Object insets, int drawerGravity) { // This space for rent } @Override public void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity) { // This space for rent } @Override public int getTopInset(Object insets) { return 0; } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) static class SlidingDrawerLayoutCompatImplApi21 implements SlidingDrawerLayoutCompatImpl { @Override public void configureApplyInsets(View drawerLayout) { if (drawerLayout instanceof SlidingDrawerLayout) { drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener()); drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } } @Override public void dispatchChildInsets(View child, Object insets, int gravity) { WindowInsets wi = (WindowInsets) insets; if (gravity == Gravity.LEFT) { wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); } else if (gravity == Gravity.RIGHT) { wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); } child.dispatchApplyWindowInsets(wi); } @Override public void applyMarginInsets(MarginLayoutParams lp, Object insets, int gravity) { WindowInsets wi = (WindowInsets) insets; if (gravity == Gravity.LEFT) { wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); } else if (gravity == Gravity.RIGHT) { wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); } lp.leftMargin = wi.getSystemWindowInsetLeft(); lp.topMargin = wi.getSystemWindowInsetTop(); lp.rightMargin = wi.getSystemWindowInsetRight(); lp.bottomMargin = wi.getSystemWindowInsetBottom(); } @Override public int getTopInset(Object insets) { return insets != null ? ((WindowInsets) insets).getSystemWindowInsetTop() : 0; } static class InsetsListener implements View.OnApplyWindowInsetsListener { @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { final SlidingDrawerLayout drawerLayout = (SlidingDrawerLayout) v; drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0); return insets.consumeSystemWindowInsets(); } } } /** * Listener for monitoring events about drawers. */ public interface DrawerListener { /** * Called when a drawer's position changes. * @param drawerView The child view that was moved * @param slideOffset The new offset of this drawer within its range, from 0-1 */ public void onDrawerSlide(View drawerView, float percent); /** * Called when a drawer has settled in a completely open state. * The drawer is interactive at this point. * * @param drawerView Drawer view that is now open */ public void onDrawerOpened(View drawerView); /** * Called when a drawer has settled in a completely closed state. * * @param drawerView Drawer view that is now closed */ public void onDrawerClosed(View drawerView); /** * Called when the drawer motion state changes. The new state will * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. * * @param newState The new drawer motion state */ public void onDrawerStateChanged(View drawerView, int newState); } /** * minMargin should be app bar height in Material design * * @param minMargin */ public static final void setDefaultMinDrawerMargin(int minMargin) { sDefaultMinDrawerMargin = minMargin; } public SlidingDrawerLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SlidingDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public SlidingDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { mLeftOpened = false; mRightOpened = false; mLeftState = STATE_CLOSED; mRightState = STATE_CLOSED; mLeftPercent = 0.0f; mRightPercent = 0.0f; mMinDrawerMargin = sDefaultMinDrawerMargin; mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback()); mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); mAnimator.addUpdateListener(this); mAnimator.addListener(this); mCancelAnimation = false; setWillNotDraw(false); } public void setDrawerListener(DrawerListener listener) { mListener = listener; } /** * Close all currently open drawer views by animating them out of view. */ public void closeDrawers() { closeDrawer(mLeftDrawer); closeDrawer(mRightDrawer); } /** * Open the specified drawer view by animating it into view. * * @param drawerView * Drawer view to open */ public void openDrawer(View drawerView) { if (drawerView == null || (drawerView != mLeftDrawer && drawerView != mRightDrawer)) return; boolean isLeft = drawerView == mLeftDrawer; switch (isLeft ? mLeftState : mRightState) { case STATE_CLOSED: // Check other side first switch (isLeft ? mRightState : mLeftState) { case STATE_CLOSED: startAnimation(isLeft, true); break; case STATE_SLIDING: if (mAnimator.isRunning()) { if (mToOpen) { cancelAnimation(); mOpenTask = isLeft ? mLeftDrawer : mRightDrawer; startAnimation(!isLeft, false); } else { mOpenTask = isLeft ? mLeftDrawer : mRightDrawer; } } else { // You finger is on Screen! } break; case STATE_OPENED: // Close other side first mOpenTask = isLeft ? mLeftDrawer : mRightDrawer; startAnimation(!isLeft, false); break; } break; case STATE_SLIDING: if (mAnimator.isRunning()) { if (mToOpen) { // Same purpose } else { cancelAnimation(); startAnimation(isLeft, true); } } else { // You finger is on Screen! } break; case STATE_OPENED: // Ok it is opened break; } } public void openDrawer(int gravity) { if (gravity == Gravity.LEFT) openDrawer(mLeftDrawer); else if (gravity == Gravity.RIGHT) openDrawer(mRightDrawer); } /** * Close the specified drawer view by animating it into view. * * @param drawerView * Drawer view to close */ public void closeDrawer(View drawerView) { if (drawerView == null || (drawerView != mLeftDrawer && drawerView != mRightDrawer)) return; boolean isLeft = drawerView == mLeftDrawer; switch (isLeft ? mLeftState : mRightState) { case STATE_CLOSED: // Ok it is closed break; case STATE_SLIDING: if (mAnimator.isRunning()) { if (mToOpen) { cancelAnimation(); startAnimation(isLeft, false); } else { // Same purpose } } else { // You finger is on Screen! } break; case STATE_OPENED: startAnimation(isLeft, false); break; } } public void closeDrawer(int gravity) { if (gravity == Gravity.LEFT) closeDrawer(mLeftDrawer); else if (gravity == Gravity.RIGHT) closeDrawer(mRightDrawer); } public boolean isDrawerOpen(View drawer) { if (drawer == mLeftDrawer) return mLeftOpened; else if (drawer == mRightDrawer) return mRightOpened; else throw new IllegalArgumentException(); } public boolean isDrawerOpen(int gravity) { if (gravity == Gravity.LEFT) return isDrawerOpen(mLeftDrawer); else if (gravity == Gravity.RIGHT) return isDrawerOpen(mRightDrawer); else throw new IllegalArgumentException(); } @Override protected void onFinishInflate() { final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final int absGravity = GravityCompat.getAbsoluteGravity( ((LayoutParams) child.getLayoutParams()).gravity, ViewCompat.getLayoutDirection(child)); if (absGravity == Gravity.NO_GRAVITY) { if (mContentView != null) throw new IllegalStateException("There is more than one content view"); mContentView = child; } else if ((absGravity & Gravity.LEFT) == Gravity.LEFT) { if (mLeftDrawer != null) throw new IllegalStateException("There is more than one left menu"); mLeftDrawer = child; } else if ((absGravity & Gravity.RIGHT) == Gravity.RIGHT) { if (mRightDrawer != null) throw new IllegalStateException("There is more than one right menu"); mRightDrawer = child; } else { throw new IllegalStateException("Child " + child + " at index " + i + " does not have a valid layout_gravity - must be Gravity.LEFT, " + "Gravity.RIGHT or Gravity.NO_GRAVITY"); } } if (mContentView == null) throw new IllegalStateException("There is no content view"); // Material is solid. // Input events cannot pass through material. if (mLeftDrawer != null) mLeftDrawer.setClickable(true); if (mRightDrawer != null) mRightDrawer.setClickable(true); } /** * @hide Internal use only; called to apply window insets when configured * with fitsSystemWindows="true" */ public void setChildInsets(Object insets, boolean draw) { mLastInsets = insets; mDrawStatusBarBackground = draw; setWillNotDraw(!draw && getBackground() == null); requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) throw new IllegalArgumentException( "SlidingDrawerLayout must be measured with MeasureSpec.EXACTLY."); setMeasuredDimension(widthSize, heightSize); final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this); final int layoutDirection = ViewCompat.getLayoutDirection(this); // Gravity value for each drawer we've seen. Only one of each permitted. final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (applyInsets) { final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection); if (ViewCompat.getFitsSystemWindows(child)) { IMPL.dispatchChildInsets(child, mLastInsets, cgrav); } else { IMPL.applyMarginInsets(lp, mLastInsets, cgrav); } } if (child == mContentView) { // Content views get measured at exactly the layout's size. final int contentWidthSpec = MeasureSpec.makeMeasureSpec( widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); final int contentHeightSpec = MeasureSpec.makeMeasureSpec( heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); child.measure(contentWidthSpec, contentHeightSpec); } else if (child == mLeftDrawer || child == mRightDrawer) { final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, mMinDrawerMargin + lp.leftMargin + lp.rightMargin, lp.width); final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, lp.topMargin + lp.bottomMargin, lp.height); child.measure(drawerWidthSpec, drawerHeightSpec); } else { throw new IllegalStateException("Don't call addView"); } } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mInLayout = true; final int width = r - l; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (child == mContentView) { child.layout(lp.leftMargin, lp.topMargin, lp.leftMargin + child.getMeasuredWidth(), lp.topMargin + child.getMeasuredHeight()); } else { // Drawer, if it wasn't onMeasure would have thrown an exception. final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); int childLeft; float percent; if (child == mLeftDrawer) { percent = mLeftPercent; childLeft = -childWidth + (int) (childWidth * percent); } else { // Right; onMeasure checked for us. percent = mRightPercent; childLeft = width - (int) (childWidth * percent); } final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (vgrav) { default: case Gravity.TOP: { child.layout(childLeft, lp.topMargin, childLeft + childWidth, lp.topMargin + childHeight); break; } case Gravity.BOTTOM: { final int height = b - t; child.layout(childLeft, height - lp.bottomMargin - child.getMeasuredHeight(), childLeft + childWidth, height - lp.bottomMargin); break; } case Gravity.CENTER_VERTICAL: { final int height = b - t; int childTop = (height - childHeight) / 2; // Offset for margins. If things don't fit right because of // bad measurement before, oh well. if (childTop < lp.topMargin) { childTop = lp.topMargin; } else if (childTop + childHeight > height - lp.bottomMargin) { childTop = height - lp.bottomMargin - childHeight; } child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); break; } } final int newVisibility = percent > 0 ? VISIBLE : INVISIBLE; if (child.getVisibility() != newVisibility) { child.setVisibility(newVisibility); } } } mInLayout = false; } @Override public void requestLayout() { if (!mInLayout) { super.requestLayout(); } } private boolean isDrawerUnder(int x, int y) { return ViewUtils.isViewUnder(mLeftDrawer, x, y) || ViewUtils.isViewUnder(mRightDrawer, x, y); } private boolean hasDrawer() { return mLeftDrawer != null || mRightDrawer != null; } private void slideLeftDrawer(int dx) { int leftWidth = mLeftDrawer.getWidth(); int oldLeft = mLeftDrawer.getLeft(); int newLeft = MathUtils.clamp(oldLeft + dx, -leftWidth, 0); int newVisible = newLeft == -leftWidth ? View.INVISIBLE : View.VISIBLE; if (mLeftDrawer.getVisibility() != newVisible) mLeftDrawer.setVisibility(newVisible); mLeftDrawer.offsetLeftAndRight(newLeft - oldLeft); updateDrawerSlide(mLeftDrawer, (newLeft + leftWidth) / (float) leftWidth); } private void slideRightDrawer(int dx) { int width = getWidth(); int rightWidth = mRightDrawer.getWidth(); int oldLeft = mRightDrawer.getLeft(); int newLeft = MathUtils.clamp(oldLeft + dx, width - rightWidth, width); int newVisible = newLeft == width ? View.INVISIBLE : View.VISIBLE; if (mRightDrawer.getVisibility() != newVisible) mRightDrawer.setVisibility(newVisible); mRightDrawer.offsetLeftAndRight(newLeft - oldLeft); updateDrawerSlide(mRightDrawer, (width - newLeft) / (float) rightWidth); } @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (Float) animation.getAnimatedValue(); int oldLeft = mTargetView.getLeft(); int newLeft = (int) MathUtils.lerp(mStartLeft, mEndLeft, value); if (mTargetView == mLeftDrawer) slideLeftDrawer(newLeft - oldLeft); else slideRightDrawer(newLeft - oldLeft); } @Override public void onAnimationStart(Animator animation) { updateDrawerState(mTargetView, STATE_SLIDING); } @Override public void onAnimationEnd(Animator animation) { if (!mCancelAnimation) { updateDrawerSlide(mTargetView, mToOpen ? 1.0f : 0.0f); updateDrawerState(mTargetView, mToOpen ? STATE_OPENED : STATE_CLOSED); if (mToOpen) dispatchOnDrawerOpened(mTargetView); else dispatchOnDrawerClosed(mTargetView); if (mOpenTask == null || mToOpen) ; else if (mOpenTask == mLeftDrawer) startAnimation(true, true); else if (mOpenTask == mRightDrawer) startAnimation(true, false); } else { mCancelAnimation = false; } mOpenTask = null; } @Override public void onAnimationCancel(Animator animation) { // Empty } @Override public void onAnimationRepeat(Animator animation) { // Empty } private void startAnimation(final boolean isLeft, final boolean isOpen) { mToOpen = isOpen; if (isLeft) { mTargetView = mLeftDrawer; mStartLeft = mLeftDrawer.getLeft(); mEndLeft = mToOpen ? 0 : -mLeftDrawer.getWidth(); } else { mTargetView = mRightDrawer; mStartLeft = mRightDrawer.getLeft(); mEndLeft = mToOpen ? getWidth() - mRightDrawer.getWidth() : getWidth(); } if (mStartLeft == mEndLeft) { // No need to animate updateDrawerSlide(mTargetView, mToOpen ? 1.0f : 0.0f); updateDrawerState(mTargetView, mToOpen ? STATE_OPENED : STATE_CLOSED); if (mToOpen) dispatchOnDrawerOpened(mTargetView); else dispatchOnDrawerClosed(mTargetView); return; } mAnimator.setDuration((long) (Ui.pix2dp(Math.abs(mEndLeft - mStartLeft)) * 1.5)); mAnimator.start(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); final float x = ev.getX(); final float y = ev.getY(); boolean interceptSlide = false; if (!hasDrawer() || isDrawerUnder((int) x, (int) y)) { return false; } if ((mLeftState != STATE_CLOSED || mRightState != STATE_CLOSED)) return true; switch (action) { case MotionEvent.ACTION_DOWN: mIntercepted = false; mInterceptPointNum = 0; mInitialMotionX = x; mInitialMotionY = y; break; case MotionEvent.ACTION_MOVE: final float adx = Math.abs(x - mInitialMotionX); final float ady = Math.abs(y - mInitialMotionY); final int slop = mDragHelper.getTouchSlop(); if (adx > slop && adx > (2 * ady)) interceptSlide = true; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mDragHelper.cancel(); break; } if (++mInterceptPointNum > 8) return false; mIntercepted = mDragHelper.shouldInterceptTouchEvent(ev) || interceptSlide; return mIntercepted; } private void cancelAnimation() { if (mAnimator.isRunning()) { mCancelAnimation = true; mAnimator.cancel(); } } @Override @SuppressLint("ClickableViewAccessibility") public boolean onTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); final float x = ev.getX(); final float y = ev.getY(); if (action == MotionEvent.ACTION_DOWN) { mInterceptPointNum = 1; mIntercepted = true; } if (!mIntercepted) return false; // Cancel animate cancelAnimation(); mDragHelper.processTouchEvent(ev); if (!hasDrawer() || isDrawerUnder((int) x, (int) y)) return false; switch (action) { case MotionEvent.ACTION_DOWN: mInitialMotionX = x; mInitialMotionY = y; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mInterceptPointNum = 0; if (mLeftState == STATE_SLIDING) { if ((mLeftOpened && mLeftPercent < CLOSE_SENSITIVITY) || (!mLeftOpened && mLeftPercent < OPEN_SENSITIVITY)) startAnimation(true, false); else if ((mLeftOpened && mLeftPercent >= CLOSE_SENSITIVITY) || (!mLeftOpened && mLeftPercent >= OPEN_SENSITIVITY)) startAnimation(true, true); } else if (mRightState == STATE_SLIDING) { if ((mRightOpened && mRightPercent < CLOSE_SENSITIVITY) || (!mRightOpened && mRightPercent < OPEN_SENSITIVITY)) startAnimation(false, false); else if ((mRightOpened && mRightPercent >= CLOSE_SENSITIVITY) || (!mRightOpened && mRightPercent >= OPEN_SENSITIVITY)) startAnimation(false, true); } break; } return true; } @Override protected int getChildDrawingOrder(int childCount, int i) { return i; } /** * Set a drawable to draw in the insets area for the status bar. * Note that this will only be activated if this DrawerLayout fitsSystemWindows. * * @param bg Background drawable to draw behind the status bar */ public void setStatusBarBackground(Drawable bg) { mStatusBarBackground = bg; } /** * Set a drawable to draw in the insets area for the status bar. * Note that this will only be activated if this DrawerLayout fitsSystemWindows. * * @param resId Resource id of a background drawable to draw behind the status bar */ public void setStatusBarBackground(int resId) { mStatusBarBackground = resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null; } /** * Set a drawable to draw in the insets area for the status bar. * Note that this will only be activated if this DrawerLayout fitsSystemWindows. * * @param color Color to use as a background drawable to draw behind the status bar * in 0xAARRGGBB format. */ public void setStatusBarBackgroundColor(int color) { mStatusBarBackground = new ColorDrawable(color); } @Override public void onDraw(Canvas c) { super.onDraw(c); if (mDrawStatusBarBackground && mStatusBarBackground != null) { final int inset = IMPL.getTopInset(mLastInsets); if (inset > 0) { mStatusBarBackground.setBounds(0, 0, getWidth(), inset); mStatusBarBackground.draw(c); } } } private void updateDrawerSlide(View drawerView, float percent) { boolean update = false; // Update percent if (drawerView == mLeftDrawer) { update = mLeftPercent != percent; mLeftPercent = percent; } else if (drawerView == mRightDrawer) { update = mRightPercent != percent; mRightPercent = percent; } // Callback if (update && mListener != null) { mListener.onDrawerSlide(drawerView, percent); } } private void updateDrawerState(View drawerView, int state) { boolean update = false; // Update state if (drawerView == mLeftDrawer) { update = mLeftState != state; mLeftState = state; } else if (drawerView == mRightDrawer) { update = mRightState != state; mRightState = state; } // Callback if (update && mListener != null) { mListener.onDrawerStateChanged(drawerView, state); } } private void dispatchOnDrawerClosed(View drawerView) { boolean update = false; // Update if (drawerView == mLeftDrawer) { update = mLeftOpened; mLeftOpened = false; } else if (drawerView == mRightDrawer) { update = mRightOpened; mRightOpened = false; } // Callback if (update && mListener != null) { mListener.onDrawerOpened(drawerView); } } private void dispatchOnDrawerOpened(View drawerView) { boolean update = false; // Update if (drawerView == mLeftDrawer) { update = !mLeftOpened; mLeftOpened = true; } else if (drawerView == mRightDrawer) { update = !mRightOpened; mRightOpened = true; } // Callback if (update && mListener != null) { mListener.onDrawerOpened(drawerView); } } @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) : p instanceof ViewGroup.MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) : new LayoutParams(p); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams && super.checkLayoutParams(p); } @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } public static class LayoutParams extends ViewGroup.MarginLayoutParams { public int gravity = Gravity.NO_GRAVITY; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); this.gravity = a.getInt(0, Gravity.NO_GRAVITY); a.recycle(); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(int width, int height, int gravity) { this(width, height); this.gravity = gravity; } public LayoutParams(LayoutParams source) { super(source); this.gravity = source.gravity; } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } public LayoutParams(ViewGroup.MarginLayoutParams source) { super(source); } } private class DragHelperCallback extends ViewDragHelper.Callback { @Override public boolean tryCaptureView(View child, int pointerId) { return child == mContentView; } @Override public int clampViewPositionVertical(View child, int top, int dy) { return child.getTop(); } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { if (!mIntercepted) return child.getLeft(); if (mLeftState == STATE_CLOSED && mRightState == STATE_CLOSED) { if (dx > 0 && mLeftDrawer != null) { slideLeftDrawer(dx); updateDrawerState(mLeftDrawer, STATE_SLIDING); } else if (dx < 0 && mRightDrawer != null) { slideRightDrawer(dx); updateDrawerState(mRightDrawer, STATE_SLIDING); } } else if (mLeftState != STATE_CLOSED) { slideLeftDrawer(dx); updateDrawerState(mLeftDrawer, STATE_SLIDING); } else if (mRightState != STATE_CLOSED) { slideRightDrawer(dx); updateDrawerState(mRightDrawer, STATE_SLIDING); } return child.getLeft(); } @Override public int getViewHorizontalDragRange(View child) { return Integer.MAX_VALUE; } } }