/*
* Copyright (C) 2013 The Android Open Source Project
* Copyright (C) 2013 Chris Banes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.co.senab.actionbarpulltorefresh.library.widget;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import uk.co.senab.actionbarpulltorefresh.library.R;
/**
* Modified version of ButteryProgressBar from:
* https://android.googlesource.com/platform/packages/apps/UnifiedEmail/+/kitkat-release/src/com/android/mail/ui/ButteryProgressBar.java
*
* Implements more of the {@link android.widget.ProgressBar} API to achieve increased compatibility.
*/
public class PullToRefreshProgressBar extends View implements
AnimationRunnable.AnimatorUpdateListener {
// The baseline width that the other constants below are optimized for.
private static final int BASE_WIDTH_DP = 300;
// A reasonable animation duration for the base width above. It will be weakly scaled up and
// down for wider and narrower widths, respectively to provide a "constant" detent velocity.
private static final int BASE_DURATION_MS = 450;
// A reasonable number of detents for the given width above. It will be weakly scaled up and
// down for wider and narrower widths, respectively.
private static final int BASE_SEGMENT_COUNT = 3;
private static final int DEFAULT_BAR_HEIGHT_DP = 4;
private static final int DEFAULT_INDETERMINATE_BAR_SPACING_DP = 5;
private static final int DEFAULT_PROGRESS_MAX = 10000;
private final AnimationRunnable mIndeterminateAnimator;
private final Paint mPaint = new Paint();
private final int mIndeterminateBarSpacing;
private final float mDensity;
private int mSegmentCount;
private boolean mIndeterminate;
private int mProgress;
private int mProgressMax;
private int mProgressBarColor;
private float mProgressBarRadiusPx;
private final RectF mDrawRect = new RectF();
public PullToRefreshProgressBar(Context c) {
this(c, null);
}
public PullToRefreshProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
mDensity = getResources().getDisplayMetrics().density;
mProgressMax = DEFAULT_PROGRESS_MAX;
mIndeterminateBarSpacing = Math.round(DEFAULT_INDETERMINATE_BAR_SPACING_DP * mDensity);
mIndeterminateAnimator = new AnimationRunnable(this);
mIndeterminateAnimator.setRepeatCount(ValueAnimator.INFINITE);
mIndeterminateAnimator.setUpdateListener(this);
mPaint.setAntiAlias(true);
mPaint.setColor(getResources().getColor(R.color.default_progress_bar_color));
}
public synchronized boolean isIndeterminate() {
return mIndeterminate;
}
public synchronized void setIndeterminate(final boolean indeterminate) {
setProgressState(mProgress, mProgressMax, indeterminate);
}
public synchronized void setProgress(int progress) {
setProgressState(progress, mProgressMax, mIndeterminate);
}
public synchronized void setProgressBarColor(int color) {
mProgressBarColor = color;
invalidate();
}
public synchronized void setProgressBarCornerRadius(float radiusPx) {
mProgressBarRadiusPx = radiusPx;
invalidate();
}
public synchronized void setMax(int max) {
setProgressState(mProgress, max, mIndeterminate);
}
public synchronized int getMax() {
return mProgressMax;
}
void drawProgress(Canvas canvas) {
mPaint.setColor(mProgressBarColor);
final float progress = Math.max(Math.min(mProgress / (float) mProgressMax, 1f), 0f);
final float barWidth = progress * canvas.getWidth();
final float l = (canvas.getWidth() - barWidth) / 2f;
mDrawRect.set(l, 0f, l + barWidth, canvas.getHeight());
canvas.drawRoundRect(mDrawRect, mProgressBarRadiusPx, mProgressBarRadiusPx, mPaint);
}
void drawIndeterminate(Canvas canvas) {
if (!mIndeterminateAnimator.isStarted()) {
return;
}
mPaint.setColor(mProgressBarColor);
final float animProgress = mIndeterminateAnimator.getAnimatedValue();
final float barWidth = canvas.getWidth() / (float) mSegmentCount;
for (int i = -1; i < mSegmentCount; i++) {
final float l = (i + animProgress) * barWidth;
final float r = l + barWidth - mIndeterminateBarSpacing;
mDrawRect.set(l, 0f, r, canvas.getHeight());
canvas.drawRect(mDrawRect, mPaint);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed) {
final float widthMultiplier = getWidth() / mDensity / BASE_WIDTH_DP;
// simple scaling by width is too aggressive, so dampen it first
final float durationMult = 0.3f * (widthMultiplier - 1) + 1;
final float segmentMult = 0.1f * (widthMultiplier - 1) + 1;
mIndeterminateAnimator.setDuration((int) (BASE_DURATION_MS * durationMult));
mSegmentCount = (int) (BASE_SEGMENT_COUNT * segmentMult);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
final int specHeight = MeasureSpec.getSize(heightMeasureSpec);
int height;
switch (MeasureSpec.getMode(heightMeasureSpec)) {
case MeasureSpec.EXACTLY:
height = specHeight;
break;
case MeasureSpec.AT_MOST:
height = Math.min(specHeight, Math.round(DEFAULT_BAR_HEIGHT_DP * mDensity));
break;
case MeasureSpec.UNSPECIFIED:
default:
height = Math.round(DEFAULT_BAR_HEIGHT_DP * mDensity);
break;
}
setMeasuredDimension(specWidth, height);
}
@Override
protected void onDraw(Canvas canvas) {
if (mIndeterminate) {
drawIndeterminate(canvas);
} else {
drawProgress(canvas);
}
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (mIndeterminate) {
if (visibility == VISIBLE) {
mIndeterminateAnimator.start();
} else {
mIndeterminateAnimator.cancel();
}
}
}
void setProgressState(int progress, int progressMax, boolean indeterminate) {
boolean invalidate = false;
if (mIndeterminate != indeterminate) {
mIndeterminate = indeterminate;
if (indeterminate != mIndeterminateAnimator.isStarted()) {
if (mIndeterminate) {
mIndeterminateAnimator.start();
} else {
mIndeterminateAnimator.cancel();
}
}
invalidate = true;
}
if (progress != mProgress) {
mProgress = progress;
if (!mIndeterminate) {
invalidate = true;
}
}
if (progressMax != mProgressMax) {
mProgressMax = progressMax;
if (!mIndeterminate) {
invalidate = true;
}
}
if (invalidate) {
invalidate();
}
}
@Override
public void onAnimationUpdate(AnimationRunnable animation) {
invalidate();
}
}