package com.scwang.smart.refresh.header; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.support.annotation.ColorInt; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Transformation; import android.widget.ImageView; import com.scwang.smart.refresh.header.material.MaterialProgressDrawable; import com.scwang.smart.refresh.header.waveswipe.WaveView; import com.scwang.smart.refresh.layout.api.RefreshHeader; import com.scwang.smart.refresh.layout.api.RefreshLayout; import com.scwang.smart.refresh.layout.constant.RefreshState; import com.scwang.smart.refresh.layout.simple.SimpleComponent; import com.scwang.smart.refresh.layout.constant.SpinnerStyle; import com.scwang.smart.refresh.layout.util.SmartUtil; import com.scwang.smartrefresh.header.R; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getSize; import static android.view.View.MeasureSpec.makeMeasureSpec; /** * 全屏水滴下拉头 * Created by scwang on 2017/6/4. * from https://github.com/recruit-lifestyle/WaveSwipeRefreshLayout */ @SuppressWarnings("unused") public class WaveSwipeHeader extends SimpleComponent implements RefreshHeader { /** * 落ちる前の回転の最大のAngle値 */ protected static final float MAX_PROGRESS_ROTATION_RATE = 0.8f; protected enum VERTICAL_DRAG_THRESHOLD { FIRST(0.1f), SECOND(0.16f + FIRST.val), THIRD(0.5f + FIRST.val); // FIRST(0.2f), SECOND(0.26f + FIRST.val), THIRD(0.7f + FIRST.val); final float val; VERTICAL_DRAG_THRESHOLD(float val) { this.val = val; } } // protected WaveView mWaveView; protected RefreshState mState; protected MaterialProgressDrawable mProgress; protected ProgressAnimationImageView mCircleView; protected float mLastFirstBounds; public WaveSwipeHeader(Context context) { this(context, null); } public WaveSwipeHeader(Context context, AttributeSet attrs) { super(context, attrs, 0); mSpinnerStyle = SpinnerStyle.MatchLayout; final ViewGroup thisGroup = this; thisGroup.addView(mWaveView = new WaveView(context)); thisGroup.addView(mCircleView = new ProgressAnimationImageView(context)); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.WaveSwipeHeader); int primaryColor = ta.getColor(R.styleable.WaveSwipeHeader_srlPrimaryColor, 0); int accentColor = ta.getColor(R.styleable.WaveSwipeHeader_srlAccentColor, 0); if (ta.hasValue(R.styleable.WaveSwipeHeader_wshPrimaryColor)) { primaryColor = ta.getColor(R.styleable.WaveSwipeHeader_wshPrimaryColor, 0); } if (ta.hasValue(R.styleable.WaveSwipeHeader_wshAccentColor)) { accentColor = ta.getColor(R.styleable.WaveSwipeHeader_wshAccentColor, 0); } if (primaryColor != 0) { mWaveView.setWaveColor(primaryColor); } if (accentColor != 0) { mProgress.setColorSchemeColors(accentColor); } else { mProgress.setColorSchemeColors(0xffffffff); } if (ta.hasValue(R.styleable.WaveSwipeHeader_srlShadowRadius)) { int radius = ta.getDimensionPixelOffset(R.styleable.WaveSwipeHeader_srlShadowRadius, 0); int color = ta.getColor(R.styleable.WaveSwipeHeader_srlShadowColor, 0xff000000); mWaveView.setShadow(radius, color); } if (ta.hasValue(R.styleable.WaveSwipeHeader_wshShadowRadius)) { int radius = ta.getDimensionPixelOffset(R.styleable.WaveSwipeHeader_wshShadowRadius, 0); int color = ta.getColor(R.styleable.WaveSwipeHeader_wshShadowColor, 0xff000000); mWaveView.setShadow(radius, color); } ta.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec)); final View waveView = mWaveView; final View circleView = mCircleView; final Drawable progress = mProgress; final int circleDiameter = progress.getIntrinsicWidth(); final int spec = MeasureSpec.makeMeasureSpec(circleDiameter, MeasureSpec.EXACTLY); circleView.measure(spec, spec); waveView.measure(makeMeasureSpec(getSize(widthMeasureSpec), EXACTLY),makeMeasureSpec(getSize(heightMeasureSpec), EXACTLY)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final View thisView = this; final View waveView = mWaveView; final View circleView = mCircleView; waveView.layout(0, 0, thisView.getMeasuredWidth(), thisView.getMeasuredHeight()); final int thisWidth = thisView.getMeasuredWidth(); final int circleWidth = circleView.getMeasuredWidth(); final int circleHeight = circleView.getMeasuredHeight(); circleView.layout((thisWidth - circleWidth) / 2, -circleHeight , (thisWidth + circleWidth) / 2, 0); if (thisView.isInEditMode()) { onMoving(true, 0.99f, SmartUtil.dp2px(99), SmartUtil.dp2px(100), SmartUtil.dp2px(100)); // onPulling(0.99f, SmartUtil.dp2px(99), SmartUtil.dp2px(100), SmartUtil.dp2px(100)); } } // // /** * @param colors セットするColor達 */ public void setColorSchemeColors(int... colors) { mProgress.setColorSchemeColors(colors); } public void setColorSchemeColorIds(@IdRes int... resources) { final View thisView = this; final Context context = thisView.getContext(); final int[] colorRes = new int[resources.length]; for (int i = 0; i < resources.length; i++) { colorRes[i] = ContextCompat.getColor(context, resources[1]); } mProgress.setColorSchemeColors(colorRes); } // // @Override public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) { if (isDragging) { if (mState == RefreshState.Refreshing) { return; } final View circleView = mCircleView; float dragPercent = Math.min(1f, percent); float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; // 0f...2f float tensionSlingshotPercent = (percent > 3f) ? 2f : (percent > 1f) ? percent - 1f : 0; float tensionPercent = (4f - tensionSlingshotPercent) * tensionSlingshotPercent / 8f; if (percent < 1f) { float strokeStart = adjustedPercent * .8f; mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ROTATION_RATE, strokeStart)); mProgress.setArrowScale(Math.min(1f, adjustedPercent)); } float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; mProgress.setProgressRotation(rotation); circleView.setTranslationY(mWaveView.getCurrentCircleCenterY()); final View thisView = this; float seed = 1f * offset / Math.min(thisView.getMeasuredWidth(), thisView.getMeasuredHeight()); float firstBounds = seed * (5f - 2 * seed) / 3.5f; float secondBounds = firstBounds - VERTICAL_DRAG_THRESHOLD.FIRST.val; float finalBounds = (firstBounds - VERTICAL_DRAG_THRESHOLD.SECOND.val) / 5; mLastFirstBounds = firstBounds; if (firstBounds < VERTICAL_DRAG_THRESHOLD.FIRST.val) { // draw a wave and not draw a circle mWaveView.beginPhase(firstBounds); } else if (firstBounds < VERTICAL_DRAG_THRESHOLD.SECOND.val) { // draw a circle with a wave mWaveView.appearPhase(firstBounds, secondBounds); } else /*if (firstBounds < VERTICAL_DRAG_THRESHOLD.THIRD.val)*/ { // draw a circle with expanding a wave mWaveView.expandPhase(firstBounds, secondBounds, finalBounds); // } else { // // stop to draw a wave and drop a circle // onDropPhase(); } } else if (mLastFirstBounds != 0 && mState == RefreshState.None) { mProgress.showArrow(false); mProgress.setProgressRotation(0); mProgress.setStartEndTrim(0f, 0f); mWaveView.startWaveAnimation(mLastFirstBounds); mLastFirstBounds = 0; } } // @Override // public void onPulling(float percent, int offset, int height, int maxDragHeight) { // // if (mState == RefreshState.Refreshing) { // return; // } // // float dragPercent = Math.min(1f, percent); // float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; // // // 0f...2f // float tensionSlingshotPercent = // (percent > 3f) ? 2f : (percent > 1f) ? percent - 1f : 0; // float tensionPercent = (4f - tensionSlingshotPercent) * tensionSlingshotPercent / 8f; // // if (percent < 1f) { // float strokeStart = adjustedPercent * .8f; // mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ROTATION_RATE, strokeStart)); // mProgress.setArrowScale(Math.min(1f, adjustedPercent)); // } // // float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; // mProgress.setProgressRotation(rotation); // mCircleView.setTranslationY(mWaveView.getCurrentCircleCenterY()); // // float seed = 1f * offset / Math.min(getMeasuredWidth(), getMeasuredHeight()); // float firstBounds = seed * (5f - 2 * seed) / 3.5f; // float secondBounds = firstBounds - VERTICAL_DRAG_THRESHOLD.FIRST.val; // float finalBounds = (firstBounds - VERTICAL_DRAG_THRESHOLD.SECOND.val) / 5; // mLastFirstBounds = firstBounds; // // if (firstBounds < VERTICAL_DRAG_THRESHOLD.FIRST.val) { // // draw a wave and not draw a circle // mWaveView.beginPhase(firstBounds); // } else if (firstBounds < VERTICAL_DRAG_THRESHOLD.SECOND.val) { // // draw a circle with a wave // mWaveView.appearPhase(firstBounds, secondBounds); // } else /*if (firstBounds < VERTICAL_DRAG_THRESHOLD.THIRD.val)*/ { // // draw a circle with expanding a wave // mWaveView.expandPhase(firstBounds, secondBounds, finalBounds); //// } else { //// // stop to draw a wave and drop a circle //// onDropPhase(); // } // } @Override public void onReleased(@NonNull RefreshLayout layout, int height, int maxDragHeight) { mLastFirstBounds = 0; mWaveView.animationDropCircle(); mProgress.setAlpha(0xff); mProgress.start(); ValueAnimator animator = ValueAnimator.ofFloat(0, 0); animator.setDuration(500); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { final View circleView = mCircleView; circleView.setTranslationY( mWaveView.getCurrentCircleCenterY() + circleView.getHeight() / 2.f); } }); animator.start(); } @Override public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) { final View circleView = mCircleView; mState = newState; switch (newState) { case None: case ReleaseToRefresh: case Refreshing: break; case PullDownToRefresh: mProgress.showArrow(true); circleView.setScaleX(1f); circleView.setScaleY(1f); mProgress.setAlpha(0xff); break; case PullDownCanceled: mProgress.showArrow(false); mProgress.setProgressRotation(0); mProgress.setStartEndTrim(0f, 0f); mWaveView.startWaveAnimation(mLastFirstBounds); mLastFirstBounds = 0; break; } } @Override public int onFinish(@NonNull RefreshLayout layout, boolean success) { final View circleView = mCircleView; Animation scaleDownAnimation = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { circleView.setScaleX(1 - interpolatedTime); circleView.setScaleY(1 - interpolatedTime); } }; scaleDownAnimation.setDuration(200); mCircleView.setAnimationListener(new Animation.AnimationListener() { public void onAnimationStart(Animation animation) {} public void onAnimationRepeat(Animation animation) {} public void onAnimationEnd(Animation animation) { mProgress.stop(); mProgress.setAlpha(0xff); mWaveView.startDisappearCircleAnimation(); } }); circleView.clearAnimation(); circleView.startAnimation(scaleDownAnimation); return 0; } /** * @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor * @deprecated 请使用 {@link RefreshLayout#setPrimaryColorsId(int...)} */ @Override@Deprecated public void setPrimaryColors(@ColorInt int ... colors) { if (colors.length > 0) { mWaveView.setWaveColor(colors[0]); if (colors.length > 1) { mProgress.setColorSchemeColors(colors[1]); } } } // // @NonNull // @Override // public SpinnerStyle getSpinnerStyle() { // return SpinnerStyle.MatchLayout; // } // // /** * 現在の向きが600dpを超えているかどうか * * @return 600dpを超えているかどうか */ public static boolean isOver600dp() { // Resources resources = context.getResources(); DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); return displayMetrics.widthPixels / displayMetrics.density >= 600; } /** * Custom view has progress drawable. * Some features of MaterialProgressDrawable are decorated. * * @author jmatsu */ protected class ProgressAnimationImageView extends ImageView { /** * AnimationのStartとEnd時にListenerにアレする */ protected Animation.AnimationListener mListener; /** * @param listener {@link Animation.AnimationListener} */ public void setAnimationListener(Animation.AnimationListener listener) { mListener = listener; } /** * ViewのAnimationのStart時にセットされたListenerの {@link Animation.AnimationListener#onAnimationStart(Animation)} * を呼ぶ */ @Override public void onAnimationStart() { super.onAnimationStart(); if (mListener != null) { mListener.onAnimationStart(getAnimation()); } } /** * ViewのAnimationのEnd時にセットされたListenerの {@link Animation.AnimationListener#onAnimationEnd(Animation)} * (Animation)} を呼ぶ */ @Override public void onAnimationEnd() { super.onAnimationEnd(); if (mListener != null) { mListener.onAnimationEnd(getAnimation()); } } /** * Constructor * {@inheritDoc} */ public ProgressAnimationImageView(Context context) { super(context); mProgress = new MaterialProgressDrawable(WaveSwipeHeader.this); // mProgress.setBackgroundColor(Color.TRANSPARENT); if (isOver600dp()) { // Make the progress be big mProgress.updateSizes(MaterialProgressDrawable.LARGE); } super.setImageDrawable(mProgress); } // public void setProgressColorSchemeColorsFromResource(@IdRes int... resources) { // final View thisView = this; // final Resources res = thisView.getResources(); // final int[] colorRes = new int[resources.length]; // // for (int i = 0; i < resources.length; i++) { // colorRes[i] = res.getColor(resources[i]); // } // // setColorSchemeColors(colorRes); // } } // }