package com.fenjuly.library; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.view.View; import java.util.ArrayList; import java.util.List; /** * Created by liurongchan with love on 15/8/11. */ public class ArrowDownloadButton extends View { private static final int BLUE_ONE = Color.rgb(46, 164, 242); private static final int WHILE = Color.rgb(255, 255, 255); private static final float RADIUS = 180; private static final int TRI_POINT_NUMBER = 17; private static final float MAX_WAVE_HEIGHT = 10; private static final float MIN_WAVE_HEIGHT = 5; private static final int PROGRESS = 100; private static final int ANGLE = 360; private static final float TEXT_Y = 67.5f; private static final float OFFSET = 10; private static final float SMALL_RADIUS = 5; private static final float TEXT_SIZE = 40; private static final float ARC_WIDTH = 20; private static final float ARROW_WIDTH = 10; private static final float TRI_WIDTH = 10; private static final float LOADING_WIDTH = 10; private static final float STEP = 2; private static final float ELASTICITY_STEP = 10; private static final float ROPE_STEP_X = 30; private static final float ROPE_STEP_Y = 32; private static final float ROPE_HEAD_STEP_Y = 17; private static final float JUMP_STEP = 45; private static final float DOWN_STEP = 7.5f; private static final float TRI_STEP = 16.875f; private static final float TIME_STEP = 20; private static final float HOOK_STEP_Y = 15; private static final float HOOK_COUNT = 4; private static final float LITTLE_STEP = 8; private static final int DURATION = 20; private static final int COMPLETE_DURATION = 20; /** * start instance **/ private static final String INSTANCE_STATE = "instance_state"; /** * */ private static final String X_I = "x"; private static final String Y_I = "y"; private static final String RADIUS_I = "radius"; private static final String MAX_WAVE_HEIGHT_I = "max_wave_height"; private static final String MIN_WAVE_HEIGHT_I = "min_wave_height"; private static final String TEXT_Y_I = "text_y"; private static final String STEP_I = "step"; private static final String ELASTICITY_STEP_I = "elasticity_step"; private static final String ROPE_STEP_X_I = "rope_step_x"; private static final String ROPE_STEP_Y_I = "rope_step_y"; private static final String ROPE_HEAD_STEP_Y_I = "rope_head_step_y"; private static final String JUMP_STEP_I = "jump_step"; private static final String DOWN_STEP_I = "down_step"; private static final String TRI_STEP_I = "tri_step"; private static final String HOOK_STEP_Y_I = "hook_step"; private static final String LITTLE_STEP_I = "little_step"; private static final String SMALL_RADIUS_I = "small_radius"; private static final String TEXT_SIZE_I = "text_size"; private static final String ARC_WIDTH_I = "arc_width"; private static final String ARROW_WIDTH_I = "arrow_width"; private static final String TRI_WIDTH_I = "tri_width"; private static final String LOADING_WIDTH_I = "loading_width"; private static final String IS_FIRST_I = "is_first"; private static final String IS_ANIMATING_I = "is_animating"; private static final String BEZIER_I = "bezier"; private static final String IS_LOADING_I = "is_loading"; private static final String IS_COMPLETED_I = "is_completed"; private static final String IS_END_I = "is_end"; private static final String COUNT_I = "count"; private static final String LENGTH_I = "length"; private static final String CURRENT_TIME_I = "current_time"; private static final String WAVE_HEIGHT_I = "wave_height"; private static final String PROGRESS_I = "progress"; private static final String HOOK_COUNT_I = "hook_count"; private static final String LENGTH_X_I = "length_x"; private static final String LENGTH_Y_I = "length_y"; private float x = 550; private float y = 550; private float radius = RADIUS; private float maxWaveHeight = MAX_WAVE_HEIGHT; private float minWaveHeight = MIN_WAVE_HEIGHT; private float textY = TEXT_Y; private float step = STEP; private float elasticityStep = ELASTICITY_STEP; private float ropeStepX = ROPE_STEP_X; private float ropeStepY = ROPE_STEP_Y; private float ropeHeadStepY = ROPE_HEAD_STEP_Y; private float jumpStep = JUMP_STEP; private float downStep = DOWN_STEP; private float triStep = TRI_STEP; private float hookStepY = HOOK_STEP_Y; private float littleStep = LITTLE_STEP; private float smallRadius = SMALL_RADIUS; private float textSize = TEXT_SIZE; private float arcWidth = ARC_WIDTH; private float arrowWidth = ARROW_WIDTH; private float triWidth = TRI_WIDTH; private float loadingWidth = LOADING_WIDTH; private Paint arrowPaint; private Paint arcPaint; private Paint smallPaint; private Paint triPaint; private Paint loadingPaint; private Paint textPaint; private Path arrowPath; private Path triPath; private Path textPath; private RectF oval; private Point a; private Point b; private Point c; private Point d; private Point e; private Point jumpPoint; private List<Point> triPoints = new ArrayList<>(); private boolean isFirst = true; private boolean isAnimating = false; private boolean bezier = false; private boolean isLoading = false; private boolean isCompleted = false; private boolean isEnd = false; private int count = 0; private float length; private int currentTime = 0; private float waveHeight = MIN_WAVE_HEIGHT; private float progress = 0; private int hookCount = 0; float lengthX = 3 * radius / 4; float lengthY = 3 * radius / 4; public float getProgress() { return progress; } public void setProgress(float progress) { if (progress > 100) { this.progress = 100; } else { this.progress = progress; } if (progress == 100) { isLoading = false; isCompleted = true; } } public ArrowDownloadButton(Context context) { this(context, null); } public ArrowDownloadButton(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ArrowDownloadButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false)); } private int measure(int measureSpec, boolean isWidth) { int result; int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom(); if (mode == MeasureSpec.EXACTLY) { result = size; } else { result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight(); result += padding; if (mode == MeasureSpec.AT_MOST) { if (isWidth) { result = Math.max(result, size); } else { result = Math.min(result, size); } } } return result; } @Override protected void onDraw(Canvas canvas) { if (isFirst) { init(); isFirst = false; } canvas.drawCircle(x, y, radius, arcPaint); drawArrow(canvas); if (isAnimating) { animating(); } if (isLoading) { loading(canvas); } if (isCompleted) { afterCompleted(canvas); } } private void init() { float temp = getHeight() > getWidth() ? getWidth() / 2 : getHeight() / 2; radius = temp - temp * OFFSET / RADIUS - temp * ELASTICITY_STEP / RADIUS - 6; x = getPaddingLeft() + getWidth() / 2; y = getPaddingTop() + getHeight() / 2; maxWaveHeight = convert(MAX_WAVE_HEIGHT); minWaveHeight = convert(MIN_WAVE_HEIGHT); textY = convert(TEXT_Y); step = convert(STEP); elasticityStep = convert(ELASTICITY_STEP); ropeStepX = convert(ROPE_STEP_X); ropeStepY = convert(ROPE_STEP_Y); ropeHeadStepY = convert(ROPE_HEAD_STEP_Y); jumpStep = convert(JUMP_STEP); downStep = convert(DOWN_STEP); triStep = convert(TRI_STEP); hookStepY = convert(HOOK_STEP_Y); littleStep = convert(LITTLE_STEP); smallRadius = convert(SMALL_RADIUS); textSize = convert(TEXT_SIZE); arcWidth = convert(ARC_WIDTH); arrowWidth = convert(ARROW_WIDTH); triWidth = convert(TRI_WIDTH); loadingWidth = convert(LOADING_WIDTH); lengthX = 3 * radius / 4; lengthY = 3 * radius / 4; arrowPath = new Path(); triPath = new Path(); textPath = new Path(); oval = new RectF(); oval.left = x - radius; oval.top = y - radius; oval.right = x + radius; oval.bottom = y + radius; length = radius / 2; initializePaints(); initializePoints(); } /** * start animating before loading */ public void startAnimating() { isAnimating = true; invalidate(); } /** * reset to initial state */ public void reset() { isAnimating = false; isLoading = false; bezier = false; isCompleted = false; isEnd = false; length = radius / 2; count = 0; hookCount = 0; jumpPoint.x = -1; progress = 0; lengthX = 3 * radius / 4; lengthY = 3 * radius / 4; a.y = y + length; b.y = y - length; e.y = y + length; c.x = x - length / 2; c.y = y + length / 2; d.x = x + length / 2; d.y = y + length / 2; invalidate(); } /** * animating */ public void animating() { if (count < 19) { length = length * 3 / 4; a.y = y + length; b.y = y - length; if (((count + 1) % 3) == 0 && count < 9) { e.y = e.y + step; c.y = c.y + step; d.y = d.y + step; } if (count > 8 && count < 12) { jumpPoint.x = x; jumpPoint.y = y - jumpStep * (count - 8); c.x = c.x - ropeStepX; c.y = c.y - ropeHeadStepY; d.x = d.x + ropeStepX; d.y = d.y - ropeHeadStepY; e.y = e.y - ropeStepY; } if (count > 11) { bezier = true; if (count == 12) { jumpPoint.y = jumpPoint.y - jumpStep * 2; } else { jumpPoint.y = jumpPoint.y + downStep; if (count < 16) { int time1 = 15 - count; e.y = y + time1 * elasticityStep; } } } count++; postInvalidateDelayed(DURATION); } else { isAnimating = false; bezier = false; if (progress != 100) { isLoading = true; } else { isLoading = false; isCompleted = true; } } } /** * under loading * * @param canvas Target Canvas */ private void loading(Canvas canvas) { Point currentPoint = triPoints.get(0); Point nextPoint; for (int i = 0; i < TRI_POINT_NUMBER; i++) { Point p = triPoints.get(i); p.x = (x - 3 * radius / 4) + triStep * i; p.y = y + calculateTri(TIME_STEP * i, currentTime); } for (int i = 1; i < TRI_POINT_NUMBER; i++) { nextPoint = triPoints.get(i); triPath.reset(); triPath.moveTo(currentPoint.x, currentPoint.y); triPath.lineTo(nextPoint.x, nextPoint.y); canvas.drawCircle(nextPoint.x, nextPoint.y, smallRadius, smallPaint); canvas.drawPath(triPath, triPaint); currentPoint = nextPoint; } textPath.moveTo(x - textSize, y + textY); textPath.lineTo(x + textSize, y + textY); canvas.drawTextOnPath((int) progress + "%", textPath, 0, 0, textPaint); currentTime = (int) (currentTime + TIME_STEP); float sweepAngle = (progress / PROGRESS * ANGLE); canvas.drawArc(oval, 270, 0 - sweepAngle, false, loadingPaint); postInvalidateDelayed(DURATION); } /** * the method do such things: * 1.draw arrow. * 2.when animate was completed, let the small ball jump. * * @param canvas Target Canvas */ protected void drawArrow(Canvas canvas) { if (jumpPoint.x != -1) { canvas.drawCircle(jumpPoint.x, jumpPoint.y, smallRadius, smallPaint); } if (bezier) { arrowPath.reset(); arrowPath.moveTo(c.x, c.y); arrowPath.quadTo(e.x, e.y, d.x, d.y); canvas.drawPath(arrowPath, arrowPaint); } else if (isLoading) { } else if (isCompleted) { } else if (isEnd) { canvas.drawCircle(x, y, radius, loadingPaint); drawArrowOrHook(canvas); } else { arrowPath.reset(); arrowPath.moveTo(a.x, a.y); arrowPath.lineTo(b.x, b.y); canvas.drawPath(arrowPath, arrowPaint); canvas.drawCircle(a.x, a.y, smallRadius, smallPaint); canvas.drawCircle(b.x, b.y, smallRadius, smallPaint); drawArrowOrHook(canvas); } } /** * draw arrow or hook * * @param canvas Target Canvas */ private void drawArrowOrHook(Canvas canvas) { arrowPath.reset(); arrowPath.moveTo(e.x, e.y); arrowPath.lineTo(c.x, c.y); canvas.drawPath(arrowPath, arrowPaint); arrowPath.reset(); arrowPath.moveTo(e.x, e.y); arrowPath.lineTo(d.x, d.y); canvas.drawPath(arrowPath, arrowPaint); canvas.drawCircle(c.x, c.y, smallRadius, smallPaint); canvas.drawCircle(d.x, d.y, smallRadius, smallPaint); canvas.drawCircle(e.x, e.y, smallRadius, smallPaint); } /** * the animate after loading * * @param canvas Target Canvas */ private void afterCompleted(Canvas canvas) { canvas.drawCircle(x, y, radius, loadingPaint); if (hookCount == HOOK_COUNT - 1) { e.y = e.y + littleStep; c.x = c.x - littleStep; d.x = d.x + littleStep; d.y = d.y - littleStep; isCompleted = false; isEnd = true; } else { e.x = x; e.y = y + hookStepY * (hookCount + 1); lengthX = lengthX * 3 / 4; c.x = x - lengthX * 3 / 4; c.y = y; d.x = x + lengthY - radius / (float) 8 * (hookCount + 1); d.y = y - hookStepY * (hookCount + 1); hookCount++; } drawArrowOrHook(canvas); postInvalidateDelayed(COMPLETE_DURATION); } protected void initializePaints() { arcPaint = new Paint(); arcPaint.setAntiAlias(true); arcPaint.setStyle(Paint.Style.STROKE); arcPaint.setStrokeWidth(arcWidth); arcPaint.setColor(BLUE_ONE); arrowPaint = new Paint(); arrowPaint.setAntiAlias(true); arrowPaint.setStyle(Paint.Style.STROKE); arrowPaint.setStrokeWidth(arrowWidth); arrowPaint.setColor(WHILE); smallPaint = new Paint(); smallPaint.setAntiAlias(true); smallPaint.setStyle(Paint.Style.FILL); smallPaint.setColor(WHILE); triPaint = new Paint(); triPaint.setAntiAlias(true); triPaint.setStyle(Paint.Style.STROKE); triPaint.setStrokeWidth(triWidth); triPaint.setColor(WHILE); loadingPaint = new Paint(); loadingPaint.setAntiAlias(true); loadingPaint.setStyle(Paint.Style.STROKE); loadingPaint.setStrokeWidth(loadingWidth); loadingPaint.setColor(WHILE); textPaint = new Paint(); textPaint.setAntiAlias(true); textPaint.setStyle(Paint.Style.FILL); textPaint.setStrokeWidth(1); textPaint.setColor(WHILE); textPaint.setTextSize(textSize); } protected void initializePoints() { a = new Point(x, y + radius / 2); b = new Point(x, y - radius / 2); c = new Point(x - radius / 4, y + radius / 4); d = new Point(x + radius / 4, y + radius / 4); e = new Point(x, y + radius / 2); jumpPoint = new Point(); for (int i = 0; i < TRI_POINT_NUMBER; i++) { Point point = new Point(); point.x = (x - 3 * radius / 4) + triStep * i; point.y = y + calculateTri(TIME_STEP * i, 0); triPoints.add(point); } } /** * calculate the wave * * @param originalTime original time * @param currentTime current time * @return wave */ private float calculateTri(float originalTime, float currentTime) { if (progress < PROGRESS / 3) { waveHeight = MIN_WAVE_HEIGHT; } else if (progress < PROGRESS * 2 / 3) { waveHeight = maxWaveHeight; } else { waveHeight = minWaveHeight; } return (float) (waveHeight * Math.sin((Math.PI / 80) * (originalTime + currentTime))); } private float convert(float original) { return radius * original / RADIUS; } @Override protected Parcelable onSaveInstanceState() { final Bundle bundle = new Bundle(); bundle.putParcelable(INSTANCE_STATE, super.onSaveInstanceState()); bundle.putFloat(X_I, x); bundle.putFloat(Y_I, y); bundle.putFloat(RADIUS_I, radius); bundle.putFloat(MAX_WAVE_HEIGHT_I, maxWaveHeight); bundle.putFloat(MIN_WAVE_HEIGHT_I, minWaveHeight); bundle.putFloat(TEXT_Y_I, textY); bundle.putFloat(STEP_I, step); bundle.putFloat(ELASTICITY_STEP_I, elasticityStep); bundle.putFloat(ROPE_STEP_X_I, ropeStepX); bundle.putFloat(ROPE_STEP_Y_I, ropeStepY); bundle.putFloat(ROPE_HEAD_STEP_Y_I, ropeHeadStepY); bundle.putFloat(JUMP_STEP_I, jumpStep); bundle.putFloat(DOWN_STEP_I, downStep); bundle.putFloat(TRI_STEP_I, triStep); bundle.putFloat(HOOK_STEP_Y_I, hookStepY); bundle.putFloat(LITTLE_STEP_I, littleStep); bundle.putFloat(SMALL_RADIUS_I, smallRadius); bundle.putFloat(TEXT_SIZE_I, textSize); bundle.putFloat(ARC_WIDTH_I, arcWidth); bundle.putFloat(ARROW_WIDTH_I, arrowWidth); bundle.putFloat(TRI_WIDTH_I, triWidth); bundle.putFloat(LOADING_WIDTH_I, loadingWidth); bundle.putBoolean(IS_FIRST_I, isFirst); bundle.putBoolean(IS_ANIMATING_I, isAnimating); bundle.putBoolean(BEZIER_I, bezier); bundle.putBoolean(IS_LOADING_I, isLoading); bundle.putBoolean(IS_COMPLETED_I, isCompleted); bundle.putBoolean(IS_END_I, isEnd); bundle.putInt(COUNT_I, count); bundle.putFloat(LENGTH_I, length); bundle.putInt(CURRENT_TIME_I, currentTime); bundle.putFloat(WAVE_HEIGHT_I, waveHeight); bundle.putFloat(PROGRESS_I, progress); bundle.putInt(HOOK_COUNT_I, hookCount); bundle.putFloat(LENGTH_X_I, lengthX); bundle.putFloat(LENGTH_Y_I, lengthY); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { final Bundle bundle = (Bundle) state; x = bundle.getFloat(X_I); y = bundle.getFloat(Y_I); radius = bundle.getFloat(RADIUS_I); maxWaveHeight = bundle.getFloat(MAX_WAVE_HEIGHT_I); minWaveHeight = bundle.getFloat(MIN_WAVE_HEIGHT_I); textY = bundle.getFloat(TEXT_Y_I); step = bundle.getFloat(STEP_I); elasticityStep = bundle.getFloat(ELASTICITY_STEP_I); ropeStepX = bundle.getFloat(ROPE_STEP_X_I); ropeStepY = bundle.getFloat(ROPE_STEP_Y_I); ropeHeadStepY = bundle.getFloat(ROPE_HEAD_STEP_Y_I); jumpStep = bundle.getFloat(JUMP_STEP_I); downStep = bundle.getFloat(DOWN_STEP_I); triStep = bundle.getFloat(TRI_STEP_I); hookStepY = bundle.getFloat(HOOK_STEP_Y_I); littleStep = bundle.getFloat(LITTLE_STEP_I); smallRadius = bundle.getFloat(SMALL_RADIUS_I); textSize = bundle.getFloat(TEXT_SIZE_I); arcWidth = bundle.getFloat(ARC_WIDTH_I); arrowWidth = bundle.getFloat(ARROW_WIDTH_I); triWidth = bundle.getFloat(TRI_WIDTH_I); loadingWidth = bundle.getFloat(LOADING_WIDTH_I); isFirst = bundle.getBoolean(IS_FIRST_I); isAnimating = bundle.getBoolean(IS_ANIMATING_I); bezier = bundle.getBoolean(BEZIER_I); isLoading = bundle.getBoolean(IS_LOADING_I); isCompleted = bundle.getBoolean(IS_COMPLETED_I); isEnd = bundle.getBoolean(IS_END_I); count = bundle.getInt(COUNT_I); length = bundle.getFloat(LENGTH_I); currentTime = bundle.getInt(CURRENT_TIME_I); waveHeight = bundle.getFloat(WAVE_HEIGHT_I); progress = bundle.getFloat(PROGRESS_I); hookCount = bundle.getInt(HOOK_COUNT_I); lengthX = bundle.getFloat(LENGTH_X_I); lengthY = bundle.getFloat(LENGTH_Y_I); } super.onRestoreInstanceState(state); } static class Point { public float x; public float y; public Point(float x, float y) { this.x = x; this.y = y; } public Point() { x = -1; y = -1; } } }