1e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira/*
2e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * Copyright (C) 2013 The Android Open Source Project
3e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira *
4e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
5e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * you may not use this file except in compliance with the License.
6e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * You may obtain a copy of the License at
7e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira *
8e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira *      http://www.apache.org/licenses/LICENSE-2.0
9e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira *
10e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * Unless required by applicable law or agreed to in writing, software
11e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
12e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * See the License for the specific language governing permissions and
14e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * limitations under the License.
15e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira */
16e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
17e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereirapackage android.support.v4.widget;
18e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
19e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.content.Context;
20e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.content.res.Resources;
216611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereiraimport android.content.res.TypedArray;
2270acb0c19be3831a2080e4f902324de16bfbf62eTor Norbyeimport android.support.annotation.ColorInt;
2370acb0c19be3831a2080e4f902324de16bfbf62eTor Norbyeimport android.support.annotation.ColorRes;
2471e4b67c03aac59452e02a4152efafcf6e94ee7fAurimas Liutikasimport android.support.annotation.VisibleForTesting;
25272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banesimport android.support.v4.view.MotionEventCompat;
26359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banesimport android.support.v4.view.NestedScrollingChild;
27359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banesimport android.support.v4.view.NestedScrollingChildHelper;
28da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereiraimport android.support.v4.view.NestedScrollingParent;
29da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereiraimport android.support.v4.view.NestedScrollingParentHelper;
30e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.support.v4.view.ViewCompat;
31e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.util.AttributeSet;
32e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.util.DisplayMetrics;
33272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banesimport android.util.Log;
34e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.view.MotionEvent;
35e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.view.View;
36e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.view.ViewConfiguration;
37e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.view.ViewGroup;
38e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.view.animation.Animation;
39e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.view.animation.Animation.AnimationListener;
40e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.view.animation.DecelerateInterpolator;
41e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereiraimport android.view.animation.Transformation;
42f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpiniimport android.widget.AbsListView;
43e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
44e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira/**
45e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * The SwipeRefreshLayout should be used whenever the user can refresh the
46e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * contents of a view via a vertical swipe gesture. The activity that
47e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * instantiates this view should add an OnRefreshListener to be notified
48e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout
49e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * will notify the listener each and every time the gesture is completed again;
50e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * the listener is responsible for correctly determining when to actually
51e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * initiate a refresh of its content. If the listener determines there should
52e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * not be a refresh, it must call setRefreshing(false) to cancel any visual
53e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * indication of a refresh. If an activity wishes to show just the progress
54689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira * animation, it should call setRefreshing(true). To disable the gesture and
55689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira * progress animation, call setEnabled(false) on the view.
56689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira * <p>
57689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira * This layout should be made the parent of the view that will be refreshed as a
58ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn * result of the gesture and can only support one direct child. This view will
59ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn * also be made the target of the gesture and will be forced to match both the
60e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * width and the height supplied in this layout. The SwipeRefreshLayout does not
61e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira * provide accessibility events; instead, a menu item must be provided to allow
62689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira * refresh of the content wherever this gesture is used.
63689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira * </p>
64e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira */
65359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banespublic class SwipeRefreshLayout extends ViewGroup implements NestedScrollingParent,
66359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        NestedScrollingChild {
67689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    // Maps to ProgressBar.Large style
68689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    public static final int LARGE = MaterialProgressDrawable.LARGE;
69689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    // Maps to ProgressBar default style
70689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    public static final int DEFAULT = MaterialProgressDrawable.DEFAULT;
71689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
7271e4b67c03aac59452e02a4152efafcf6e94ee7fAurimas Liutikas    @VisibleForTesting
7371e4b67c03aac59452e02a4152efafcf6e94ee7fAurimas Liutikas    static final int CIRCLE_DIAMETER = 40;
7471e4b67c03aac59452e02a4152efafcf6e94ee7fAurimas Liutikas    @VisibleForTesting
7571e4b67c03aac59452e02a4152efafcf6e94ee7fAurimas Liutikas    static final int CIRCLE_DIAMETER_LARGE = 56;
7671e4b67c03aac59452e02a4152efafcf6e94ee7fAurimas Liutikas
77272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes    private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName();
78272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
79d0b65a16762b554ee6531844b021e3e0975cbf18Mindy Pereira    private static final int MAX_ALPHA = 255;
80dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA);
81d0b65a16762b554ee6531844b021e3e0975cbf18Mindy Pereira
82e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
83272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes    private static final int INVALID_POINTER = -1;
84689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private static final float DRAG_RATE = .5f;
85dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
86dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    // Max amount of circle that can be filled by progress during swipe gesture,
87dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    // where 1.0 is a full circle
88dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private static final float MAX_PROGRESS_ANGLE = .8f;
89dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
90dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private static final int SCALE_DOWN_DURATION = 150;
91dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
92dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private static final int ALPHA_ANIMATION_DURATION = 300;
93dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
94dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private static final int ANIMATE_TO_TRIGGER_DURATION = 200;
95dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
96dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private static final int ANIMATE_TO_START_DURATION = 200;
97689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
98689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    // Default background for the progress spinner
99689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA;
1000f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira    // Default offset in dips from the top of the view to where the progress spinner should stop
101689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private static final int DEFAULT_CIRCLE_TARGET = 64;
102e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
103689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private View mTarget; // the target of the gesture
104e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    private OnRefreshListener mListener;
105e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    private boolean mRefreshing = false;
106e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    private int mTouchSlop;
107dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private float mTotalDragDistance = -1;
108fc60a78760589b76087e2973add98d0aa35d50abChris Banes
109da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    // If nested scrolling is enabled, the total amount that needed to be
110da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    // consumed by this as the nested scrolling parent is used in place of the
111da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    // overscroll determined by MOVE events in the onTouch handler
112da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    private float mTotalUnconsumed;
113359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    private final NestedScrollingParentHelper mNestedScrollingParentHelper;
114359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    private final NestedScrollingChildHelper mNestedScrollingChildHelper;
115359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    private final int[] mParentScrollConsumed = new int[2];
116fc60a78760589b76087e2973add98d0aa35d50abChris Banes    private final int[] mParentOffsetInWindow = new int[2];
117fc60a78760589b76087e2973add98d0aa35d50abChris Banes    private boolean mNestedScrollInProgress;
118359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
119e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    private int mMediumAnimationDuration;
120e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    private int mCurrentTargetOffsetTop;
121cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira    // Whether or not the starting offset has been determined.
122cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira    private boolean mOriginalOffsetCalculated = false;
123272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
124272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes    private float mInitialMotionY;
1259c6db363573da735f9ecf15e68e2c2c375acc657Mindy Pereira    private float mInitialDownY;
126272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes    private boolean mIsBeingDragged;
127272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes    private int mActivePointerId = INVALID_POINTER;
128689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    // Whether this item is scaled up rather than clipped
129689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private boolean mScale;
130272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
131e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    // Target is returning to its start offset because it was cancelled or a
132e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    // refresh was triggered.
133e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    private boolean mReturningToStart;
134e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    private final DecelerateInterpolator mDecelerateInterpolator;
1356611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira    private static final int[] LAYOUT_ATTRS = new int[] {
1366611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira        android.R.attr.enabled
1376611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira    };
138e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
139689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private CircleImageView mCircleView;
1404e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira    private int mCircleViewIndex = -1;
141689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
142689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    protected int mFrom;
143689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
1445e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira    private float mStartingScale;
1455e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira
146689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    protected int mOriginalOffsetTop;
147689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
148689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private MaterialProgressDrawable mProgress;
149689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
150689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private Animation mScaleAnimation;
15112ffe36178df269e0c2d3b33f7de360e74c63f71Mindy Pereira
152689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private Animation mScaleDownAnimation;
153689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
154dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private Animation mAlphaStartAnimation;
155dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
156dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private Animation mAlphaMaxAnimation;
157689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
1585e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira    private Animation mScaleDownToStartAnimation;
1595e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira
160dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private float mSpinnerFinalOffset;
161689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
162689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private boolean mNotify;
163689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
164689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private int mCircleWidth;
165689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
166689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private int mCircleHeight;
167689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
168cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira    // Whether the client has set a custom starting position;
169cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira    private boolean mUsingCustomStart;
170cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira
171689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
172e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        @Override
173689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        public void onAnimationStart(Animation animation) {
174e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
175e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
176e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        @Override
177689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        public void onAnimationRepeat(Animation animation) {
178e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
179e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
180e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        @Override
181e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        public void onAnimationEnd(Animation animation) {
182689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            if (mRefreshing) {
183dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                // Make sure the progress view is fully visible
184dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                mProgress.setAlpha(MAX_ALPHA);
185689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                mProgress.start();
186689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                if (mNotify) {
187689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                    if (mListener != null) {
188689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                        mListener.onRefresh();
189689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                    }
190689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                }
191fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas                mCurrentTargetOffsetTop = mCircleView.getTop();
192689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            } else {
193fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas                reset();
194689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            }
195e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
196e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    };
197e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
198fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas    private void reset() {
199fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        mCircleView.clearAnimation();
200fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        mProgress.stop();
201fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        mCircleView.setVisibility(View.GONE);
202fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        setColorViewAlpha(MAX_ALPHA);
203fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        // Return the circle to its start position
204fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        if (mScale) {
205fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas            setAnimationProgress(0 /* animation complete and view is hidden */);
206fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        } else {
207fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas            setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop,
208fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas                    true /* requires update */);
209fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        }
210fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        mCurrentTargetOffsetTop = mCircleView.getTop();
211fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas    }
212fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas
213fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas    @Override
214fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas    protected void onDetachedFromWindow() {
215fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        super.onDetachedFromWindow();
216fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas        reset();
217fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas    }
218fe8329efc8f69a64a03fee24b9ceee1aa2474711Aurimas Liutikas
219d0b65a16762b554ee6531844b021e3e0975cbf18Mindy Pereira    private void setColorViewAlpha(int targetAlpha) {
220d0b65a16762b554ee6531844b021e3e0975cbf18Mindy Pereira        mCircleView.getBackground().setAlpha(targetAlpha);
221d0b65a16762b554ee6531844b021e3e0975cbf18Mindy Pereira        mProgress.setAlpha(targetAlpha);
222d0b65a16762b554ee6531844b021e3e0975cbf18Mindy Pereira    }
223d0b65a16762b554ee6531844b021e3e0975cbf18Mindy Pereira
224689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    /**
225689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * The refresh indicator starting and resting position is always positioned
226689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * near the top of the refreshing content. This position is a consistent
227689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * location, but can be adjusted in either direction based on whether or not
228689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * there is a toolbar or actionbar present.
229689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *
230689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * @param scale Set to true if there is no view at a higher z-order than
231689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *            where the progress spinner is set to appear.
232689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * @param start The offset in pixels from the top of this view at which the
233689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *            progress spinner should appear.
234689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * @param end The offset in pixels from the top of this view at which the
235689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *            progress spinner should come to rest after a successful swipe
236689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *            gesture.
237689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     */
238689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    public void setProgressViewOffset(boolean scale, int start, int end) {
239689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mScale = scale;
240689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleView.setVisibility(View.GONE);
241689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mOriginalOffsetTop = mCurrentTargetOffsetTop = start;
242dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mSpinnerFinalOffset = end;
243cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira        mUsingCustomStart = true;
244689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleView.invalidate();
245689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    }
246e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
247689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    /**
248dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     * The refresh indicator resting position is always positioned near the top
249dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     * of the refreshing content. This position is a consistent location, but
250dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     * can be adjusted in either direction based on whether or not there is a
251dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     * toolbar or actionbar present.
252dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     *
253dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     * @param scale Set to true if there is no view at a higher z-order than
254dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     *            where the progress spinner is set to appear.
255dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     * @param end The offset in pixels from the top of this view at which the
256dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     *            progress spinner should come to rest after a successful swipe
257dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     *            gesture.
258dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     */
259dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    public void setProgressViewEndTarget(boolean scale, int end) {
260dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mSpinnerFinalOffset = end;
261dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        mScale = scale;
262dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mCircleView.invalidate();
263dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    }
264dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
265dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    /**
266dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     * One of DEFAULT, or LARGE.
267689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     */
268689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    public void setSize(int size) {
269dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) {
270689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            return;
271e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
272689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        final DisplayMetrics metrics = getResources().getDisplayMetrics();
273689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        if (size == MaterialProgressDrawable.LARGE) {
274689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);
275689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        } else {
276689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
277e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
278dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        // force the bounds of the progress circle inside the circle view to
279dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        // update by setting it to null before updating its size and then
280dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        // re-setting it
281dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        mCircleView.setImageDrawable(null);
282689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mProgress.updateSizes(size);
283dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        mCircleView.setImageDrawable(mProgress);
284689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    }
285e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
286e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    /**
287e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * Simple constructor to use when creating a SwipeRefreshLayout from code.
288689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *
289e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * @param context
290e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     */
291e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    public SwipeRefreshLayout(Context context) {
292e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        this(context, null);
293e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
294e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
295e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    /**
296e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * Constructor that is called when inflating SwipeRefreshLayout from XML.
297689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *
298e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * @param context
2996611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira     * @param attrs
300e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     */
3016611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira    public SwipeRefreshLayout(Context context, AttributeSet attrs) {
3026611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira        super(context, attrs);
303e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
304e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
305e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
306e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        mMediumAnimationDuration = getResources().getInteger(
307e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira                android.R.integer.config_mediumAnimTime);
308e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
309e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        setWillNotDraw(false);
310e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
3116611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira
3126611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira        final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
3136611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira        setEnabled(a.getBoolean(0, true));
3146611d8cf18999a874e37245e9ecf269e0e69846bMindy Pereira        a.recycle();
315e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
316689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        final DisplayMetrics metrics = getResources().getDisplayMetrics();
317689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
318689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density);
319689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
3200f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        createProgressView();
3210f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        ViewCompat.setChildrenDrawingOrderEnabled(this, true);
322dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        // the absolute offset has to take into account that the circle starts at an offset
323dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density;
324dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        mTotalDragDistance = mSpinnerFinalOffset;
325da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
326359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
327359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
328359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        setNestedScrollingEnabled(true);
329e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
330e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
3314e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira    protected int getChildDrawingOrder(int childCount, int i) {
3324e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira        if (mCircleViewIndex < 0) {
3334e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira            return i;
3344e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira        } else if (i == childCount - 1) {
3354e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira            // Draw the selected child last
3364e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira            return mCircleViewIndex;
3374e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira        } else if (i >= mCircleViewIndex) {
3384e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira            // Move the children after the selected child earlier one
3394e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira            return i + 1;
3404e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira        } else {
3414e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira            // Keep the children before the selected child the same
3424e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira            return i;
3430f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        }
3440f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira    }
3450f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira
3460f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira    private void createProgressView() {
34796dfc170940d2f7218317242115eda1273863966Mindy Pereira        mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/2);
348689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mProgress = new MaterialProgressDrawable(getContext(), this);
349dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira        mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
350689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleView.setImageDrawable(mProgress);
351e51e32e8c7ce74a19e1ad69c75f0b6d5dadb313cMindy Pereira        mCircleView.setVisibility(View.GONE);
3520f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        addView(mCircleView);
353e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
354e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
355e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    /**
356e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * Set the listener to be notified when a refresh is triggered via the swipe
357e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * gesture.
358e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     */
359e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    public void setOnRefreshListener(OnRefreshListener listener) {
360e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        mListener = listener;
361e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
362e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
363e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    /**
364dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     * Pre API 11, alpha is used to make the progress circle appear instead of scale.
365dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira     */
366dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private boolean isAlphaUsedForScale() {
367dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        return android.os.Build.VERSION.SDK_INT < 11;
368dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    }
369dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
370dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    /**
371e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * Notify the widget that refresh state has changed. Do not call this when
372e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * refresh is triggered by a swipe gesture.
373e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     *
374e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * @param refreshing Whether or not the view should show refresh progress.
375e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     */
376e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    public void setRefreshing(boolean refreshing) {
377689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        if (refreshing && mRefreshing != refreshing) {
378689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            // scale and show
379689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            mRefreshing = refreshing;
3805e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            int endTarget = 0;
3815e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            if (!mUsingCustomStart) {
3825e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira                endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop);
3835e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            } else {
3845e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira                endTarget = (int) mSpinnerFinalOffset;
3855e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            }
3865e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop,
387689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                    true /* requires update */);
388689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            mNotify = false;
389689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            startScaleUpAnimation(mRefreshListener);
390689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        } else {
391689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            setRefreshing(refreshing, false /* notify */);
392689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        }
393689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    }
394689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
395689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private void startScaleUpAnimation(AnimationListener listener) {
396689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleView.setVisibility(View.VISIBLE);
397dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        if (android.os.Build.VERSION.SDK_INT >= 11) {
398dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            // Pre API 11, alpha is used in place of scale up to show the
399dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            // progress circle appearing.
400dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            // Don't adjust the alpha during appearance otherwise.
401dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            mProgress.setAlpha(MAX_ALPHA);
402dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        }
403689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mScaleAnimation = new Animation() {
404689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            @Override
405689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            public void applyTransformation(float interpolatedTime, Transformation t) {
40674437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira                setAnimationProgress(interpolatedTime);
407689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            }
408689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        };
409689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mScaleAnimation.setDuration(mMediumAnimationDuration);
410689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        if (listener != null) {
411689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            mCircleView.setAnimationListener(listener);
412689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        }
413c27bc76d7eac50363e6cd063ae41996833f52ebeMindy Pereira        mCircleView.clearAnimation();
414689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleView.startAnimation(mScaleAnimation);
415689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    }
416689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
41774437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira    /**
41874437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira     * Pre API 11, this does an alpha animation.
41974437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira     * @param progress
42074437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira     */
42174437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira    private void setAnimationProgress(float progress) {
422dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        if (isAlphaUsedForScale()) {
42374437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira            setColorViewAlpha((int) (progress * MAX_ALPHA));
42474437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira        } else {
42574437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira            ViewCompat.setScaleX(mCircleView, progress);
42674437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira            ViewCompat.setScaleY(mCircleView, progress);
42774437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira        }
42874437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira    }
42974437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira
430689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private void setRefreshing(boolean refreshing, final boolean notify) {
431e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        if (mRefreshing != refreshing) {
432689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            mNotify = notify;
433e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira            ensureTarget();
434e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira            mRefreshing = refreshing;
435e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira            if (mRefreshing) {
436689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
437e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira            } else {
438689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                startScaleDownAnimation(mRefreshListener);
439e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira            }
440e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
441e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
442e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
443689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private void startScaleDownAnimation(Animation.AnimationListener listener) {
444689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mScaleDownAnimation = new Animation() {
445689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            @Override
446689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            public void applyTransformation(float interpolatedTime, Transformation t) {
44774437a01478b39230bb5b4657b74480ede53ae94Mindy Pereira                setAnimationProgress(1 - interpolatedTime);
448689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            }
449689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        };
450dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
451e51e32e8c7ce74a19e1ad69c75f0b6d5dadb313cMindy Pereira        mCircleView.setAnimationListener(listener);
452c27bc76d7eac50363e6cd063ae41996833f52ebeMindy Pereira        mCircleView.clearAnimation();
453689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleView.startAnimation(mScaleDownAnimation);
454689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    }
455689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
456dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private void startProgressAlphaStartAnimation() {
457dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA);
458dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    }
459dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
460dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private void startProgressAlphaMaxAnimation() {
461dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA);
462dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    }
463dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
464dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) {
465dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        // Pre API 11, alpha is used in place of scale. Don't also use it to
466dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        // show the trigger point.
467dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        if (mScale && isAlphaUsedForScale()) {
468dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            return null;
469dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        }
470dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        Animation alpha = new Animation() {
471dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            @Override
472dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            public void applyTransformation(float interpolatedTime, Transformation t) {
473dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                mProgress
474dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                        .setAlpha((int) (startingAlpha+ ((endingAlpha - startingAlpha)
475dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                                * interpolatedTime)));
476dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            }
477dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        };
478dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        alpha.setDuration(ALPHA_ANIMATION_DURATION);
479dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        // Clear out the previous animation listeners.
480dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mCircleView.setAnimationListener(null);
481dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mCircleView.clearAnimation();
482dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mCircleView.startAnimation(alpha);
483dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        return alpha;
484dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    }
485dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
486d08402a890f1ac4d3de832a9a781cbc5ebde804fMindy Pereira    /**
487d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira     * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)}
488d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira     */
489d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira    @Deprecated
490d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira    public void setProgressBackgroundColor(int colorRes) {
491d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira        setProgressBackgroundColorSchemeResource(colorRes);
492d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira    }
493d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira
494d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira    /**
495d08402a890f1ac4d3de832a9a781cbc5ebde804fMindy Pereira     * Set the background color of the progress spinner disc.
496d08402a890f1ac4d3de832a9a781cbc5ebde804fMindy Pereira     *
497d08402a890f1ac4d3de832a9a781cbc5ebde804fMindy Pereira     * @param colorRes Resource id of the color.
498d08402a890f1ac4d3de832a9a781cbc5ebde804fMindy Pereira     */
49970acb0c19be3831a2080e4f902324de16bfbf62eTor Norbye    public void setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) {
500d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira        setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes));
501d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira    }
502d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira
503d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira    /**
504d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira     * Set the background color of the progress spinner disc.
505d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira     *
506d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira     * @param color
507d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira     */
50870acb0c19be3831a2080e4f902324de16bfbf62eTor Norbye    public void setProgressBackgroundColorSchemeColor(@ColorInt int color) {
509d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira        mCircleView.setBackgroundColor(color);
510d9df810a8e9ac28148c853249f0951329053c784Mindy Pereira        mProgress.setBackgroundColor(color);
511689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    }
512689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
513e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    /**
514689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * @deprecated Use {@link #setColorSchemeResources(int...)}
5155f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes     */
5165f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes    @Deprecated
51770acb0c19be3831a2080e4f902324de16bfbf62eTor Norbye    public void setColorScheme(@ColorInt int... colors) {
518689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        setColorSchemeResources(colors);
5195f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes    }
5205f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes
5215f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes    /**
522689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * Set the color resources used in the progress animation from color resources.
5235f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes     * The first color will also be the color of the bar that grows in response
5245f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes     * to a user swipe gesture.
525689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *
526689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * @param colorResIds
5275f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes     */
52870acb0c19be3831a2080e4f902324de16bfbf62eTor Norbye    public void setColorSchemeResources(@ColorRes int... colorResIds) {
5295f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes        final Resources res = getResources();
530689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        int[] colorRes = new int[colorResIds.length];
531689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        for (int i = 0; i < colorResIds.length; i++) {
532689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            colorRes[i] = res.getColor(colorResIds[i]);
533689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        }
534689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        setColorSchemeColors(colorRes);
5355f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes    }
5365f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes
5375f7fb42c9ff8a1e43a5462d19b9fcd9741f6895bChris Banes    /**
538689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * Set the colors used in the progress animation. The first
539689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * color will also be the color of the bar that grows in response to a user
540689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * swipe gesture.
541689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *
542689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * @param colors
543e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     */
544cb9a4f5c5c208c5525b8f3cceb0a8e714cb8f724Aurimas Liutikas    public void setColorSchemeColors(@ColorInt int... colors) {
545e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        ensureTarget();
546689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mProgress.setColorSchemeColors(colors);
547e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
548e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
549e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    /**
550e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * @return Whether the SwipeRefreshWidget is actively showing refresh
551e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     *         progress.
552e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     */
553e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    public boolean isRefreshing() {
554e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        return mRefreshing;
555e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
556e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
557e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    private void ensureTarget() {
558689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        // Don't bother getting the parent height if the parent hasn't been laid
559689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        // out yet.
560e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        if (mTarget == null) {
5610f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira            for (int i = 0; i < getChildCount(); i++) {
5620f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira                View child = getChildAt(i);
5630f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira                if (!child.equals(mCircleView)) {
5640f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira                    mTarget = child;
5650f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira                    break;
5660f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira                }
5670f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira            }
568e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
569e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
570e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
571689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    /**
572689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * Set the distance to trigger a sync in dips
573689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     *
574689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     * @param distance
575689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira     */
576689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    public void setDistanceToTriggerSync(int distance) {
577dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mTotalDragDistance = distance;
578e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
579e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
580e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    @Override
581e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
582689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        final int width = getMeasuredWidth();
583e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        final int height = getMeasuredHeight();
584e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        if (getChildCount() == 0) {
585e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira            return;
586e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
5870f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        if (mTarget == null) {
5880f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira            ensureTarget();
5890f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        }
5900f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        if (mTarget == null) {
5910f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira            return;
5920f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        }
5930f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        final View child = mTarget;
594e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        final int childLeft = getPaddingLeft();
595689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        final int childTop = getPaddingTop();
596e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        final int childWidth = width - getPaddingLeft() - getPaddingRight();
597e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        final int childHeight = height - getPaddingTop() - getPaddingBottom();
598e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
599c39bdbe9d24491caf92194229393ff58c16aed7fMindy Pereira        int circleWidth = mCircleView.getMeasuredWidth();
600c39bdbe9d24491caf92194229393ff58c16aed7fMindy Pereira        int circleHeight = mCircleView.getMeasuredHeight();
601c39bdbe9d24491caf92194229393ff58c16aed7fMindy Pereira        mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,
602c39bdbe9d24491caf92194229393ff58c16aed7fMindy Pereira                (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);
603e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
604e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
605e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    @Override
606e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
607e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
6080f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        if (mTarget == null) {
6090f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira            ensureTarget();
610e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
6110f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        if (mTarget == null) {
6120f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira            return;
6130f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        }
6140f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        mTarget.measure(MeasureSpec.makeMeasureSpec(
6150f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira                getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
6160f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
6170f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira                getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
6180f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira        mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY),
6190f3c6c1be783cfb67a2d11a1a027de0847949064Mindy Pereira                MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY));
620cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira        if (!mUsingCustomStart && !mOriginalOffsetCalculated) {
621cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira            mOriginalOffsetCalculated = true;
622cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira            mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
623cdf346714ac98eb4f40acf33f4328eb68edfb2efMindy Pereira        }
6244e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira        mCircleViewIndex = -1;
6254e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira        // Get the index of the circleview.
6264e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira        for (int index = 0; index < getChildCount(); index++) {
6274e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira            if (getChildAt(index) == mCircleView) {
6284e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira                mCircleViewIndex = index;
6294e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira                break;
6304e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira            }
6314e987010ab5116581826eb40aece85a46968cfa1Mindy Pereira        }
632e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
633e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
634f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini    /**
635062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira     * Get the diameter of the progress circle that is displayed as part of the
636062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira     * swipe to refresh layout. This is not valid until a measure pass has
637062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira     * completed.
638062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira     *
639062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira     * @return Diameter in pixels of the progress circle view.
640062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira     */
641062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira    public int getProgressCircleDiameter() {
642062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira        return mCircleView != null ?mCircleView.getMeasuredHeight() : 0;
643062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira    }
644062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira
645062bc5d81cac509fcd2feacf164e0bf4c009b2c7Mindy Pereira    /**
646f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini     * @return Whether it is possible for the child view of this layout to
647f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini     *         scroll up. Override this if the child view is a custom view.
648f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini     */
649f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini    public boolean canChildScrollUp() {
650f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini        if (android.os.Build.VERSION.SDK_INT < 14) {
651ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn            if (mTarget instanceof AbsListView) {
652ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn                final AbsListView absListView = (AbsListView) mTarget;
653f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini                return absListView.getChildCount() > 0
654f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
655ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn                                .getTop() < absListView.getPaddingTop());
656f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini            } else {
657f9b9ce76405345744bd57482d21c35a657836f87Yigit Boyar                return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
658f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini            }
659f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini        } else {
660ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn            return ViewCompat.canScrollVertically(mTarget, -1);
661f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini        }
662f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini    }
663f25dedc8624a17b43426781516ac88e8ff01d5bdMindy DelliCarpini
664e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    @Override
665e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    public boolean onInterceptTouchEvent(MotionEvent ev) {
666e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        ensureTarget();
667272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
668272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        final int action = MotionEventCompat.getActionMasked(ev);
669272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
670272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
671e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira            mReturningToStart = false;
672e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
673272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
674fc60a78760589b76087e2973add98d0aa35d50abChris Banes        if (!isEnabled() || mReturningToStart || canChildScrollUp()
675fc60a78760589b76087e2973add98d0aa35d50abChris Banes                || mRefreshing || mNestedScrollInProgress) {
676272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            // Fail fast if we're not in a state where a swipe is possible
677272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            return false;
678272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        }
679272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
680272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        switch (action) {
681272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            case MotionEvent.ACTION_DOWN:
682689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira                setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
683272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
684272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                mIsBeingDragged = false;
6859c6db363573da735f9ecf15e68e2c2c375acc657Mindy Pereira                final float initialDownY = getMotionEventY(ev, mActivePointerId);
6869c6db363573da735f9ecf15e68e2c2c375acc657Mindy Pereira                if (initialDownY == -1) {
687dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                    return false;
688dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                }
6899c6db363573da735f9ecf15e68e2c2c375acc657Mindy Pereira                mInitialDownY = initialDownY;
69071ea2e2a57d48a75c0086964727931d8edf710d1Mindy Pereira                break;
691272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
692272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            case MotionEvent.ACTION_MOVE:
693272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                if (mActivePointerId == INVALID_POINTER) {
694272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                    Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
695272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                    return false;
696272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                }
697272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
698dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                final float y = getMotionEventY(ev, mActivePointerId);
699dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                if (y == -1) {
700272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                    return false;
701272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                }
7029c6db363573da735f9ecf15e68e2c2c375acc657Mindy Pereira                final float yDiff = y - mInitialDownY;
703dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                if (yDiff > mTouchSlop && !mIsBeingDragged) {
70471ea2e2a57d48a75c0086964727931d8edf710d1Mindy Pereira                    mInitialMotionY = mInitialDownY + mTouchSlop;
705272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                    mIsBeingDragged = true;
706dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira                    mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
707272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                }
708272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                break;
709272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
710272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            case MotionEventCompat.ACTION_POINTER_UP:
711272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                onSecondaryPointerUp(ev);
712272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                break;
713272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
714272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            case MotionEvent.ACTION_UP:
715272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            case MotionEvent.ACTION_CANCEL:
716ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn                mIsBeingDragged = false;
717ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn                mActivePointerId = INVALID_POINTER;
718272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                break;
719e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
720272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
721272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        return mIsBeingDragged;
722e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
723e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
724dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private float getMotionEventY(MotionEvent ev, int activePointerId) {
725dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        final int index = MotionEventCompat.findPointerIndex(ev, activePointerId);
726dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        if (index < 0) {
727dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            return -1;
728dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        }
729dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        return MotionEventCompat.getY(ev, index);
730dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    }
731dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
732e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    @Override
733e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    public void requestDisallowInterceptTouchEvent(boolean b) {
734da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        // if this is a List < L or another view that doesn't support nested
735da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        // scrolling, ignore this request so that the vertical scroll event
736da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        // isn't stolen
737da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
738da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
739da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            // Nope.
740da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        } else {
741da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            super.requestDisallowInterceptTouchEvent(b);
742da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        }
743da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    }
744da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
745359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    // NestedScrollingParent
746359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
747da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    @Override
748da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
7495eeb3073886ae76705086c83a841f466b40280bbChris Banes        return isEnabled() && !mReturningToStart && !mRefreshing
750fc60a78760589b76087e2973add98d0aa35d50abChris Banes                && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
751da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    }
752da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
753da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    @Override
754da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    public void onNestedScrollAccepted(View child, View target, int axes) {
755da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        // Reset the counter of how much leftover scroll needs to be consumed.
756da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
757fc60a78760589b76087e2973add98d0aa35d50abChris Banes        // Dispatch up to the nested parent
758fc60a78760589b76087e2973add98d0aa35d50abChris Banes        startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
759da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        mTotalUnconsumed = 0;
760fc60a78760589b76087e2973add98d0aa35d50abChris Banes        mNestedScrollInProgress = true;
761da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    }
762da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
763da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    @Override
764da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
765da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        // If we are in the middle of consuming, a scroll, then we want to move the spinner back up
766da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        // before allowing the list to scroll
767da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        if (dy > 0 && mTotalUnconsumed > 0) {
768da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            if (dy > mTotalUnconsumed) {
769da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                consumed[1] = dy - (int) mTotalUnconsumed;
770da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                mTotalUnconsumed = 0;
771da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            } else {
772da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                mTotalUnconsumed -= dy;
773da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                consumed[1] = dy;
774da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            }
775da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            moveSpinner(mTotalUnconsumed);
776da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        }
777359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
77833eba8caa44bd2f92123c15afd8a020ffef059aaMindy Pereira        // If a client layout is using a custom start position for the circle
77933eba8caa44bd2f92123c15afd8a020ffef059aaMindy Pereira        // view, they mean to hide it again before scrolling the child view
78033eba8caa44bd2f92123c15afd8a020ffef059aaMindy Pereira        // If we get back to mTotalUnconsumed == 0 and there is more to go, hide
78133eba8caa44bd2f92123c15afd8a020ffef059aaMindy Pereira        // the circle so it isn't exposed if its blocking content is moved
78233eba8caa44bd2f92123c15afd8a020ffef059aaMindy Pereira        if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0
78333eba8caa44bd2f92123c15afd8a020ffef059aaMindy Pereira                && Math.abs(dy - consumed[1]) > 0) {
78433eba8caa44bd2f92123c15afd8a020ffef059aaMindy Pereira            mCircleView.setVisibility(View.GONE);
78533eba8caa44bd2f92123c15afd8a020ffef059aaMindy Pereira        }
78633eba8caa44bd2f92123c15afd8a020ffef059aaMindy Pereira
787359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        // Now let our nested parent consume the leftovers
788359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        final int[] parentConsumed = mParentScrollConsumed;
789359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
790359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes            consumed[0] += parentConsumed[0];
791359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes            consumed[1] += parentConsumed[1];
792359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        }
793da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    }
794da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
795da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    @Override
796da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    public int getNestedScrollAxes() {
797da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        return mNestedScrollingParentHelper.getNestedScrollAxes();
798da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    }
799da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
800da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    @Override
801da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    public void onStopNestedScroll(View target) {
802da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        mNestedScrollingParentHelper.onStopNestedScroll(target);
803fc60a78760589b76087e2973add98d0aa35d50abChris Banes        mNestedScrollInProgress = false;
804da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        // Finish the spinner for nested scrolling if we ever consumed any
805da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        // unconsumed nested scroll
806da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        if (mTotalUnconsumed > 0) {
807da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            finishSpinner(mTotalUnconsumed);
808da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            mTotalUnconsumed = 0;
809da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        }
810359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        // Dispatch up our nested parent
811359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        stopNestedScroll();
812da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    }
813da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
814da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    @Override
815fc60a78760589b76087e2973add98d0aa35d50abChris Banes    public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,
816fc60a78760589b76087e2973add98d0aa35d50abChris Banes            final int dxUnconsumed, final int dyUnconsumed) {
817fc60a78760589b76087e2973add98d0aa35d50abChris Banes        // Dispatch up to the nested parent first
818fc60a78760589b76087e2973add98d0aa35d50abChris Banes        dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
819fc60a78760589b76087e2973add98d0aa35d50abChris Banes                mParentOffsetInWindow);
820fc60a78760589b76087e2973add98d0aa35d50abChris Banes
821fc60a78760589b76087e2973add98d0aa35d50abChris Banes        // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are
822fc60a78760589b76087e2973add98d0aa35d50abChris Banes        // sometimes between two nested scrolling views, we need a way to be able to know when any
823fc60a78760589b76087e2973add98d0aa35d50abChris Banes        // nested scrolling parent has stopped handling events. We do that by using the
824fc60a78760589b76087e2973add98d0aa35d50abChris Banes        // 'offset in window 'functionality to see if we have been moved from the event.
825fc60a78760589b76087e2973add98d0aa35d50abChris Banes        // This is a decent indication of whether we should take over the event stream or not.
826fc60a78760589b76087e2973add98d0aa35d50abChris Banes        final int dy = dyUnconsumed + mParentOffsetInWindow[1];
8275eeb3073886ae76705086c83a841f466b40280bbChris Banes        if (dy < 0 && !canChildScrollUp()) {
828fc60a78760589b76087e2973add98d0aa35d50abChris Banes            mTotalUnconsumed += Math.abs(dy);
829da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            moveSpinner(mTotalUnconsumed);
830da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        }
831da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    }
832da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
833359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    // NestedScrollingChild
834da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
835da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    @Override
836359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    public void setNestedScrollingEnabled(boolean enabled) {
837359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
838359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    }
839359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
840359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    @Override
841359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    public boolean isNestedScrollingEnabled() {
842359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        return mNestedScrollingChildHelper.isNestedScrollingEnabled();
843359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    }
844359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
845359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    @Override
846359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    public boolean startNestedScroll(int axes) {
847359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        return mNestedScrollingChildHelper.startNestedScroll(axes);
848359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    }
849359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
850359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    @Override
851359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    public void stopNestedScroll() {
852359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        mNestedScrollingChildHelper.stopNestedScroll();
853359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    }
854359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
855359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    @Override
856359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    public boolean hasNestedScrollingParent() {
857359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        return mNestedScrollingChildHelper.hasNestedScrollingParent();
858359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    }
859359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
860359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    @Override
861359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
862359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes            int dyUnconsumed, int[] offsetInWindow) {
863359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
864359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes                dxUnconsumed, dyUnconsumed, offsetInWindow);
865359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    }
866359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
867359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    @Override
868359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
869359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
870359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    }
871359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
872359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    @Override
873acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira    public boolean onNestedPreFling(View target, float velocityX,
874acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira            float velocityY) {
875acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira        return dispatchNestedPreFling(velocityX, velocityY);
876acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira    }
877acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira
878acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira    @Override
879acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira    public boolean onNestedFling(View target, float velocityX, float velocityY,
880acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira            boolean consumed) {
881acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira        return dispatchNestedFling(velocityX, velocityY, consumed);
882acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira    }
883acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira
884acefb3dda2f5b7e2e1a6c046ad1420876c5b4b38Mindy Pereira    @Override
885359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
886359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
887359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    }
888359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
889359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    @Override
890359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
891359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes        return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
892359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes    }
893359e30274ccef5f95130ac09a7bd593d8e18bb92Chris Banes
894dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    private boolean isAnimationRunning(Animation animation) {
895dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        return animation != null && animation.hasStarted() && !animation.hasEnded();
896dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira    }
897dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira
898da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    private void moveSpinner(float overscrollTop) {
899da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        mProgress.showArrow(true);
900da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        float originalDragPercent = overscrollTop / mTotalDragDistance;
901da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
902da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
903da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
904da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
905da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset - mOriginalOffsetTop
906da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                : mSpinnerFinalOffset;
907da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2)
908da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                / slingshotDist);
909da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
910da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                (tensionSlingshotPercent / 4), 2)) * 2f;
911da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        float extraMove = (slingshotDist) * tensionPercent * 2;
912da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
913da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);
914da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        // where 1.0f is a full circle
915da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        if (mCircleView.getVisibility() != View.VISIBLE) {
916da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            mCircleView.setVisibility(View.VISIBLE);
917da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        }
918da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        if (!mScale) {
919da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            ViewCompat.setScaleX(mCircleView, 1f);
920da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            ViewCompat.setScaleY(mCircleView, 1f);
921da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        }
922f629b8e6d20e9b5ae373bfad904a3c8932a82ca6Aurimas Liutikas
923f629b8e6d20e9b5ae373bfad904a3c8932a82ca6Aurimas Liutikas        if (mScale) {
924f629b8e6d20e9b5ae373bfad904a3c8932a82ca6Aurimas Liutikas            setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance));
925f629b8e6d20e9b5ae373bfad904a3c8932a82ca6Aurimas Liutikas        }
926da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        if (overscrollTop < mTotalDragDistance) {
927da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA
928da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    && !isAnimationRunning(mAlphaStartAnimation)) {
929da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                // Animate the alpha
930da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                startProgressAlphaStartAnimation();
931da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            }
932da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        } else {
933da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) {
934da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                // Animate the alpha
935da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                startProgressAlphaMaxAnimation();
936da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            }
937da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        }
938f629b8e6d20e9b5ae373bfad904a3c8932a82ca6Aurimas Liutikas        float strokeStart = adjustedPercent * .8f;
939f629b8e6d20e9b5ae373bfad904a3c8932a82ca6Aurimas Liutikas        mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
940f629b8e6d20e9b5ae373bfad904a3c8932a82ca6Aurimas Liutikas        mProgress.setArrowScale(Math.min(1f, adjustedPercent));
941f629b8e6d20e9b5ae373bfad904a3c8932a82ca6Aurimas Liutikas
942da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
943da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        mProgress.setProgressRotation(rotation);
944da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);
945da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    }
946da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
947da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    private void finishSpinner(float overscrollTop) {
948da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        if (overscrollTop > mTotalDragDistance) {
949da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            setRefreshing(true, true /* notify */);
950da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        } else {
951da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            // cancel refresh
952da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            mRefreshing = false;
953da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            mProgress.setStartEndTrim(0f, 0f);
954da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            Animation.AnimationListener listener = null;
955da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            if (!mScale) {
956da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                listener = new Animation.AnimationListener() {
957da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
958da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    @Override
959da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    public void onAnimationStart(Animation animation) {
960da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    }
961da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
962da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    @Override
963da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    public void onAnimationEnd(Animation animation) {
964da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                        if (!mScale) {
965da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                            startScaleDownAnimation(null);
966da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                        }
967da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    }
968da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
969da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    @Override
970da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    public void onAnimationRepeat(Animation animation) {
971da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    }
972da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
973da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                };
974da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            }
975da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
976da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira            mProgress.showArrow(false);
977da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira        }
978da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira    }
979da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira
980e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    @Override
981272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes    public boolean onTouchEvent(MotionEvent ev) {
982272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        final int action = MotionEventCompat.getActionMasked(ev);
983a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira        int pointerIndex = -1;
984272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
985272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
986272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            mReturningToStart = false;
987272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        }
988272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
989fc60a78760589b76087e2973add98d0aa35d50abChris Banes        if (!isEnabled() || mReturningToStart || canChildScrollUp() || mNestedScrollInProgress) {
990272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            // Fail fast if we're not in a state where a swipe is possible
991272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            return false;
992272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        }
993272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
994e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        switch (action) {
995e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira            case MotionEvent.ACTION_DOWN:
996272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
997272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                mIsBeingDragged = false;
998e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira                break;
999272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
1000dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira            case MotionEvent.ACTION_MOVE: {
1001a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1002272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                if (pointerIndex < 0) {
1003272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
1004272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                    return false;
1005272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                }
1006272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
1007272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                final float y = MotionEventCompat.getY(ev, pointerIndex);
1008dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira                final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
1009272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                if (mIsBeingDragged) {
1010da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                    if (overscrollTop > 0) {
1011da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                        moveSpinner(overscrollTop);
1012272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                    } else {
1013da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                        return false;
1014e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira                    }
1015e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira                }
1016e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira                break;
1017dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira            }
1018272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            case MotionEventCompat.ACTION_POINTER_DOWN: {
1019a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                pointerIndex = MotionEventCompat.getActionIndex(ev);
1020a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                if (pointerIndex < 0) {
1021a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                    Log.e(LOG_TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
1022a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                    return false;
1023a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                }
1024a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                mActivePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
1025272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                break;
1026272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            }
1027272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
1028272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            case MotionEventCompat.ACTION_POINTER_UP:
1029272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                onSecondaryPointerUp(ev);
1030272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                break;
1031272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
1032a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira            case MotionEvent.ACTION_UP: {
1033a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1034a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                if (pointerIndex < 0) {
1035a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                    Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
103607a4db40e79aae23694b205f99b013ee2e4f2307Mindy Pereira                    return false;
103707a4db40e79aae23694b205f99b013ee2e4f2307Mindy Pereira                }
1038a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira
1039dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira                final float y = MotionEventCompat.getY(ev, pointerIndex);
1040dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira                final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
1041ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn                mIsBeingDragged = false;
1042da1b0a00654a315d645b941cf3a4cd2a746fe405Mindy Pereira                finishSpinner(overscrollTop);
1043ca9e92293ff32b4a8a15513b0a7afe42c127cdb7Andrew Flynn                mActivePointerId = INVALID_POINTER;
1044272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes                return false;
1045dd27d55a5152f86c07758067e587bdd5b719abafMindy Pereira            }
1046a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira            case MotionEvent.ACTION_CANCEL:
1047a8ba5574e2cb80191fadb0d7963483133b55a2feMindy Pereira                return false;
1048e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
1049272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
1050272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        return true;
1051e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
1052e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
1053689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private void animateOffsetToCorrectPosition(int from, AnimationListener listener) {
1054689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mFrom = from;
1055689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mAnimateToCorrectPosition.reset();
1056dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
1057689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
1058689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        if (listener != null) {
1059689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            mCircleView.setAnimationListener(listener);
1060689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        }
1061c27bc76d7eac50363e6cd063ae41996833f52ebeMindy Pereira        mCircleView.clearAnimation();
1062689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleView.startAnimation(mAnimateToCorrectPosition);
1063e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
1064e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
1065689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private void animateOffsetToStartPosition(int from, AnimationListener listener) {
10665e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        if (mScale) {
10675e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            // Scale the item back down
10685e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            startScaleDownReturnToStartAnimation(from, listener);
10695e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        } else {
10705e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            mFrom = from;
10715e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            mAnimateToStartPosition.reset();
10725e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);
10735e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
10745e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            if (listener != null) {
10755e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira                mCircleView.setAnimationListener(listener);
10765e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            }
10775e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            mCircleView.clearAnimation();
10785e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            mCircleView.startAnimation(mAnimateToStartPosition);
1079e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        }
1080e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
1081e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
1082689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private final Animation mAnimateToCorrectPosition = new Animation() {
1083689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        @Override
1084689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        public void applyTransformation(float interpolatedTime, Transformation t) {
1085689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            int targetTop = 0;
10865e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            int endTarget = 0;
10875e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            if (!mUsingCustomStart) {
10885e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira                endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop));
10895e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            } else {
10905e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira                endTarget = (int) mSpinnerFinalOffset;
10915e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            }
1092dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira            targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
1093689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            int offset = targetTop - mCircleView.getTop();
1094689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira            setTargetOffsetTopAndBottom(offset, false /* requires update */);
10953fba72ed900aa288256bdf29427df39753964ff0Mindy Pereira            mProgress.setArrowScale(1 - interpolatedTime);
1096689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        }
1097689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    };
109812ffe36178df269e0c2d3b33f7de360e74c63f71Mindy Pereira
10995e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira    private void moveToStart(float interpolatedTime) {
11005e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        int targetTop = 0;
11015e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
11025e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        int offset = targetTop - mCircleView.getTop();
11035e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        setTargetOffsetTopAndBottom(offset, false /* requires update */);
11045e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira    }
11055e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira
1106689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private final Animation mAnimateToStartPosition = new Animation() {
1107689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        @Override
1108689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        public void applyTransformation(float interpolatedTime, Transformation t) {
11095e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            moveToStart(interpolatedTime);
1110689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        }
1111689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    };
1112689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira
11135e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira    private void startScaleDownReturnToStartAnimation(int from,
11145e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            Animation.AnimationListener listener) {
11155e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        mFrom = from;
11165e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        if (isAlphaUsedForScale()) {
11175e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            mStartingScale = mProgress.getAlpha();
11185e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        } else {
11195e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            mStartingScale = ViewCompat.getScaleX(mCircleView);
11205e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        }
11215e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        mScaleDownToStartAnimation = new Animation() {
11225e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            @Override
11235e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            public void applyTransformation(float interpolatedTime, Transformation t) {
11245e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira                float targetScale = (mStartingScale + (-mStartingScale  * interpolatedTime));
11255e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira                setAnimationProgress(targetScale);
11265e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira                moveToStart(interpolatedTime);
11275e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            }
11285e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        };
11295e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION);
11305e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        if (listener != null) {
11315e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira            mCircleView.setAnimationListener(listener);
11325e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        }
11335e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        mCircleView.clearAnimation();
11345e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira        mCircleView.startAnimation(mScaleDownToStartAnimation);
11355e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira    }
11365e31e2ad5b0c1f62b93a4e554d16f8b9d28b5a15Mindy Pereira
1137689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira    private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) {
1138689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleView.bringToFront();
1139689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCircleView.offsetTopAndBottom(offset);
1140689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        mCurrentTargetOffsetTop = mCircleView.getTop();
1141dc943d6192dab40df68b6b583c84e64045ea1bc8Mindy Pereira        if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {
11420b7c53c6ee75c8473bcefd0dff9c6e3ed6c20410Mindy Pereira            invalidate();
1143689ab224266f62aefb35ef0b80af6edf66eb8972Mindy Pereira        }
1144e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
1145e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira
1146272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes    private void onSecondaryPointerUp(MotionEvent ev) {
1147272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
1148272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
1149272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        if (pointerId == mActivePointerId) {
1150272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            // This was our active pointer going up. Choose a new
1151272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            // active pointer and adjust accordingly.
1152272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1153272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
1154272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes        }
1155272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes    }
1156272acf0aa6bc9bf0d744b68a5b8b092c2c3e46d4Chris Banes
1157e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    /**
1158e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * Classes that wish to be notified when the swipe gesture correctly
1159e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     * triggers a refresh should implement this interface.
1160e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira     */
1161e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    public interface OnRefreshListener {
1162e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira        public void onRefresh();
1163e9a361cf082bf8fbe908d1abfdc327209ec01d82Mindy Pereira    }
1164241d15309b21a8857de2bda0afaf06668bdd348eMindy Pereira}
1165