package com.gastudio.gadownloading.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.CornerPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathDashPathEffect;
import android.graphics.PathMeasure;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import com.gastudio.gadownloading.R;
import java.util.ArrayList;
import java.util.List;
/**
* author: GAStudio
* qq:1935400187
* 技术交流qq群:277582728
*/
public class GADownloadingView extends View {
private static final int ZERO_PROGRESS = 0;
private static final int FULL_PROGRESS = 100;
private static final int HALF_PROGRESS = 50;
private static final int INVALID_PROGRESS = -1;
private static final float FULL_NORMALIZED_TIME = 1F;
private static final float FULL_NORMALIZED_PROGRESS = 1F;
private static final float HALF_NORMALIZED_PROGRESS = 0.5F;
private static final String FULL_PROGRESS_STR = "100%";
private static final String FULL_PROGRESS_DONE_STR = "done";
private static final String FAILED_PROGRESS_STR = "failed";
private static final int FULL_ALPHA = 255;
private static final int FULL_ANGLE = 360;
private static final int HALF_FULL_ANGLE = FULL_ANGLE / 2;
private static final int DEFAULT_ARROW_COLOR = 0xFFFFFFFF;
private static final int DEFAULT_LOADING_CIRCLE_BG_COLOR = 0xFF491C14;
private static final int DEFAULT_LOADING_LINE_COLOR = 0xFF491c14;
private static final int DEFAULT_PROGRESS_LINE_LEFT_COLOR = 0XFFFFFFFF;
private static final int DEFAULT_PROGRESS_TEXT_COLOR = 0xFF000000;
private static final int DEFAULT_DONE_PROGRESS_TEXT_COLOR = 0xFF5F9C62;
private static final int BEFORE_PROGRESS_CIRCLE_SCALE_DURATION = 450;
private static final int BEFORE_PROGRESS_INNER_CIRCLE_SCALE_DURATION
= BEFORE_PROGRESS_CIRCLE_SCALE_DURATION / 2;
private static final int BEFORE_PROGRESS_INNER_CIRCLE_SCALE_DELAY
= BEFORE_PROGRESS_CIRCLE_SCALE_DURATION - BEFORE_PROGRESS_INNER_CIRCLE_SCALE_DURATION;
private static final int BEFORE_PROGRESS_CIRCLE_TO_LINE_DURATION = 150;
private static final int BEFORE_PROGRESS_ARROW_MOVE_AND_LINE_OSCILL = 800;
private static final float[] CIRCLE_TO_LINE_SEASONS = new float[]{0, 0.4f, 0.8f, 1f};
private static final float[] CIRCLE_TO_LINE_WIDTH_FACTOR = new float[]{1f, 1.2f, 2.4f, 3.45f};
private static final float[] CIRCLE_TO_LINE_HEIGHT_FACTOR = new float[]{1f, 0.73f, 0.36f, 0f};
private static final float[] CIRCLE_TO_LINE_FST_CON_X_FACTOR = new float[]{-0.65f, 0.18f, 0.72f, 1.04f};
private static final float[] CIRCLE_TO_LINE_FST_CON_Y_FACTOR = new float[]{0f, 0.036f, 0f, 0f};
private static final float[] CIRCLE_TO_LINE_SEC_CON_X_FACTOR = new float[]{-0.65f, 0.06f, 0.72f, 1.04f};
private static final float[] CIRCLE_TO_LINE_SEC_CON_Y_FACTOR = new float[]{1f, 0.73f, 0.36f, 0f};
// value of MAX_LINE_WIDTH_FACTOR is max value in CIRCLE_TO_LINE_WIDTH_FACTOR array
public static final float MAX_LINE_WIDTH_FACTOR = 3.45f;
private static final int FULL_PROGRESS_ANIMATION_DURATION = 3000;
private static final int MIN_PROGRESS_ANIMATION_DURATION = 100;
private static final int DONE_ANIMATION_DURATION = 500;
private static final int DONE_LINE_PACK_UP_ANIMATION_DURATION = 500;
private static final int DONE_LINE_PACK_UP_ARROW_SHAKE_ANIMATION_DURATION = 500;
private static final int DONE_REST_TO_CIRCLE_DOWN_ANIMATION_DURATION = 1000;
private static final int DONE_DIALOG_TO_ARROW_DURATION
= DONE_REST_TO_CIRCLE_DOWN_ANIMATION_DURATION / 4;
private static final int DONE_DIALOG_UP_DOWN_DURATION
= DONE_REST_TO_CIRCLE_DOWN_ANIMATION_DURATION - DONE_DIALOG_TO_ARROW_DURATION;
private static final int DONE_LINE_PACK_UP_ARROW_SHAKE_BASE_POINT_DIAMETER = 2;
private static final int FAILED_ANIMATION_DURATION = 1000;
private static final int FAILED_BOMB_ANIMATION_DURATION =
FAILED_ANIMATION_DURATION / 3;
private static final int FAILED_ROPE_OSCILLATION_ANIMATION_DURATION = 250;
private static final int FAILED_ARROW_MOVE_ANIMATION_DURATION = 800;
private static final int FAILED_ROPE_PACK_UP_ANIMATION_DURATION =
(FAILED_ARROW_MOVE_ANIMATION_DURATION - FAILED_ROPE_OSCILLATION_ANIMATION_DURATION) / 2;
private static final int FAILED_CIRCLE_SCALE_ANIMATION_DURATION =
(FAILED_ARROW_MOVE_ANIMATION_DURATION - FAILED_ROPE_OSCILLATION_ANIMATION_DURATION) / 2;
// state of arrow's rectangle and triangle
private static final float DEFAULT_INIT_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO = 0.5F;
private static final float DEFAULT_INIT_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO = 0.5F;
private static final float DEFAULT_INIT_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO = 1F;
private static final float DEFAULT_INIT_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO = 0.5F;
private static final float DEFAULT_MIDDLE_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO = 0.7F;
private static final float DEFAULT_END_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO = 1F;
private static final float DEFAULT_END_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO = 0.5F;
private static final float DEFAULT_END_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO = 0.34F;
private static final float DEFAULT_END_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO = 0.17F;
private static final float DEFAULT_ARROW_TOP_CONNER_RADIUS_TO_CIRCLE_RADIUS_RATIO = 0.05f;
private static final float DEFAULT_ARROW_MOVE_MAX_HEIGHT_TO_CIRCLE_DIAMETER_RATIO = MAX_LINE_WIDTH_FACTOR / 2
+ (DEFAULT_INIT_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO
+ DEFAULT_INIT_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO) / 4;
private static final float SUGGEST_X_AXIS_PADDING_TO_CIRCLE_DIAMETER_RATIO = 0.75F;
private static final int SUGGEST_LOADING_VIEW_WIDTH = 300;
private static final int SUGGEST_LOADING_VIEW_HEIGHT = (int) (SUGGEST_LOADING_VIEW_WIDTH
/ (SUGGEST_X_AXIS_PADDING_TO_CIRCLE_DIAMETER_RATIO + MAX_LINE_WIDTH_FACTOR)
* DEFAULT_ARROW_MOVE_MAX_HEIGHT_TO_CIRCLE_DIAMETER_RATIO);
// progress text size and line stroke width
private static final int DEFAULT_PROGRESS_TEXT_SIZE = 12;
private static final int MIN_PROGRESS_TEXT_SIZE = 8;
private static final float PROGRESS_TEXT_SIZE_TO_VIEW_WIDTH_RATIO
= (float) DEFAULT_PROGRESS_TEXT_SIZE / SUGGEST_LOADING_VIEW_WIDTH;
private static final int DEFAULT_LOADING_LINE_STROKE_WIDTH = 3;
private static final int MIN_LOADING_LINE_STROKE_WIDTH = 1;
private static final float LOADING_LINE_STROKE_WIDTH_TO_VIEW_WIDTH_RATIO
= (float) DEFAULT_LOADING_LINE_STROKE_WIDTH / SUGGEST_LOADING_VIEW_WIDTH;
private static final float CHANGE_ARROW_TO_DIALOG_RECT_CHANGE = 0.563F;
private static final float CHANGE_ARROW_TO_DIALOG_BOUNCE_TIME = 0.625F;
private static final float ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_1 = 0.313F;
private static final float ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_2 = 0.626F;
private static final float ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_3 = 0.813F;
private static final float ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_4 = 1F;
private static final int ROTATE_ARROW_TO_DIALOG_INIT_ANGLE = 0;
private static final int ROTATE_ARROW_TO_DIALOG_ANGLE_1 = -30;
private static final int ROTATE_ARROW_TO_DIALOG_ANGLE_2 = 20;
private static final int ROTATE_ARROW_TO_DIALOG_ANGLE_3 = -10;
private static final float DEFAULT_PROGRESS_MAX_HEIGHT_TO_BASELINE_RATIO = 0.1F;
private static final float DEFAULT_LINE_OSCILLATION_MAX_HEIGHT_TO_BASELINE_RATIO = 0.15F;
private static final float ARROW_BOUNCE_LENGTH_RATIO = 0.2F;
private static final float ARROW_BOUNCE_LENGTH_RATIO_2 = 0.1F;
// Animation State
private static final int STATE_BEFORE_PROGRESS_CIRCLE_SCALE = 1;
private static final int STATE_BEFORE_PROGRESS_INNER_CIRCLE_SCALE = 2;
private static final int STATE_BEFORE_PROGRESS_CIRCLE_TO_LINE = 3;
private static final int STATE_BEFORE_PROGRESS_ARROW_MOVE_LINE_OSCILL = 4;
private static final int STATE_IN_PROGRESS = 5;
private static final int STATE_DONE_ROTATE = 6;
private static final int STATE_DONE_LINE_PACK_UP = 7;
private static final int STATE_DONE_ARROW_SHAKE = 8;
private static final int STATE_DONE_REST_TO_CIRCLE = 9;
private static final int STATE_FAILED_ARROW_SHAKE = 10;
private static final int STATE_FAILED_LINE_OSCILL = 11;
private static final int STATE_FAILED_LINE_PACK_UP = 12;
private static final int STATE_FAILED_CIRCLE_SCALE = 13;
private static final int DONE_LINE_PACK_UP_ARROW_ANGLE = 10;
// other
private Paint mDefaultPaint;
private Xfermode mXfermode;
private Camera mCamera;
private List<Animator> mAnimatorList;
// loadingView
private int mLoadingViewCenterX;
private int mLoadingViewCenterY;
private int mLoadingViewWidth;
private int mLoadingViewHeight;
// arrow
private Path mArrowPath;
private RectF mArrowRectF;
private int mInitArrowRectWidth;
private int mInitArrowRectHeight;
private int mInitArrowTriWidth;
private int mInitArrowTriHeight;
private int mInitArrowJointConnerRadius;
private int mEndArrowRectHeight;
private int mLastArrowRectWidth;
private int mLastArrowRectHeight;
private int mLastArrowTriWidth;
private int mLastArrowTriHeight;
private int mLastArrowOffsetX;
private int mLastArrowOffsetY;
private float[] mChangeArrowToDialogParamters = new float[5];
private Matrix mArrowRotateMatrix;
// arrow move
private float[] mArrowMovePoint;
private Path mArrowMovePath;
private Rect mArrowMovePathRect;
private PathMeasure mArrowPathMeasure;
private float mArrowMovePathLength;
// arrow bounce
private Path mArrowBouncePath;
private PathMeasure mArrowBouncePathMeasure;
private float mArrowBouncePathLength;
// circle
private RectF mCircleRectF;
private int mCircleRadius;
private int mCircleDiameter;
// line
private Path mOscillationLinePath;
private Paint mBaseLinePaint;
private Path mBaseLinePath;
private RectF mBaseLineRectF;
private int mBaseLineLen;
private int mHalfBaseLineLen;
private int mBaseLineStrokeWidth;
private int mBaseLineX;
private int mBaseLineY;
private int mBaseLineCenterX;
private int mProgressLineMaxHeight;
private int mLineOscillationMaxHeight;
// progress text
private Paint mProgressTextPaint;
private Rect mProgressTextRect;
private int mProgressTextSize;
// before progress
private AnimatorSet mBefProgressAnimatorSet;
private ValueAnimator mBefProgCircleScaleAnimator;
private float mBefProgCircleScalingFactor;
private ValueAnimator mBefProgInnerCircleScaleAnimator;
private float mBefProgInnerCircleScalingFactor;
private ValueAnimator mBefProgCircleToLineAnimator;
private float mBefProgCircleToLineNormalizedTime;
private ValueAnimator mBefProgArrowMoveAnimator;
private float mBefProgArrowMoveNormalizedTime;
private ValueAnimator mBefProgLineOscillAnimator;
private float mBefProgLineOscillationFactor;
// in progress
private float mProgressValue;
private int mLastProgress;
private int mNextProgress;
private ValueAnimator mProgressAnimator;
private RectF mProgressLinePathRectF;
private int mLastValidProgress;
private String mLastValidProgressTextStr;
// done
private AnimatorSet mDoneAnimatorSet;
private ValueAnimator mDoneRotateAnimator;
private float mDoneRotateNormalizedTime;
private ValueAnimator mDoneLinePackUpAnimator;
private float mDoneLinePackUpNormalizedTime;
private ValueAnimator mDoneArrowShakeAnimator;
private float mDoneArrowShakeAngle;
private ValueAnimator mDoneRestToCircleAnimator;
private float mDoneRestToCircleScalingFactor;
private ValueAnimator mDoneDialogToArrowAnimator;
private float mDoneDialogToArrowNormalizedTime;
private ValueAnimator mDoneDialogToArrowUpDownAnimator;
private float mDoneDialogToArrowUpDownFactor;
//fail
private boolean mIsFailed;
private AnimatorSet mFailedAnimatorSet;
private ValueAnimator mFailedArrowShakeAnimator;
private float mFailedArrowUpAndDownFactor;
private ValueAnimator mFailedArrowRotateAnimator;
private float mFailedArrowRotateAngle;
private ValueAnimator mFailedLineOscillationAnimator;
private float mFailedLineOscillationFactor;
private ValueAnimator mFailedArrowMoveAnimator;
private float mFailedArrowMoveNormalizeTiem;
private ValueAnimator mFailedLinePackUpAnimator;
private float mFailedLinePackUpFactor;
private ValueAnimator mFailedCircleScaleAnimator;
private float mFailedCircleScaleFactor;
private ValueAnimator mFailedBombAnimator;
private float mFailedBombAnimatorPer;
private Paint mFailedBombPaint;
private Paint mFailedBombBellowPaint;
private Path mFailedBombPath;
private Path mFailedBombPathBellow;
private int mCurrentState;
private int mArrowColor = DEFAULT_ARROW_COLOR;
private int mLoadingCircleBackColor = DEFAULT_LOADING_CIRCLE_BG_COLOR;
private int mLoadingLineColor = DEFAULT_LOADING_LINE_COLOR;
private int mProgressLineColor = DEFAULT_PROGRESS_LINE_LEFT_COLOR;
private int mProgressTextColor = DEFAULT_PROGRESS_TEXT_COLOR;
private int mDoneTextColor = DEFAULT_DONE_PROGRESS_TEXT_COLOR;
public GADownloadingView(Context context) {
this(context, null);
}
public GADownloadingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GADownloadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//add by zyl
//this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.GADownloadingView, defStyleAttr, 0);
mArrowColor = typedArray.getColor(R.styleable.GADownloadingView_arrow_color, DEFAULT_ARROW_COLOR);
mLoadingCircleBackColor = typedArray.getColor(R.styleable.GADownloadingView_loading_circle_back_color, DEFAULT_LOADING_CIRCLE_BG_COLOR);
mLoadingLineColor = typedArray.getColor(R.styleable.GADownloadingView_loading_line_color, DEFAULT_LOADING_LINE_COLOR);
mProgressLineColor = typedArray.getColor(R.styleable.GADownloadingView_progress_line_color, DEFAULT_PROGRESS_LINE_LEFT_COLOR);
mProgressTextColor = typedArray.getColor(R.styleable.GADownloadingView_progress_text_color, DEFAULT_PROGRESS_TEXT_COLOR);
mDoneTextColor = typedArray.getColor(R.styleable.GADownloadingView_done_text_color, DEFAULT_DONE_PROGRESS_TEXT_COLOR);
typedArray.recycle();
init();
}
private void init() {
mDefaultPaint = new Paint();
mDefaultPaint.setAntiAlias(true);
mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
mBaseLinePaint = new Paint();
mBaseLinePaint.setAntiAlias(true);
mBaseLinePaint.setStyle(Paint.Style.STROKE);
mBaseLinePaint.setStrokeCap(Paint.Cap.ROUND);
mBaseLinePaint.setPathEffect(new CornerPathEffect(10f));
mBaseLinePath = new Path();
mBaseLineRectF = new RectF();
mArrowPath = new Path();
mCircleRectF = new RectF();
mArrowRectF = new RectF();
mIsFailed = false;
mNextProgress = ZERO_PROGRESS;
mLastValidProgressTextStr = "";
mLastValidProgress = INVALID_PROGRESS;
mProgressTextPaint = new Paint();
mProgressTextPaint.setColor(mProgressTextColor);
mProgressTextPaint.setAntiAlias(true);
mProgressTextRect = new Rect();
mLoadingViewWidth = dipToPx(getContext(), SUGGEST_LOADING_VIEW_WIDTH);
mLoadingViewHeight = dipToPx(getContext(), SUGGEST_LOADING_VIEW_HEIGHT);
mAnimatorList = new ArrayList<Animator>();
}
private void updateArrowPath(int rectWith, int rectHeight, int triWidth, int triHeight) {
if (mArrowPath == null) {
mArrowPath = new Path();
} else if (mLastArrowRectWidth == rectWith && mLastArrowRectHeight == rectHeight
&& mLastArrowTriWidth == triWidth && mLastArrowTriHeight == triHeight) {
return;
} else {
mArrowPath.reset();
}
mLastArrowRectWidth = rectWith;
mLastArrowRectHeight = rectHeight;
mLastArrowTriWidth = triWidth;
mLastArrowTriHeight = triHeight;
int arrowWidth = Math.max(rectWith, triWidth);
int halfArrowWidth = arrowWidth / 2;
int arrowHeight = rectHeight + triHeight;
int rectPaddingLeft = (arrowWidth - rectWith) / 2;
int triPaddingLeft = (arrowWidth - triWidth) / 2;
// move to bottom center
//path的moveTo方法将起始轮廓点移至x,y坐标点,默认情况为0,0点
mArrowPath.moveTo(halfArrowWidth, 0);
// rect bottom left edge
mArrowPath.lineTo(rectPaddingLeft, 0);
// rect left edge
mArrowPath.lineTo(rectPaddingLeft, rectHeight);
// tri bottom left edge
mArrowPath.lineTo(triPaddingLeft, rectHeight);
// tri left edge
mArrowPath.lineTo(halfArrowWidth, arrowHeight);
// tri right edge
mArrowPath.lineTo(arrowWidth - triPaddingLeft, rectHeight);
// tri bottom right edge
mArrowPath.lineTo(arrowWidth - rectPaddingLeft, rectHeight);
// rect right edge
mArrowPath.lineTo(arrowWidth - rectPaddingLeft, 0);
// rect right bottom edge
mArrowPath.lineTo(halfArrowWidth, 0);
// update path RectF
if (mArrowRectF == null) {
mArrowRectF = new RectF();
}
mArrowPath.computeBounds(mArrowRectF, true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthSpecMode != MeasureSpec.EXACTLY) {
widthMeasureSpec =
MeasureSpec.makeMeasureSpec(mLoadingViewWidth, MeasureSpec.EXACTLY);
}
if (heightSpecMode != MeasureSpec.EXACTLY) {
heightMeasureSpec =
MeasureSpec.makeMeasureSpec(mLoadingViewHeight, MeasureSpec.EXACTLY);
}
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.i("zyl log 12221","onSizeChanged");
super.onSizeChanged(w, h, oldw, oldh);
mLoadingViewCenterX = w / 2;
mLoadingViewCenterY = h / 2;
mCircleDiameter = (int) Math.min(w / (MAX_LINE_WIDTH_FACTOR
+ SUGGEST_X_AXIS_PADDING_TO_CIRCLE_DIAMETER_RATIO),
h / (DEFAULT_ARROW_MOVE_MAX_HEIGHT_TO_CIRCLE_DIAMETER_RATIO));
int actualViewWidth = (int) (mCircleDiameter * MAX_LINE_WIDTH_FACTOR
+ SUGGEST_X_AXIS_PADDING_TO_CIRCLE_DIAMETER_RATIO);
mCircleRadius = mCircleDiameter / 2;
mCircleRectF.set(0, 0, mCircleDiameter, mCircleDiameter);
mCircleRectF.offsetTo((w - mCircleRectF.width()) / 2,
(h - mCircleRectF.height()) / 2);
mInitArrowRectWidth = (int) (mCircleRadius
* DEFAULT_INIT_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO);
mInitArrowRectHeight = (int) (mCircleRadius
* DEFAULT_INIT_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO);
mInitArrowTriWidth = (int) (mCircleRadius
* DEFAULT_INIT_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO);
mInitArrowTriHeight = (int) (mCircleRadius
* DEFAULT_INIT_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO);
mInitArrowJointConnerRadius = (int) (mCircleRadius
* DEFAULT_ARROW_TOP_CONNER_RADIUS_TO_CIRCLE_RADIUS_RATIO);
mEndArrowRectHeight = (int) (mCircleRadius
* DEFAULT_END_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO);
mArrowRectF.set(mCircleRectF);
mArrowRectF.inset(
(mCircleRectF.width() - Math.max(mInitArrowRectWidth, mInitArrowTriWidth)) / 2,
(mCircleRectF.height() - mInitArrowRectHeight - mInitArrowTriHeight) / 2);
mBaseLineStrokeWidth = dipToPx(getContext(), (int) Math.max(MIN_LOADING_LINE_STROKE_WIDTH,
LOADING_LINE_STROKE_WIDTH_TO_VIEW_WIDTH_RATIO * actualViewWidth / getScreenDensity(getContext())));
mBaseLinePaint.setStrokeWidth(mBaseLineStrokeWidth);
mBaseLineLen = (int) (mCircleDiameter * MAX_LINE_WIDTH_FACTOR);
mHalfBaseLineLen = mBaseLineLen / 2;
mBaseLineX = mLoadingViewCenterX - mHalfBaseLineLen;
mBaseLineY = mLoadingViewCenterY - mBaseLineStrokeWidth / 2;
mBaseLineCenterX = mLoadingViewCenterX;
mProgressLineMaxHeight =
(int) (mBaseLineLen * DEFAULT_PROGRESS_MAX_HEIGHT_TO_BASELINE_RATIO);
mLineOscillationMaxHeight =
(int) (mBaseLineLen * DEFAULT_LINE_OSCILLATION_MAX_HEIGHT_TO_BASELINE_RATIO);
mProgressTextSize = dipToPx(getContext(), (int) Math.max(MIN_PROGRESS_TEXT_SIZE,
PROGRESS_TEXT_SIZE_TO_VIEW_WIDTH_RATIO * actualViewWidth / getScreenDensity(getContext())));
mProgressTextPaint.setTextSize(mProgressTextSize);
mDefaultPaint.setPathEffect(new CornerPathEffect(mInitArrowJointConnerRadius));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i("zyl log 1222","onDraw----->mCurrentState = " + mCurrentState);
//mCurrentState = STATE_BEFORE_PROGRESS_INNER_CIRCLE_SCALE;
switch (mCurrentState) {
// in this state, scale circle and arrow
case STATE_BEFORE_PROGRESS_CIRCLE_SCALE:
drawCircleAndArrowScale(canvas, mBefProgCircleScalingFactor);
break;
// in this state, scale circle, arrow and inner circle
case STATE_BEFORE_PROGRESS_INNER_CIRCLE_SCALE:
drawCircleAndArrowScaleAndInnerScale(canvas, mBefProgCircleScalingFactor,
mBefProgInnerCircleScalingFactor);
break;
// in this state, circle change to a line, while the arrow moves up along the y-axis
case STATE_BEFORE_PROGRESS_CIRCLE_TO_LINE:
drawBeforeProgressCircleToLine(canvas, mBefProgCircleToLineNormalizedTime);
break;
// in this state, arrow move to left, while line oscillate
case STATE_BEFORE_PROGRESS_ARROW_MOVE_LINE_OSCILL:
drawBeforeProgressArrowMoveAndLineOscill(canvas,
mBefProgArrowMoveNormalizedTime, mBefProgLineOscillationFactor);
break;
// in this state, draw arrow move by progress
case STATE_IN_PROGRESS:
drawProgress(canvas, mProgressValue);
break;
// in this state, arrow is rotate by y
case STATE_DONE_ROTATE:
drawArrowRotate(canvas, mDoneRotateNormalizedTime);
break;
// in this state, arrow move to center, line pack up
case STATE_DONE_LINE_PACK_UP:
drawDoneLinePackUp(canvas, mDoneLinePackUpNormalizedTime);
break;
// in this state, arrow shake
case STATE_DONE_ARROW_SHAKE:
drawDoneLinePackUpArrowShake(canvas, mDoneArrowShakeAngle);
break;
case STATE_DONE_REST_TO_CIRCLE:
drawDoneRestToCircle(canvas, mDoneRestToCircleScalingFactor,
mDoneDialogToArrowNormalizedTime, mDoneDialogToArrowUpDownFactor);
break;
case STATE_FAILED_ARROW_SHAKE:
drawFailedArrowShake(canvas, mFailedArrowUpAndDownFactor, mFailedArrowRotateAngle, 15f);
break;
case STATE_FAILED_LINE_OSCILL:
drawFailedRopeOscillation(canvas, mFailedLineOscillationFactor);
drawFailedArrowMove(canvas, mFailedArrowMoveNormalizeTiem);
break;
case STATE_FAILED_LINE_PACK_UP:
drawFailedLinePackUp(canvas, mFailedLinePackUpFactor);
drawFailedArrowMove(canvas, mFailedArrowMoveNormalizeTiem);
break;
case STATE_FAILED_CIRCLE_SCALE:
drawFailedCircleScale(canvas, mFailedCircleScaleFactor);
drawFailedArrowMove(canvas, mFailedArrowMoveNormalizeTiem);
break;
default:
break;
}
}
private void drawBombPoint(Canvas canvas, float normalizedTime, int lastProgress, int highestPointHeight) {
if (mFailedBombPaint == null || mFailedBombBellowPaint == null) {
Path circle = new Path();
// generate bomb point shape
circle.addCircle(0, 0, mBaseLineStrokeWidth / 2, Path.Direction.CCW);
circle.addCircle(mBaseLineStrokeWidth, 0, mBaseLineStrokeWidth / 3, Path.Direction.CCW);
circle.addCircle(mBaseLineStrokeWidth * 2, 0, mBaseLineStrokeWidth / 4, Path.Direction.CCW);
circle.addCircle(mBaseLineStrokeWidth * 3, 0, mBaseLineStrokeWidth / 5, Path.Direction.CCW);
mFailedBombPaint = new Paint();
mFailedBombPaint.setStrokeWidth(mBaseLineStrokeWidth);
mFailedBombPaint.setAntiAlias(true);
mFailedBombPaint.setColor(mProgressLineColor);
mFailedBombPaint.setStyle(Paint.Style.STROKE);
//PathDashPathEffect可以让我们自己定义路径虚线的样式
mFailedBombPaint.setPathEffect(new PathDashPathEffect(circle,
mBaseLineStrokeWidth * 3, 0, PathDashPathEffect.Style.TRANSLATE));
mFailedBombBellowPaint = new Paint(mFailedBombPaint);
mFailedBombBellowPaint.setPathEffect(new PathDashPathEffect(circle,
mBaseLineStrokeWidth * 3, HALF_FULL_ANGLE, PathDashPathEffect.Style.TRANSLATE));
}
if (mFailedBombPath == null || mFailedBombPathBellow == null) {
mFailedBombPath = new Path();
float normalizeProgress = (float) lastProgress / FULL_PROGRESS;
int baseLineEndX = (int) (mBaseLineX + normalizeProgress * mBaseLineLen);
int baseLineEndY;
float k = (float) highestPointHeight / mHalfBaseLineLen;
if (normalizeProgress < HALF_NORMALIZED_PROGRESS) {
baseLineEndY = (int) (mHalfBaseLineLen * k * normalizeProgress / HALF_NORMALIZED_PROGRESS) + mBaseLineY;
} else {
baseLineEndY = (int) (mHalfBaseLineLen * k * (1 - normalizeProgress) / HALF_NORMALIZED_PROGRESS) + mBaseLineY;
}
mFailedBombPath.moveTo(mBaseLineX, mBaseLineY - mBaseLineStrokeWidth / 2);
mFailedBombPath.lineTo(baseLineEndX, baseLineEndY);
mFailedBombPathBellow = new Path(mFailedBombPath);
mFailedBombPathBellow.offset(0, mBaseLineStrokeWidth / 2);
}
mFailedBombPaint.setAlpha((int) (FULL_ALPHA * (1 - normalizedTime)));
mFailedBombBellowPaint.setAlpha((int) (FULL_ALPHA * (1 - normalizedTime)));
canvas.save();
canvas.translate(0, -mBaseLineStrokeWidth * normalizedTime);
canvas.drawPath(mFailedBombPath, mFailedBombPaint);
canvas.translate(0, mBaseLineStrokeWidth * 2 * normalizedTime);
canvas.drawPath(mFailedBombPathBellow, mFailedBombBellowPaint);
canvas.restore();
}
private void drawBeforeProgressArrowMoveAndLineOscill(Canvas canvas, float normalizeTime, float lineOsscilFactor) {
// update arrow paramters
updateArrowToDialogParamters(mChangeArrowToDialogParamters, normalizeTime);
// height point of path move
int maxMovePathHeight = (int) (mCircleDiameter * DEFAULT_ARROW_MOVE_MAX_HEIGHT_TO_CIRCLE_DIAMETER_RATIO);
updateArrowMovePoint(normalizeTime,
CHANGE_ARROW_TO_DIALOG_BOUNCE_TIME, mHalfBaseLineLen,
maxMovePathHeight, mLoadingViewCenterX - mHalfBaseLineLen,
mLoadingViewCenterY - maxMovePathHeight, false);//依据一条三次贝塞尔曲线,计算箭头中心点坐标?
// move the center of arrow by path 02.16 !!!
mLastArrowOffsetX = (int) (mArrowMovePoint[0] - mArrowRectF.width() / 2);
mLastArrowOffsetY = (int) (mArrowMovePoint[1] - mArrowRectF.height());
drawArrowTrans(canvas, mLastArrowOffsetX, mLastArrowOffsetY, mChangeArrowToDialogParamters);//绘制箭头
int maxHeightPointOfLineOscill = (int) (mBaseLineLen * DEFAULT_LINE_OSCILLATION_MAX_HEIGHT_TO_BASELINE_RATIO);
updateLineOscillationPath(lineOsscilFactor, mBaseLineLen,
mBaseLineX, mBaseLineY, maxHeightPointOfLineOscill, mHalfBaseLineLen);//震荡曲线
canvas.drawPath(mOscillationLinePath, mBaseLinePaint);
}
private void drawBeforeProgressCircleToLine(Canvas canvas, float normalizeTime) {
mBaseLinePaint.setColor(mLoadingLineColor);
// update path in center of bounds[0, 0, mCircleDiameter, mCircleDiameter]
Log.i("zyl log 1226","drawBeforeProgressCircleToLine------->mCircleDiameter = " + mCircleDiameter);//156
Log.i("zyl log 1226","drawBeforeProgressCircleToLine------->normalizeTime = " + normalizeTime);
updateCircleToLinePath(mBaseLinePath, mCircleDiameter, normalizeTime);
// offset the path to the place of circle
Log.i("zyl log 0215","mCircleRectF.left = " + mCircleRectF.left);
Log.i("zyl log 0215","mCircleRectF.top = " + mCircleRectF.top);
Log.i("zyl log 0215","---------------------------------------------");
mBaseLinePath.offset(mCircleRectF.left, mCircleRectF.top);
canvas.drawPath(mBaseLinePath, mBaseLinePaint);
// compute bounds to get the lowest (center) point of the path
mBaseLinePath.computeBounds(mBaseLineRectF, false);//???
// because the bounds of arrow is always [0, 0, width, height]
mLastArrowOffsetX = (int) (mCircleRectF.centerX() - mArrowRectF.centerX());
mLastArrowOffsetY = (int) (mCircleRectF.centerY() - mArrowRectF.centerY());
// calculate the bottom of arrow
int arrowBottom = (int) (mLastArrowOffsetY + mArrowRectF.height());
// determine if the bottom of the rope is below the bottom of the arrow
if (mBaseLineRectF.bottom <= arrowBottom) {
// move arrow up
mLastArrowOffsetY += (int) (mBaseLineRectF.bottom - arrowBottom);
}
drawArrowTrans(canvas, mLastArrowOffsetX, mLastArrowOffsetY, 0);
}
private void drawProgress(Canvas canvas, float progress) {
float normalizedProgress = progress / FULL_PROGRESS;
drawProgressLinePath(canvas, normalizedProgress, mBaseLineLen,
mBaseLineX, mBaseLineY, mProgressLineMaxHeight, mProgressLineColor);//绘制折线
mLastArrowOffsetX = (int) (mProgressLinePathRectF.left - mArrowRectF.width() / 2
+ mBaseLineLen * normalizedProgress);
mLastArrowOffsetY = (int) (mProgressLinePathRectF.bottom - mArrowRectF.height());
drawArrowTrans(canvas, mLastArrowOffsetX, mLastArrowOffsetY, 0);
// draw progress text
if (mLastValidProgress != (int) progress) {
mLastValidProgressTextStr = String.valueOf((int) progress) + "%";
mProgressTextPaint.getTextBounds(mLastValidProgressTextStr, 0,
mLastValidProgressTextStr.length(), mProgressTextRect);
}
int offsetHeight = (mEndArrowRectHeight - mProgressTextRect.height()) / 2;
int textBaseLineX = (int) (mLastArrowOffsetX
+ (mArrowRectF.width() - mProgressTextRect.width()) / 2);
int textBastLineY = mLastArrowOffsetY + offsetHeight - mProgressTextRect.top;
mProgressTextPaint.setColor(mProgressTextColor);
canvas.drawText(mLastValidProgressTextStr, textBaseLineX, textBastLineY
, mProgressTextPaint);
}
private void drawArrowRotate(Canvas canvas, float normalizedTime) {
if (mArrowRotateMatrix == null) {
mArrowRotateMatrix = new Matrix();
} else {
mArrowRotateMatrix.reset();
}
mArrowRotateMatrix.reset();
float angle;
String tmpStr = mLastValidProgressTextStr;
if (normalizedTime <= HALF_NORMALIZED_PROGRESS) {
// first text is 100%, rotate angle is 0 to 90
mLastValidProgressTextStr = FULL_PROGRESS_STR;
angle = HALF_FULL_ANGLE * normalizedTime;
mProgressTextPaint.setColor(mProgressTextColor);
} else {
// second text is done, rotate angle is 270 to 360
mLastValidProgressTextStr = FULL_PROGRESS_DONE_STR;
angle = HALF_FULL_ANGLE * normalizedTime + HALF_FULL_ANGLE;
mProgressTextPaint.setColor(mDoneTextColor);
}
if (mCamera == null) {
mCamera = new Camera();
}
mCamera.save();
mCamera.rotateY(angle);
mCamera.getMatrix(mArrowRotateMatrix);//计算对应于当前转换的矩阵,并将其复制到提供的矩阵对象
mCamera.restore();
// 保证绕Arrow的中心进行旋转
mArrowRotateMatrix.preTranslate(-mArrowRectF.centerX(), -mArrowRectF.centerY());
mArrowRotateMatrix.postTranslate(mArrowRectF.centerX(), mArrowRectF.centerY());
mLastArrowOffsetX = (int) (mBaseLineX + mBaseLineLen - mArrowRectF.width() / 2);
mLastArrowOffsetY = (int) (mBaseLineY - mArrowRectF.height());
canvas.save();
canvas.translate(mLastArrowOffsetX, mLastArrowOffsetY);
// 应用上述Camera变换的结果
canvas.concat(mArrowRotateMatrix);
mDefaultPaint.setColor(mArrowColor);
// 绘制Arrow
canvas.drawPath(mArrowPath, mDefaultPaint);
// str is changed, need re-calculate bounds
if (!tmpStr.equals(mLastValidProgressTextStr)) {
mProgressTextPaint.getTextBounds(mLastValidProgressTextStr,
0, mLastValidProgressTextStr.length(), mProgressTextRect);
}
int textBaseLineX = (int) (mArrowRectF.left + (mArrowRectF.width() - mProgressTextRect.width()) / 2);
int textBaseLineY = (int) (mArrowRectF.bottom - mArrowRectF.height() / 2);
canvas.drawText(mLastValidProgressTextStr, textBaseLineX, textBaseLineY, mProgressTextPaint);
canvas.restore();
// the FULL_NORMALIZED_TIME is means, no need bounce
drawProgressLinePath(canvas, FULL_NORMALIZED_TIME, mBaseLineLen,
mBaseLineX, mBaseLineY, mProgressLineMaxHeight, mProgressLineColor);
}
private void drawDoneLinePackUp(Canvas canvas, float normalizedTime) {
int adjustLen = (int) (mBaseLineLen * (FULL_NORMALIZED_TIME - normalizedTime));
// keep a min len
if (adjustLen < DONE_LINE_PACK_UP_ARROW_SHAKE_BASE_POINT_DIAMETER) {
adjustLen = DONE_LINE_PACK_UP_ARROW_SHAKE_BASE_POINT_DIAMETER;
}
int adjustBaseLineX = mBaseLineCenterX - adjustLen / 2;
mLastArrowOffsetX = (int) (adjustBaseLineX + adjustLen - mArrowRectF.width() / 2);
mLastArrowOffsetY = (int) (mBaseLineY - mArrowRectF.height());
mBaseLinePaint.setColor(mProgressLineColor);
canvas.drawLine(adjustBaseLineX, mBaseLineY, adjustBaseLineX + adjustLen, mBaseLineY, mBaseLinePaint);
drawArrowTrans(canvas, mLastArrowOffsetX, mLastArrowOffsetY, DONE_LINE_PACK_UP_ARROW_ANGLE);
}
private void drawDoneLinePackUpArrowShake(Canvas canvas, float angle) {
int adjustLen = DONE_LINE_PACK_UP_ARROW_SHAKE_BASE_POINT_DIAMETER;
mBaseLinePaint.setColor(mProgressLineColor);
canvas.drawLine(mBaseLineCenterX, mBaseLineY, mBaseLineCenterX + adjustLen, mBaseLineY, mBaseLinePaint);
drawArrowTrans(canvas,
(int) (mBaseLineCenterX - mArrowRectF.width() / 2),
(int) (mBaseLineY - mArrowRectF.height()), angle);
}
private void drawDoneRestToCircle(Canvas canvas, float circleScaleFactor,
float dialogToArrowNormalizeTime, float arrowUpDownFactor) {
// draw bg circle
float circleRadius = mCircleRadius * circleScaleFactor;
mDefaultPaint.setColor(mLoadingCircleBackColor);
canvas.drawCircle(mCircleRectF.centerX(), mCircleRectF.centerY(), circleRadius, mDefaultPaint);
// draw arrow
// generate dialog to arrow
updateArrowToDialogParamters(mChangeArrowToDialogParamters, 1 - dialogToArrowNormalizeTime);
updateArrowPath((int) (mCircleRadius * mChangeArrowToDialogParamters[0]),
(int) (mCircleRadius * mChangeArrowToDialogParamters[1]),
(int) (mCircleRadius * mChangeArrowToDialogParamters[2]),
(int) (mCircleRadius * mChangeArrowToDialogParamters[3]));
int offsetArrowX = (int) (mCircleRectF.centerX() - mArrowRectF.width() / 2);
// dialogToArrowNormalizeTime * 0.5 can ensure the arrow move end point smoothly
int offsetArrowY = (int) (mCircleRectF.centerY() - mArrowRectF.height()
* (1 - dialogToArrowNormalizeTime * 0.5 + arrowUpDownFactor));
canvas.save();
canvas.translate(offsetArrowX, offsetArrowY);
mDefaultPaint.setColor(mArrowColor);
canvas.drawPath(mArrowPath, mDefaultPaint);
canvas.restore();
}
private void drawFailedArrowShake(Canvas canvas, float upAndDownFactor, float rotateFactor, float maxRotateAngle) {
float normalizedProgress = mProgressValue / FULL_PROGRESS;
int offsetLine = (int) (mProgressLineMaxHeight * upAndDownFactor);
int offsetArrow;
if (normalizedProgress < HALF_NORMALIZED_PROGRESS) {
offsetArrow = (int) (offsetLine * normalizedProgress / HALF_NORMALIZED_PROGRESS);
} else {
offsetArrow = (int) (offsetLine * (FULL_NORMALIZED_PROGRESS - normalizedProgress) / HALF_NORMALIZED_PROGRESS);
}
drawProgressLinePath(canvas, normalizedProgress, mBaseLineLen,
mBaseLineX, mBaseLineY, mProgressLineMaxHeight + offsetLine, mLoadingLineColor);
drawBombPoint(canvas, mFailedBombAnimatorPer, (int) mProgressValue, mProgressLineMaxHeight);
if (!mLastValidProgressTextStr.equals(FAILED_PROGRESS_STR)) {
mLastValidProgressTextStr = FAILED_PROGRESS_STR;
mProgressTextPaint.getTextBounds(FAILED_PROGRESS_STR,
0, FAILED_PROGRESS_STR.length(), mProgressTextRect);
}
canvas.save();
canvas.translate(mLastArrowOffsetX, mLastArrowOffsetY + offsetArrow);
canvas.rotate(maxRotateAngle * rotateFactor, mArrowRectF.centerX(), mArrowRectF.bottom);
canvas.save();
canvas.rotate(HALF_FULL_ANGLE, mArrowRectF.centerX(), mArrowRectF.bottom);
canvas.drawPath(mArrowPath, mDefaultPaint);
canvas.restore();
int textBaseLineX = (int) (mArrowRectF.left + (mArrowRectF.width() - mProgressTextRect.width()) / 2);
int textBaseLineY = (int) (mArrowRectF.bottom + mArrowRectF.height() +
-(mEndArrowRectHeight - mProgressTextRect.height()) / 2
- mProgressTextRect.bottom);
canvas.drawText(FAILED_PROGRESS_STR, textBaseLineX, textBaseLineY, mProgressTextPaint);
canvas.restore();
}
private void drawFailedRopeOscillation(Canvas canvas, float ropeOsillFactor) {
updateLineOscillationPath(ropeOsillFactor, mBaseLineLen,
mBaseLineX, mBaseLineY, mLineOscillationMaxHeight, mHalfBaseLineLen);
mBaseLinePaint.setColor(mLoadingLineColor);
canvas.drawPath(mOscillationLinePath, mBaseLinePaint);
}
private void drawFailedArrowMove(Canvas canvas, float normalizedTime) {
int circleRadius = (int) (mCircleRectF.width() / 2);
int halfLineLen = (int) (circleRadius * MAX_LINE_WIDTH_FACTOR);
int startX = (int) (mLoadingViewCenterX
+ (mProgressValue - HALF_PROGRESS) / HALF_PROGRESS * halfLineLen);
int width = Math.abs(startX - mLoadingViewCenterX);
int maxMovePathHeight = (int) (circleRadius
* DEFAULT_ARROW_MOVE_MAX_HEIGHT_TO_CIRCLE_DIAMETER_RATIO);
updateArrowMovePoint(normalizedTime, FULL_NORMALIZED_TIME,
width, maxMovePathHeight, Math.min(startX, mLoadingViewCenterX),
mLoadingViewCenterY - maxMovePathHeight, mProgressValue < HALF_PROGRESS);
int offsetArrowX = (int) (mArrowMovePoint[0] - mArrowRectF.width() / 2);
int offsetArrowY = (int) (mArrowMovePoint[1] - mArrowRectF.height() / 2);
updateFailedDialogToArrowParamters(mChangeArrowToDialogParamters, normalizedTime);
updateArrowPath((int) (mCircleRadius * mChangeArrowToDialogParamters[0]),
(int) (mCircleRadius * mChangeArrowToDialogParamters[1]),
(int) (mCircleRadius * mChangeArrowToDialogParamters[2]),
(int) (mCircleRadius * mChangeArrowToDialogParamters[3]));
mDefaultPaint.setColor(mArrowColor);
canvas.save();
canvas.translate(offsetArrowX, offsetArrowY);
canvas.concat(mArrowRotateMatrix);
canvas.drawPath(mArrowPath, mDefaultPaint);
canvas.restore();
}
private void drawFailedLinePackUp(Canvas canvas, float packUpFactor) {
int halfLineLen = (int) (mHalfBaseLineLen * packUpFactor);
mBaseLinePaint.setColor(mLoadingLineColor);
canvas.drawLine(mLoadingViewCenterX - halfLineLen, mLoadingViewCenterY,
mLoadingViewCenterX + halfLineLen, mLoadingViewCenterY, mBaseLinePaint);
}
private void drawFailedCircleScale(Canvas canvas, float scaleFactor) {
int circleRadius = (int) (mCircleRadius * scaleFactor);
mDefaultPaint.setColor(mLoadingCircleBackColor);
canvas.drawCircle(mCircleRectF.centerX(), mCircleRectF.centerY(), circleRadius, mDefaultPaint);
}
private void updateArrowMovePoint(float normalizedTime, float startBounceTime,
int width, int height, int left, int top, boolean isLTR) {
if (mArrowMovePath == null) {
mArrowMovePath = new Path();
}
if (mArrowPathMeasure == null) {
mArrowPathMeasure = new PathMeasure();
}
if (mArrowBouncePath == null) {
mArrowBouncePath = new Path();
}
if (mArrowBouncePathMeasure == null) {
mArrowBouncePathMeasure = new PathMeasure();
}
if (mArrowMovePathRect == null) {
mArrowMovePathRect = new Rect();
}
if (mArrowMovePathRect.width() != width || mArrowMovePathRect.height() != height
|| mArrowMovePathRect.left != left || mArrowMovePathRect.top != top) {
mArrowMovePathRect.set(left, top, left + width, top + height);
mArrowMovePath.reset();
mArrowBouncePath.reset();
}
// move path
if (mArrowMovePath.isEmpty()) {
mArrowMovePath.moveTo(mArrowMovePathRect.left, mArrowMovePathRect.bottom);
mArrowMovePath.cubicTo(mArrowMovePathRect.left + mArrowMovePathRect.width() / 4,
mArrowMovePathRect.top,
mArrowMovePathRect.right,
mArrowMovePathRect.top,
mArrowMovePathRect.right, mArrowMovePathRect.bottom);
mArrowPathMeasure.setPath(mArrowMovePath, false);
mArrowMovePathLength = mArrowPathMeasure.getLength();
}
// bounce path
if (mArrowBouncePath.isEmpty()) {
mArrowBouncePath.moveTo(mArrowMovePathRect.right, mArrowMovePathRect.bottom);
mArrowBouncePath.lineTo(mArrowMovePathRect.right, mArrowMovePathRect.bottom
- mArrowRectF.height() * ARROW_BOUNCE_LENGTH_RATIO);
mArrowBouncePath.lineTo(mArrowMovePathRect.right, mArrowMovePathRect.bottom);
mArrowBouncePath.lineTo(mArrowMovePathRect.right, mArrowMovePathRect.bottom
- mArrowRectF.height() * ARROW_BOUNCE_LENGTH_RATIO_2);
mArrowBouncePath.lineTo(mArrowMovePathRect.right, mArrowMovePathRect.bottom);
mArrowBouncePathMeasure.setPath(mArrowBouncePath, false);
mArrowBouncePathLength = mArrowBouncePathMeasure.getLength();
}
if (mArrowMovePoint == null) {
mArrowMovePoint = new float[2];
}
// move
if (normalizedTime <= startBounceTime) {
mArrowPathMeasure.getPosTan(mArrowMovePathLength
* normalizedTime / startBounceTime, mArrowMovePoint, null);
} else {
// bounce
mArrowBouncePathMeasure.getPosTan(mArrowBouncePathLength
* (normalizedTime - startBounceTime)
/ (FULL_NORMALIZED_TIME - startBounceTime), mArrowMovePoint, null);
}
if (!isLTR) {
mArrowMovePoint[0] = mArrowMovePathRect.centerX()
- (mArrowMovePoint[0] - mArrowMovePathRect.centerX());
}
}
/**
* generate arrow change to dialog paramters
*
* @param paramters must be length = 5
* paramteArr[0] and paramteArr[1] indicate arrow rectangle's width and height,respectively.
* paramteArr[2] and paramteArr[3] indicate arrow triangle's width and height,respectively.
* paramteArr[4] indicates arrow rotate angle.
*/
private void updateArrowToDialogParamters(float[] paramters, float normalizeTime) {
if (paramters == null || paramters.length != 5) {
return;
}
// rect width
if (normalizeTime <= CHANGE_ARROW_TO_DIALOG_BOUNCE_TIME) {
paramters[0] = (DEFAULT_END_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO
- DEFAULT_INIT_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO)
* normalizeTime / CHANGE_ARROW_TO_DIALOG_BOUNCE_TIME
+ DEFAULT_INIT_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO;
} else {
paramters[0] = DEFAULT_END_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO;
}
// rect height
if (normalizeTime <= CHANGE_ARROW_TO_DIALOG_RECT_CHANGE) {
paramters[1] = (DEFAULT_MIDDLE_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO
- DEFAULT_INIT_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO)
* normalizeTime / CHANGE_ARROW_TO_DIALOG_RECT_CHANGE
+ DEFAULT_INIT_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO;
} else if (normalizeTime <= CHANGE_ARROW_TO_DIALOG_BOUNCE_TIME) {
paramters[1] = (DEFAULT_END_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO
- DEFAULT_MIDDLE_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO)
* (normalizeTime - CHANGE_ARROW_TO_DIALOG_RECT_CHANGE)
/ (CHANGE_ARROW_TO_DIALOG_BOUNCE_TIME - CHANGE_ARROW_TO_DIALOG_RECT_CHANGE)
+ DEFAULT_MIDDLE_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO;
} else {
paramters[1] = DEFAULT_END_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO;
}
// tri width and height
if (normalizeTime < CHANGE_ARROW_TO_DIALOG_BOUNCE_TIME) {
// tri width
paramters[2] = (DEFAULT_END_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO
- DEFAULT_INIT_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO)
* normalizeTime / CHANGE_ARROW_TO_DIALOG_BOUNCE_TIME
+ DEFAULT_INIT_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO;
// tri height
paramters[3] = (DEFAULT_END_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO
- DEFAULT_INIT_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO)
* normalizeTime / CHANGE_ARROW_TO_DIALOG_BOUNCE_TIME
+ DEFAULT_INIT_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO;
} else {
// tri width
paramters[2] = DEFAULT_END_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO;
// tri height
paramters[3] = DEFAULT_END_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO;
}
// angle
if (normalizeTime <= ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_1) {
paramters[4] = (ROTATE_ARROW_TO_DIALOG_ANGLE_1 - ROTATE_ARROW_TO_DIALOG_INIT_ANGLE)
* normalizeTime / ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_1
+ ROTATE_ARROW_TO_DIALOG_INIT_ANGLE;
} else if (normalizeTime <= ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_2) {
paramters[4] = (ROTATE_ARROW_TO_DIALOG_ANGLE_2 - ROTATE_ARROW_TO_DIALOG_ANGLE_1)
* (normalizeTime - ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_1)
/ (ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_2 - ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_1)
+ ROTATE_ARROW_TO_DIALOG_ANGLE_1;
} else if (normalizeTime <= ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_3) {
paramters[4] = (ROTATE_ARROW_TO_DIALOG_ANGLE_3 - ROTATE_ARROW_TO_DIALOG_ANGLE_2)
* (normalizeTime - ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_2)
/ (ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_3 - ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_2)
+ ROTATE_ARROW_TO_DIALOG_ANGLE_2;
} else {
paramters[4] = (ROTATE_ARROW_TO_DIALOG_INIT_ANGLE - ROTATE_ARROW_TO_DIALOG_ANGLE_3)
* (normalizeTime - ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_3)
/ (ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_4 - ROTATE_ARROW_TO_DIALOG_ANGLE_SEASON_3)
+ ROTATE_ARROW_TO_DIALOG_ANGLE_3;
}
}
private void updateFailedDialogToArrowParamters(float[] paramters, float normalizeTime) {
if (paramters == null || paramters.length != 5) {
return;
}
// rect width
paramters[0] = (DEFAULT_INIT_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO
- DEFAULT_END_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO)
* normalizeTime + DEFAULT_END_ARROW_RECT_WIDTH_TO_CIRCLE_RADIUS_RATIO;
// rect height
paramters[1] = (DEFAULT_INIT_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO
- DEFAULT_END_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO)
* normalizeTime + DEFAULT_END_ARROW_RECT_HEIGHT_TO_CIRCLE_RADIUS_RATIO;
// tri width and height
// tri width
paramters[2] = (DEFAULT_INIT_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO
- DEFAULT_END_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO)
* normalizeTime + DEFAULT_END_ARROW_TRI_WIDTH_TO_CIRCLE_RADIUS_RATIO;
// tri height
paramters[3] = (DEFAULT_INIT_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO
- DEFAULT_END_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO)
* normalizeTime + DEFAULT_END_ARROW_TRI_HEIGHT_TO_CIRCLE_RADIUS_RATIO;
}
/**
* Assumed the circle height is h, width is w, we can obtain the following content:
* this animation have four State:
*
* State 1: Circle to special line 1
* width: w -> 1.2w
* height: w -> 0.73w
* firstConXFactor: -0.65w to 0.18w
* firstConYFactor: 0 to 0.036w
* SecondConXFactor: -0.65w to 0.06w
* SecondConYFactor: 1w to 0.73w
*
* State 2: line 1 to line 2
* width: 1.2w -> 2.4w
* height: w -> 0.36w
* firstConXFactor: 0.18w to 0.72w
* firstConYFactor: 0.036w to 0
* SecondConXFactor: 0.06w to 0.72w
* SecondConYFactor: 0.73w to 0.36w
*
* State 3: line 2 to line 3
* width: 2.4w -> 2.4w
* height: 0.36w -> 0
* firstConXFactor: 0.72w to 1.04w
* firstConYFactor: 0 to 0
* SecondConXFactor: 0.72w to 1.04w
* SecondConYFactor: 0.36w to 0
*/
//思路一:利用Photoshop等画图工具模拟出曲线,固定锚点,计算控制点的位置?
//思路二:网页版贝塞尔生成器
private void updateCircleToLinePath(Path linePath, int circleDiameter, float normalizedTime) {
if (linePath == null) {
return;
}
int index = 0;
float adjustNormalizedTime = 0;
if (normalizedTime <= CIRCLE_TO_LINE_SEASONS[1]) {//0.4
adjustNormalizedTime = normalizedTime / CIRCLE_TO_LINE_SEASONS[1];
} else if (normalizedTime < CIRCLE_TO_LINE_SEASONS[2]) {//0.8
index = 1;
adjustNormalizedTime = (normalizedTime - CIRCLE_TO_LINE_SEASONS[1])
/ (CIRCLE_TO_LINE_SEASONS[2] - CIRCLE_TO_LINE_SEASONS[1]);
} else {
index = 2;
adjustNormalizedTime = (normalizedTime - CIRCLE_TO_LINE_SEASONS[2])
/ (CIRCLE_TO_LINE_SEASONS[3] - CIRCLE_TO_LINE_SEASONS[2]);
}
// the path bounds width
int boundWidth = (int) (((CIRCLE_TO_LINE_WIDTH_FACTOR[index + 1]
- CIRCLE_TO_LINE_WIDTH_FACTOR[index])
* adjustNormalizedTime + CIRCLE_TO_LINE_WIDTH_FACTOR[index]) * circleDiameter);
// the distance of cubic line1' x1 to cubic line2's x2
int adjustBoundWidth = boundWidth;//锚点关于Y轴的偏移量!!!
if (normalizedTime <= CIRCLE_TO_LINE_SEASONS[1]) {
adjustBoundWidth = (int) (boundWidth * adjustNormalizedTime);
}
// the path bounds height
int boundHeight = (int) (((CIRCLE_TO_LINE_HEIGHT_FACTOR[index + 1]
- CIRCLE_TO_LINE_HEIGHT_FACTOR[index])
* adjustNormalizedTime + CIRCLE_TO_LINE_HEIGHT_FACTOR[index]) * circleDiameter);
// calculate the four points
float firstControlXFactor = (CIRCLE_TO_LINE_FST_CON_X_FACTOR[index + 1]
- CIRCLE_TO_LINE_FST_CON_X_FACTOR[index])
* adjustNormalizedTime + CIRCLE_TO_LINE_FST_CON_X_FACTOR[index];
float firstControlYFactor = (CIRCLE_TO_LINE_FST_CON_Y_FACTOR[index + 1]
- CIRCLE_TO_LINE_FST_CON_Y_FACTOR[index])
* adjustNormalizedTime + CIRCLE_TO_LINE_FST_CON_Y_FACTOR[index];
float secondControlXFactor = (CIRCLE_TO_LINE_SEC_CON_X_FACTOR[index + 1]
- CIRCLE_TO_LINE_SEC_CON_X_FACTOR[index])
* adjustNormalizedTime + CIRCLE_TO_LINE_SEC_CON_X_FACTOR[index];
float secondControlYFactor = (CIRCLE_TO_LINE_SEC_CON_Y_FACTOR[index + 1]
- CIRCLE_TO_LINE_SEC_CON_Y_FACTOR[index])
* adjustNormalizedTime + CIRCLE_TO_LINE_SEC_CON_Y_FACTOR[index];
Log.i("zyl log 0105","firstControlXFactor = " + firstControlXFactor);
Log.i("zyl log 0105","firstControlYFactor = " + firstControlYFactor);
Log.i("zyl log 0105","secondControlXFactor = " + secondControlXFactor);
Log.i("zyl log 0105","secondControlYFactor = " + secondControlYFactor);
int firstControlX = (int) (circleDiameter * firstControlXFactor);
int firstControlY = (int) (circleDiameter * firstControlYFactor);
int secondControlX = (int) (circleDiameter * secondControlXFactor);
int secondControlY = (int) (circleDiameter * secondControlYFactor);
linePath.reset();
// left line
Log.i("zyl log 0109","firstControlX = " + firstControlX);
Log.i("zyl log 0109","firstControlY = " + firstControlY);
Log.i("zyl log 0109","secondControlX = " + secondControlX);
Log.i("zyl log 0109","secondControlY = " + secondControlY);
Log.i("zyl log 0109","adjustBoundWidth/2 = " + adjustBoundWidth/2);
Log.i("zyl log 0109","boundHeight = " + boundHeight);
Log.i("zyl log 0109","==================================================");
linePath.cubicTo(firstControlX, firstControlY,
secondControlX, secondControlY, adjustBoundWidth / 2, boundHeight);
// left right line
Log.i("zyl log 0112","adjustBoundWidth - secondControlX = " + (adjustBoundWidth - secondControlX));
Log.i("zyl log 0112","secondControlY = " + secondControlY);
Log.i("zyl log 0112","adjustBoundWidth - firstControlX = " + (adjustBoundWidth - firstControlX));
Log.i("zyl log 0112","firstControlY = " + firstControlY);
Log.i("zyl log 0112","adjustBoundWidth = " + adjustBoundWidth);
Log.i("zyl log 0112","boundHeight = " + boundHeight);
Log.i("zyl log 0112","==================================================");
//linePath.cubicTo(adjustBoundWidth - secondControlX,
//secondControlY, adjustBoundWidth - firstControlX, firstControlY, adjustBoundWidth, 0);
// translate path to move the origin to the center
int offsetX = (circleDiameter - adjustBoundWidth) / 2;
int offsetY = (circleDiameter - boundHeight) / 2;
//Log.i("zyl log 0111","offsetX = " + offsetX);
//Log.i("zyl log 0111","offsetY = " + offsetY);
//Log.i("zyl log 0111","---------------------------------------------");
linePath.offset(offsetX, offsetY);//将path平移dx,dy
}
private void drawCircleAndArrowScale(Canvas canvas, float scaleFactor) {
// draw bg circle
canvas.save();
//save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
//restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
Log.i("drawCircleAndArrowScale","scaleFactor = " + scaleFactor);
Log.i("drawCircleAndArrowScale","mCircleRectF.centerX() = " + mCircleRectF.centerX());
Log.i("drawCircleAndArrowScale","mCircleRectF.centerY() = " + mCircleRectF.centerY());
Log.i("drawCircleAndArrowScale","mCircleRadius = " + mCircleRadius);
//前两个参数为将画布在x、y方向上缩放的倍数,而px和py 分别为缩放的基准点
canvas.scale(scaleFactor, scaleFactor, mCircleRectF.centerX(), mCircleRectF.centerY());//scaleFactor初始值为0,所以初始化的时候圆圈和箭头都不会出现
mDefaultPaint.setColor(mLoadingCircleBackColor);
//画圆
canvas.drawCircle(mCircleRectF.centerX(), mCircleRectF.centerY(), mCircleRadius, mDefaultPaint);
//canvas.drawCircle(mCircleRectF.centerX()+150, mCircleRectF.centerY()+150, mCircleRadius, mDefaultPaint);
// draw arrow
updateArrowPath(mInitArrowRectWidth,
mInitArrowRectHeight, mInitArrowTriWidth, mInitArrowTriHeight);
// translate the canvas causes the center point of the arrow to coincide with the center point of the circle
//把当前画布的原点移到(x,y),后面的操作都以(x,y)作为参照点
Log.i("drawCircleAndArrowScale","mArrowRectF.width() = " + mArrowRectF.width());
Log.i("drawCircleAndArrowScale","mArrowRectF.height() = " + mArrowRectF.height());
canvas.translate(mCircleRectF.centerX() - mArrowRectF.width() / 2 * scaleFactor,
mCircleRectF.centerY() - mArrowRectF.height() / 2 * scaleFactor);
mDefaultPaint.setColor(mArrowColor);
canvas.drawPath(mArrowPath, mDefaultPaint);
canvas.restore();
}
int test = 50;
private void drawCircleAndArrowScaleAndInnerScale(Canvas canvas, float scalingFactor, float innerCircleScalingFactor) {
// draw bg circle
Log.i("zyl log 12221","drawCircleAndArrowScaleAndInnerScale------->scalingFactor = " + scalingFactor);
Log.i("zyl log 12221","drawCircleAndArrowScaleAndInnerScale------->innerCircleScalingFactor = " + innerCircleScalingFactor);
canvas.save();
canvas.scale(scalingFactor, scalingFactor, mCircleRectF.centerX(), mCircleRectF.centerY());
int layoutCont = canvas.saveLayer(mCircleRectF, mDefaultPaint, Canvas.ALL_SAVE_FLAG);
//1.创建一个新的Layer到“栈”中,可以使用saveLayer, savaLayerAlpha, 从“栈”中推出一个Layer,可以使用restore,restoreToCount。
// 但Layer入栈时,后续的DrawXXX操作都发生在这个Layer上,而Layer退栈时,就会把本层绘制的图像“绘制”到上层或是Canvas上.
//2.saveLayer可以为canvas创建一个新的透明图层,在新的图层上绘制,并不会直接绘制到屏幕上,
// 而会在restore之后,绘制到上一个图层或者屏幕上(如果没有上一个图层)
//3.为什么????会需要一个新的图层,例如在处理xfermode的时候,原canvas上的图(包括背景)会影响src和dst的合成,这个时候,使用一个新的透明图层是一个很好的选择。
// 又例如需要当前绘制的图形都带有一定的透明度,那么创建一个带有透明度的图层,也是一个方便的选择
//4.参数:a)RectF:指定Layer的大小和范围 b)画笔 c)Flags:代表了需要保存哪方面的内容
mDefaultPaint.setColor(mLoadingCircleBackColor);
canvas.drawCircle(mCircleRectF.centerX(), mCircleRectF.centerY(), mCircleRadius, mDefaultPaint);
mDefaultPaint.setXfermode(mXfermode);//取下层绘制非交集部分(大小正方形中套圆)
// draw bg circle 2
int innerCircleRadius = (int) (mCircleRadius * innerCircleScalingFactor);
//mDefaultPaint.setColor(mArrowColor);
canvas.drawCircle(mCircleRectF.centerX(), mCircleRectF.centerY(), innerCircleRadius, mDefaultPaint);
mDefaultPaint.setXfermode(null);
canvas.restoreToCount(layoutCont);
// draw arrow
updateArrowPath(mInitArrowRectWidth,
mInitArrowRectHeight, mInitArrowTriWidth, mInitArrowTriHeight);
// translate the canvas causes the center point of the arrow to coincide with the center point of the circle
canvas.translate(mCircleRectF.centerX() - mArrowRectF.width() / 2 * scalingFactor,
mCircleRectF.centerY() - mArrowRectF.height() / 2 * scalingFactor);
mDefaultPaint.setColor(mArrowColor);
canvas.drawPath(mArrowPath, mDefaultPaint);
canvas.restore();
/*canvas.save(); //保存当前canvas的状态
//canvas.translate(100, 100);
canvas.drawCircle(test+5, test+5, 50, mDefaultPaint);
test = test + 5;
canvas.restore(); //恢复保存的Canvas的状态
//canvas.drawCircle(50, 50, 50, mDefaultPaint);*/
//add by zyl 2017.01.10
/*canvas.save();
Path path = new Path();
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
//canvas.drawCircle(40, 40, 10, paint);
//path.moveTo(50,100);
//path.lineTo(100,100);
//path.cubicTo(-101, 0, -101, 156, 0, 156);
//path.cubicTo(-84, 0, -87, 150, 9.5f, 150);
//path.cubicTo(-70, 1, -74, 145, 19, 145);
//path.cubicTo(-33, 2, -43, 133, 45, 133);
path.cubicTo(0, 4, -14, 122, 70, 122);
path.offset(200,200);
canvas.drawPath(path, paint);
//canvas.drawPoint(-101, 0, mDefaultPaint);
//canvas.drawPoint(-101, 156, mDefaultPaint);
//canvas.drawPoint(0, 156, mDefaultPaint);
canvas.drawCircle(0, 100, 5, mDefaultPaint);
canvas.drawCircle(101, 0, 5, mDefaultPaint);
canvas.drawCircle(101, 156, 5, mDefaultPaint);
canvas.drawCircle(50, 50, 5, mDefaultPaint);
canvas.restore();*/
//end
}
private void drawArrowTrans(Canvas canvas, int offsetX, int offsetY, float rotateAngle) {
if (mArrowPath == null) {
mArrowPath = new Path();
updateArrowPath(mInitArrowRectWidth, mInitArrowRectHeight,
mInitArrowTriWidth, mLastArrowTriWidth);
}
if (mArrowRectF == null) {
mArrowRectF = new RectF();
mArrowPath.computeBounds(mArrowRectF, true);
}
if (mArrowRotateMatrix == null) {
mArrowRotateMatrix = new Matrix();
} else {
mArrowRotateMatrix.reset();
}
mDefaultPaint.setColor(mArrowColor);
canvas.save();
canvas.translate(offsetX, offsetY);
// arrow shake
if (rotateAngle != 0) {
mArrowRotateMatrix.postRotate(rotateAngle,
mArrowRectF.centerX(), mArrowRectF.bottom);
canvas.concat(mArrowRotateMatrix);
}
canvas.drawPath(mArrowPath, mDefaultPaint);
canvas.restore();
}
private void drawArrowTrans(Canvas canvas, int offsetX, int offsetY, float[] paramters) {
updateArrowPath((int) (mCircleRadius * paramters[0]),
(int) (mCircleRadius * paramters[1]),
(int) (mCircleRadius * paramters[2]),
(int) (mCircleRadius * paramters[3]));
drawArrowTrans(canvas, offsetX, offsetY, paramters[4]);
}
/**
* @param oscillFactor line oscillation factor
* @param baseLineX The the original X coordinate of starting point of the base straight line
* @param baseLineY The the original Y coordinate of starting point of the base straight line
* @param highestPointHeight The height highest point
* @param highestPointPaddingLeft Indicates the distance from the top of the rope to the left of the rope
*/
private void updateLineOscillationPath(
float oscillFactor, int baselineLen,
int baseLineX, int baseLineY, int highestPointHeight, int highestPointPaddingLeft) {
if (mOscillationLinePath == null) {
mOscillationLinePath = new Path();
} else {
mOscillationLinePath.reset();
}
// calculate the y of the control point
highestPointHeight *= oscillFactor;
int controlPointY = (int) (highestPointHeight * baselineLen
* baselineLen / (2f * highestPointPaddingLeft * (baselineLen - highestPointPaddingLeft)));
// move to start point
mOscillationLinePath.moveTo(baseLineX, baseLineY);
mOscillationLinePath.quadTo(baseLineX + highestPointPaddingLeft,
baseLineY - controlPointY,
baseLineX + baselineLen, baseLineY);
}
private void drawProgressLinePath(//02.16
Canvas canvas, float normalizeProgress, int baselineLen,
int baseLineX, int baseLineY, int highestPointHeight, int leftLineColor) {
int halfLen = baselineLen / 2;
int middlePointX = (int) (baseLineX + baselineLen * normalizeProgress);
int middlePointY;
float k = (float) highestPointHeight / halfLen;
if (normalizeProgress < HALF_NORMALIZED_PROGRESS) {
middlePointY = (int) (halfLen * k
* normalizeProgress / HALF_NORMALIZED_PROGRESS) + baseLineY;
} else {
middlePointY = (int) (halfLen * k
* (1 - normalizeProgress) / HALF_NORMALIZED_PROGRESS) + baseLineY;
}
// draw right part first
mBaseLinePaint.setColor(mLoadingLineColor);
canvas.drawLine(middlePointX, middlePointY, baseLineX + baselineLen,
baseLineY, mBaseLinePaint);
// draw left part
mBaseLinePaint.setColor(leftLineColor);
canvas.drawLine(baseLineX, baseLineY, middlePointX, middlePointY, mBaseLinePaint);
if (mProgressLinePathRectF == null) {
mProgressLinePathRectF = new RectF();
}
mProgressLinePathRectF.set(baseLineX, baseLineY, baseLineX + baselineLen, middlePointY);
}
public void performAnimation() {
mIsFailed = false;
mLastProgress = ZERO_PROGRESS;
mNextProgress = ZERO_PROGRESS;
releaseAnimation();
// circle and arrow scale
mBefProgCircleScaleAnimator = ValueAnimator.ofFloat(1f, 0.91f, 1f);//外圆
mBefProgCircleScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBefProgCircleScalingFactor = (float) animation.getAnimatedValue();
Log.i("zyl log 1222","performAnimation 1-->mBefProgCircleScalingFactor ="+mBefProgCircleScalingFactor);
invalidate();
}
});
mBefProgCircleScaleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {//当动画开始的时候,设置一个状态值(1)
super.onAnimationStart(animation);
Log.i("zyl log 1222","11111111111111111111111111111111111");
mCurrentState = STATE_BEFORE_PROGRESS_CIRCLE_SCALE;
}
});
mBefProgCircleScaleAnimator.setDuration(BEFORE_PROGRESS_CIRCLE_SCALE_DURATION);//450
mAnimatorList.add(mBefProgCircleScaleAnimator);//添加到动画数组
// inner circle arrow scale
mBefProgInnerCircleScaleAnimator = ValueAnimator.ofFloat(0, 0.9f);//内圆
mBefProgInnerCircleScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i("zyl log 1222","performAnimation 2-->mBefProgInnerCircleScaleAnimator ="+mBefProgInnerCircleScalingFactor);
mBefProgInnerCircleScalingFactor = (float) animation.getAnimatedValue();
}
});
mBefProgInnerCircleScaleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_BEFORE_PROGRESS_INNER_CIRCLE_SCALE;
}
});
mBefProgInnerCircleScaleAnimator.setDuration(BEFORE_PROGRESS_INNER_CIRCLE_SCALE_DURATION);//225
//当每一个动画跑起来的时候,都会设置一个标致位,去绘制画布;此处内圆的Delay时间为外圆动画时间的一半,所以当内圆动画开始执行的时候,虽然外圆的动画还在运行,但是动画执行的绘制效果已经不存在了
mBefProgInnerCircleScaleAnimator.setStartDelay(BEFORE_PROGRESS_INNER_CIRCLE_SCALE_DELAY);//225
mAnimatorList.add(mBefProgInnerCircleScaleAnimator);
// circle to line animation
mBefProgCircleToLineAnimator = ValueAnimator.ofFloat(0, 1f);
mBefProgCircleToLineAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBefProgCircleToLineNormalizedTime = (float) animation.getAnimatedValue();
invalidate();
}
});
mBefProgCircleToLineAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_BEFORE_PROGRESS_CIRCLE_TO_LINE;
}
});
mBefProgCircleToLineAnimator.setDuration(5000);//BEFORE_PROGRESS_CIRCLE_TO_LINE_DURATION);
mAnimatorList.add(mBefProgCircleToLineAnimator);
// arrow move animation
mBefProgArrowMoveAnimator = ValueAnimator.ofFloat(0, 1f);
mBefProgArrowMoveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBefProgArrowMoveNormalizedTime = (float) animation.getAnimatedValue();
invalidate();
}
});
mBefProgArrowMoveAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_BEFORE_PROGRESS_ARROW_MOVE_LINE_OSCILL;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
performProgressAnimation();
}
});
mBefProgArrowMoveAnimator.setDuration(BEFORE_PROGRESS_ARROW_MOVE_AND_LINE_OSCILL);
mAnimatorList.add(mBefProgArrowMoveAnimator);
// line oscill animation
mBefProgLineOscillAnimator = ValueAnimator.ofFloat(0, 1, -0.5f, 0.25f, -0.125f, 0);
mBefProgLineOscillAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBefProgLineOscillationFactor = (float) animation.getAnimatedValue();
}
});
mBefProgLineOscillAnimator.setDuration(BEFORE_PROGRESS_ARROW_MOVE_AND_LINE_OSCILL);
AnimatorSet arrowMoveAndLineOscillSet = new AnimatorSet();
arrowMoveAndLineOscillSet.playTogether(mBefProgArrowMoveAnimator, mBefProgLineOscillAnimator);
mAnimatorList.add(arrowMoveAndLineOscillSet);
mBefProgressAnimatorSet = new AnimatorSet();
mBefProgressAnimatorSet.playSequentially(mBefProgCircleScaleAnimator, mBefProgCircleToLineAnimator,
arrowMoveAndLineOscillSet);//循环动画中又嵌套了一个循环动画
mBefProgressAnimatorSet.playTogether(mBefProgInnerCircleScaleAnimator);
mBefProgressAnimatorSet.setInterpolator(new LinearInterpolator());
mAnimatorList.add(mBefProgressAnimatorSet);
mBefProgressAnimatorSet.start();
}
private void performProgressAnimation() {
int startProgress = mLastProgress;
int endProgress = mNextProgress;
mLastProgress = mNextProgress;
if (mIsFailed) {
performFailedAnimation();
return;
}
mProgressAnimator = ValueAnimator.ofFloat(startProgress, endProgress);
mProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mProgressValue = (float) animation.getAnimatedValue();
invalidate();
}
});
mProgressAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_IN_PROGRESS;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mIsFailed) {
performFailedAnimation();
} else if (mLastProgress != FULL_PROGRESS) {
performProgressAnimation();
} else {
performDoneAnimation();
}
}
});
mProgressAnimator.setInterpolator(new LinearInterpolator());
mProgressAnimator.setDuration(Math.max(
FULL_PROGRESS_ANIMATION_DURATION
* (endProgress - startProgress) / FULL_PROGRESS,
MIN_PROGRESS_ANIMATION_DURATION));
mAnimatorList.add(mProgressAnimator);
mProgressAnimator.start();
}
private void performDoneAnimation() {
// done rotate animation
mDoneRotateAnimator = ValueAnimator.ofFloat(0, 1);
mDoneRotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDoneRotateNormalizedTime = (float) animation.getAnimatedValue();
invalidate();
}
});
mDoneRotateAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_DONE_ROTATE;
}
});
mDoneRotateAnimator.setDuration(DONE_ANIMATION_DURATION);
mAnimatorList.add(mDoneRotateAnimator);
// line pack up animation
mDoneLinePackUpAnimator = ValueAnimator.ofFloat(0, 1);
mDoneLinePackUpAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
mDoneLinePackUpNormalizedTime = value;
invalidate();
}
});
mDoneLinePackUpAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_DONE_LINE_PACK_UP;
}
});
mDoneLinePackUpAnimator.setDuration(DONE_LINE_PACK_UP_ANIMATION_DURATION);
mAnimatorList.add(mDoneLinePackUpAnimator);
// arrow shake animator and line change to a point
mDoneArrowShakeAnimator = ValueAnimator.ofFloat(0, -20, 20, -10, 10);
mDoneArrowShakeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDoneArrowShakeAngle = (float) animation.getAnimatedValue();
invalidate();
}
});
mDoneArrowShakeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_DONE_ARROW_SHAKE;
}
});
mDoneArrowShakeAnimator.setDuration(DONE_LINE_PACK_UP_ARROW_SHAKE_ANIMATION_DURATION);
mAnimatorList.add(mDoneArrowShakeAnimator);
// circle scale animation
mDoneRestToCircleAnimator = ValueAnimator.ofFloat(0f, 1.1f, .9f, 1f);
mDoneRestToCircleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDoneRestToCircleScalingFactor = (float) animation.getAnimatedValue();
invalidate();
}
});
mDoneRestToCircleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_DONE_REST_TO_CIRCLE;
}
});
mDoneRestToCircleAnimator.setDuration(DONE_REST_TO_CIRCLE_DOWN_ANIMATION_DURATION);
mAnimatorList.add(mDoneRestToCircleAnimator);
// dialog change to arrow
mDoneDialogToArrowAnimator = ValueAnimator.ofFloat(0, 1f);
mDoneDialogToArrowAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDoneDialogToArrowNormalizedTime = (float) animation.getAnimatedValue();
}
});
mDoneDialogToArrowAnimator.setDuration(DONE_DIALOG_TO_ARROW_DURATION);
mAnimatorList.add(mDoneDialogToArrowAnimator);
// arrow up and down animation
mDoneDialogToArrowUpDownAnimator = ValueAnimator.ofFloat(0f, -0.2f, 0.1f, -0.05f, 0f);
mDoneDialogToArrowUpDownAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDoneDialogToArrowUpDownFactor = (float) animation.getAnimatedValue();
}
});
mDoneDialogToArrowUpDownAnimator.setDuration(DONE_DIALOG_UP_DOWN_DURATION);
mAnimatorList.add(mDoneDialogToArrowUpDownAnimator);
AnimatorSet dialogToArrowAnimatorSet = new AnimatorSet();
dialogToArrowAnimatorSet.playSequentially(mDoneDialogToArrowAnimator, mDoneDialogToArrowUpDownAnimator);
mAnimatorList.add(dialogToArrowAnimatorSet);
AnimatorSet restToCircleAnimatorSet = new AnimatorSet();
restToCircleAnimatorSet.playTogether(mDoneRestToCircleAnimator, dialogToArrowAnimatorSet);
mAnimatorList.add(restToCircleAnimatorSet);
// done animator set
mDoneAnimatorSet = new AnimatorSet();
mDoneAnimatorSet.setInterpolator(new LinearInterpolator());
mDoneAnimatorSet.playSequentially(mDoneRotateAnimator,
mDoneLinePackUpAnimator, mDoneArrowShakeAnimator, restToCircleAnimatorSet);
mAnimatorList.add(mDoneAnimatorSet);
mDoneAnimatorSet.start();
}
private void performFailedAnimation() {
// failed arrow shake animation
mFailedArrowShakeAnimator = ValueAnimator.ofFloat(0, 0.05f, -0.05f, 0.1f, -0.15f, 0.25f, -1f);
mFailedArrowShakeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFailedArrowUpAndDownFactor = (float) animation.getAnimatedValue();
invalidate();
}
});
mFailedArrowShakeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_FAILED_ARROW_SHAKE;
}
});
mFailedArrowShakeAnimator.setDuration(FAILED_ANIMATION_DURATION);
mAnimatorList.add(mFailedArrowShakeAnimator);
// failed arrow rotate animation
mFailedArrowRotateAnimator = ValueAnimator.ofFloat(0, 0.5f, -0.5f, 0.25f, 0.25f, 0f);
mFailedArrowRotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFailedArrowRotateAngle = (float) animation.getAnimatedValue();
}
});
mFailedArrowRotateAnimator.setDuration(FAILED_ANIMATION_DURATION);
mAnimatorList.add(mFailedArrowRotateAnimator);
// failed bomb animator
mFailedBombAnimator = ValueAnimator.ofFloat(0, 1f);
mFailedBombAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFailedBombAnimatorPer = (float) animation.getAnimatedValue();
}
});
mFailedBombAnimator.setDuration(FAILED_BOMB_ANIMATION_DURATION);
mAnimatorList.add(mFailedBombAnimator);
// arrow shake rotate and line bomb animatorSet
AnimatorSet arrowShakeRotateAndLineBomb = new AnimatorSet();
arrowShakeRotateAndLineBomb.playTogether(
mFailedArrowShakeAnimator, mFailedArrowRotateAnimator, mFailedBombAnimator);
mAnimatorList.add(arrowShakeRotateAndLineBomb);
// failed line oscillation animation
mFailedLineOscillationAnimator = ValueAnimator.ofFloat(0, 0.2F, -0.2F, 0.1F, -0.1F, 0F);
mFailedLineOscillationAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFailedLineOscillationFactor = (float) animation.getAnimatedValue();
}
});
mFailedLineOscillationAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_FAILED_LINE_OSCILL;
}
});
mFailedLineOscillationAnimator.setDuration(FAILED_ROPE_OSCILLATION_ANIMATION_DURATION);
mAnimatorList.add(mFailedLineOscillationAnimator);
// failed line pack up Animator
mFailedLinePackUpAnimator = ValueAnimator.ofFloat(1, 0f);
mFailedLinePackUpAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFailedLinePackUpFactor = (float) animation.getAnimatedValue();
}
});
mFailedLinePackUpAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_FAILED_LINE_PACK_UP;
}
});
mFailedLinePackUpAnimator.setDuration(FAILED_ROPE_PACK_UP_ANIMATION_DURATION);
mAnimatorList.add(mFailedLinePackUpAnimator);
// failed circle scale animation
mFailedCircleScaleAnimator = ValueAnimator.ofFloat(0, 1.1f, 0.9f, 1.0f);
mFailedCircleScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFailedCircleScaleFactor = (float) animation.getAnimatedValue();
}
});
mFailedCircleScaleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mCurrentState = STATE_FAILED_CIRCLE_SCALE;
}
});
mFailedCircleScaleAnimator.setInterpolator(new LinearInterpolator());
mFailedCircleScaleAnimator.setDuration(FAILED_CIRCLE_SCALE_ANIMATION_DURATION);
mAnimatorList.add(mFailedCircleScaleAnimator);
AnimatorSet lineOscillPackAndCircleScaleSet = new AnimatorSet();
lineOscillPackAndCircleScaleSet.playSequentially(
mFailedLineOscillationAnimator, mFailedLinePackUpAnimator, mFailedCircleScaleAnimator);
mAnimatorList.add(lineOscillPackAndCircleScaleSet);
// failed arrow move animation
mFailedArrowMoveAnimator = ValueAnimator.ofFloat(0, .2f, .4f, .6f, .8f, 1f, .95f, 1f);
mFailedArrowMoveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mFailedArrowMoveNormalizeTiem = (float) animation.getAnimatedValue();
invalidate();
}
});
mFailedArrowMoveAnimator.setDuration(FAILED_ARROW_MOVE_ANIMATION_DURATION);
mAnimatorList.add(mFailedArrowMoveAnimator);
AnimatorSet lineOscillAndArrowMoveSet = new AnimatorSet();
lineOscillAndArrowMoveSet.playTogether(lineOscillPackAndCircleScaleSet, mFailedArrowMoveAnimator);
mAnimatorList.add(lineOscillAndArrowMoveSet);
mFailedAnimatorSet = new AnimatorSet();
mFailedAnimatorSet.setInterpolator(new LinearInterpolator());
mFailedAnimatorSet.playSequentially(arrowShakeRotateAndLineBomb, lineOscillAndArrowMoveSet);
mAnimatorList.add(mFailedAnimatorSet);
mFailedAnimatorSet.start();
}
public void releaseAnimation() {
if (mAnimatorList == null || mAnimatorList.size() == 0) {
return;
}
for (Animator animator : mAnimatorList) {
if (animator == null) {
continue;
}
if (animator instanceof ValueAnimator) {
((ValueAnimator) animator).removeAllUpdateListeners();
}
animator.removeAllListeners();
animator.cancel();
animator = null;
}
mAnimatorList.clear();
}
public void updateProgress(int progress) {
// adjust progress in area 0 to 100
if (progress < ZERO_PROGRESS) {
progress = ZERO_PROGRESS;
} else if (progress > FULL_PROGRESS) {
progress = FULL_PROGRESS;
}
mNextProgress = Math.max(progress, mNextProgress);
}
public void onFail() {
mIsFailed = true;
}
private int dipToPx(Context context, int dip) {
return (int) (dip * getScreenDensity(context) + 0.5f);
}
private float getScreenDensity(Context context) {
try {
DisplayMetrics dm = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
.getMetrics(dm);
return dm.density;
} catch (Exception e) {
return DisplayMetrics.DENSITY_DEFAULT;
}
}
}