diff --git a/res/layout/favorite.xml b/res/layout/favorite.xml index a153a147a..6e20a31d3 100644 --- a/res/layout/favorite.xml +++ b/res/layout/favorite.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/res/layout/gallery_list.xml b/res/layout/gallery_list.xml index dc912c427..e29d58cd1 100644 --- a/res/layout/gallery_list.xml +++ b/res/layout/gallery_list.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/src/com/hippo/ehviewer/ui/FavouriteActivity.java b/src/com/hippo/ehviewer/ui/FavouriteActivity.java index 2842a3863..71a7cd1e9 100644 --- a/src/com/hippo/ehviewer/ui/FavouriteActivity.java +++ b/src/com/hippo/ehviewer/ui/FavouriteActivity.java @@ -35,7 +35,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.PathShape; import android.os.Bundle; -import android.support.v4.widget.DrawerLayout; import android.view.ActionMode; import android.view.Gravity; import android.view.LayoutInflater; @@ -80,6 +79,7 @@ import com.hippo.ehviewer.widget.LoadImageView; import com.hippo.ehviewer.widget.MaterialToast; import com.hippo.ehviewer.widget.PullViewGroup; import com.hippo.ehviewer.widget.RatingView; +import com.hippo.ehviewer.widget.SlidingDrawerLayout; public class FavouriteActivity extends AbsActivity implements ListView.MultiChoiceModeListener, @@ -96,7 +96,7 @@ public class FavouriteActivity extends AbsActivity private ActionBar mActionBar; private int mThemeColor; - private DrawerLayout mDrawerLayout; + private SlidingDrawerLayout mDrawerLayout; private ListView mMenu; private FrameLayout mContentView; private FitWindowView mStandard; @@ -173,7 +173,7 @@ public class FavouriteActivity extends AbsActivity materialMenu.setState(MaterialMenuDrawable.IconState.ARROW); // Get View - mDrawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout); + mDrawerLayout = (SlidingDrawerLayout) findViewById(R.id.drawerlayout); mMenu = (ListView) mDrawerLayout.findViewById(R.id.favorite_menu_list); mContentView = (FrameLayout) mDrawerLayout.findViewById(R.id.content); mStandard = (FitWindowView) mDrawerLayout.findViewById(R.id.standard); @@ -303,7 +303,7 @@ public class FavouriteActivity extends AbsActivity lp.rightMargin = magicSpacing; mActionableToastBar.setLayoutParams(lp); mList.setPadding(mList.getPaddingLeft(), t, mList.getPaddingRight(), b); - ((DrawerLayout.LayoutParams) mMenu.getLayoutParams()).topMargin = t; + ((SlidingDrawerLayout.LayoutParams) mMenu.getLayoutParams()).topMargin = t; mMenu.setPadding(mMenu.getPaddingLeft(), mMenu.getPaddingTop(), mMenu.getPaddingRight(), b); Ui.translucent(this, mThemeColor, t - Ui.ACTION_BAR_HEIGHT); diff --git a/src/com/hippo/ehviewer/ui/GalleryListActivity.java b/src/com/hippo/ehviewer/ui/GalleryListActivity.java index 1d17e6482..0adfd9414 100644 --- a/src/com/hippo/ehviewer/ui/GalleryListActivity.java +++ b/src/com/hippo/ehviewer/ui/GalleryListActivity.java @@ -41,7 +41,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Message; import android.provider.MediaStore; -import android.support.v4.widget.DrawerLayout; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ImageSpan; @@ -101,6 +100,7 @@ import com.hippo.ehviewer.widget.GalleryListView.OnGetListListener; import com.hippo.ehviewer.widget.LoadImageView; import com.hippo.ehviewer.widget.MaterialToast; import com.hippo.ehviewer.widget.RatingView; +import com.hippo.ehviewer.widget.SlidingDrawerLayout; import com.hippo.ehviewer.widget.SuggestionHelper; import com.hippo.ehviewer.widget.SuggestionTextView; import com.hippo.ehviewer.widget.TagListView; @@ -132,7 +132,7 @@ import com.hippo.ehviewer.windowsanimate.WindowsAnimate; */ public class GalleryListActivity extends AbsActivity implements View.OnClickListener, SearchView.OnQueryTextListener, SearchView.OnFocusChangeListener, - DrawerLayout.DrawerListener, EhClient.OnLoginListener, + SlidingDrawerLayout.DrawerListener, EhClient.OnLoginListener, GalleryListView.GalleryListViewHelper, FitWindowView.OnFitSystemWindowsListener { @SuppressWarnings("unused") @@ -162,7 +162,7 @@ public class GalleryListActivity extends AbsActivity implements View.OnClickList private MaterialMenuIcon mMaterialMenu; private boolean mDirection; - private DrawerLayout mDrawerLayout; + private SlidingDrawerLayout mDrawerLayout; private View mLeftMenu; private View mRightMenu; private FitWindowView mStandard; @@ -938,7 +938,7 @@ public class GalleryListActivity extends AbsActivity implements View.OnClickList } @Override - public void onDrawerStateChanged(int i) { + public void onDrawerStateChanged(View drawerView, int newState) { // Empty } @@ -946,8 +946,8 @@ public class GalleryListActivity extends AbsActivity implements View.OnClickList public void onFitSystemWindows(int l, int t, int r, int b) { mStaggeredGridView.setPadding(mStaggeredGridView.getPaddingLeft(), t, mStaggeredGridView.getPaddingRight(), b); - ((DrawerLayout.LayoutParams) mLeftMenu.getLayoutParams()).topMargin = t; - ((DrawerLayout.LayoutParams) mRightMenu.getLayoutParams()).topMargin = t; + ((SlidingDrawerLayout.LayoutParams) mLeftMenu.getLayoutParams()).topMargin = t; + ((SlidingDrawerLayout.LayoutParams) mRightMenu.getLayoutParams()).topMargin = t; mMenuList.setPadding(mMenuList.getPaddingLeft(), mMenuList.getPaddingTop(), mMenuList.getPaddingRight(), b); mQuickSearchList.setPadding(mQuickSearchList.getPaddingLeft(), mQuickSearchList.getPaddingTop(), @@ -1017,7 +1017,7 @@ public class GalleryListActivity extends AbsActivity implements View.OnClickList mMaterialMenu.setState(MaterialMenuDrawable.IconState.ARROW); // Get View - mDrawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout); + mDrawerLayout = (SlidingDrawerLayout) findViewById(R.id.drawerlayout); mLeftMenu = mDrawerLayout.findViewById(R.id.list_menu_left); mRightMenu = mDrawerLayout.findViewById(R.id.list_menu_right); @@ -1050,7 +1050,7 @@ public class GalleryListActivity extends AbsActivity implements View.OnClickList mActionBar.setHomeAsUpIndicator(mResources.getDrawable(R.drawable.ic_navigation_drawer)); mDrawerLayout.setDrawerListener(this); if (!mShowDrawer) - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mLeftMenu); + ; //mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mLeftMenu); // Content mGalleryListView.setGalleryListViewHelper(this); diff --git a/src/com/hippo/ehviewer/util/ViewUtils.java b/src/com/hippo/ehviewer/util/ViewUtils.java index d8789528f..2349b953e 100644 --- a/src/com/hippo/ehviewer/util/ViewUtils.java +++ b/src/com/hippo/ehviewer/util/ViewUtils.java @@ -149,4 +149,23 @@ public final class ViewUtils { if (oldLp != null) v.setLayoutParams(oldLp); } + + /** + * Determine if the supplied view is under the given point in the + * parent view's coordinate system. + * + * @param view Child view of the parent to hit test + * @param x X position to test in the parent's coordinate system + * @param y Y position to test in the parent's coordinate system + * @return true if the supplied view is under the given point, false otherwise + */ + public static boolean isViewUnder(View view, int x, int y) { + if (view == null) { + return false; + } + return x >= view.getLeft() && + x < view.getRight() && + y >= view.getTop() && + y < view.getBottom(); + } } diff --git a/src/com/hippo/ehviewer/widget/SlidingDrawerLayout.java b/src/com/hippo/ehviewer/widget/SlidingDrawerLayout.java new file mode 100644 index 000000000..1d91a5b30 --- /dev/null +++ b/src/com/hippo/ehviewer/widget/SlidingDrawerLayout.java @@ -0,0 +1,1023 @@ +/* + * 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)) * 2)); + 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 > (3 * ady)) + interceptSlide = true; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mDragHelper.cancel(); + break; + } + + if (mInterceptPointNum > 8) + return false; + else + mInterceptPointNum++; + + 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) { + + if (!mIntercepted) + return false; + + // Cancel animate + cancelAnimation(); + + final int action = MotionEventCompat.getActionMasked(ev); + final float x = ev.getX(); + final float y = ev.getY(); + + 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: + + 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; + } + + mDragHelper.processTouchEvent(ev); + + 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; + } + } +}