package io.virtualapp.widgets; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.support.v7.widget.AppCompatImageView; import android.util.AttributeSet; import android.util.Log; import android.view.animation.DecelerateInterpolator; import io.virtualapp.R; import static android.graphics.Canvas.ALL_SAVE_FLAG; public class LauncherIconView extends AppCompatImageView implements ShimmerViewBase { private static final int SMOOTH_ANIM_THRESHOLD = 5; private static final String TAG = "LauncherIconView"; private ShimmerViewHelper mShimmerViewHelper; private Shimmer mShimmer; private float mProgress; private int mHeight; private int mWidth; private int mStrokeWidth; private float mRadius; private float mInterDelta; private int mMaskColor; private float mMaxMaskRadius; private float mMaskAnimDelta; private boolean mIsSquare; private boolean mMaskAnimRunning; private long mMediumAnimTime; private Paint mShimmerPaint; private Paint mPaint; private RectF mProgressOval; private ValueAnimator mInterAnim; private ValueAnimator mProgressAnimator; public LauncherIconView(Context context) { super(context); init(context, null); } public LauncherIconView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public LauncherIconView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { mMediumAnimTime = getContext().getResources().getInteger(android.R.integer.config_mediumAnimTime); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressImageView); try { this.mProgress = a.getInteger(R.styleable.ProgressImageView_pi_progress, 0); this.mStrokeWidth = a.getDimensionPixelOffset(R.styleable.ProgressImageView_pi_stroke, 8); this.mRadius = a.getDimensionPixelOffset(R.styleable.ProgressImageView_pi_radius, 0); this.mIsSquare = a.getBoolean(R.styleable.ProgressImageView_pi_force_square, false); this.mMaskColor = a.getColor(R.styleable.ProgressImageView_pi_mask_color, Color.argb(180, 0, 0, 0)); this.mPaint = new Paint(); mPaint.setColor(mMaskColor); mPaint.setAntiAlias(true); this.mShimmerPaint = new Paint(); mShimmerPaint.setColor(Color.WHITE); } finally { a.recycle(); } mShimmerViewHelper = new ShimmerViewHelper(this, mShimmerPaint, attrs); } private void initParams() { if (mWidth == 0) mWidth = getWidth(); if (mHeight == 0) mHeight = getHeight(); if (mWidth != 0 && mHeight != 0) { if (mRadius == 0) mRadius = Math.min(mWidth, mHeight) / 4f; if (mMaxMaskRadius == 0) mMaxMaskRadius = (float) (0.5f * Math.sqrt(mWidth * mWidth + mHeight * mHeight)); if (mProgressOval == null) mProgressOval = new RectF( mWidth / 2f - mRadius + mStrokeWidth, mHeight / 2f - mRadius + mStrokeWidth, mWidth / 2f + mRadius - mStrokeWidth, mHeight / 2f + mRadius - mStrokeWidth); } } @Override protected void onDraw(Canvas canvas) { if (mShimmerViewHelper != null) { mShimmerViewHelper.onDraw(); } super.onDraw(canvas); int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, ALL_SAVE_FLAG); initParams(); if (mProgress < 100) { drawMask(canvas); if (mProgress == 0) updateInterAnim(canvas); else drawProgress(canvas); } if (mMaskAnimRunning) updateMaskAnim(canvas); canvas.restoreToCount(sc); } private void drawMask(Canvas canvas) { canvas.drawRect(0, 0, mWidth, mHeight, mPaint); } private void drawProgress(Canvas canvas) { mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius, mPaint); mPaint.setXfermode(null); //start angle : -90 ~ 270;sweep Angle : 360 ~ 0; canvas.drawArc(mProgressOval, -90 + mProgress * 3.6f, 360 - mProgress * 3.6f, true, mPaint); } private void updateInterAnim(Canvas canvas) { // if (!mInterAnimRunning) mInterDelta = 0.f; //outer circle mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); canvas.drawCircle(mWidth / 2.f, mHeight / 2.f, mRadius, mPaint); mPaint.setXfermode(null); //inner circle canvas.drawCircle(mWidth / 2.f, mHeight / 2.f, mRadius - mInterDelta, mPaint); } private void updateMaskAnim(Canvas canvas) { canvas.drawRect(0, 0, mWidth, mHeight, mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius + mMaskAnimDelta, mPaint);//mRatio : 0 ~ mRatio * 1.5 mPaint.setXfermode(null); } private void startInterAnim(final int progress) { if (mInterAnim != null) mInterAnim.cancel(); mInterAnim = ValueAnimator.ofFloat(0.f, mStrokeWidth); mInterAnim.setInterpolator(new DecelerateInterpolator()); mInterAnim.setDuration(getContext().getResources().getInteger(android.R.integer.config_shortAnimTime)); mInterAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mInterDelta = (float) animation.getAnimatedValue(); invalidate(); } }); mInterAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); // mInterAnimRunning = true; } @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); // mInterAnimRunning = false; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); // mInterAnimRunning = false; if (progress > 0) startProgressAnim(0, progress); } }); mInterAnim.start(); } private void startProgressAnim(float from, float to) { if (mProgressAnimator != null) mProgressAnimator.cancel(); final boolean isReverse = from > to; mProgressAnimator = ValueAnimator.ofFloat(from, to); mProgressAnimator.setInterpolator(new DecelerateInterpolator()); mProgressAnimator.setDuration(mMediumAnimTime); mProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mProgress = (float) animation.getAnimatedValue(); if (0 < mProgress && mProgress < 100) invalidate(); else if (mProgress == 100 && !isReverse) startMaskAnim(); } }); mProgressAnimator.start(); } private void startMaskAnim() { if (mProgressAnimator != null) mProgressAnimator.cancel(); ValueAnimator animator = ValueAnimator.ofFloat(0.f, mMaxMaskRadius); animator.setInterpolator(new DecelerateInterpolator()); animator.setDuration(mMediumAnimTime); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mMaskAnimRunning = true; mMaskAnimDelta = (float) animation.getAnimatedValue(); invalidate(); } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); mMaskAnimRunning = true; } @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); mMaskAnimRunning = false; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mMaskAnimRunning = false; } }); animator.start(); } /** * get the stroke width. * * @return the stroke width in pixel. */ public int getStrokeWidth() { return mStrokeWidth; } /** * set the stroke width.default is 8dp. * * @param strokeWidth stroke width in pixel */ public void setStrokeWidth(int strokeWidth) { this.mStrokeWidth = strokeWidth; this.mProgressOval = null; invalidate(); } /** * get the radius of inner progress circle. * * @return the inner circle radius in pixel. */ public float getRadius() { return mRadius; } /** * set the radius of the inner progress circle. * * @param radius radius in pixel */ public void setRadius(float radius) { this.mRadius = radius; this.mProgressOval = null; invalidate(); } /** * get the color for mask . * * @return the mask color */ public int getMaskColor() { return mMaskColor; } /** * set the color for mask. Argb will looks better. Default is Color.argb(180,0,0,0) * * @param maskColor the color value. */ public void setMaskColor(int maskColor) { mMaskColor = maskColor; mPaint.setColor(mMaskColor); invalidate(); } /** * get current progress. * * @return current progress value. */ public int getProgress() { return (int) mProgress; } /** * @param progress the progress ,range [0,100] */ public void setProgress(int progress) { setProgress(progress, true); } /** * @param progress the progress in [0,100] * @param animate true to enable smooth animation when progress changed more than 5. */ public void setProgress(int progress, boolean animate) { progress = Math.min(Math.max(progress, 0), 100); Log.d(TAG, "setProgress: p:" + progress + ",mp:" + mProgress); if (Math.abs(progress - mProgress) > SMOOTH_ANIM_THRESHOLD && animate) { if (mProgress == 0) { startInterAnim(progress); } else { startProgressAnim(mProgress, progress); } } else if (progress == 100 && animate) { mProgress = 100; startMaskAnim(); } else { mProgress = progress; if (mProgress == 0.f) mInterDelta = 0.f; invalidate(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mIsSquare) { int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); int size = measuredWidth == 0 ? MeasureSpec.getSize(heightMeasureSpec) : measuredWidth; setMeasuredDimension(size, size); } } @Override public float getGradientX() { return mShimmerViewHelper.getGradientX(); } @Override public void setGradientX(float gradientX) { mShimmerViewHelper.setGradientX(gradientX); } @Override public boolean isShimmering() { return mShimmerViewHelper.isShimmering(); } @Override public void setShimmering(boolean isShimmering) { mShimmerViewHelper.setShimmering(isShimmering); } @Override public boolean isSetUp() { return mShimmerViewHelper.isSetUp(); } @Override public void setAnimationSetupCallback(ShimmerViewHelper.AnimationSetupCallback callback) { mShimmerViewHelper.setAnimationSetupCallback(callback); } @Override public int getPrimaryColor() { return mShimmerViewHelper.getPrimaryColor(); } @Override public void setPrimaryColor(int primaryColor) { mShimmerViewHelper.setPrimaryColor(primaryColor); } @Override public int getReflectionColor() { return mShimmerViewHelper.getReflectionColor(); } @Override public void setReflectionColor(int reflectionColor) { mShimmerViewHelper.setReflectionColor(reflectionColor); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mShimmerViewHelper != null) { mShimmerViewHelper.onSizeChanged(); } } public void stopShimmer() { if (mShimmer != null && mShimmer.isAnimating()) { mShimmer.cancel(); mShimmer = null; } } public void startShimmer() { stopShimmer(); mShimmer = new Shimmer(); mShimmer.setRepeatCount(1) .setStartDelay(800L) .setDirection(Shimmer.ANIMATION_DIRECTION_LTR) .start(this); } }