package com.lcodecore.tkrefreshlayout.header.progresslayout; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Resources; import android.support.annotation.ColorInt; import android.support.annotation.ColorRes; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.widget.FrameLayout; import com.lcodecore.tkrefreshlayout.IHeaderView; import com.lcodecore.tkrefreshlayout.OnAnimEndListener; /** * Created by lcodecore on 2016/11/27. */ public class ProgressLayout extends FrameLayout implements IHeaderView { private int mCircleWidth; private int mCircleHeight; private static final int CIRCLE_DIAMETER = 40; private static final int CIRCLE_DIAMETER_LARGE = 56; // Maps to ProgressBar.Large style public static final int LARGE = MaterialProgressDrawable.LARGE; // Maps to ProgressBar default style public static final int DEFAULT = MaterialProgressDrawable.DEFAULT; // Default background for the progress spinner private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; // Default offset in dips from the top of the view to where the progress spinner should stop private static final int DEFAULT_CIRCLE_TARGET = 64; private static final float MAX_PROGRESS_ANGLE = .8f; private static final int MAX_ALPHA = 255; private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA); private CircleImageView mCircleView; private MaterialProgressDrawable mProgress; public ProgressLayout(Context context) { this(context, null); } public ProgressLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ProgressLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); final DisplayMetrics metrics = getResources().getDisplayMetrics(); mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density); createProgressView(); ViewCompat.setChildrenDrawingOrderEnabled(this, true); // the absolute offset has to take into account that the circle starts at an offset } private void createProgressView() { mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER / 2); mProgress = new MaterialProgressDrawable(getContext(), this); mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); mCircleView.setImageDrawable(mProgress); mCircleView.setVisibility(View.GONE); LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER); mCircleView.setLayoutParams(params); addView(mCircleView); } /** * Set the background color of the progress spinner disc. * * @param colorRes Resource id of the color. */ public void setProgressBackgroundColorSchemeResource(@ColorRes int colorRes) { setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes)); } /** * Set the background color of the progress spinner disc. * * @param color */ public void setProgressBackgroundColorSchemeColor(@ColorInt int color) { mCircleView.setBackgroundColor(color); mProgress.setBackgroundColor(color); } /** * Set the color resources used in the progress animation from color resources. * The first color will also be the color of the bar that grows in response * to a user swipe gesture. * * @param colorResIds */ public void setColorSchemeResources(@ColorRes int... colorResIds) { final Resources res = getResources(); int[] colorRes = new int[colorResIds.length]; for (int i = 0; i < colorResIds.length; i++) { colorRes[i] = res.getColor(colorResIds[i]); } setColorSchemeColors(colorRes); } /** * Set the colors used in the progress animation. The first * color will also be the color of the bar that grows in response to a user * swipe gesture. * * @param colors */ public void setColorSchemeColors(int... colors) { mProgress.setColorSchemeColors(colors); } /** * One of DEFAULT, or LARGE. */ public void setSize(int size) { if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) { return; } final DisplayMetrics metrics = getResources().getDisplayMetrics(); if (size == MaterialProgressDrawable.LARGE) { mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); } else { mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density); } // force the bounds of the progress circle inside the circle view to // update by setting it to null before updating its size and then // re-setting it mCircleView.setImageDrawable(null); mProgress.updateSizes(size); mCircleView.setImageDrawable(mProgress); } @Override public void reset() { mCircleView.clearAnimation(); mProgress.stop(); mCircleView.setVisibility(View.GONE); mCircleView.getBackground().setAlpha(MAX_ALPHA); mProgress.setAlpha(MAX_ALPHA); ViewCompat.setScaleX(mCircleView, 0); ViewCompat.setScaleY(mCircleView, 0); ViewCompat.setAlpha(mCircleView, 1); } @Override public View getView() { return this; } private boolean mIsBeingDragged = false; @Override public void onPullingDown(float fraction, float maxHeadHeight, float headHeight) { if (!mIsBeingDragged) { mIsBeingDragged = true; mProgress.setAlpha(STARTING_PROGRESS_ALPHA); } if (mCircleView.getVisibility() != View.VISIBLE) { mCircleView.setVisibility(View.VISIBLE); } if (fraction >= 1f) { ViewCompat.setScaleX(mCircleView, 1f); ViewCompat.setScaleY(mCircleView, 1f); } else { ViewCompat.setScaleX(mCircleView, fraction); ViewCompat.setScaleY(mCircleView, fraction); } if (fraction <= 1f) { mProgress.setAlpha((int) (STARTING_PROGRESS_ALPHA + (MAX_ALPHA - STARTING_PROGRESS_ALPHA) * fraction)); } float adjustedPercent = (float) Math.max(fraction - .4, 0) * 5 / 3; float strokeStart = adjustedPercent * .8f; mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); mProgress.setArrowScale(Math.min(1f, adjustedPercent)); float rotation = (-0.25f + .4f * adjustedPercent) * .5f; mProgress.setProgressRotation(rotation); } @Override public void onPullReleasing(float fraction, float maxHeadHeight, float headHeight) { mIsBeingDragged = false; if (fraction >= 1f) { ViewCompat.setScaleX(mCircleView, 1f); ViewCompat.setScaleY(mCircleView, 1f); } else { ViewCompat.setScaleX(mCircleView, fraction); ViewCompat.setScaleY(mCircleView, fraction); } } @Override public void startAnim(float maxHeadHeight, float headHeight) { mCircleView.setVisibility(View.VISIBLE); mCircleView.getBackground().setAlpha(MAX_ALPHA); mProgress.setAlpha(MAX_ALPHA); ViewCompat.setScaleX(mCircleView, 1f); ViewCompat.setScaleY(mCircleView, 1f); mProgress.setArrowScale(1f); mProgress.start(); } @Override public void onFinish(final OnAnimEndListener animEndListener) { mCircleView.animate().scaleX(0).scaleY(0).alpha(0).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { reset(); animEndListener.onAnimEnd(); } }).start(); } }