Java tutorial
/** * Copyright 2016 JustWayward Team * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.waitou.rxjava; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.os.Parcel; import android.os.Parcelable; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; public class LoadingView extends View { //the size in wrap_content model private static final int CIRCLE_DIAMETER = 56; private static final float CENTER_RADIUS = 15f; private static final float STROKE_WIDTH = 3.5f; private static final float MAX_PROGRESS_ARC = 300f; private static final float MIN_PROGRESS_ARC = 20f; private static final long ANIMATOR_DURATION = 1332; private Rect bounds; private Ring mRing; private Animator animator = null; private AnimatorSet animatorSet = null; private boolean mIsAnimatorCancel = false; private Interpolator interpolator = null; //the ring's RectF private final RectF mTempBounds = new RectF(); //?paint private Paint mPaint; private final int DEFAULT_COLOR = 0xFF3B99DF; private boolean mAnimationStarted = false; //the ring style static final int RING_STYLE_SQUARE = 0; static final int RING_STYLE_ROUND = 1; //the animator style static final int PROGRESS_STYLE_MATERIAL = 0; static final int PROGRESS_STYLE_LINEAR = 1; private float mRotation = 0f; public LoadingView(Context context) { this(context, null); } public LoadingView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mRing = new Ring(); bounds = new Rect(); mPaint = new Paint(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mRing.strokeWidth); if (attrs != null) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LoadingView, 0, 0); setColor(a.getInt(R.styleable.LoadingView_loadding_color, DEFAULT_COLOR)); setRingStyle(a.getInt(R.styleable.LoadingView_ring_style, RING_STYLE_SQUARE)); setProgressStyle(a.getInt(R.styleable.LoadingView_progress_style, PROGRESS_STYLE_MATERIAL)); setStrokeWidth(a.getDimension(R.styleable.LoadingView_ring_width, dp2px(STROKE_WIDTH))); setCenterRadius(a.getDimension(R.styleable.LoadingView_ring_radius, dp2px(CENTER_RADIUS))); a.recycle(); } } /** * set the ring strokeWidth * * @param stroke */ public void setStrokeWidth(float stroke) { mRing.strokeWidth = stroke; mPaint.setStrokeWidth(stroke); } public void setCenterRadius(float radius) { mRing.ringCenterRadius = radius; } public void setRingStyle(int style) { switch (style) { case RING_STYLE_SQUARE: mPaint.setStrokeCap(Paint.Cap.SQUARE); break; case RING_STYLE_ROUND: mPaint.setStrokeCap(Paint.Cap.ROUND); break; } } /** * set the animator's interpolator * * @param style */ public void setProgressStyle(int style) { switch (style) { case PROGRESS_STYLE_MATERIAL: interpolator = new FastOutSlowInInterpolator(); break; case PROGRESS_STYLE_LINEAR: interpolator = new LinearInterpolator(); break; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); int width = (int) dp2px(CIRCLE_DIAMETER); int height = (int) dp2px(CIRCLE_DIAMETER); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(width, height); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(width, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, height); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); final Ring ring = mRing; ring.setInsets(w, h); bounds.set(0, 0, w, h); } private void buildAnimator() { final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATOR_DURATION); valueAnimator.setRepeatCount(-1); valueAnimator.setInterpolator(new LinearInterpolator()); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mRotation = (float) valueAnimator.getAnimatedValue(); invalidate(); } }); animator = valueAnimator; animatorSet = buildFlexibleAnimation(); animatorSet.addListener(animatorListener); } @Override protected void onDraw(Canvas canvas) { if (!mIsAnimatorCancel) { final Rect bounds = getBounds(); final int saveCount = canvas.save(); canvas.rotate(mRotation * 360, bounds.exactCenterX(), bounds.exactCenterY()); drawRing(canvas, bounds); canvas.restoreToCount(saveCount); } else { canvas.restore(); } } /** * draw the ring * * @param canvas to draw the Ring * @param bounds the ring's rect */ private void drawRing(Canvas canvas, Rect bounds) { final RectF arcBounds = mTempBounds; final Ring ring = mRing; arcBounds.set(bounds); arcBounds.inset(ring.strokeInset, ring.strokeInset); canvas.drawArc(arcBounds, ring.start, ring.sweep, false, mPaint); } public void start() { if (mAnimationStarted) { return; } if (animator == null || animatorSet == null) { mRing.reset(); buildAnimator(); } animator.start(); animatorSet.start(); mAnimationStarted = true; mIsAnimatorCancel = false; } public void stop() { mIsAnimatorCancel = true; if (animator != null) { animator.end(); animator.cancel(); } if (animatorSet != null) { animatorSet.end(); animatorSet.cancel(); } animator = null; animatorSet = null; mAnimationStarted = false; mRing.reset(); mRotation = 0; invalidate(); } public Rect getBounds() { return bounds; } public void setBounds(Rect bounds) { this.bounds = bounds; } /** * build FlexibleAnimation to control the progress * * @return Animatorset for control the progress */ private AnimatorSet buildFlexibleAnimation() { final Ring ring = mRing; AnimatorSet set = new AnimatorSet(); ValueAnimator increment = ValueAnimator.ofFloat(0, MAX_PROGRESS_ARC - MIN_PROGRESS_ARC) .setDuration(ANIMATOR_DURATION / 2); increment.setInterpolator(new LinearInterpolator()); increment.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float sweeping = ring.sweeping; final float value = (float) animation.getAnimatedValue(); ring.sweep = sweeping + value; invalidate(); } }); increment.addListener(animatorListener); ValueAnimator reduce = ValueAnimator.ofFloat(0, MAX_PROGRESS_ARC - MIN_PROGRESS_ARC) .setDuration(ANIMATOR_DURATION / 2); reduce.setInterpolator(interpolator); reduce.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float sweeping = ring.sweeping; float starting = ring.starting; float value = (float) animation.getAnimatedValue(); ring.sweep = sweeping - value; ring.start = starting + value; } }); set.play(reduce).after(increment); return set; } public void setColor(int color) { mRing.color = color; mPaint.setColor(color); } public int getColor() { return mRing.color; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); start(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); stop(); } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (visibility == VISIBLE) { start(); } else { stop(); } } /** * turn dp to px * * @param dp value * @return result px value */ private float dp2px(float dp) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); } @Override protected Parcelable onSaveInstanceState() { Parcelable parcelable = super.onSaveInstanceState(); SavedState state = new SavedState(parcelable); state.ring = mRing; return state; } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState savedState = (SavedState) state; super.onRestoreInstanceState(state); mRing = savedState.ring; } static class Ring implements Parcelable { public float strokeInset = 0f; public float strokeWidth = 0f; public float ringCenterRadius = 0f; public float start = 0f; public float end = 0f; public float sweep = 0f; public float sweeping = MIN_PROGRESS_ARC; public float starting = 0f; public float ending = 0f; public int color; public void restore() { starting = start; sweeping = sweep; ending = end; } public void reset() { end = 0f; start = 0f; sweeping = MIN_PROGRESS_ARC; sweep = 0f; starting = 0f; } public void setInsets(int width, int height) { final float minEdge = (float) Math.min(width, height); float insets; if (ringCenterRadius <= 0 || minEdge < 0) { insets = (float) Math.ceil(strokeWidth / 2.0f); } else { insets = (minEdge / 2.0f - ringCenterRadius); } strokeInset = insets; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeFloat(this.strokeInset); dest.writeFloat(this.strokeWidth); dest.writeFloat(this.ringCenterRadius); dest.writeFloat(this.start); dest.writeFloat(this.end); dest.writeFloat(this.sweep); dest.writeFloat(this.sweeping); dest.writeFloat(this.starting); dest.writeFloat(this.ending); dest.writeInt(this.color); } public Ring() { } protected Ring(Parcel in) { this.strokeInset = in.readFloat(); this.strokeWidth = in.readFloat(); this.ringCenterRadius = in.readFloat(); this.start = in.readFloat(); this.end = in.readFloat(); this.sweep = in.readFloat(); this.sweeping = in.readFloat(); this.starting = in.readFloat(); this.ending = in.readFloat(); this.color = in.readInt(); } public static final Creator<Ring> CREATOR = new Creator<Ring>() { @Override public Ring createFromParcel(Parcel source) { return new Ring(source); } @Override public Ring[] newArray(int size) { return new Ring[size]; } }; } /** * */ static class SavedState extends BaseSavedState { public Ring ring; public SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); ring = in.readParcelable(Ring.class.getClassLoader()); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(this.ring, flags); } public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { @Override public SavedState createFromParcel(Parcel source) { return new SavedState(source); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; } /** * Listen the animatorSet and the IncrementAnimator; */ Animator.AnimatorListener animatorListener = new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { if (mIsAnimatorCancel) return; if (animation instanceof ValueAnimator) { mRing.sweeping = mRing.sweep; } else if (animation instanceof AnimatorSet) { mRing.restore(); animatorSet.start(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }; }