From 27abd5f5e03189bc0728de4efd85e0962c9e1d2c Mon Sep 17 00:00:00 2001 From: seven332 Date: Fri, 22 May 2015 21:45:48 +0800 Subject: [PATCH] Adjust DrawerLayout for KK --- .../hippo/ehviewer/ui/ContentActivity.java | 22 +- .../ehviewer/widget/DrawerLeftPanel.java | 31 +- .../hippo/ehviewer/widget/FitStageLayout.java | 56 + .../ehviewer/widget/StatusBarLayout.java | 163 -- .../java/com/hippo/widget/DrawerLayout.java | 1789 +++++++++++++++++ .../java/com/hippo/widget/FitPaddingImpl.java | 20 + app/src/main/res/layout/activity_content.xml | 21 +- 7 files changed, 1891 insertions(+), 211 deletions(-) create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/FitStageLayout.java delete mode 100644 app/src/main/java/com/hippo/ehviewer/widget/StatusBarLayout.java create mode 100644 app/src/main/java/com/hippo/widget/DrawerLayout.java create mode 100644 app/src/main/java/com/hippo/widget/FitPaddingImpl.java diff --git a/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java index d39484eb1..100c337f9 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java @@ -21,12 +21,10 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.NonNull; -import android.support.v4.widget.DrawerLayout; import android.view.Gravity; import android.view.View; import android.widget.AdapterView; -import com.hippo.vectorold.content.VectorContext; import com.hippo.ehviewer.Constants; import com.hippo.ehviewer.R; import com.hippo.ehviewer.ui.scene.GalleryListScene; @@ -34,16 +32,18 @@ import com.hippo.ehviewer.ui.scene.SettingsHeaderScene; import com.hippo.ehviewer.ui.scene.SignInScene; import com.hippo.ehviewer.util.Config; import com.hippo.ehviewer.widget.DrawerLeftPanel; -import com.hippo.ehviewer.widget.StatusBarLayout; +import com.hippo.ehviewer.widget.FitStageLayout; import com.hippo.scene.Announcer; import com.hippo.scene.SimpleCurtain; import com.hippo.scene.StageActivity; import com.hippo.scene.StageLayout; import com.hippo.util.Messenger; +import com.hippo.vectorold.content.VectorContext; +import com.hippo.widget.DrawerLayout; import com.hippo.widget.DrawerListView; public final class ContentActivity extends StageActivity - implements StatusBarLayout.OnGetFitPaddingBottomListener, + implements FitStageLayout.OnFitPaddingBottomListener, DrawerLeftPanel.Helper { public static final int DRAWER_LIST_NONE = -1; @@ -57,8 +57,7 @@ public final class ContentActivity extends StageActivity private Resources mResources; private DrawerLayout mDrawerLayout; - private StatusBarLayout mStatusBarLayout; - private StageLayout mContentLayout; + private FitStageLayout mFitStageLayout; private DrawerLeftPanel mDrawerLeftPanel; private DrawerListView mDrawerListView; @@ -110,12 +109,11 @@ public final class ContentActivity extends StageActivity mResources = getResources(); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - mStatusBarLayout = (StatusBarLayout) mDrawerLayout.getChildAt(0); - mContentLayout = (StageLayout) mStatusBarLayout.findViewById(R.id.stage); + mFitStageLayout = (FitStageLayout) mDrawerLayout.getChildAt(0); mDrawerLeftPanel = (DrawerLeftPanel) mDrawerLayout.getChildAt(1); mDrawerListView = mDrawerLeftPanel.getDrawerListView(); - mStatusBarLayout.setOnGetFitPaddingBottomListener(this); + mFitStageLayout.setOnFitPaddingBottomListener(this); mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow_left, Gravity.LEFT); @@ -149,7 +147,7 @@ public final class ContentActivity extends StageActivity @Override public @NonNull StageLayout getStageLayout() { - return mContentLayout; + return mFitStageLayout; } @Override @@ -181,8 +179,8 @@ public final class ContentActivity extends StageActivity } @Override - public void onGetFitPaddingBottom(int b) { - setFitPaddingBottom(b); + public void onFitPaddingBottom(int bottom) { + setFitPaddingBottom(bottom); } @Override diff --git a/app/src/main/java/com/hippo/ehviewer/widget/DrawerLeftPanel.java b/app/src/main/java/com/hippo/ehviewer/widget/DrawerLeftPanel.java index e91e864ae..a966daea7 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/DrawerLeftPanel.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/DrawerLeftPanel.java @@ -17,11 +17,9 @@ package com.hippo.ehviewer.widget; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; -import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -37,8 +35,9 @@ import com.hippo.ehviewer.util.Config; import com.hippo.util.FrescoUtils; import com.hippo.util.Messenger; import com.hippo.widget.DrawerListView; +import com.hippo.widget.FitPaddingImpl; -public class DrawerLeftPanel extends LinearLayout { +public class DrawerLeftPanel extends LinearLayout implements FitPaddingImpl{ private static final String STATE_KEY_SUPER = "super"; private static final String STATE_KEY_DRAWER_LIST_VIEW = "drawer_list_view"; @@ -206,43 +205,35 @@ public class DrawerLeftPanel extends LinearLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - @SuppressWarnings("deprecation") @Override - protected boolean fitSystemWindows(@NonNull Rect insets) { - int fitPaddingTop = insets.top; - int fitPaddingBottom = insets.bottom; - - if (mFitPaddingTop != fitPaddingTop) { - mFitPaddingTop = fitPaddingTop; + public void onFitPadding(int left, int top, int right, int bottom) { + if (mFitPaddingTop != top) { + mFitPaddingTop = top; ViewGroup.LayoutParams lp; lp = mKKStatusBarBg.getLayoutParams(); - lp.height = fitPaddingTop; + lp.height = top; mKKStatusBarBg.setLayoutParams(lp); lp = mSuperUserPanel.getLayoutParams(); - lp.height = mSuperUserPanelHeight + fitPaddingTop; + lp.height = mSuperUserPanelHeight + top; mSuperUserPanel.setLayoutParams(lp); mUserPanel.setPadding(mUserPanel.getPaddingLeft(), - mUserPanelOriginalPaddingTop + fitPaddingTop, + mUserPanelOriginalPaddingTop + top, mUserPanel.getPaddingRight(), mUserPanel.getPaddingBottom()); } - if (mFitPaddingBottom != fitPaddingBottom) { - mFitPaddingBottom = fitPaddingBottom; + if (mFitPaddingBottom != bottom) { + mFitPaddingBottom = bottom; mDrawerListView.setPadding(mDrawerListView.getPaddingLeft(), mDrawerListView.getPaddingTop(), mDrawerListView.getPaddingRight(), - mDrawerListViewOriginalPaddingBottom + fitPaddingBottom); + mDrawerListViewOriginalPaddingBottom + bottom); } - - insets.set(insets.left, 0, insets.right, 0); - - return super.fitSystemWindows(insets); } public interface Helper { diff --git a/app/src/main/java/com/hippo/ehviewer/widget/FitStageLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/FitStageLayout.java new file mode 100644 index 000000000..f91aaae53 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/FitStageLayout.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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 + * 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.content.Context; +import android.util.AttributeSet; + +import com.hippo.scene.StageLayout; +import com.hippo.widget.FitPaddingImpl; + +public class FitStageLayout extends StageLayout implements FitPaddingImpl { + + private OnFitPaddingBottomListener mOnFitPaddingBottomListener; + + public FitStageLayout(Context context) { + super(context); + } + + public FitStageLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FitStageLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setOnFitPaddingBottomListener(OnFitPaddingBottomListener listener) { + mOnFitPaddingBottomListener = listener; + } + + @Override + public void onFitPadding(int left, int top, int right, int bottom) { + setPadding(0, top, 0, 0); + + if (mOnFitPaddingBottomListener != null) { + mOnFitPaddingBottomListener.onFitPaddingBottom(bottom); + } + } + + public interface OnFitPaddingBottomListener { + void onFitPaddingBottom(int bottom); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/StatusBarLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/StatusBarLayout.java deleted file mode 100644 index 5743d09d3..000000000 --- a/app/src/main/java/com/hippo/ehviewer/widget/StatusBarLayout.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2015 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 - * 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.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.os.Build; -import android.support.annotation.NonNull; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; - -import com.hippo.ehviewer.R; -import com.hippo.util.Log; - -public class StatusBarLayout extends ViewGroup { - - private int mFitPaddingTop = -1; - private OnGetFitPaddingBottomListener mOnGetFitPaddingBottomListener; - - private Paint mPaint; - private int mStatusBarColor = Color.BLACK; - - public StatusBarLayout(Context context) { - super(context); - init(context, null); - } - - public StatusBarLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } - - public StatusBarLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs); - } - - private void init(Context context, AttributeSet attrs) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - mPaint = new Paint(); - if (attrs != null) { - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StatusBarLayout); - int color = a.getColor(R.styleable.StatusBarLayout_statusBarColor, Color.BLACK); - mPaint.setColor(color); - setWillNotDraw(Color.alpha(color) == 0); - a.recycle(); - } else { - setWillNotDraw(false); - } - } - } - - public void setStatusBarColor(int color) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - if (mStatusBarColor != color) { - mStatusBarColor = color; - mPaint.setColor(color); - setWillNotDraw(Color.alpha(color) == 0); - invalidate(); - } - } - } - - @Override - protected void onDraw(Canvas canvas) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - if (mFitPaddingTop > 0) { - canvas.drawRect(0, 0, getWidth(), mFitPaddingTop, mPaint); - } - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int count = getChildCount(); - - int maxHeight = 0; - int maxWidth = 0; - - // Find out how big everyone wants to be - measureChildren(widthMeasureSpec, heightMeasureSpec); - - // Find rightmost and bottom-most child - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - if (child.getVisibility() != GONE) { - int childRight; - int childBottom; - - childRight = child.getMeasuredWidth(); - childBottom = child.getMeasuredHeight(); - - maxWidth = Math.max(maxWidth, childRight); - maxHeight = Math.max(maxHeight, childBottom); - } - } - - // Account for padding too - maxWidth += getPaddingLeft() + getPaddingRight(); - maxHeight += getPaddingTop() + getPaddingBottom(); - - // Check against minimum height and width - maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); - maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); - - setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0), - resolveSizeAndState(maxHeight, heightMeasureSpec, 0)); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int paddingLeft = getPaddingLeft(); - int paddingTop = getPaddingTop(); - int size = getChildCount(); - for (int i = 0; i < size; i++) { - View child = getChildAt(i); - if (child.getVisibility() == View.GONE) { - continue; - } - child.layout(paddingLeft, paddingTop, paddingLeft + child.getMeasuredWidth(), paddingTop + child.getMeasuredHeight()); - } - } - - public void setOnGetFitPaddingBottomListener(OnGetFitPaddingBottomListener listener) { - mOnGetFitPaddingBottomListener = listener; - } - - @SuppressWarnings("deprecation") - @Override - protected boolean fitSystemWindows(@NonNull Rect insets) { - mFitPaddingTop = insets.top; - - if (mOnGetFitPaddingBottomListener != null) { - mOnGetFitPaddingBottomListener.onGetFitPaddingBottom(insets.bottom); - } - - insets.set(insets.left, insets.top, insets.right, 0); - - return super.fitSystemWindows(insets); - } - - public interface OnGetFitPaddingBottomListener { - void onGetFitPaddingBottom(int b); - } -} diff --git a/app/src/main/java/com/hippo/widget/DrawerLayout.java b/app/src/main/java/com/hippo/widget/DrawerLayout.java new file mode 100644 index 000000000..a5c4c20cf --- /dev/null +++ b/app/src/main/java/com/hippo/widget/DrawerLayout.java @@ -0,0 +1,1789 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * 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.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.support.annotation.DrawableRes; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.view.AccessibilityDelegateCompat; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.KeyEventCompat; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewGroupCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.widget.ViewDragHelper; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * DrawerLayout acts as a top-level container for window content that allows for + * interactive "drawer" views to be pulled out from the edge of the window. + * + *

Drawer positioning and layout is controlled using the android:layout_gravity + * attribute on child views corresponding to which side of the view you want the drawer + * to emerge from: left or right. (Or start/end on platform versions that support layout direction.) + *

+ * + *

To use a DrawerLayout, position your primary content view as the first child with + * a width and height of match_parent. Add drawers as child views after the main + * content view and set the layout_gravity appropriately. Drawers commonly use + * match_parent for height with a fixed width.

+ * + *

{@link DrawerListener} can be used to monitor the state and motion of drawer views. + * Avoid performing expensive operations such as layout during animation as it can cause + * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state. + * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.

+ * + *

As per the Android Design + * guide, any drawers positioned to the left/start should + * always contain content for navigating around the application, whereas any drawers + * positioned to the right/end should always contain actions to take on the current content. + * This preserves the same navigation left, actions right structure present in the Action Bar + * and elsewhere.

+ * + *

For more information about how to use DrawerLayout, read Creating a Navigation + * Drawer.

+ */ +@SuppressLint("RtlHardcoded") +public class DrawerLayout extends ViewGroup { + private static final String TAG = "DrawerLayout"; + + @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING}) + @Retention(RetentionPolicy.SOURCE) + private @interface State {} + + /** + * Indicates that any drawers are in an idle, settled state. No animation is in progress. + */ + public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; + + /** + * Indicates that a drawer is currently being dragged by the user. + */ + public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING; + + /** + * Indicates that a drawer is in the process of settling to a final position. + */ + public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; + + @IntDef({LOCK_MODE_UNLOCKED, LOCK_MODE_LOCKED_CLOSED, LOCK_MODE_LOCKED_OPEN}) + @Retention(RetentionPolicy.SOURCE) + private @interface LockMode {} + + /** + * The drawer is unlocked. + */ + public static final int LOCK_MODE_UNLOCKED = 0; + + /** + * The drawer is locked closed. The user may not open it, though + * the app may open it programmatically. + */ + public static final int LOCK_MODE_LOCKED_CLOSED = 1; + + /** + * The drawer is locked open. The user may not close it, though the app + * may close it programmatically. + */ + public static final int LOCK_MODE_LOCKED_OPEN = 2; + + @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END}) + @Retention(RetentionPolicy.SOURCE) + private @interface EdgeGravity {} + + + private static final int MIN_DRAWER_MARGIN = 64; // dp + + private static final int DEFAULT_SCRIM_COLOR = 0x99000000; + + /** + * Length of time to delay before peeking the drawer. + */ + private static final int PEEK_DELAY = 160; // ms + + /** + * Minimum velocity that will be detected as a fling + */ + private static final int MIN_FLING_VELOCITY = 400; // dips per second + + /** + * Experimental feature. + */ + private static final boolean ALLOW_EDGE_LOCK = false; + + private static final boolean CHILDREN_DISALLOW_INTERCEPT = true; + + private static final float TOUCH_SLOP_SENSITIVITY = 1.f; + + private static final int[] LAYOUT_ATTRS = new int[] { + android.R.attr.layout_gravity + }; + + /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */ + private static final boolean CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19; + + private final ChildAccessibilityDelegate mChildAccessibilityDelegate = + new ChildAccessibilityDelegate(); + + private int mMinDrawerMargin; + + private int mScrimColor = DEFAULT_SCRIM_COLOR; + private float mScrimOpacity; + private Paint mScrimPaint = new Paint(); + + private final ViewDragHelper mLeftDragger; + private final ViewDragHelper mRightDragger; + private final ViewDragCallback mLeftCallback; + private final ViewDragCallback mRightCallback; + private int mDrawerState; + private boolean mInLayout; + private boolean mFirstLayout = true; + private int mLockModeLeft; + private int mLockModeRight; + private boolean mChildrenCanceledTouch; + + private DrawerListener mListener; + + private float mInitialMotionX; + private float mInitialMotionY; + + private Drawable mShadowLeft; + private Drawable mShadowRight; + + private CharSequence mTitleLeft; + private CharSequence mTitleRight; + + /** + * 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 + */ + void onDrawerSlide(View drawerView, float slideOffset); + + /** + * 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 + */ + void onDrawerOpened(View drawerView); + + /** + * Called when a drawer has settled in a completely closed state. + * + * @param drawerView Drawer view that is now closed + */ + 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 + */ + void onDrawerStateChanged(@State int newState); + } + + /** + * Stub/no-op implementations of all methods of {@link DrawerListener}. + * Override this if you only care about a few of the available callback methods. + */ + public static abstract class SimpleDrawerListener implements DrawerListener { + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + } + + @Override + public void onDrawerOpened(View drawerView) { + } + + @Override + public void onDrawerClosed(View drawerView) { + } + + @Override + public void onDrawerStateChanged(int newState) { + } + } + + public DrawerLayout(Context context) { + this(context, null); + } + + public DrawerLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DrawerLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); + final float density = getResources().getDisplayMetrics().density; + mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f); + final float minVel = MIN_FLING_VELOCITY * density; + + mLeftCallback = new ViewDragCallback(Gravity.LEFT); + mRightCallback = new ViewDragCallback(Gravity.RIGHT); + + mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback); + mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); + mLeftDragger.setMinVelocity(minVel); + mLeftCallback.setDragger(mLeftDragger); + + mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback); + mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT); + mRightDragger.setMinVelocity(minVel); + mRightCallback.setDragger(mRightDragger); + + // So that we can catch the back button + setFocusableInTouchMode(true); + + ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); + + ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate()); + ViewGroupCompat.setMotionEventSplittingEnabled(this, false); + } + + /** + * Set a simple drawable used for the left or right shadow. + * The drawable provided must have a nonzero intrinsic width. + * + * @param shadowDrawable Shadow drawable to use at the edge of a drawer + * @param gravity Which drawer the shadow should apply to + */ + public void setDrawerShadow(Drawable shadowDrawable, @EdgeGravity int gravity) { + /* + * TODO Someone someday might want to set more complex drawables here. + * They're probably nuts, but we might want to consider registering callbacks, + * setting states, etc. properly. + */ + + final int absGravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)); + if ((absGravity & Gravity.LEFT) == Gravity.LEFT) { + mShadowLeft = shadowDrawable; + invalidate(); + } + if ((absGravity & Gravity.RIGHT) == Gravity.RIGHT) { + mShadowRight = shadowDrawable; + invalidate(); + } + } + + /** + * Set a simple drawable used for the left or right shadow. + * The drawable provided must have a nonzero intrinsic width. + * + * @param resId Resource id of a shadow drawable to use at the edge of a drawer + * @param gravity Which drawer the shadow should apply to + */ + public void setDrawerShadow(@DrawableRes int resId, @EdgeGravity int gravity) { + setDrawerShadow(getResources().getDrawable(resId), gravity); + } + + /** + * Set a color to use for the scrim that obscures primary content while a drawer is open. + * + * @param color Color to use in 0xAARRGGBB format. + */ + public void setScrimColor(int color) { + mScrimColor = color; + invalidate(); + } + + /** + * Set a listener to be notified of drawer events. + * + * @param listener Listener to notify when drawer events occur + * @see DrawerListener + */ + public void setDrawerListener(DrawerListener listener) { + mListener = listener; + } + + /** + * Enable or disable interaction with all drawers. + * + *

This allows the application to restrict the user's ability to open or close + * any drawer within this layout. DrawerLayout will still respond to calls to + * {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked.

+ * + *

Locking drawers open or closed will implicitly open or close + * any drawers as appropriate.

+ * + * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, + * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. + */ + public void setDrawerLockMode(@LockMode int lockMode) { + setDrawerLockMode(lockMode, Gravity.LEFT); + setDrawerLockMode(lockMode, Gravity.RIGHT); + } + + /** + * Enable or disable interaction with the given drawer. + * + *

This allows the application to restrict the user's ability to open or close + * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)}, + * {@link #closeDrawer(int)} and friends if a drawer is locked.

+ * + *

Locking a drawer open or closed will implicitly open or close + * that drawer as appropriate.

+ * + * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, + * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. + * @param edgeGravity Gravity.LEFT, RIGHT, START or END. + * Expresses which drawer to change the mode for. + * + * @see #LOCK_MODE_UNLOCKED + * @see #LOCK_MODE_LOCKED_CLOSED + * @see #LOCK_MODE_LOCKED_OPEN + */ + public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) { + final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, ViewCompat.getLayoutDirection(this)); + if (absGravity == Gravity.LEFT) { + mLockModeLeft = lockMode; + } else if (absGravity == Gravity.RIGHT) { + mLockModeRight = lockMode; + } + if (lockMode != LOCK_MODE_UNLOCKED) { + // Cancel interaction in progress + final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger; + helper.cancel(); + } + switch (lockMode) { + case LOCK_MODE_LOCKED_OPEN: + final View toOpen = findDrawerWithGravity(absGravity); + if (toOpen != null) { + openDrawer(toOpen); + } + break; + case LOCK_MODE_LOCKED_CLOSED: + final View toClose = findDrawerWithGravity(absGravity); + if (toClose != null) { + closeDrawer(toClose); + } + break; + // default: do nothing + } + } + + /** + * Enable or disable interaction with the given drawer. + * + *

This allows the application to restrict the user's ability to open or close + * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)}, + * {@link #closeDrawer(int)} and friends if a drawer is locked.

+ * + *

Locking a drawer open or closed will implicitly open or close + * that drawer as appropriate.

+ * + * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, + * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. + * @param drawerView The drawer view to change the lock mode for + * + * @see #LOCK_MODE_UNLOCKED + * @see #LOCK_MODE_LOCKED_CLOSED + * @see #LOCK_MODE_LOCKED_OPEN + */ + public void setDrawerLockMode(@LockMode int lockMode, View drawerView) { + if (!isDrawerView(drawerView)) { + throw new IllegalArgumentException("View " + drawerView + " is not a " + + "drawer with appropriate layout_gravity"); + } + final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; + //noinspection ResourceType + setDrawerLockMode(lockMode, gravity); + } + + /** + * Check the lock mode of the drawer with the given gravity. + * + * @param edgeGravity Gravity of the drawer to check + * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or + * {@link #LOCK_MODE_LOCKED_OPEN}. + */ + @LockMode + public int getDrawerLockMode(@EdgeGravity int edgeGravity) { + final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, ViewCompat.getLayoutDirection(this)); + if (absGravity == Gravity.LEFT) { + return mLockModeLeft; + } else if (absGravity == Gravity.RIGHT) { + return mLockModeRight; + } + return LOCK_MODE_UNLOCKED; + } + + /** + * Check the lock mode of the given drawer view. + * + * @param drawerView Drawer view to check lock mode + * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or + * {@link #LOCK_MODE_LOCKED_OPEN}. + */ + @LockMode + public int getDrawerLockMode(View drawerView) { + final int absGravity = getDrawerViewAbsoluteGravity(drawerView); + if (absGravity == Gravity.LEFT) { + return mLockModeLeft; + } else if (absGravity == Gravity.RIGHT) { + return mLockModeRight; + } + return LOCK_MODE_UNLOCKED; + } + + /** + * Sets the title of the drawer with the given gravity. + *

+ * When accessibility is turned on, this is the title that will be used to + * identify the drawer to the active accessibility service. + * + * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which + * drawer to set the title for. + * @param title The title for the drawer. + */ + public void setDrawerTitle(@EdgeGravity int edgeGravity, CharSequence title) { + final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, ViewCompat.getLayoutDirection(this)); + if (absGravity == Gravity.LEFT) { + mTitleLeft = title; + } else if (absGravity == Gravity.RIGHT) { + mTitleRight = title; + } + } + + /** + * Returns the title of the drawer with the given gravity. + * + * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which + * drawer to return the title for. + * @return The title of the drawer, or null if none set. + * @see #setDrawerTitle(int, CharSequence) + */ + @Nullable + public CharSequence getDrawerTitle(@EdgeGravity int edgeGravity) { + final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, ViewCompat.getLayoutDirection(this)); + if (absGravity == Gravity.LEFT) { + return mTitleLeft; + } else if (absGravity == Gravity.RIGHT) { + return mTitleRight; + } + return null; + } + + /** + * Resolve the shared state of all drawers from the component ViewDragHelpers. + * Should be called whenever a ViewDragHelper's state changes. + */ + void updateDrawerState(int forGravity, @State int activeState, View activeDrawer) { + final int leftState = mLeftDragger.getViewDragState(); + final int rightState = mRightDragger.getViewDragState(); + + final int state; + if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) { + state = STATE_DRAGGING; + } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) { + state = STATE_SETTLING; + } else { + state = STATE_IDLE; + } + + if (activeDrawer != null && activeState == STATE_IDLE) { + final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams(); + if (lp.onScreen == 0) { + dispatchOnDrawerClosed(activeDrawer); + } else if (lp.onScreen == 1) { + dispatchOnDrawerOpened(activeDrawer); + } + } + + if (state != mDrawerState) { + mDrawerState = state; + + if (mListener != null) { + mListener.onDrawerStateChanged(state); + } + } + } + + void dispatchOnDrawerClosed(View drawerView) { + final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); + if (lp.knownOpen) { + lp.knownOpen = false; + if (mListener != null) { + mListener.onDrawerClosed(drawerView); + } + + updateChildrenImportantForAccessibility(drawerView, false); + + // Only send WINDOW_STATE_CHANGE if the host has window focus. This + // may change if support for multiple foreground windows (e.g. IME) + // improves. + if (hasWindowFocus()) { + final View rootView = getRootView(); + if (rootView != null) { + rootView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } + } + } + } + + void dispatchOnDrawerOpened(View drawerView) { + final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); + if (!lp.knownOpen) { + lp.knownOpen = true; + if (mListener != null) { + mListener.onDrawerOpened(drawerView); + } + + updateChildrenImportantForAccessibility(drawerView, true); + + // Only send WINDOW_STATE_CHANGE if the host has window focus. + if (hasWindowFocus()) { + sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } + + drawerView.requestFocus(); + } + } + + private void updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (!isDrawerOpen && !isDrawerView(child) + || isDrawerOpen && child == drawerView) { + // Drawer is closed and this is a content view or this is an + // open drawer view, so it should be visible. + ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); + } else { + ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } + } + } + + void dispatchOnDrawerSlide(View drawerView, float slideOffset) { + if (mListener != null) { + mListener.onDrawerSlide(drawerView, slideOffset); + } + } + + void setDrawerViewOffset(View drawerView, float slideOffset) { + final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); + if (slideOffset == lp.onScreen) { + return; + } + + lp.onScreen = slideOffset; + dispatchOnDrawerSlide(drawerView, slideOffset); + } + + float getDrawerViewOffset(View drawerView) { + return ((LayoutParams) drawerView.getLayoutParams()).onScreen; + } + + /** + * @return the absolute gravity of the child drawerView, resolved according + * to the current layout direction + */ + int getDrawerViewAbsoluteGravity(View drawerView) { + final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; + return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)); + } + + boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) { + final int absGravity = getDrawerViewAbsoluteGravity(drawerView); + return (absGravity & checkFor) == checkFor; + } + + View findOpenDrawer() { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (((LayoutParams) child.getLayoutParams()).knownOpen) { + return child; + } + } + return null; + } + + /** + * @param gravity the gravity of the child to return. If specified as a + * relative value, it will be resolved + * according to the current + * layout direction. + * @return the drawer with the specified gravity + */ + View findDrawerWithGravity(int gravity) { + final int absHorizGravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK; + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final int childAbsGravity = getDrawerViewAbsoluteGravity(child); + if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) { + return child; + } + } + return null; + } + + /** + * Simple gravity to string - only supports LEFT and RIGHT for debugging output. + * + * @param gravity Absolute gravity value + * @return LEFT or RIGHT as appropriate, or a hex string + */ + static String gravityToString(@EdgeGravity int gravity) { + if ((gravity & Gravity.LEFT) == Gravity.LEFT) { + return "LEFT"; + } + if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { + return "RIGHT"; + } + return Integer.toHexString(gravity); + } + + @SuppressWarnings("deprecation") + @Override + protected boolean fitSystemWindows(@NonNull Rect insets) { + int fitPaddingTop = insets.top; + int fitPaddingBottom = insets.bottom; + + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child instanceof FitPaddingImpl) { + ((FitPaddingImpl) child).onFitPadding(0 ,fitPaddingTop, 0, fitPaddingBottom); + } + } + + insets.set(insets.left, 0 , insets.right, 0); + return super.fitSystemWindows(insets); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mFirstLayout = true; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mFirstLayout = true; + } + + @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) { + if (isInEditMode()) { + // Don't crash the layout editor. Consume all of the space if specified + // or pick a magic number from thin air otherwise. + // TODO Better communication with tools of this bogus state. + // It will crash on a real device. + if (widthMode == MeasureSpec.AT_MOST) { + widthMode = MeasureSpec.EXACTLY; + } else if (widthMode == MeasureSpec.UNSPECIFIED) { + widthMode = MeasureSpec.EXACTLY; + widthSize = 300; + } + if (heightMode == MeasureSpec.AT_MOST) { + heightMode = MeasureSpec.EXACTLY; + } + else if (heightMode == MeasureSpec.UNSPECIFIED) { + heightMode = MeasureSpec.EXACTLY; + heightSize = 300; + } + } else { + throw new IllegalArgumentException( + "DrawerLayout must be measured with MeasureSpec.EXACTLY."); + } + } + + setMeasuredDimension(widthSize, heightSize); + + // Gravity value for each drawer we've seen. Only one of each permitted. + int foundDrawers = 0; + 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 (isContentView(child)) { + // 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 (isDrawerView(child)) { + final int childGravity = + getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK; + if ((foundDrawers & childGravity) != 0) { + //noinspection ResourceType + throw new IllegalStateException("Child drawer has absolute gravity " + + gravityToString(childGravity) + " but this " + TAG + " already has a " + + "drawer view along that edge"); + } + 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("Child " + child + " at index " + i + + " does not have a valid layout_gravity - must be Gravity.LEFT, " + + "Gravity.RIGHT or Gravity.NO_GRAVITY"); + } + } + } + + @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 (isContentView(child)) { + 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; + + final float newOffset; + if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { + childLeft = -childWidth + (int) (childWidth * lp.onScreen); + newOffset = (float) (childWidth + childLeft) / childWidth; + } else { // Right; onMeasure checked for us. + childLeft = width - (int) (childWidth * lp.onScreen); + newOffset = (float) (width - childLeft) / childWidth; + } + + final boolean changeOffset = newOffset != lp.onScreen; + + 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; + } + } + + if (changeOffset) { + setDrawerViewOffset(child, newOffset); + } + + final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE; + if (child.getVisibility() != newVisibility) { + child.setVisibility(newVisibility); + } + } + } + mInLayout = false; + mFirstLayout = false; + } + + @Override + public void requestLayout() { + if (!mInLayout) { + super.requestLayout(); + } + } + + @Override + public void computeScroll() { + final int childCount = getChildCount(); + float scrimOpacity = 0; + for (int i = 0; i < childCount; i++) { + final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen; + scrimOpacity = Math.max(scrimOpacity, onscreen); + } + mScrimOpacity = scrimOpacity; + + // "|" used on purpose; both need to run. + if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) { + ViewCompat.postInvalidateOnAnimation(this); + } + } + + private static boolean hasOpaqueBackground(View v) { + final Drawable bg = v.getBackground(); + return bg != null && bg.getOpacity() == PixelFormat.OPAQUE; + } + + @Override + protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) { + final int height = getHeight(); + final boolean drawingContent = isContentView(child); + int clipLeft = 0, clipRight = getWidth(); + + final int restoreCount = canvas.save(); + if (drawingContent) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View v = getChildAt(i); + if (v == child || v.getVisibility() != VISIBLE || + !hasOpaqueBackground(v) || !isDrawerView(v) || + v.getHeight() < height) { + continue; + } + + if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) { + final int vright = v.getRight(); + if (vright > clipLeft) clipLeft = vright; + } else { + final int vleft = v.getLeft(); + if (vleft < clipRight) clipRight = vleft; + } + } + canvas.clipRect(clipLeft, 0, clipRight, getHeight()); + } + final boolean result = super.drawChild(canvas, child, drawingTime); + canvas.restoreToCount(restoreCount); + + if (mScrimOpacity > 0 && drawingContent) { + final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; + final int imag = (int) (baseAlpha * mScrimOpacity); + final int color = imag << 24 | (mScrimColor & 0xffffff); + mScrimPaint.setColor(color); + + canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint); + } else if (mShadowLeft != null && checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { + final int shadowWidth = mShadowLeft.getIntrinsicWidth(); + final int childRight = child.getRight(); + final int drawerPeekDistance = mLeftDragger.getEdgeSize(); + final float alpha = + Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f)); + mShadowLeft.setBounds(childRight, child.getTop(), + childRight + shadowWidth, child.getBottom()); + mShadowLeft.setAlpha((int) (0xff * alpha)); + mShadowLeft.draw(canvas); + } else if (mShadowRight != null && checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) { + final int shadowWidth = mShadowRight.getIntrinsicWidth(); + final int childLeft = child.getLeft(); + final int showing = getWidth() - childLeft; + final int drawerPeekDistance = mRightDragger.getEdgeSize(); + final float alpha = + Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f)); + mShadowRight.setBounds(childLeft - shadowWidth, child.getTop(), + childLeft, child.getBottom()); + mShadowRight.setAlpha((int) (0xff * alpha)); + mShadowRight.draw(canvas); + } + return result; + } + + boolean isContentView(View child) { + return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY; + } + + boolean isDrawerView(View child) { + final int gravity = ((LayoutParams) child.getLayoutParams()).gravity; + final int absGravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(child)); + return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + final int action = MotionEventCompat.getActionMasked(ev); + + // "|" used deliberately here; both methods should be invoked. + final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) | + mRightDragger.shouldInterceptTouchEvent(ev); + + boolean interceptForTap = false; + + switch (action) { + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + mInitialMotionX = x; + mInitialMotionY = y; + if (mScrimOpacity > 0) { + final View child = mLeftDragger.findTopChildUnder((int) x, (int) y); + if (child != null && isContentView(child)) { + interceptForTap = true; + } + } + mChildrenCanceledTouch = false; + break; + } + + case MotionEvent.ACTION_MOVE: { + // If we cross the touch slop, don't perform the delayed peek for an edge touch. + if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) { + mLeftCallback.removeCallbacks(); + mRightCallback.removeCallbacks(); + } + break; + } + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: { + closeDrawers(true); + mChildrenCanceledTouch = false; + } + } + + return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch; + } + + @Override + public boolean onTouchEvent(@NonNull MotionEvent ev) { + mLeftDragger.processTouchEvent(ev); + mRightDragger.processTouchEvent(ev); + + final int action = ev.getAction(); + boolean wantTouchEvents = true; + + switch (action & MotionEventCompat.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + mInitialMotionX = x; + mInitialMotionY = y; + mChildrenCanceledTouch = false; + break; + } + + case MotionEvent.ACTION_UP: { + final float x = ev.getX(); + final float y = ev.getY(); + boolean peekingOnly = true; + final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y); + if (touchedView != null && isContentView(touchedView)) { + final float dx = x - mInitialMotionX; + final float dy = y - mInitialMotionY; + final int slop = mLeftDragger.getTouchSlop(); + if (dx * dx + dy * dy < slop * slop) { + // Taps close a dimmed open drawer but only if it isn't locked open. + final View openDrawer = findOpenDrawer(); + if (openDrawer != null) { + peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN; + } + } + } + closeDrawers(peekingOnly); + break; + } + + case MotionEvent.ACTION_CANCEL: { + closeDrawers(true); + mChildrenCanceledTouch = false; + break; + } + } + + return wantTouchEvents; + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (CHILDREN_DISALLOW_INTERCEPT || + (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) && + !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) { + // If we have an edge touch we want to skip this and track it for later instead. + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + if (disallowIntercept) { + closeDrawers(true); + } + } + + /** + * Close all currently open drawer views by animating them out of view. + */ + public void closeDrawers() { + closeDrawers(false); + } + + void closeDrawers(boolean peekingOnly) { + boolean needsInvalidate = false; + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) { + continue; + } + + final int childWidth = child.getWidth(); + + if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { + needsInvalidate |= mLeftDragger.smoothSlideViewTo(child, + -childWidth, child.getTop()); + } else { + needsInvalidate |= mRightDragger.smoothSlideViewTo(child, + getWidth(), child.getTop()); + } + + lp.isPeeking = false; + } + + mLeftCallback.removeCallbacks(); + mRightCallback.removeCallbacks(); + + if (needsInvalidate) { + invalidate(); + } + } + + /** + * Open the specified drawer view by animating it into view. + * + * @param drawerView Drawer view to open + */ + public void openDrawer(View drawerView) { + if (!isDrawerView(drawerView)) { + throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); + } + + if (mFirstLayout) { + final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); + lp.onScreen = 1.f; + lp.knownOpen = true; + + updateChildrenImportantForAccessibility(drawerView, true); + } else { + if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { + mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop()); + } else { + mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(), + drawerView.getTop()); + } + } + invalidate(); + } + + /** + * Open the specified drawer by animating it out of view. + * + * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. + * GravityCompat.START or GravityCompat.END may also be used. + */ + public void openDrawer(@EdgeGravity int gravity) { + final View drawerView = findDrawerWithGravity(gravity); + if (drawerView == null) { + throw new IllegalArgumentException("No drawer view found with gravity " + + gravityToString(gravity)); + } + openDrawer(drawerView); + } + + /** + * Close the specified drawer view by animating it into view. + * + * @param drawerView Drawer view to close + */ + public void closeDrawer(View drawerView) { + if (!isDrawerView(drawerView)) { + throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); + } + + if (mFirstLayout) { + final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); + lp.onScreen = 0.f; + lp.knownOpen = false; + } else { + if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { + mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), + drawerView.getTop()); + } else { + mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop()); + } + } + invalidate(); + } + + /** + * Close the specified drawer by animating it out of view. + * + * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. + * GravityCompat.START or GravityCompat.END may also be used. + */ + public void closeDrawer(@EdgeGravity int gravity) { + final View drawerView = findDrawerWithGravity(gravity); + if (drawerView == null) { + throw new IllegalArgumentException("No drawer view found with gravity " + + gravityToString(gravity)); + } + closeDrawer(drawerView); + } + + /** + * Check if the given drawer view is currently in an open state. + * To be considered "open" the drawer must have settled into its fully + * visible state. To check for partial visibility use + * {@link #isDrawerVisible(View)}. + * + * @param drawer Drawer view to check + * @return true if the given drawer view is in an open state + * @see #isDrawerVisible(View) + */ + public boolean isDrawerOpen(View drawer) { + if (!isDrawerView(drawer)) { + throw new IllegalArgumentException("View " + drawer + " is not a drawer"); + } + return ((LayoutParams) drawer.getLayoutParams()).knownOpen; + } + + /** + * Check if the given drawer view is currently in an open state. + * To be considered "open" the drawer must have settled into its fully + * visible state. If there is no drawer with the given gravity this method + * will return false. + * + * @param drawerGravity Gravity of the drawer to check + * @return true if the given drawer view is in an open state + */ + public boolean isDrawerOpen(@EdgeGravity int drawerGravity) { + final View drawerView = findDrawerWithGravity(drawerGravity); + if (drawerView != null) { + return isDrawerOpen(drawerView); + } + return false; + } + + /** + * Check if a given drawer view is currently visible on-screen. The drawer + * may be only peeking onto the screen, fully extended, or anywhere inbetween. + * + * @param drawer Drawer view to check + * @return true if the given drawer is visible on-screen + * @see #isDrawerOpen(View) + */ + public boolean isDrawerVisible(View drawer) { + if (!isDrawerView(drawer)) { + throw new IllegalArgumentException("View " + drawer + " is not a drawer"); + } + return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0; + } + + /** + * Check if a given drawer view is currently visible on-screen. The drawer + * may be only peeking onto the screen, fully extended, or anywhere in between. + * If there is no drawer with the given gravity this method will return false. + * + * @param drawerGravity Gravity of the drawer to check + * @return true if the given drawer is visible on-screen + */ + public boolean isDrawerVisible(@EdgeGravity int drawerGravity) { + final View drawerView = findDrawerWithGravity(drawerGravity); + return drawerView != null && isDrawerVisible(drawerView); + } + + private boolean hasPeekingDrawer() { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); + if (lp.isPeeking) { + return true; + } + } + return false; + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams + ? new LayoutParams((LayoutParams) p) + : p instanceof 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); + } + + private boolean hasVisibleDrawer() { + return findVisibleDrawer() != null; + } + + private View findVisibleDrawer() { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (isDrawerView(child) && isDrawerVisible(child)) { + return child; + } + } + return null; + } + + void cancelChildViewTouch() { + // Cancel child touches + if (!mChildrenCanceledTouch) { + final long now = SystemClock.uptimeMillis(); + final MotionEvent cancelEvent = MotionEvent.obtain(now, now, + MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + getChildAt(i).dispatchTouchEvent(cancelEvent); + } + cancelEvent.recycle(); + mChildrenCanceledTouch = true; + } + } + + @Override + public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) { + KeyEventCompat.startTracking(event); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + final View visibleDrawer = findVisibleDrawer(); + if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) { + closeDrawers(); + } + return visibleDrawer != null; + } + return super.onKeyUp(keyCode, event); + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + final SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + + if (ss.openDrawerGravity != Gravity.NO_GRAVITY) { + final View toOpen = findDrawerWithGravity(ss.openDrawerGravity); + if (toOpen != null) { + openDrawer(toOpen); + } + } + + setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT); + setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + final SavedState ss = new SavedState(superState); + + final View openDrawer = findOpenDrawer(); + if (openDrawer != null) { + ss.openDrawerGravity = ((LayoutParams) openDrawer.getLayoutParams()).gravity; + } + + ss.lockModeLeft = mLockModeLeft; + ss.lockModeRight = mLockModeRight; + + return ss; + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + super.addView(child, index, params); + + final View openDrawer = findOpenDrawer(); + if (openDrawer != null || isDrawerView(child)) { + // A drawer is already open or the new view is a drawer, so the + // new view should start out hidden. + ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } else { + // Otherwise this is a content view and no drawer is open, so the + // new view should start out visible. + ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + + // We only need a delegate here if the framework doesn't understand + // NO_HIDE_DESCENDANTS importance. + if (!CAN_HIDE_DESCENDANTS) { + ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate); + } + } + + private static boolean includeChildForAccessibility(View child) { + // If the child is not important for accessibility we make + // sure this hides the entire subtree rooted at it as the + // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not + // supported on older platforms but we want to hide the entire + // content and not opened drawers if a drawer is opened. + return ViewCompat.getImportantForAccessibility(child) + != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + && ViewCompat.getImportantForAccessibility(child) + != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO; + } + + /** + * State persisted across instances + */ + protected static class SavedState extends BaseSavedState { + int openDrawerGravity = Gravity.NO_GRAVITY; + int lockModeLeft = LOCK_MODE_UNLOCKED; + int lockModeRight = LOCK_MODE_UNLOCKED; + + public SavedState(Parcel in) { + super(in); + openDrawerGravity = in.readInt(); + } + + public SavedState(Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(openDrawerGravity); + } + + public static final Creator CREATOR = + new Creator() { + @Override + public SavedState createFromParcel(Parcel source) { + return new SavedState(source); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private class ViewDragCallback extends ViewDragHelper.Callback { + private final int mAbsGravity; + private ViewDragHelper mDragger; + + private final Runnable mPeekRunnable = new Runnable() { + @Override public void run() { + peekDrawer(); + } + }; + + public ViewDragCallback(int gravity) { + mAbsGravity = gravity; + } + + public void setDragger(ViewDragHelper dragger) { + mDragger = dragger; + } + + public void removeCallbacks() { + DrawerLayout.this.removeCallbacks(mPeekRunnable); + } + + @Override + public boolean tryCaptureView(View child, int pointerId) { + // Only capture views where the gravity matches what we're looking for. + // This lets us use two ViewDragHelpers, one for each side drawer. + return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity) + && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED; + } + + @Override + public void onViewDragStateChanged(int state) { + updateDrawerState(mAbsGravity, state, mDragger.getCapturedView()); + } + + @Override + public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + float offset; + final int childWidth = changedView.getWidth(); + + // This reverses the positioning shown in onLayout. + if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) { + offset = (float) (childWidth + left) / childWidth; + } else { + final int width = getWidth(); + offset = (float) (width - left) / childWidth; + } + setDrawerViewOffset(changedView, offset); + changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE); + invalidate(); + } + + @Override + public void onViewCaptured(View capturedChild, int activePointerId) { + final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams(); + lp.isPeeking = false; + + closeOtherDrawer(); + } + + private void closeOtherDrawer() { + final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT; + final View toClose = findDrawerWithGravity(otherGrav); + if (toClose != null) { + closeDrawer(toClose); + } + } + + @Override + public void onViewReleased(View releasedChild, float xvel, float yvel) { + // Offset is how open the drawer is, therefore left/right values + // are reversed from one another. + final float offset = getDrawerViewOffset(releasedChild); + final int childWidth = releasedChild.getWidth(); + + int left; + if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) { + left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth; + } else { + final int width = getWidth(); + left = xvel < 0 || xvel == 0 && offset > 0.5f ? width - childWidth : width; + } + + mDragger.settleCapturedViewAt(left, releasedChild.getTop()); + invalidate(); + } + + @Override + public void onEdgeTouched(int edgeFlags, int pointerId) { + postDelayed(mPeekRunnable, PEEK_DELAY); + } + + private void peekDrawer() { + final View toCapture; + final int childLeft; + final int peekDistance = mDragger.getEdgeSize(); + final boolean leftEdge = mAbsGravity == Gravity.LEFT; + if (leftEdge) { + toCapture = findDrawerWithGravity(Gravity.LEFT); + childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance; + } else { + toCapture = findDrawerWithGravity(Gravity.RIGHT); + childLeft = getWidth() - peekDistance; + } + // Only peek if it would mean making the drawer more visible and the drawer isn't locked + if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) || + (!leftEdge && toCapture.getLeft() > childLeft)) && + getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { + final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams(); + mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop()); + lp.isPeeking = true; + invalidate(); + + closeOtherDrawer(); + + cancelChildViewTouch(); + } + } + + @Override + public boolean onEdgeLock(int edgeFlags) { + if (ALLOW_EDGE_LOCK) { + final View drawer = findDrawerWithGravity(mAbsGravity); + if (drawer != null && !isDrawerOpen(drawer)) { + closeDrawer(drawer); + } + return true; + } + return false; + } + + @Override + public void onEdgeDragStarted(int edgeFlags, int pointerId) { + final View toCapture; + if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { + toCapture = findDrawerWithGravity(Gravity.LEFT); + } else { + toCapture = findDrawerWithGravity(Gravity.RIGHT); + } + + if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { + mDragger.captureChildView(toCapture, pointerId); + } + } + + @Override + public int getViewHorizontalDragRange(View child) { + return isDrawerView(child) ? child.getWidth() : 0; + } + + @Override + public int clampViewPositionHorizontal(View child, int left, int dx) { + if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { + return Math.max(-child.getWidth(), Math.min(left, 0)); + } else { + final int width = getWidth(); + return Math.max(width - child.getWidth(), Math.min(left, width)); + } + } + + @Override + public int clampViewPositionVertical(View child, int top, int dy) { + return child.getTop(); + } + } + + public static class LayoutParams extends MarginLayoutParams { + + public int gravity = Gravity.NO_GRAVITY; + float onScreen; + boolean isPeeking; + boolean knownOpen; + + 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(MarginLayoutParams source) { + super(source); + } + } + + class AccessibilityDelegate extends AccessibilityDelegateCompat { + private final Rect mTmpRect = new Rect(); + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + if (CAN_HIDE_DESCENDANTS) { + super.onInitializeAccessibilityNodeInfo(host, info); + } else { + // Obtain a node for the host, then manually generate the list + // of children to only include non-obscured views. + final AccessibilityNodeInfoCompat superNode = + AccessibilityNodeInfoCompat.obtain(info); + super.onInitializeAccessibilityNodeInfo(host, superNode); + + info.setSource(host); + final ViewParent parent = ViewCompat.getParentForAccessibility(host); + if (parent instanceof View) { + info.setParent((View) parent); + } + copyNodeInfoNoChildren(info, superNode); + superNode.recycle(); + + addChildrenForAccessibility(info, (ViewGroup) host); + } + + info.setClassName(DrawerLayout.class.getName()); + + // This view reports itself as focusable so that it can intercept + // the back button, but we should prevent this view from reporting + // itself as focusable to accessibility services. + info.setFocusable(false); + info.setFocused(false); + } + + @Override + public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(host, event); + + event.setClassName(DrawerLayout.class.getName()); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + // Special case to handle window state change events. As far as + // accessibility services are concerned, state changes from + // DrawerLayout invalidate the entire contents of the screen (like + // an Activity or Dialog) and they should announce the title of the + // new content. + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + final List eventText = event.getText(); + final View visibleDrawer = findVisibleDrawer(); + if (visibleDrawer != null) { + final int edgeGravity = getDrawerViewAbsoluteGravity(visibleDrawer); + //noinspection ResourceType + final CharSequence title = getDrawerTitle(edgeGravity); + if (title != null) { + eventText.add(title); + } + } + + return true; + } + + return super.dispatchPopulateAccessibilityEvent(host, event); + } + + @Override + public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, + AccessibilityEvent event) { + return (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) && + super.onRequestSendAccessibilityEvent(host, child, event); + } + + private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) { + final int childCount = v.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = v.getChildAt(i); + if (includeChildForAccessibility(child)) { + info.addChild(child); + } + } + } + + /** + * This should really be in AccessibilityNodeInfoCompat, but there unfortunately + * seem to be a few elements that are not easily cloneable using the underlying API. + * Leave it private here as it's not general-purpose useful. + */ + private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, + AccessibilityNodeInfoCompat src) { + final Rect rect = mTmpRect; + + src.getBoundsInParent(rect); + dest.setBoundsInParent(rect); + + src.getBoundsInScreen(rect); + dest.setBoundsInScreen(rect); + + dest.setVisibleToUser(src.isVisibleToUser()); + dest.setPackageName(src.getPackageName()); + dest.setClassName(src.getClassName()); + dest.setContentDescription(src.getContentDescription()); + + dest.setEnabled(src.isEnabled()); + dest.setClickable(src.isClickable()); + dest.setFocusable(src.isFocusable()); + dest.setFocused(src.isFocused()); + dest.setAccessibilityFocused(src.isAccessibilityFocused()); + dest.setSelected(src.isSelected()); + dest.setLongClickable(src.isLongClickable()); + + dest.addAction(src.getActions()); + } + } + + final class ChildAccessibilityDelegate extends AccessibilityDelegateCompat { + @Override + public void onInitializeAccessibilityNodeInfo(View child, + AccessibilityNodeInfoCompat info) { + super.onInitializeAccessibilityNodeInfo(child, info); + + if (!includeChildForAccessibility(child)) { + // If we are ignoring the sub-tree rooted at the child, + // break the connection to the rest of the node tree. + // For details refer to includeChildForAccessibility. + info.setParent(null); + } + } + } +} diff --git a/app/src/main/java/com/hippo/widget/FitPaddingImpl.java b/app/src/main/java/com/hippo/widget/FitPaddingImpl.java new file mode 100644 index 000000000..58fa96e13 --- /dev/null +++ b/app/src/main/java/com/hippo/widget/FitPaddingImpl.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2015 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 + * 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.widget; + +public interface FitPaddingImpl { + void onFitPadding(int left, int top, int right, int bottom); +} diff --git a/app/src/main/res/layout/activity_content.xml b/app/src/main/res/layout/activity_content.xml index 90d528e25..01405e499 100644 --- a/app/src/main/res/layout/activity_content.xml +++ b/app/src/main/res/layout/activity_content.xml @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - - - - + android:layout_height="match_parent"/> - +