Fields Summary |
---|
public static final int | LARGE |
public static final int | DEFAULT |
private static final String | LOG_TAG |
private static final int | MAX_ALPHA |
private static final int | STARTING_PROGRESS_ALPHA |
private static final int | CIRCLE_DIAMETER |
private static final int | CIRCLE_DIAMETER_LARGE |
private static final float | DECELERATE_INTERPOLATION_FACTOR |
private static final int | INVALID_POINTER |
private static final float | DRAG_RATE |
private static final float | MAX_PROGRESS_ANGLE |
private static final int | SCALE_DOWN_DURATION |
private static final int | ALPHA_ANIMATION_DURATION |
private static final int | ANIMATE_TO_TRIGGER_DURATION |
private static final int | ANIMATE_TO_START_DURATION |
private static final int | CIRCLE_BG_LIGHT |
private static final int | DEFAULT_CIRCLE_TARGET |
private android.view.View | mTarget |
private OnRefreshListener | mListener |
private boolean | mRefreshing |
private int | mTouchSlop |
private float | mTotalDragDistance |
private int | mMediumAnimationDuration |
private int | mCurrentTargetOffsetTop |
private boolean | mOriginalOffsetCalculated |
private float | mInitialMotionY |
private float | mInitialDownY |
private boolean | mIsBeingDragged |
private int | mActivePointerId |
private boolean | mScale |
private boolean | mReturningToStart |
private final android.view.animation.DecelerateInterpolator | mDecelerateInterpolator |
private static final int[] | LAYOUT_ATTRS |
private CircleImageView | mCircleView |
private int | mCircleViewIndex |
protected int | mFrom |
private float | mStartingScale |
protected int | mOriginalOffsetTop |
private MaterialProgressDrawable | mProgress |
private android.view.animation.Animation | mScaleAnimation |
private android.view.animation.Animation | mScaleDownAnimation |
private android.view.animation.Animation | mAlphaStartAnimation |
private android.view.animation.Animation | mAlphaMaxAnimation |
private android.view.animation.Animation | mScaleDownToStartAnimation |
private float | mSpinnerFinalOffset |
private boolean | mNotify |
private int | mCircleWidth |
private int | mCircleHeight |
private boolean | mUsingCustomStart |
private android.view.animation.Animation.AnimationListener | mRefreshListener |
private final android.view.animation.Animation | mAnimateToCorrectPosition |
private final android.view.animation.Animation | mAnimateToStartPosition |
Methods Summary |
---|
private void | animateOffsetToCorrectPosition(int from, android.view.animation.Animation.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, android.view.animation.Animation.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);
}
|
public boolean | canChildScrollUp()
if (android.os.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);
}
|
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);
|
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;
}
}
}
|
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 float | getMotionEventY(android.view.MotionEvent ev, int activePointerId)
final int index = MotionEventCompat.findPointerIndex(ev, activePointerId);
if (index < 0) {
return -1;
}
return MotionEventCompat.getY(ev, index);
|
public int | getProgressCircleDiameter()Get the diameter of the progress circle that is displayed as part of the
swipe to refresh layout. This is not valid until a measure pass has
completed.
return mCircleView != null ?mCircleView.getMeasuredHeight() : 0;
|
private boolean | isAlphaUsedForScale()Pre API 11, alpha is used to make the progress circle appear instead of scale.
return android.os.Build.VERSION.SDK_INT < 11;
|
private boolean | isAnimationRunning(android.view.animation.Animation animation)
return animation != null && animation.hasStarted() && !animation.hasEnded();
|
public boolean | isRefreshing()
return mRefreshing;
|
private void | moveToStart(float interpolatedTime)
int targetTop = 0;
targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
int offset = targetTop - mCircleView.getTop();
setTargetOffsetTopAndBottom(offset, false /* requires update */);
|
public boolean | onInterceptTouchEvent(android.view.MotionEvent ev)
ensureTarget();
final int action = MotionEventCompat.getActionMasked(ev);
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
}
if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) {
// Fail fast if we're not in a state where a swipe is possible
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mIsBeingDragged = false;
final float initialDownY = getMotionEventY(ev, mActivePointerId);
if (initialDownY == -1) {
return false;
}
mInitialDownY = initialDownY;
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER) {
Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
return false;
}
final float y = getMotionEventY(ev, mActivePointerId);
if (y == -1) {
return false;
}
final float yDiff = y - mInitialDownY;
if (yDiff > mTouchSlop && !mIsBeingDragged) {
mInitialMotionY = mInitialDownY + mTouchSlop;
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;
|
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);
|
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;
mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
}
mCircleViewIndex = -1;
// Get the index of the circleview.
for (int index = 0; index < getChildCount(); index++) {
if (getChildAt(index) == mCircleView) {
mCircleViewIndex = index;
break;
}
}
|
private void | onSecondaryPointerUp(android.view.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);
}
|
public boolean | onTouchEvent(android.view.MotionEvent ev)
final int action = MotionEventCompat.getActionMasked(ev);
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
}
if (!isEnabled() || mReturningToStart || canChildScrollUp()) {
// Fail fast if we're not in a state where a swipe is possible
return false;
}
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) {
Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
return false;
}
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
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);
// 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 = 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) {
Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
}
return false;
}
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
mIsBeingDragged = false;
if (overscrollTop > mTotalDragDistance) {
setRefreshing(true, true /* notify */);
} else {
// cancel refresh
mRefreshing = false;
mProgress.setStartEndTrim(0f, 0f);
Animation.AnimationListener listener = null;
if (!mScale) {
listener = new Animation.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;
|
public void | requestDisallowInterceptTouchEvent(boolean b)
// Nope.
|
private void | setAnimationProgress(float progress)Pre API 11, this does an alpha animation.
if (isAlphaUsedForScale()) {
setColorViewAlpha((int) (progress * MAX_ALPHA));
} else {
ViewCompat.setScaleX(mCircleView, progress);
ViewCompat.setScaleY(mCircleView, progress);
}
|
public void | setColorScheme(int colors)
setColorSchemeResources(colors);
|
public void | setColorSchemeColors(int colors)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.
ensureTarget();
mProgress.setColorSchemeColors(colors);
|
public void | setColorSchemeResources(int colorResIds)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.
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);
|
private void | setColorViewAlpha(int targetAlpha)
mCircleView.getBackground().setAlpha(targetAlpha);
mProgress.setAlpha(targetAlpha);
|
public void | setDistanceToTriggerSync(int distance)Set the distance to trigger a sync in dips
mTotalDragDistance = distance;
|
public void | setOnRefreshListener(android.support.v4.widget.SwipeRefreshLayout$OnRefreshListener listener)Set the listener to be notified when a refresh is triggered via the swipe
gesture.
mListener = listener;
|
public void | setProgressBackgroundColor(int colorRes)
setProgressBackgroundColorSchemeResource(colorRes);
|
public void | setProgressBackgroundColorSchemeColor(int color)Set the background color of the progress spinner disc.
mCircleView.setBackgroundColor(color);
mProgress.setBackgroundColor(color);
|
public void | setProgressBackgroundColorSchemeResource(int colorRes)Set the background color of the progress spinner disc.
setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes));
|
public void | setProgressViewEndTarget(boolean scale, int end)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.
mSpinnerFinalOffset = end;
mScale = scale;
mCircleView.invalidate();
|
public void | setProgressViewOffset(boolean scale, int start, int end)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.
mScale = scale;
mCircleView.setVisibility(View.GONE);
mOriginalOffsetTop = mCurrentTargetOffsetTop = start;
mSpinnerFinalOffset = end;
mUsingCustomStart = true;
mCircleView.invalidate();
|
public void | setRefreshing(boolean refreshing)Notify the widget that refresh state has changed. Do not call this when
refresh is triggered by a swipe gesture.
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 | setRefreshing(boolean refreshing, boolean notify)
if (mRefreshing != refreshing) {
mNotify = notify;
ensureTarget();
mRefreshing = refreshing;
if (mRefreshing) {
animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
} else {
startScaleDownAnimation(mRefreshListener);
}
}
|
public void | setSize(int size)One of DEFAULT, or LARGE.
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);
|
private void | setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate)
mCircleView.bringToFront();
mCircleView.offsetTopAndBottom(offset);
mCurrentTargetOffsetTop = mCircleView.getTop();
if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {
invalidate();
}
|
private android.view.animation.Animation | startAlphaAnimation(int startingAlpha, 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;
|
private void | startProgressAlphaMaxAnimation()
mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA);
|
private void | startProgressAlphaStartAnimation()
mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA);
|
private void | startScaleDownAnimation(android.view.animation.Animation.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);
|
private void | startScaleDownReturnToStartAnimation(int from, android.view.animation.Animation.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 | startScaleUpAnimation(android.view.animation.Animation.AnimationListener listener)
mCircleView.setVisibility(View.VISIBLE);
if (android.os.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);
|