package com.miris.ui.view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.OvershootInterpolator;
import com.miris.R;
/**
* Created by Miris on 09.02.15.
*/
public class SendingProgressView extends View {
public static final int STATE_NOT_STARTED = 0;
public static final int STATE_PROGRESS_STARTED = 1;
public static final int STATE_DONE_STARTED = 2;
public static final int STATE_FINISHED = 3;
private static final int PROGRESS_STROKE_SIZE = 10;
private static final int INNER_CIRCLE_PADDING = 30;
private static final int MAX_DONE_BG_OFFSET = 800;
private static final int MAX_DONE_IMG_OFFSET = 400;
private int state = STATE_NOT_STARTED;
private float currentProgress = 0;
private float currentDoneBgOffset = MAX_DONE_BG_OFFSET;
private float currentCheckmarkOffset = MAX_DONE_IMG_OFFSET;
private Paint progressPaint;
private Paint doneBgPaint;
private Paint maskPaint;
private RectF progressBounds;
private Bitmap checkmarkBitmap;
private Bitmap innerCircleMaskBitmap;
private int checkmarkXPosition = 0;
private int checkmarkYPosition = 0;
private Paint checkmarkPaint;
private Bitmap tempBitmap;
private Canvas tempCanvas;
private ObjectAnimator simulateProgressAnimator;
private ObjectAnimator doneBgAnimator;
private ObjectAnimator checkmarkAnimator;
private OnLoadingFinishedListener onLoadingFinishedListener;
public SendingProgressView(Context context) {
super(context);
init();
}
public SendingProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SendingProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SendingProgressView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setupProgressPaint();
setupDonePaints();
setupSimulateProgressAnimator();
setupDoneAnimators();
}
private void setupProgressPaint() {
progressPaint = new Paint();
progressPaint.setAntiAlias(true);
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setColor(0xffffffff);
progressPaint.setStrokeWidth(PROGRESS_STROKE_SIZE);
}
private void setupSimulateProgressAnimator() {
simulateProgressAnimator = ObjectAnimator.ofFloat(this, "currentProgress", 0, 100).setDuration(2000);
simulateProgressAnimator.setInterpolator(new AccelerateInterpolator());
simulateProgressAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
changeState(STATE_DONE_STARTED);
}
});
}
private void setupDonePaints() {
doneBgPaint = new Paint();
doneBgPaint.setAntiAlias(true);
doneBgPaint.setStyle(Paint.Style.FILL);
doneBgPaint.setColor(0xff39cb72);
checkmarkPaint = new Paint();
maskPaint = new Paint();
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
}
private void setupDoneAnimators() {
doneBgAnimator = ObjectAnimator.ofFloat(this, "currentDoneBgOffset", MAX_DONE_BG_OFFSET, 0).setDuration(300);
doneBgAnimator.setInterpolator(new DecelerateInterpolator());
checkmarkAnimator = ObjectAnimator.ofFloat(this, "currentCheckmarkOffset", MAX_DONE_IMG_OFFSET, 0).setDuration(300);
checkmarkAnimator.setInterpolator(new OvershootInterpolator());
checkmarkAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
changeState(STATE_FINISHED);
}
});
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateProgressBounds();
setupCheckmarkBitmap();
setupDoneMaskBitmap();
resetTempCanvas();
}
private void updateProgressBounds() {
progressBounds = new RectF(
PROGRESS_STROKE_SIZE, PROGRESS_STROKE_SIZE,
getWidth() - PROGRESS_STROKE_SIZE, getWidth() - PROGRESS_STROKE_SIZE
);
}
private void setupCheckmarkBitmap() {
checkmarkBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_done_white_48dp);
checkmarkXPosition = getWidth() / 2 - checkmarkBitmap.getWidth() / 2;
checkmarkYPosition = getWidth() / 2 - checkmarkBitmap.getHeight() / 2;
}
private void setupDoneMaskBitmap() {
innerCircleMaskBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
Canvas srcCanvas = new Canvas(innerCircleMaskBitmap);
srcCanvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - INNER_CIRCLE_PADDING, new Paint());
}
private void resetTempCanvas() {
int getLayoutWidth = getWidth();
if (getLayoutWidth == 0) {
getLayoutWidth = 600;
}
tempBitmap = Bitmap.createBitmap(getLayoutWidth, getLayoutWidth, Bitmap.Config.ARGB_8888);
tempCanvas = new Canvas(tempBitmap);
}
@Override
protected void onDraw(Canvas canvas) {
if (state == STATE_PROGRESS_STARTED) {
drawArcForCurrentProgress();
} else if (state == STATE_DONE_STARTED) {
drawFrameForDoneAnimation();
postInvalidate();
} else if (state == STATE_FINISHED) {
drawFinishedState();
}
canvas.drawBitmap(tempBitmap, 0, 0, null);
}
private void drawArcForCurrentProgress() {
tempCanvas.drawArc(progressBounds, -90f, 360 * currentProgress / 100, false, progressPaint);
}
private void drawFrameForDoneAnimation() {
tempCanvas.drawCircle(getWidth() / 2, getWidth() / 2 + currentDoneBgOffset, getWidth() / 2 - INNER_CIRCLE_PADDING, doneBgPaint);
tempCanvas.drawBitmap(checkmarkBitmap, checkmarkXPosition, checkmarkYPosition + currentCheckmarkOffset, checkmarkPaint);
tempCanvas.drawBitmap(innerCircleMaskBitmap, 0, 0, maskPaint);
tempCanvas.drawArc(progressBounds, 0, 360f, false, progressPaint);
}
private void drawFinishedState() {
tempCanvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - INNER_CIRCLE_PADDING, doneBgPaint);
tempCanvas.drawBitmap(checkmarkBitmap, checkmarkXPosition, checkmarkYPosition, checkmarkPaint);
tempCanvas.drawArc(progressBounds, 0, 360f, false, progressPaint);
}
private void changeState(int state) {
if (this.state == state) {
return;
}
if (tempBitmap != null) {
tempBitmap.recycle();
}
resetTempCanvas();
this.state = state;
if (state == STATE_PROGRESS_STARTED) {
setCurrentProgress(0);
simulateProgressAnimator.start();
} else if (state == STATE_DONE_STARTED) {
setCurrentDoneBgOffset(MAX_DONE_BG_OFFSET);
setCurrentCheckmarkOffset(MAX_DONE_IMG_OFFSET);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(doneBgAnimator, checkmarkAnimator);
animatorSet.start();
} else if (state == STATE_FINISHED) {
if (onLoadingFinishedListener != null) {
onLoadingFinishedListener.onLoadingFinished();
}
}
}
public void simulateProgress() {
changeState(STATE_PROGRESS_STARTED);
}
public void setCurrentProgress(float currentProgress) {
this.currentProgress = currentProgress;
postInvalidate();
}
public void setCurrentDoneBgOffset(float currentDoneBgOffset) {
this.currentDoneBgOffset = currentDoneBgOffset;
postInvalidate();
}
public void setCurrentCheckmarkOffset(float currentCheckmarkOffset) {
this.currentCheckmarkOffset = currentCheckmarkOffset;
postInvalidate();
}
public void setOnLoadingFinishedListener(OnLoadingFinishedListener onLoadingFinishedListener) {
this.onLoadingFinishedListener = onLoadingFinishedListener;
}
public interface OnLoadingFinishedListener {
public void onLoadingFinished();
}
}