package com.fanxin.huangfangyi.main.widget.pull;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import com.fanxin.huangfangyi.R;
/**
* The SwipeRefreshLayout should be used whenever the user can refresh the
* contents of a view via a vertical swipe gesture. The activity that
* instantiates this view should add an OnRefreshListener to be notified
* whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout
* will notify the listener each and every time the gesture is completed again;
* the listener is responsible for correctly determining when to actually
* initiate a refresh of its content. If the listener determines there should
* not be a refresh, it must call setRefreshing(false) to cancel any visual
* indication of a refresh. If an activity wishes to show just the progress
* animation, it should call setRefreshing(true). To disable the gesture and
* progress animation, call setEnabled(false) on the view.
* <p>
* This layout should be made the parent of the view that will be refreshed as a
* result of the gesture and can only support one direct child. This view will
* also be made the target of the gesture and will be forced to match both the
* width and the height supplied in this layout. The SwipeRefreshLayout does not
* provide accessibility events; instead, a menu item must be provided to allow
* refresh of the content wherever this gesture is used.
* </p>
*/
/**
* 下拉刷新、加载更多、分页索引
* @author xutao
*
*/
public class SwipyRefreshLayout extends ViewGroup {
/** 是不是下拉 **/
public boolean isTop;
/** 第一页 **/
public int firstIndex = 0;
/** 页数索引 **/
public int index = firstIndex;
// Maps to ProgressBar.Large style
public static final int LARGE = MaterialProgressDrawable.LARGE;
// Maps to ProgressBar default style
public static final int DEFAULT = MaterialProgressDrawable.DEFAULT;
private static final String LOG_TAG = SwipyRefreshLayout.class.getSimpleName();
private static final int MAX_ALPHA = 255;
private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA);
private static final int CIRCLE_DIAMETER = 40;
private static final int CIRCLE_DIAMETER_LARGE = 56;
private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
private static final int INVALID_POINTER = -1;
private static final float DRAG_RATE = .5f;
// Max amount of circle that can be filled by progress during swipe gesture,
// where 1.0 is a full circle
private static final float MAX_PROGRESS_ANGLE = .8f;
private static final int SCALE_DOWN_DURATION = 150;
private static final int ALPHA_ANIMATION_DURATION = 300;
private static final int ANIMATE_TO_TRIGGER_DURATION = 200;
private static final int ANIMATE_TO_START_DURATION = 200;
// Default background for the progress spinner
private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA;
// Default offset in dips from the top of the view to where the progress spinner should stop
private static final int DEFAULT_CIRCLE_TARGET = 64;
private View mTarget; // the target of the gesture
private SwipyRefreshLayoutDirection mDirection;
private boolean mBothDirection;
private OnRefreshListener mListener;
private boolean mRefreshing = false;
private int mTouchSlop;
private float mTotalDragDistance = -1;
private int mMediumAnimationDuration;
private int mCurrentTargetOffsetTop;
// Whether or not the starting offset has been determined.
private boolean mOriginalOffsetCalculated = false;
private float mInitialMotionY;
private boolean mIsBeingDragged;
private int mActivePointerId = INVALID_POINTER;
// Whether this item is scaled up rather than clipped
private boolean mScale;
// Target is returning to its start offset because it was cancelled or a
// refresh was triggered.
private boolean mReturningToStart;
private final DecelerateInterpolator mDecelerateInterpolator;
private static final int[] LAYOUT_ATTRS = new int[]{
android.R.attr.enabled
};
private CircleImageView mCircleView;
private int mCircleViewIndex = -1;
protected int mFrom;
private float mStartingScale;
protected int mOriginalOffsetTop;
private MaterialProgressDrawable mProgress;
private Animation mScaleAnimation;
private Animation mScaleDownAnimation;
private Animation mAlphaStartAnimation;
private Animation mAlphaMaxAnimation;
private Animation mScaleDownToStartAnimation;
private float mSpinnerFinalOffset;
private boolean mNotify;
private int mCircleWidth;
private int mCircleHeight;
// Whether the client has set a custom starting position;
private boolean mUsingCustomStart;
private AnimationListener mRefreshListener = new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (mRefreshing) {
// Make sure the progress view is fully visible
mProgress.setAlpha(MAX_ALPHA);
mProgress.start();
if (mNotify) {
if (mListener != null) {
if (isTop) {
index = firstIndex;
mListener.onRefresh(index);
}else {
index ++;
mListener.onLoad(index);
}
}
}
} else {
mProgress.stop();
mCircleView.setVisibility(View.GONE);
setColorViewAlpha(MAX_ALPHA);
// Return the circle to its start position
if (mScale) {
setAnimationProgress(0 /* animation complete and view is hidden */);
} else {
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop,
true /* requires update */);
}
}
mCurrentTargetOffsetTop = mCircleView.getTop();
}
};
private void setColorViewAlpha(int targetAlpha) {
mCircleView.getBackground().setAlpha(targetAlpha);
mProgress.setAlpha(targetAlpha);
}
/**
* The refresh indicator starting and resting position is always positioned
* near the top of the refreshing content. This position is a consistent
* location, but can be adjusted in either direction based on whether or not
* there is a toolbar or actionbar present.
*
* @param scale Set to true if there is no view at a higher z-order than
* where the progress spinner is set to appear.
* @param start The offset in pixels from the top of this view at which the
* progress spinner should appear.
* @param end The offset in pixels from the top of this view at which the
* progress spinner should come to rest after a successful swipe
* gesture.
*/
/*
public void setProgressViewOffset(boolean scale, int start, int end) {
mScale = scale;
mCircleView.setVisibility(View.GONE);
mOriginalOffsetTop = mCurrentTargetOffsetTop = start;
mSpinnerFinalOffset = end;
mUsingCustomStart = true;
mCircleView.invalidate();
}*/
/**
* The refresh indicator resting position is always positioned near the top
* of the refreshing content. This position is a consistent location, but
* can be adjusted in either direction based on whether or not there is a
* toolbar or actionbar present.
*
* @param scale Set to true if there is no view at a higher z-order than
* where the progress spinner is set to appear.
* @param end The offset in pixels from the top of this view at which the
* progress spinner should come to rest after a successful swipe
* gesture.
*/
/*
public void setProgressViewEndTarget(boolean scale, int end) {
mSpinnerFinalOffset = end;
mScale = scale;
mCircleView.invalidate();
}*/
/**
* One of DEFAULT, or LARGE.
*/
public void setSize(int size) {
if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) {
return;
}
final DisplayMetrics metrics = getResources().getDisplayMetrics();
if (size == MaterialProgressDrawable.LARGE) {
mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);
} else {
mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
}
// force the bounds of the progress circle inside the circle view to
// update by setting it to null before updating its size and then
// re-setting it
mCircleView.setImageDrawable(null);
mProgress.updateSizes(size);
mCircleView.setImageDrawable(mProgress);
}
/**
* Simple constructor to use when creating a SwipeRefreshLayout from code.
*
* @param context
*/
public SwipyRefreshLayout(Context context) {
this(context, null);
}
/**
* Constructor that is called when inflating SwipeRefreshLayout from XML.
*
* @param context
* @param attrs
*/
@TargetApi(Build.VERSION_CODES.CUPCAKE)
public SwipyRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMediumAnimationDuration = getResources().getInteger(
android.R.integer.config_mediumAnimTime);
setWillNotDraw(false);
mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
setEnabled(a.getBoolean(0, true));
a.recycle();
final TypedArray a2 = context.obtainStyledAttributes(attrs, R.styleable.SwipyRefreshLayout);
SwipyRefreshLayoutDirection direction
= SwipyRefreshLayoutDirection.getFromInt(a2.getInt(R.styleable.SwipyRefreshLayout_direction, 0));
if (direction != SwipyRefreshLayoutDirection.BOTH) {
mDirection = direction;
mBothDirection = false;
} else {
mDirection = SwipyRefreshLayoutDirection.TOP;
mBothDirection = true;
}
a2.recycle();
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density);
createProgressView();
ViewCompat.setChildrenDrawingOrderEnabled(this, true);
// the absolute offset has to take into account that the circle starts at an offset
mSpinnerFinalOffset = DEFAULT_CIRCLE_TARGET * metrics.density;
mTotalDragDistance = mSpinnerFinalOffset;
//设置刷新动画颜色
setColorSchemeResources(android.R.color.holo_blue_light,
android.R.color.holo_red_light,
android.R.color.holo_orange_light,
android.R.color.holo_green_light);
}
protected int getChildDrawingOrder(int childCount, int i) {
if (mCircleViewIndex < 0) {
return i;
} else if (i == childCount - 1) {
// Draw the selected child last
return mCircleViewIndex;
} else if (i >= mCircleViewIndex) {
// Move the children after the selected child earlier one
return i + 1;
} else {
// Keep the children before the selected child the same
return i;
}
}
private void createProgressView() {
mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER / 2);
mProgress = new MaterialProgressDrawable(getContext(), this);
mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
mCircleView.setImageDrawable(mProgress);
mCircleView.setVisibility(View.GONE);
addView(mCircleView);
}
/**
* Set the listener to be notified when a refresh is triggered via the swipe
* gesture.
*/
public void setOnRefreshListener(OnRefreshListener listener) {
mListener = listener;
}
/**
* Pre API 11, alpha is used to make the progress circle appear instead of scale.
*/
private boolean isAlphaUsedForScale() {
return Build.VERSION.SDK_INT < 11;
}
/**
* Notify the widget that refresh state has changed. Do not call this when
* refresh is triggered by a swipe gesture.
*
* @param refreshing Whether or not the view should show refresh progress.
*/
public void setRefreshing(boolean refreshing) {
if (refreshing && mRefreshing != refreshing) {
// scale and show
mRefreshing = refreshing;
int endTarget = 0;
if (!mUsingCustomStart) {
endTarget = (int) (mSpinnerFinalOffset + mOriginalOffsetTop);
} else {
endTarget = (int) mSpinnerFinalOffset;
}
setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop,
true /* requires update */);
mNotify = false;
startScaleUpAnimation(mRefreshListener);
} else {
setRefreshing(refreshing, false /* notify */);
}
}
private void startScaleUpAnimation(AnimationListener listener) {
mCircleView.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= 11) {
// Pre API 11, alpha is used in place of scale up to show the
// progress circle appearing.
// Don't adjust the alpha during appearance otherwise.
mProgress.setAlpha(MAX_ALPHA);
}
mScaleAnimation = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
setAnimationProgress(interpolatedTime);
}
};
mScaleAnimation.setDuration(mMediumAnimationDuration);
if (listener != null) {
mCircleView.setAnimationListener(listener);
}
mCircleView.clearAnimation();
mCircleView.startAnimation(mScaleAnimation);
}
/**
* Pre API 11, this does an alpha animation.
*
* @param progress
*/
private void setAnimationProgress(float progress) {
if (isAlphaUsedForScale()) {
setColorViewAlpha((int) (progress * MAX_ALPHA));
} else {
ViewCompat.setScaleX(mCircleView, progress);
ViewCompat.setScaleY(mCircleView, progress);
}
}
private void setRefreshing(boolean refreshing, final boolean notify) {
if (mRefreshing != refreshing) {
mNotify = notify;
ensureTarget();
mRefreshing = refreshing;
if (mRefreshing) {
animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
} else {
startScaleDownAnimation(mRefreshListener);
}
}
}
private void startScaleDownAnimation(AnimationListener listener) {
mScaleDownAnimation = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
setAnimationProgress(1 - interpolatedTime);
}
};
mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
mCircleView.setAnimationListener(listener);
mCircleView.clearAnimation();
mCircleView.startAnimation(mScaleDownAnimation);
}
@SuppressLint("NewApi") private void startProgressAlphaStartAnimation() {
mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA);
}
@SuppressLint("NewApi") private void startProgressAlphaMaxAnimation() {
mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA);
}
private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) {
// Pre API 11, alpha is used in place of scale. Don't also use it to
// show the trigger point.
if (mScale && isAlphaUsedForScale()) {
return null;
}
Animation alpha = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
mProgress
.setAlpha((int) (startingAlpha + ((endingAlpha - startingAlpha)
* interpolatedTime)));
}
};
alpha.setDuration(ALPHA_ANIMATION_DURATION);
// Clear out the previous animation listeners.
mCircleView.setAnimationListener(null);
mCircleView.clearAnimation();
mCircleView.startAnimation(alpha);
return alpha;
}
/**
* Set the background color of the progress spinner disc.
*
* @param colorRes Resource id of the color.
*/
public void setProgressBackgroundColor(int colorRes) {
mCircleView.setBackgroundColor(colorRes);
mProgress.setBackgroundColor(getResources().getColor(colorRes));
}
/**
* @deprecated Use {@link #setColorSchemeResources(int...)}
*/
@Deprecated
public void setColorScheme(int... colors) {
setColorSchemeResources(colors);
}
/**
* Set the color resources used in the progress animation from color resources.
* The first color will also be the color of the bar that grows in response
* to a user swipe gesture.
*
* @param colorResIds
*/
public void setColorSchemeResources(int... colorResIds) {
final Resources res = getResources();
int[] colorRes = new int[colorResIds.length];
for (int i = 0; i < colorResIds.length; i++) {
colorRes[i] = res.getColor(colorResIds[i]);
}
setColorSchemeColors(colorRes);
}
/**
* Set the colors used in the progress animation. The first
* color will also be the color of the bar that grows in response to a user
* swipe gesture.
*
* @param colors
*/
public void setColorSchemeColors(int... colors) {
ensureTarget();
mProgress.setColorSchemeColors(colors);
}
/**
* @return Whether the SwipeRefreshWidget is actively showing refresh
* progress.
*/
public boolean isRefreshing() {
return mRefreshing;
}
private void ensureTarget() {
// Don't bother getting the parent height if the parent hasn't been laid
// out yet.
if (mTarget == null) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (!child.equals(mCircleView)) {
mTarget = child;
break;
}
}
}
}
/**
* Set the distance to trigger a sync in dips
*
* @param distance
*/
public void setDistanceToTriggerSync(int distance) {
mTotalDragDistance = distance;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int width = getMeasuredWidth();
final int height = getMeasuredHeight();
if (getChildCount() == 0) {
return;
}
if (mTarget == null) {
ensureTarget();
}
if (mTarget == null) {
return;
}
final View child = mTarget;
final int childLeft = getPaddingLeft();
final int childTop = getPaddingTop();
final int childWidth = width - getPaddingLeft() - getPaddingRight();
final int childHeight = height - getPaddingTop() - getPaddingBottom();
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
int circleWidth = mCircleView.getMeasuredWidth();
int circleHeight = mCircleView.getMeasuredHeight();
mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,
(width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mTarget == null) {
ensureTarget();
}
if (mTarget == null) {
return;
}
mTarget.measure(MeasureSpec.makeMeasureSpec(
getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY));
if (!mUsingCustomStart && !mOriginalOffsetCalculated) {
mOriginalOffsetCalculated = true;
switch (mDirection) {
case BOTTOM:
mCurrentTargetOffsetTop = mOriginalOffsetTop = getMeasuredHeight() - mCircleView.getMeasuredHeight();
break;
case TOP:
default:
mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
break;
}
}
mCircleViewIndex = -1;
// Get the index of the circleview.
for (int index = 0; index < getChildCount(); index++) {
if (getChildAt(index) == mCircleView) {
mCircleViewIndex = index;
break;
}
}
}
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
*/
public boolean canChildScrollUp() {
if (Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}
// public boolean canChildScrollUp() {
// if (android.os.Build.VERSION.SDK_INT < 14) {
// if (mTarget instanceof AbsListView) {
// final AbsListView absListView = (AbsListView) mTarget;
// if (absListView.getLastVisiblePosition() + 1 == absListView.getCount()) {
// int lastIndex = absListView.getLastVisiblePosition() - absListView.getFirstVisiblePosition();
//
// boolean res = absListView.getChildAt(lastIndex).getBottom() == absListView.getPaddingBottom();
//
// return res;
// }
// return true;
// } else {
// return mTarget.getScrollY() > 0;
// }
// } else {
// return ViewCompat.canScrollVertically(mTarget, 1);
// }
// }
public boolean canChildScrollDown() {
if (Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
try {
if (absListView.getCount() > 0) {
if (absListView.getLastVisiblePosition() + 1 == absListView.getCount()) {
int lastIndex = absListView.getLastVisiblePosition() - absListView.getFirstVisiblePosition();
return absListView.getChildAt(lastIndex).getBottom() == absListView.getPaddingBottom();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
} else {
return true;
}
} else {
return ViewCompat.canScrollVertically(mTarget, 1);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTarget();
final int action = MotionEventCompat.getActionMasked(ev);
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
}
switch (mDirection) {
case BOTTOM:
if (!isEnabled() || mReturningToStart || (!mBothDirection && canChildScrollDown()) || mRefreshing) {
// Fail fast if we're not in a state where a swipe is possible
return false;
}
break;
case TOP:
default:
if (!isEnabled() || mReturningToStart || (!mBothDirection && canChildScrollUp()) || mRefreshing) {
// Fail fast if we're not in a state where a swipe is possible
return false;
}
break;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mIsBeingDragged = false;
final float initialMotionY = getMotionEventY(ev, mActivePointerId);
if (initialMotionY == -1) {
return false;
}
mInitialMotionY = initialMotionY;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER) {
return false;
}
final float y = getMotionEventY(ev, mActivePointerId);
if (y == -1) {
return false;
}
if (mBothDirection) {
if (y > mInitialMotionY) {
setRawDirection(SwipyRefreshLayoutDirection.TOP);
} else if (y < mInitialMotionY) {
setRawDirection(SwipyRefreshLayoutDirection.BOTTOM);
}
if ((mDirection == SwipyRefreshLayoutDirection.BOTTOM && canChildScrollDown())
|| (mDirection == SwipyRefreshLayoutDirection.TOP && canChildScrollUp())) {
return false;
}
}
float yDiff;
switch (mDirection) {
case BOTTOM:
yDiff = mInitialMotionY - y;
break;
case TOP:
default:
yDiff = y - mInitialMotionY;
break;
}
if (yDiff > mTouchSlop && !mIsBeingDragged) {
mIsBeingDragged = true;
mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
}
break;
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
break;
}
return mIsBeingDragged;
}
private float getMotionEventY(MotionEvent ev, int activePointerId) {
final int index = MotionEventCompat.findPointerIndex(ev, activePointerId);
if (index < 0) {
return -1;
}
return MotionEventCompat.getY(ev, index);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
// Nope.
}
private boolean isAnimationRunning(Animation animation) {
return animation != null && animation.hasStarted() && !animation.hasEnded();
}
@SuppressLint("NewApi") @Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
}
switch (mDirection) {
case BOTTOM:
if (!isEnabled() || mReturningToStart || canChildScrollDown() || mRefreshing) {
// Fail fast if we're not in a state where a swipe is possible
return false;
}
break;
case TOP:
default:
if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) {
// Fail fast if we're not in a state where a swipe is possible
return false;
}
break;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mIsBeingDragged = false;
break;
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
if (pointerIndex < 0) {
return false;
}
final float y = MotionEventCompat.getY(ev, pointerIndex);
float overscrollTop;
switch (mDirection) {
case BOTTOM:
overscrollTop = (mInitialMotionY - y) * DRAG_RATE;
break;
case TOP:
default:
overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
break;
}
if (mIsBeingDragged) {
mProgress.showArrow(true);
float originalDragPercent = overscrollTop / mTotalDragDistance;
if (originalDragPercent < 0) {
return false;
}
float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset
- mOriginalOffsetTop : mSpinnerFinalOffset;
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 = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);
int targetY;
if (mDirection == SwipyRefreshLayoutDirection.TOP) {
targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);
} else {
targetY = mOriginalOffsetTop - (int) ((slingshotDist * dragPercent) + extraMove);
}
// where 1.0f is a full circle
if (mCircleView.getVisibility() != View.VISIBLE) {
mCircleView.setVisibility(View.VISIBLE);
}
if (!mScale) {
ViewCompat.setScaleX(mCircleView, 1f);
ViewCompat.setScaleY(mCircleView, 1f);
}
if (overscrollTop < mTotalDragDistance) {
if (mScale) {
setAnimationProgress(overscrollTop / mTotalDragDistance);
}
if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA
&& !isAnimationRunning(mAlphaStartAnimation)) {
// Animate the alpha
startProgressAlphaStartAnimation();
}
float strokeStart = (float) (adjustedPercent * .8f);
mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
mProgress.setArrowScale(Math.min(1f, adjustedPercent));
} else {
if (mProgress.getAlpha() < MAX_ALPHA
&& !isAnimationRunning(mAlphaMaxAnimation)) {
// Animate the alpha
startProgressAlphaMaxAnimation();
}
}
float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
mProgress.setProgressRotation(rotation);
setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop,
true /* requires update */);
}
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN: {
final int index = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
break;
}
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
if (mActivePointerId == INVALID_POINTER) {
if (action == MotionEvent.ACTION_UP) {
}
return false;
}
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float y = MotionEventCompat.getY(ev, pointerIndex);
float overscrollTop;
switch (mDirection) {
case BOTTOM:
overscrollTop = (mInitialMotionY - y) * DRAG_RATE;
isTop = false;
break;
case TOP:
default:
overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
isTop = true;
break;
}
mIsBeingDragged = false;
if (overscrollTop > mTotalDragDistance) {
setRefreshing(true, true /* notify */);
} else {
// cancel refresh
mRefreshing = false;
mProgress.setStartEndTrim(0f, 0f);
AnimationListener listener = null;
if (!mScale) {
listener = new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (!mScale) {
startScaleDownAnimation(null);
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};
}
animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
mProgress.showArrow(false);
}
mActivePointerId = INVALID_POINTER;
return false;
}
}
return true;
}
private void animateOffsetToCorrectPosition(int from, AnimationListener listener) {
mFrom = from;
mAnimateToCorrectPosition.reset();
mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
if (listener != null) {
mCircleView.setAnimationListener(listener);
}
mCircleView.clearAnimation();
mCircleView.startAnimation(mAnimateToCorrectPosition);
}
private void animateOffsetToStartPosition(int from, AnimationListener listener) {
if (mScale) {
// Scale the item back down
startScaleDownReturnToStartAnimation(from, listener);
} else {
mFrom = from;
mAnimateToStartPosition.reset();
mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);
mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
if (listener != null) {
mCircleView.setAnimationListener(listener);
}
mCircleView.clearAnimation();
mCircleView.startAnimation(mAnimateToStartPosition);
}
}
private final Animation mAnimateToCorrectPosition = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
int targetTop = 0;
int endTarget = 0;
if (!mUsingCustomStart) {
switch (mDirection) {
case BOTTOM:
endTarget = getMeasuredHeight() - (int) (mSpinnerFinalOffset);
break;
case TOP:
default:
endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop));
break;
}
} else {
endTarget = (int) mSpinnerFinalOffset;
}
targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
int offset = targetTop - mCircleView.getTop();
setTargetOffsetTopAndBottom(offset, false /* requires update */);
}
};
private void moveToStart(float interpolatedTime) {
int targetTop = 0;
targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
int offset = targetTop - mCircleView.getTop();
setTargetOffsetTopAndBottom(offset, false /* requires update */);
}
private final Animation mAnimateToStartPosition = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
moveToStart(interpolatedTime);
}
};
@SuppressLint("NewApi") private void startScaleDownReturnToStartAnimation(int from,
AnimationListener listener) {
mFrom = from;
if (isAlphaUsedForScale()) {
mStartingScale = mProgress.getAlpha();
} else {
mStartingScale = ViewCompat.getScaleX(mCircleView);
}
mScaleDownToStartAnimation = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime));
setAnimationProgress(targetScale);
moveToStart(interpolatedTime);
}
};
mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION);
if (listener != null) {
mCircleView.setAnimationListener(listener);
}
mCircleView.clearAnimation();
mCircleView.startAnimation(mScaleDownToStartAnimation);
}
private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) {
mCircleView.bringToFront();
mCircleView.offsetTopAndBottom(offset);
mCurrentTargetOffsetTop = mCircleView.getTop();
if (requiresUpdate && Build.VERSION.SDK_INT < 11) {
invalidate();
}
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
}
}
/**
* Classes that wish to be notified when the swipe gesture correctly
* triggers a refresh should implement this interface.
*/
public interface OnRefreshListener {
public void onRefresh(int index);
public void onLoad(int index);
}
public SwipyRefreshLayoutDirection getDirection() {
return mBothDirection ? SwipyRefreshLayoutDirection.BOTH : mDirection;
}
public void setDirection(SwipyRefreshLayoutDirection direction) {
if (direction == SwipyRefreshLayoutDirection.BOTH) {
mBothDirection = true;
} else {
mBothDirection = false;
mDirection = direction;
}
switch (mDirection) {
case BOTTOM:
mCurrentTargetOffsetTop = mOriginalOffsetTop = getMeasuredHeight() - mCircleView.getMeasuredHeight();
break;
case TOP:
default:
mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
break;
}
}
// only TOP or Bottom
private void setRawDirection(SwipyRefreshLayoutDirection direction) {
if (mDirection == direction) {
return;
}
mDirection = direction;
switch (mDirection) {
case BOTTOM:
mCurrentTargetOffsetTop = mOriginalOffsetTop = getMeasuredHeight() - mCircleView.getMeasuredHeight();
break;
case TOP:
default:
mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
break;
}
}
/**
* @return 获得从第一页开始索引
*/
public int getFirstIndex() {
return firstIndex;
}
/**
* 设置从第几页开始(默认值为0)
* @param firstIndex 第几页
*/
public void setFirstIndex(int firstIndex) {
this.firstIndex = firstIndex;
}
/**
* @return 获得当前索引
*/
public int getIndex() {
return index;
}
}