diff --git a/build.gradle b/build.gradle
index c63c5b9..e979bd0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.0.0-alpha1'
+ classpath 'com.android.tools.build:gradle:2.1.0'
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 137ca20..e21db57 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Nov 30 13:15:11 EET 2015
+#Tue May 03 22:31:47 PDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
diff --git a/library/src/main/java/com/yalantis/phoenix/PullToRefreshView.java b/library/src/main/java/com/yalantis/phoenix/PullToRefreshView.java
index 91dba70..acea222 100644
--- a/library/src/main/java/com/yalantis/phoenix/PullToRefreshView.java
+++ b/library/src/main/java/com/yalantis/phoenix/PullToRefreshView.java
@@ -4,6 +4,10 @@
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.NestedScrollingChild;
+import android.support.v4.view.NestedScrollingChildHelper;
+import android.support.v4.view.NestedScrollingParent;
+import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
@@ -23,7 +27,8 @@
import java.security.InvalidParameterException;
-public class PullToRefreshView extends ViewGroup {
+public class PullToRefreshView extends ViewGroup implements NestedScrollingParent,
+ NestedScrollingChild {
private static final int DRAG_MAX_DISTANCE = 120;
private static final float DRAG_RATE = .5f;
@@ -56,6 +61,16 @@ public class PullToRefreshView extends ViewGroup {
private int mTargetPaddingRight;
private int mTargetPaddingLeft;
+ // If nested scrolling is enabled, the total amount that needed to be
+ // consumed by this as the nested scrolling parent is used in place of the
+ // overscroll determined by MOVE events in the onTouch handler
+ private float mTotalUnconsumed;
+ private final NestedScrollingParentHelper mNestedScrollingParentHelper;
+ private final NestedScrollingChildHelper mNestedScrollingChildHelper;
+ private final int[] mParentScrollConsumed = new int[2];
+ private final int[] mParentOffsetInWindow = new int[2];
+ private boolean mNestedScrollInProgress;
+
public PullToRefreshView(Context context) {
this(context, null);
}
@@ -78,6 +93,10 @@ public PullToRefreshView(Context context, AttributeSet attrs) {
setWillNotDraw(false);
ViewCompat.setChildrenDrawingOrderEnabled(this, true);
+
+ mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
+ mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
+ setNestedScrollingEnabled(true);
}
public void setRefreshStyle(int type) {
@@ -136,13 +155,14 @@ private void ensureTarget() {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
+ ensureTarget();
- if (!isEnabled() || canChildScrollUp() || mRefreshing) {
+ final int action = MotionEventCompat.getActionMasked(ev);
+
+ if (!isEnabled() || canChildScrollUp() || mRefreshing || mNestedScrollInProgress) {
return false;
}
- final int action = MotionEventCompat.getActionMasked(ev);
-
switch (action) {
case MotionEvent.ACTION_DOWN:
setTargetOffsetTop(0, true);
@@ -164,6 +184,7 @@ public boolean onInterceptTouchEvent(MotionEvent ev) {
}
final float yDiff = y - mInitialMotionY;
if (yDiff > mTouchSlop && !mIsBeingDragged) {
+ mInitialMotionY = mInitialMotionY + mTouchSlop;
mIsBeingDragged = true;
}
break;
@@ -189,6 +210,11 @@ public boolean onTouchEvent(@NonNull MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
+ if (!isEnabled() || canChildScrollUp() || mRefreshing || mNestedScrollInProgress) {
+ // Fail fast if we're not in a state where a swipe is possible
+ return false;
+ }
+
switch (action) {
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
@@ -198,23 +224,7 @@ public boolean onTouchEvent(@NonNull MotionEvent ev) {
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = y - mInitialMotionY;
- final float scrollTop = yDiff * DRAG_RATE;
- mCurrentDragPercent = scrollTop / mTotalDragDistance;
- if (mCurrentDragPercent < 0) {
- return false;
- }
- float boundedDragPercent = Math.min(1f, Math.abs(mCurrentDragPercent));
- float extraOS = Math.abs(scrollTop) - mTotalDragDistance;
- float slingshotDist = mTotalDragDistance;
- float tensionSlingshotPercent = Math.max(0,
- Math.min(extraOS, slingshotDist * 2) / slingshotDist);
- float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
- (tensionSlingshotPercent / 4), 2)) * 2f;
- float extraMove = (slingshotDist) * tensionPercent / 2;
- int targetY = (int) ((slingshotDist * boundedDragPercent) + extraMove);
-
- mBaseRefreshView.setPercent(mCurrentDragPercent, true);
- setTargetOffsetTop(targetY - mCurrentOffsetTop, true);
+ moveAnimation(yDiff);
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN:
@@ -225,7 +235,7 @@ public boolean onTouchEvent(@NonNull MotionEvent ev) {
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL: {
+ case MotionEvent.ACTION_CANCEL:
if (mActivePointerId == INVALID_POINTER) {
return false;
}
@@ -241,7 +251,7 @@ public boolean onTouchEvent(@NonNull MotionEvent ev) {
}
mActivePointerId = INVALID_POINTER;
return false;
- }
+
}
return true;
@@ -388,7 +398,7 @@ private boolean canChildScrollUp() {
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
- return mTarget.getScrollY() > 0;
+ return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
@@ -421,5 +431,186 @@ public interface OnRefreshListener {
void onRefresh();
}
+ // NestedScrollingParent
+
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return isEnabled() && !mRefreshing
+ && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ // Reset the counter of how much leftover scroll needs to be consumed.
+ mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
+ // Dispatch up to the nested parent
+ startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
+ mTotalUnconsumed = 0;
+ mNestedScrollInProgress = true;
+ }
+
+ @Override
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ // If we are in the middle of consuming, a scroll, then we want to move the spinner back up
+ // before allowing the list to scroll
+ if (dy > 0 && mTotalUnconsumed > 0) {
+ if (dy > mTotalUnconsumed) {
+ consumed[1] = dy - (int) mTotalUnconsumed;
+ mTotalUnconsumed = 0;
+ } else {
+ mTotalUnconsumed -= dy;
+ consumed[1] = dy;
+ }
+ moveAnimation(mTotalUnconsumed);
+ }
+
+ // If a client layout is using a custom start position for the circle
+ // view, they mean to hide it again before scrolling the child view
+ // If we get back to mTotalUnconsumed == 0 and there is more to go, hide
+ // the circle so it isn't exposed if its blocking content is moved
+ /*
+ if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0
+ && Math.abs(dy - consumed[1]) > 0) {
+ mSomeView.setVisibility(View.GONE);
+ }
+ */
+
+ // Now let our nested parent consume the leftovers
+ final int[] parentConsumed = mParentScrollConsumed;
+ if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
+ consumed[0] += parentConsumed[0];
+ consumed[1] += parentConsumed[1];
+ }
+ }
+
+ @Override
+ public int getNestedScrollAxes() {
+ return mNestedScrollingParentHelper.getNestedScrollAxes();
+ }
+
+ @Override
+ public void onStopNestedScroll(View target) {
+ mNestedScrollingParentHelper.onStopNestedScroll(target);
+ mNestedScrollInProgress = false;
+ // Finish the spinner for nested scrolling if we ever consumed any
+ // unconsumed nested scroll
+ if (mTotalUnconsumed > 0) {
+ finishAnimation(mTotalUnconsumed);
+ mTotalUnconsumed = 0;
+ }
+
+ // Dispatch up our nested parent
+ stopNestedScroll();
+ }
+
+ @Override
+ public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,
+ final int dxUnconsumed, final int dyUnconsumed) {
+ // Dispatch up to the nested parent first
+ dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+ mParentOffsetInWindow);
+
+ // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are
+ // sometimes between two nested scrolling views, we need a way to be able to know when any
+ // nested scrolling parent has stopped handling events. We do that by using the
+ // 'offset in window 'functionality to see if we have been moved from the event.
+ // This is a decent indication of whether we should take over the event stream or not.
+ final int dy = dyUnconsumed + mParentOffsetInWindow[1];
+ if (dy < 0 && !canChildScrollUp()) {
+ mTotalUnconsumed += Math.abs(dy);
+ moveAnimation(mTotalUnconsumed);
+ }
+ }
+
+ private void moveAnimation(float overscrollTop) {
+ final float scrollTop = overscrollTop * DRAG_RATE;
+ mCurrentDragPercent = scrollTop / mTotalDragDistance;
+ if (mCurrentDragPercent < 0) {
+ return;
+ }
+ float boundedDragPercent = Math.min(1f, Math.abs(mCurrentDragPercent));
+ float extraOS = Math.abs(scrollTop) - mTotalDragDistance;
+ float slingshotDist = mTotalDragDistance;
+ float tensionSlingshotPercent = Math.max(0,
+ Math.min(extraOS, slingshotDist * 2) / slingshotDist);
+ float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
+ (tensionSlingshotPercent / 4), 2)) * 2f;
+ float extraMove = (slingshotDist) * tensionPercent / 2;
+ int targetY = (int) ((slingshotDist * boundedDragPercent) + extraMove);
+
+ mBaseRefreshView.setPercent(mCurrentDragPercent, true);
+ setTargetOffsetTop(targetY - mCurrentOffsetTop, true);
+ }
+
+ private void finishAnimation(float overscrollTop) {
+ if (overscrollTop > mTotalDragDistance) {
+ setRefreshing(true, true /* notify */);
+ } else {
+ // cancel refresh
+ mRefreshing = false;
+ animateOffsetToStartPosition();
+ }
+ }
+
+ // NestedScrollingChild
+
+ @Override
+ public void setNestedScrollingEnabled(boolean enabled) {
+ mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
+ }
+
+ @Override
+ public boolean isNestedScrollingEnabled() {
+ return mNestedScrollingChildHelper.isNestedScrollingEnabled();
+ }
+
+ @Override
+ public boolean startNestedScroll(int axes) {
+ return mNestedScrollingChildHelper.startNestedScroll(axes);
+ }
+
+ @Override
+ public void stopNestedScroll() {
+ mNestedScrollingChildHelper.stopNestedScroll();
+ }
+
+ @Override
+ public boolean hasNestedScrollingParent() {
+ return mNestedScrollingChildHelper.hasNestedScrollingParent();
+ }
+
+ @Override
+ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
+ int dyUnconsumed, int[] offsetInWindow) {
+ return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
+ dxUnconsumed, dyUnconsumed, offsetInWindow);
+ }
+
+ @Override
+ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
+ return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
+ }
+
+ @Override
+ public boolean onNestedPreFling(View target, float velocityX,
+ float velocityY) {
+ return dispatchNestedPreFling(velocityX, velocityY);
+ }
+
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY,
+ boolean consumed) {
+ return dispatchNestedFling(velocityX, velocityY, consumed);
+ }
+
+ @Override
+ public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
+ return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
+ }
+
+ @Override
+ public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
+ return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
+ }
}
diff --git a/sample/src/main/java/com/yalantis/phoenix/sample/ListViewFragment.java b/sample/src/main/java/com/yalantis/phoenix/sample/ListViewFragment.java
index 9b76277..e68bafe 100644
--- a/sample/src/main/java/com/yalantis/phoenix/sample/ListViewFragment.java
+++ b/sample/src/main/java/com/yalantis/phoenix/sample/ListViewFragment.java
@@ -3,6 +3,7 @@
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.v4.view.ViewCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -29,6 +30,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
ListView listView = (ListView) rootView.findViewById(R.id.list_view);
listView.setAdapter(new SampleAdapter(getActivity(), R.layout.list_item, mSampleList));
+ ViewCompat.setNestedScrollingEnabled(listView, true);
+
mPullToRefreshView = (PullToRefreshView) rootView.findViewById(R.id.pull_to_refresh);
mPullToRefreshView.setOnRefreshListener(new PullToRefreshView.OnRefreshListener() {
@Override
diff --git a/sample/src/main/res/layout/activity_pull_to_refresh.xml b/sample/src/main/res/layout/activity_pull_to_refresh.xml
index 3035688..e310c55 100644
--- a/sample/src/main/res/layout/activity_pull_to_refresh.xml
+++ b/sample/src/main/res/layout/activity_pull_to_refresh.xml
@@ -1,23 +1,44 @@
-
+
-
+ android:layout_height="@dimen/appbar_height"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
-
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/fragment_list_view.xml b/sample/src/main/res/layout/fragment_list_view.xml
index e830e7c..821d43a 100644
--- a/sample/src/main/res/layout/fragment_list_view.xml
+++ b/sample/src/main/res/layout/fragment_list_view.xml
@@ -1,11 +1,9 @@
-
-
-
+
-
diff --git a/sample/src/main/res/layout/fragment_recycler_view.xml b/sample/src/main/res/layout/fragment_recycler_view.xml
index cd9c85a..1e31525 100644
--- a/sample/src/main/res/layout/fragment_recycler_view.xml
+++ b/sample/src/main/res/layout/fragment_recycler_view.xml
@@ -1,11 +1,9 @@
-
-
-
+
-
diff --git a/sample/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml
index 82806c6..64bb47e 100644
--- a/sample/src/main/res/values/dimens.xml
+++ b/sample/src/main/res/values/dimens.xml
@@ -1,4 +1,5 @@
222dp
+ 112dp
\ No newline at end of file