package com.scwang.smart.refresh.header; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import com.scwang.smart.refresh.layout.api.RefreshKernel; import com.scwang.smart.refresh.layout.api.RefreshLayout; import com.scwang.smart.refresh.layout.simple.SimpleComponent; import com.scwang.smart.refresh.layout.api.RefreshHeader; import com.scwang.smart.refresh.layout.constant.SpinnerStyle; import com.scwang.smart.refresh.layout.util.SmartUtil; /** * CircleRefresh * Created by scwang on 2018/7/18. * from https://github.com/tuesda/CircleRefreshLayout */ public class BezierCircleHeader extends SimpleComponent implements RefreshHeader { // protected Path mPath; protected Paint mBackPaint; protected Paint mFrontPaint; protected Paint mOuterPaint; protected int mHeight; protected float mWaveHeight; protected float mHeadHeight; protected float mSpringRatio; protected float mFinishRatio; protected float mBollY;//弹出球体的Y坐标 protected float mBollRadius;//球体半径 protected boolean mShowOuter; protected boolean mShowBoll;//是否显示中心球体 protected boolean mShowBollTail;//是否显示球体拖拽的尾巴 protected int mRefreshStop = 90; protected int mRefreshStart = 90; protected boolean mOuterIsStart = true; protected static final int TARGET_DEGREE = 270; protected boolean mWavePulling = false; protected RefreshKernel mKernel; // // public BezierCircleHeader(Context context) { this(context, null); } public BezierCircleHeader(Context context, AttributeSet attrs) { super(context, attrs, 0); mSpinnerStyle = SpinnerStyle.FixedBehind; final View thisView = this; thisView.setMinimumHeight(SmartUtil.dp2px(100)); mBackPaint = new Paint(); mBackPaint.setColor(0xff11bbff); mBackPaint.setAntiAlias(true); mFrontPaint = new Paint(); mFrontPaint.setColor(0xffffffff); mFrontPaint.setAntiAlias(true); mOuterPaint = new Paint(); mOuterPaint.setAntiAlias(true); mOuterPaint.setColor(0xffffffff); mOuterPaint.setStyle(Paint.Style.STROKE); mOuterPaint.setStrokeWidth(SmartUtil.dp2px(2f)); mPath = new Path(); } // // @Override protected void dispatchDraw(Canvas canvas) { final View thisView = this; final int viewWidth = thisView.getWidth(); final int viewHeight = mHeight;//thisView.getHeight(); //noinspection EqualsBetweenInconvertibleTypes final boolean footer = mKernel != null && (this.equals(mKernel.getRefreshLayout().getRefreshFooter())); if (footer) { canvas.save(); canvas.translate(0, thisView.getHeight()); canvas.scale(1, -1); } if (thisView.isInEditMode()) { mShowBoll = true; mShowOuter = true; mHeadHeight = viewHeight; mRefreshStop = 270; mBollY = mHeadHeight / 2; mBollRadius = mHeadHeight / 6; } drawWave(canvas, viewWidth, viewHeight); drawSpringUp(canvas, viewWidth); drawBoll(canvas, viewWidth); drawOuter(canvas, viewWidth); drawFinish(canvas, viewWidth); if (footer) { canvas.restore(); } super.dispatchDraw(canvas); } protected void drawWave(Canvas canvas, int viewWidth, int viewHeight) { float baseHeight = Math.min(mHeadHeight, viewHeight); if (mWaveHeight != 0) { mPath.reset(); mPath.lineTo(viewWidth, 0); mPath.lineTo(viewWidth, baseHeight); mPath.quadTo(viewWidth / 2f, baseHeight + mWaveHeight * 2, 0, baseHeight); mPath.close(); canvas.drawPath(mPath, mBackPaint); } else { canvas.drawRect(0, 0, viewWidth, baseHeight, mBackPaint); } } protected void drawSpringUp(Canvas canvas, int viewWidth) { if (mSpringRatio > 0) { float leftX = (viewWidth / 2f - 4 * mBollRadius + mSpringRatio * 3 * mBollRadius); if (mSpringRatio < 0.9) { mPath.reset(); mPath.moveTo(leftX, mBollY); mPath.quadTo(viewWidth / 2f, mBollY - mBollRadius * mSpringRatio * 2, viewWidth - leftX, mBollY); canvas.drawPath(mPath, mFrontPaint); } else { canvas.drawCircle(viewWidth / 2f, mBollY, mBollRadius, mFrontPaint); } } } protected void drawBoll(Canvas canvas, int viewWidth) { if (mShowBoll) { canvas.drawCircle(viewWidth / 2f, mBollY, mBollRadius, mFrontPaint); drawBollTail(canvas, viewWidth, (mHeadHeight + mWaveHeight) / mHeadHeight); } } protected void drawBollTail(Canvas canvas, int viewWidth, float fraction) { if (mShowBollTail) { final float bottom = mHeadHeight + mWaveHeight; final float startY = mBollY + mBollRadius * fraction / 2; final float startX = viewWidth / 2f + (float) Math.sqrt(mBollRadius * mBollRadius * (1 - fraction * fraction / 4)); final float bezier1x = (viewWidth / 2f + (mBollRadius * 3 / 4) * (1 - fraction)); final float bezier2x = bezier1x + mBollRadius; mPath.reset(); mPath.moveTo(startX, startY); mPath.quadTo(bezier1x, bottom, bezier2x, bottom); mPath.lineTo(viewWidth - bezier2x, bottom); mPath.quadTo(viewWidth - bezier1x, bottom, viewWidth - startX, startY); canvas.drawPath(mPath, mFrontPaint); } } protected void drawOuter(Canvas canvas, int viewWidth) { if (mShowOuter) { float outerR = mBollRadius + mOuterPaint.getStrokeWidth() * 2; mRefreshStart += mOuterIsStart ? 3 : 10; mRefreshStop += mOuterIsStart ? 10 : 3; mRefreshStart = mRefreshStart % 360; mRefreshStop = mRefreshStop % 360; int swipe = mRefreshStop - mRefreshStart; swipe = swipe < 0 ? swipe + 360 : swipe; canvas.drawArc(new RectF(viewWidth / 2f - outerR, mBollY - outerR, viewWidth / 2f + outerR, mBollY + outerR), mRefreshStart, swipe, false, mOuterPaint); if (swipe >= TARGET_DEGREE) { mOuterIsStart = false; } else if (swipe <= 10) { mOuterIsStart = true; } final View thisView = this; thisView.invalidate(); } } protected void drawFinish(Canvas canvas, int viewWidth) { if (mFinishRatio > 0) { int beforeColor = mOuterPaint.getColor(); if (mFinishRatio < 0.3) { canvas.drawCircle(viewWidth / 2f, mBollY, mBollRadius, mFrontPaint); int outerR = (int) (mBollRadius + mOuterPaint.getStrokeWidth() * 2 * (1+mFinishRatio / 0.3f)); int afterColor = ColorUtils.setAlphaComponent(beforeColor, (int) (0xff * (1 - mFinishRatio / 0.3f))); mOuterPaint.setColor(afterColor); canvas.drawArc(new RectF(viewWidth / 2f - outerR, mBollY - outerR, viewWidth / 2f + outerR, mBollY + outerR), 0, 360, false, mOuterPaint); } mOuterPaint.setColor(beforeColor); if (mFinishRatio >= 0.3 && mFinishRatio < 0.7) { float fraction = (mFinishRatio - 0.3f) / 0.4f; mBollY = (int) (mHeadHeight / 2 + (mHeadHeight - mHeadHeight / 2) * fraction); canvas.drawCircle(viewWidth / 2f, mBollY, mBollRadius, mFrontPaint); if (mBollY >= mHeadHeight - mBollRadius * 2) { mShowBollTail = true; drawBollTail(canvas, viewWidth, fraction); } mShowBollTail = false; } if (mFinishRatio >= 0.7 && mFinishRatio <= 1) { float fraction = (mFinishRatio - 0.7f) / 0.3f; int leftX = (int) (viewWidth / 2f - mBollRadius - 2 * mBollRadius * fraction); mPath.reset(); mPath.moveTo(leftX, mHeadHeight); mPath.quadTo(viewWidth / 2f, mHeadHeight - (mBollRadius * (1 - fraction)), viewWidth - leftX, mHeadHeight); canvas.drawPath(mPath, mFrontPaint); } } } // // @Override public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) { mKernel = kernel; } @Override public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) { mHeight = offset; if (isDragging || mWavePulling) { mWavePulling = true; mHeadHeight = height; mWaveHeight = Math.max(offset - height, 0) * .8f; } this.invalidate(); } @Override public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) { mWavePulling = false; mHeadHeight = height; mBollRadius = height / 6f; DecelerateInterpolator interpolator = new DecelerateInterpolator(); final float reboundHeight = Math.min(mWaveHeight * 0.8f, mHeadHeight / 2); ValueAnimator waveAnimator = ValueAnimator.ofFloat( mWaveHeight, 0, -(reboundHeight*1.0f),0, -(reboundHeight*0.4f),0 ); waveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { float speed = 0; float springBollY; float springRatio = 0; int status = 0;//0 还没开始弹起 1 向上弹起 2 在弹起的最高点停住 @Override public void onAnimationUpdate(ValueAnimator animation) { float curValue = (float) animation.getAnimatedValue(); if (status == 0 && curValue <= 0) { status = 1; speed = Math.abs(curValue - mWaveHeight); } if (status == 1) { springRatio = -curValue / reboundHeight; if (springRatio >= mSpringRatio) { mSpringRatio = springRatio; mBollY = mHeadHeight + curValue; speed = Math.abs(curValue - mWaveHeight); } else { status = 2; mSpringRatio = 0; mShowBoll = true; mShowBollTail = true; springBollY = mBollY; } } if (status == 2) { if (mBollY > mHeadHeight / 2) { mBollY = Math.max(mHeadHeight / 2, mBollY - speed); float bally = animation.getAnimatedFraction() * (mHeadHeight / 2 - springBollY) + springBollY; if (mBollY > bally) { mBollY = bally; } } } if (mShowBollTail && curValue < mWaveHeight) { mShowOuter = true; mShowBollTail = false; mOuterIsStart = true; mRefreshStart = 90; mRefreshStop = 90; } if (!mWavePulling) { mWaveHeight = curValue; final View thisView = BezierCircleHeader.this; thisView.invalidate(); } } }); waveAnimator.setInterpolator(interpolator); waveAnimator.setDuration(1000); waveAnimator.start(); } @Override public int onFinish(@NonNull RefreshLayout layout, boolean success) { mShowBoll = false; mShowOuter = false; final int DURATION_FINISH = 800; //动画时长 ValueAnimator animator = ValueAnimator.ofFloat(0, 1); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { final View thisView = BezierCircleHeader.this; mFinishRatio = (float) animation.getAnimatedValue(); thisView.invalidate(); } }); animator.setInterpolator(new AccelerateInterpolator()); animator.setDuration(DURATION_FINISH); animator.start(); return DURATION_FINISH; } /** * @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor * @deprecated 请使用 {@link RefreshLayout#setPrimaryColorsId(int...)} */ @Override@Deprecated public void setPrimaryColors(@ColorInt int ... colors) { if (colors.length > 0) { mBackPaint.setColor(colors[0]); if (colors.length > 1) { mFrontPaint.setColor(colors[1]); mOuterPaint.setColor(colors[1]); } } } // @NonNull // @Override // public SpinnerStyle getSpinnerStyle() { // return SpinnerStyle.Scale; // } // }