10bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska/*
20bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * Copyright (C) 2017 The Android Open Source Project
30bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska *
40bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * Licensed under the Apache License, Version 2.0 (the "License");
50bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * you may not use this file except in compliance with the License.
60bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * You may obtain a copy of the License at
70bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska *
80bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska *      http://www.apache.org/licenses/LICENSE-2.0
90bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska *
100bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * Unless required by applicable law or agreed to in writing, software
110bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * distributed under the License is distributed on an "AS IS" BASIS,
120bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * See the License for the specific language governing permissions and
140bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * limitations under the License.
150bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska */
160bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
179b028c55b13889bf75b5dd43b5b0f4051834ae1dAga Madurskapackage android.support.wear.widget;
180bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
190bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
200bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
210bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.content.Context;
220bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.content.res.Resources;
230bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.support.annotation.Nullable;
240bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.support.annotation.RestrictTo;
250bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.support.annotation.UiThread;
260bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.util.AttributeSet;
270bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.util.Log;
280bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.view.MotionEvent;
290bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.view.VelocityTracker;
300bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.view.View;
310bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.view.ViewConfiguration;
320bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.view.ViewGroup;
330bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaimport android.widget.FrameLayout;
340bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
350bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska/**
360bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * Special layout that finishes its activity when swiped away.
370bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska *
380bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * <p>This is a modified copy of the internal framework class
390bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * com.android.internal.widget.SwipeDismissLayout.
400bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska *
410bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska * @hide
420bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska */
430bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska@RestrictTo(LIBRARY_GROUP)
440bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska@UiThread
450bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurskaclass SwipeDismissLayout extends FrameLayout {
460bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private static final String TAG = "SwipeDismissLayout";
470bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
480bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    public static final float DEFAULT_DISMISS_DRAG_WIDTH_RATIO = .33f;
490bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    // A value between 0.0 and 1.0 determining the percentage of the screen on the left-hand-side
500bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    // where edge swipe gestures are permitted to begin.
510bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private static final float EDGE_SWIPE_THRESHOLD = 0.1f;
520bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
530bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /** Called when the layout is about to consider a swipe. */
540bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    @UiThread
550bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    interface OnPreSwipeListener {
560bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        /**
570bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * Notifies listeners that the view is now considering to start a dismiss gesture from a
580bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * particular point on the screen. The default implementation returns true for all
590bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * coordinates so that is is possible to start a swipe-to-dismiss gesture from any location.
600bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * If any one instance of this Callback returns false for a given set of coordinates,
610bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * swipe-to-dismiss will not be allowed to start in that point.
620bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         *
630bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * @param xDown the x coordinate of the initial {@link android.view.MotionEvent#ACTION_DOWN}
640bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         *              event for this motion
650bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * @param yDown the y coordinate of the initial {@link android.view.MotionEvent#ACTION_DOWN}
660bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         *              event for this motion
670bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * @return {@code true} if these coordinates should be considered as a start of a swipe
680bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * gesture, {@code false} otherwise
690bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         */
700bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        boolean onPreSwipe(SwipeDismissLayout swipeDismissLayout, float xDown, float yDown);
710bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
720bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
730bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /**
740bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * Interface enabling listeners to react to when the swipe gesture is done and the view should
750bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * probably be dismissed from the UI.
760bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     */
770bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    @UiThread
780bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    interface OnDismissedListener {
790bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        void onDismissed(SwipeDismissLayout layout);
800bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
810bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
820bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /**
830bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * Interface enabling listeners to react to changes in the progress of the swipe-to-dismiss
840bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * gesture.
850bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     */
860bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    @UiThread
870bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    interface OnSwipeProgressChangedListener {
880bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        /**
890bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * Called when the layout has been swiped and the position of the window should change.
900bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         *
910bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * @param layout    the layout associated with this listener.
920bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * @param progress  a number in [0, 1] representing how far to the right the window has
930bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         *                  been swiped
940bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * @param translate a number in [0, w], where w is the width of the layout. This is
950bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         *                  equivalent to progress * layout.getWidth()
960bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         */
970bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        void onSwipeProgressChanged(SwipeDismissLayout layout, float progress, float translate);
980bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
990bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        /**
1000bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * Called when the layout started to be swiped away but then the gesture was canceled.
1010bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         *
1020bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         * @param layout    the layout associated with this listener
1030bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska         */
1049138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska        void onSwipeCanceled(SwipeDismissLayout layout);
1050bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
1060bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1070bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    // Cached ViewConfiguration and system-wide constant values
1080bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private int mSlop;
1090bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private int mMinFlingVelocity;
1100bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private float mGestureThresholdPx;
1110bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1120bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    // Transient properties
1130bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private int mActiveTouchId;
1140bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private float mDownX;
1150bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private float mDownY;
1160bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private boolean mSwipeable;
1170bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private boolean mSwiping;
1180bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    // This variable holds information about whether the initial move of a longer swipe
1190bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    // (consisting of multiple move events) has conformed to the definition of a horizontal
1200bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    // swipe-to-dismiss. A swipe gesture is only ever allowed to be recognized if this variable is
1210bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    // set to true. Otherwise, the motion events will be allowed to propagate to the children.
1220bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private boolean mCanStartSwipe = true;
1230bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private boolean mDismissed;
1240bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private boolean mDiscardIntercept;
1250bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private VelocityTracker mVelocityTracker;
1260bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private float mTranslationX;
1279138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska    private boolean mDisallowIntercept;
1280bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1290bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    @Nullable
1300bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private OnPreSwipeListener mOnPreSwipeListener;
1310bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private OnDismissedListener mDismissedListener;
1320bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private OnSwipeProgressChangedListener mProgressListener;
1330bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1340bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private float mLastX;
1350bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private float mDismissMinDragWidthRatio = DEFAULT_DISMISS_DRAG_WIDTH_RATIO;
1360bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1370bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    SwipeDismissLayout(Context context) {
1380bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        this(context, null);
1390bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
1400bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1410bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    SwipeDismissLayout(Context context, AttributeSet attrs) {
1420bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        this(context, attrs, 0);
1430bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
1440bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1450bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    SwipeDismissLayout(Context context, AttributeSet attrs, int defStyle) {
1460bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        this(context, attrs, defStyle, 0);
1470bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
1480bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1490bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    SwipeDismissLayout(Context context, AttributeSet attrs, int defStyle, int defStyleRes) {
1500bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        super(context, attrs, defStyle, defStyleRes);
1510bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        ViewConfiguration vc = ViewConfiguration.get(context);
1520bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mSlop = vc.getScaledTouchSlop();
1530bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
1540bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mGestureThresholdPx =
1550bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                Resources.getSystem().getDisplayMetrics().widthPixels * EDGE_SWIPE_THRESHOLD;
1560bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1570bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        // By default, the view is swipeable.
1580bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        setSwipeable(true);
1590bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
1600bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1610bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /**
1620bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * Sets the minimum ratio of the screen after which the swipe gesture is treated as swipe-to-
1630bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * dismiss.
1640bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     *
1650bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @param ratio  the ratio of the screen at which the swipe gesture is treated as
1660bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     *               swipe-to-dismiss. should be provided as a fraction of the screen
1670bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     */
1680bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    public void setDismissMinDragWidthRatio(float ratio) {
1690bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mDismissMinDragWidthRatio = ratio;
1700bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
1710bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1720bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /**
1730bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * Returns the current ratio of te screen at which the swipe gesture is treated as
1740bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * swipe-to-dismiss.
1750bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     *
1760bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @return the current ratio of te screen at which the swipe gesture is treated as
1770bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * swipe-to-dismiss
1780bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     */
1790bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    public float getDismissMinDragWidthRatio() {
1800bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        return mDismissMinDragWidthRatio;
1810bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
1820bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1830bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /**
1840bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * Sets the layout to swipeable or not. This effectively turns the functionality of this layout
1850bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * on or off.
1860bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     *
1870bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @param swipeable whether the layout should react to the swipe gesture
1880bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     */
1890bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    public void setSwipeable(boolean swipeable) {
1900bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mSwipeable = swipeable;
1910bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
1920bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1930bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /** Returns true if the layout reacts to swipe gestures. */
1940bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    public boolean isSwipeable() {
1950bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        return mSwipeable;
1960bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
1970bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
1980bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    void setOnPreSwipeListener(@Nullable OnPreSwipeListener listener) {
1990bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mOnPreSwipeListener = listener;
2000bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
2010bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2020bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    void setOnDismissedListener(@Nullable OnDismissedListener listener) {
2030bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mDismissedListener = listener;
2040bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
2050bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2060bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    void setOnSwipeProgressChangedListener(@Nullable OnSwipeProgressChangedListener listener) {
2070bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mProgressListener = listener;
2080bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
2090bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2100bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    @Override
2119138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
2129138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska        mDisallowIntercept = disallowIntercept;
2139138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska        if (getParent() != null) {
2149138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska            getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
2159138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska        }
2169138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska    }
2179138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska
2189138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska    @Override
2190bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    public boolean onInterceptTouchEvent(MotionEvent ev) {
2200bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (!mSwipeable) {
2210bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            return super.onInterceptTouchEvent(ev);
2220bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
2230bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2240bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        // offset because the view is translated during swipe
2250bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        ev.offsetLocation(mTranslationX, 0);
2260bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2270bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        switch (ev.getActionMasked()) {
2280bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            case MotionEvent.ACTION_DOWN:
2290bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                resetMembers();
2300bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mDownX = ev.getRawX();
2310bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mDownY = ev.getRawY();
2320bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mActiveTouchId = ev.getPointerId(0);
2330bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mVelocityTracker = VelocityTracker.obtain();
2340bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mVelocityTracker.addMovement(ev);
2350bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                break;
2360bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2370bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            case MotionEvent.ACTION_POINTER_DOWN:
2380bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                int actionIndex = ev.getActionIndex();
2390bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mActiveTouchId = ev.getPointerId(actionIndex);
2400bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                break;
2410bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            case MotionEvent.ACTION_POINTER_UP:
2420bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                actionIndex = ev.getActionIndex();
2430bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                int pointerId = ev.getPointerId(actionIndex);
2440bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                if (pointerId == mActiveTouchId) {
2450bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    // This was our active pointer going up. Choose a new active pointer.
2460bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    int newActionIndex = actionIndex == 0 ? 1 : 0;
2470bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    mActiveTouchId = ev.getPointerId(newActionIndex);
2480bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                }
2490bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                break;
2500bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2510bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            case MotionEvent.ACTION_CANCEL:
2520bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            case MotionEvent.ACTION_UP:
2530bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                resetMembers();
2540bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                break;
2550bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2560bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            case MotionEvent.ACTION_MOVE:
2570bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                if (mVelocityTracker == null || mDiscardIntercept) {
2580bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    break;
2590bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                }
2600bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2610bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                int pointerIndex = ev.findPointerIndex(mActiveTouchId);
2620bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                if (pointerIndex == -1) {
2630bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    Log.e(TAG, "Invalid pointer index: ignoring.");
2640bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    mDiscardIntercept = true;
2650bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    break;
2660bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                }
2670bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                float dx = ev.getRawX() - mDownX;
2680bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                float x = ev.getX(pointerIndex);
2690bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                float y = ev.getY(pointerIndex);
2700bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2710bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                if (dx != 0 && mDownX >= mGestureThresholdPx && canScroll(this, false, dx, x, y)) {
2720bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    mDiscardIntercept = true;
2730bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    break;
2740bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                }
2750bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                updateSwiping(ev);
2760bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                break;
2770bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
2780bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2799138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska        if ((mOnPreSwipeListener == null && !mDisallowIntercept)
2809138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska                || mOnPreSwipeListener.onPreSwipe(this, mDownX, mDownY)) {
2810bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            return (!mDiscardIntercept && mSwiping);
2820bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
2830bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        return false;
2840bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
2850bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2860bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    @Override
2870bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    public boolean canScrollHorizontally(int direction) {
2880bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        // This view can only be swiped horizontally from left to right - this means a negative
2890bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        // SCROLLING direction. We return false if the view is not visible to avoid capturing swipe
2900bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        // gestures when the view is hidden.
2910bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        return direction < 0 && isSwipeable() && getVisibility() == View.VISIBLE;
2920bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
2930bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
2940bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /**
2950bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * Helper function determining if a particular move gesture was verbose enough to qualify as a
2960bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * beginning of a swipe.
2970bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     *
2980bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @param dx distance traveled in the x direction, from the initial touch down
2990bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @param dy distance traveled in the y direction, from the initial touch down
3000bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @return {@code true} if the gesture was long enough to be considered a potential swipe
3010bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     */
3020bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private boolean isPotentialSwipe(float dx, float dy) {
3030bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        return (dx * dx) + (dy * dy) > mSlop * mSlop;
3040bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
3050bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
3060bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    @Override
3070bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    public boolean onTouchEvent(MotionEvent ev) {
3080bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (!mSwipeable) {
3090bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            return super.onTouchEvent(ev);
3100bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
3110bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
3120bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (mVelocityTracker == null) {
3130bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            return super.onTouchEvent(ev);
3140bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
31537bc8b03d33de5c375c26d915fcb056a42c5abc2Aga Madurska
31637bc8b03d33de5c375c26d915fcb056a42c5abc2Aga Madurska        if (mOnPreSwipeListener != null && !mOnPreSwipeListener.onPreSwipe(this, mDownX, mDownY)) {
31737bc8b03d33de5c375c26d915fcb056a42c5abc2Aga Madurska            return super.onTouchEvent(ev);
31837bc8b03d33de5c375c26d915fcb056a42c5abc2Aga Madurska        }
31937bc8b03d33de5c375c26d915fcb056a42c5abc2Aga Madurska
3200bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        // offset because the view is translated during swipe
3210bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        ev.offsetLocation(mTranslationX, 0);
3220bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        switch (ev.getActionMasked()) {
3230bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            case MotionEvent.ACTION_UP:
3240bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                updateDismiss(ev);
3250bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                if (mDismissed) {
3260bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    dismiss();
3270bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                } else if (mSwiping) {
3280bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    cancel();
3290bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                }
3300bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                resetMembers();
3310bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                break;
3320bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
3330bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            case MotionEvent.ACTION_CANCEL:
3340bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                cancel();
3350bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                resetMembers();
3360bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                break;
3370bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
3380bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            case MotionEvent.ACTION_MOVE:
3390bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mVelocityTracker.addMovement(ev);
3400bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mLastX = ev.getRawX();
3410bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                updateSwiping(ev);
3420bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                if (mSwiping) {
3430bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    setProgress(ev.getRawX() - mDownX);
3440bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    break;
3450bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                }
3460bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
3470bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        return true;
3480bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
3490bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
3500bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private void setProgress(float deltaX) {
3510bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mTranslationX = deltaX;
3520bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (mProgressListener != null && deltaX >= 0) {
3530bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            mProgressListener.onSwipeProgressChanged(this, deltaX / getWidth(), deltaX);
3540bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
3550bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
3560bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
3570bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private void dismiss() {
3580bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (mDismissedListener != null) {
3590bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            mDismissedListener.onDismissed(this);
3600bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
3610bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
3620bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
3639138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska    private void cancel() {
3640bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (mProgressListener != null) {
3659138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska            mProgressListener.onSwipeCanceled(this);
3660bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
3670bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
3680bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
3690bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /** Resets internal members when canceling or finishing a given gesture. */
3700bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private void resetMembers() {
3710bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (mVelocityTracker != null) {
3720bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            mVelocityTracker.recycle();
3730bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
3740bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mVelocityTracker = null;
3750bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mTranslationX = 0;
3760bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mDownX = 0;
3770bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mDownY = 0;
3780bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mSwiping = false;
3790bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mDismissed = false;
3800bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mDiscardIntercept = false;
3810bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mCanStartSwipe = true;
3829138a30e06ea881190a9150f59c2f3310b27dd04Aga Madurska        mDisallowIntercept = false;
3830bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
3840bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
3850bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private void updateSwiping(MotionEvent ev) {
3860bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (!mSwiping) {
3870bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            float deltaX = ev.getRawX() - mDownX;
3880bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            float deltaY = ev.getRawY() - mDownY;
3890bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            if (isPotentialSwipe(deltaX, deltaY)) {
390d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska                // There are three conditions on which we want want to start swiping:
391d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska                // 1. The swipe is from left to right AND
392d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska                // 2. It is horizontal AND
393d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska                // 3. We actually can start swiping
394d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska                mSwiping = mCanStartSwipe && Math.abs(deltaY) < Math.abs(deltaX) && deltaX > 0;
3950bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mCanStartSwipe = mSwiping;
3960bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            }
3970bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
3980bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
3990bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
4000bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    private void updateDismiss(MotionEvent ev) {
4010bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        float deltaX = ev.getRawX() - mDownX;
4020bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mVelocityTracker.addMovement(ev);
4030bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        mVelocityTracker.computeCurrentVelocity(1000);
4040bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (!mDismissed) {
4050bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            if ((deltaX > (getWidth() * mDismissMinDragWidthRatio) && ev.getRawX() >= mLastX)
4060bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    || mVelocityTracker.getXVelocity() >= mMinFlingVelocity) {
4070bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mDismissed = true;
4080bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            }
4090bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
4100bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        // Check if the user tried to undo this.
4110bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (mDismissed && mSwiping) {
4120bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            // Check if the user's finger is actually flinging back to left
4130bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            if (mVelocityTracker.getXVelocity() < -mMinFlingVelocity) {
4140bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                mDismissed = false;
4150bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            }
4160bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
4170bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
4180bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
4190bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    /**
4200bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * Tests scrollability within child views of v in the direction of dx.
4210bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     *
4220bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @param v      view to test for horizontal scrollability
4230bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @param checkV whether the view v passed should itself be checked for scrollability
4240bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     *               ({@code true}), or just its children ({@code false})
4250bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @param dx     delta scrolled in pixels. Only the sign of this is used
4260bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @param x      x coordinate of the active touch point
4270bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @param y      y coordinate of the active touch point
4280bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     * @return {@code true} if child views of v can be scrolled by delta of dx
4290bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska     */
4300bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    protected boolean canScroll(View v, boolean checkV, float dx, float x, float y) {
4310bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        if (v instanceof ViewGroup) {
4320bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            final ViewGroup group = (ViewGroup) v;
4330bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            final int scrollX = v.getScrollX();
4340bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            final int scrollY = v.getScrollY();
4350bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            final int count = group.getChildCount();
4360bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            for (int i = count - 1; i >= 0; i--) {
4370bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                final View child = group.getChildAt(i);
4380bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                if (x + scrollX >= child.getLeft()
4390bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                        && x + scrollX < child.getRight()
4400bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                        && y + scrollY >= child.getTop()
4410bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                        && y + scrollY < child.getBottom()
4420bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                        && canScroll(
4430bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                        child, true, dx, x + scrollX - child.getLeft(),
4440bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                        y + scrollY - child.getTop())) {
4450bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                    return true;
4460bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska                }
4470bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska            }
4480bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        }
4490bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska
4500bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska        return checkV && v.canScrollHorizontally((int) -dx);
4510bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska    }
4520bb1bb33c39fa5eef545f8513753a8e3b8dfede7Aga Madurska}
453