com.lovejjfg.demo.TouchCircleView.java Source code

Java tutorial

Introduction

Here is the source code for com.lovejjfg.demo.TouchCircleView.java

Source

/*
 * Copyright (c) 2017.  Joe
 * 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
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 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.lovejjfg.demo;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
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.Path;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Property;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

import java.util.ArrayList;

/**
 * Created by Joe on 2016/4/3.
 * Email lovejjfg@gmail.com
 */
@SuppressWarnings("unused")
public class TouchCircleView extends View {

    private static String TAG = "HeaderRefresh";
    float firstRange;//draw arc
    float secRange;//draw arrow
    float thirdRange;//dra second arc no use
    private RectF outRectF;
    private RectF innerRectf;
    private RectF secondRectf;
    private Paint innerPaint;
    private Paint paint;
    private Paint mCurrentPaint;
    private Paint mixPaint;

    private Path path;
    private Path mArrow;
    private Path mHook;
    private Path mError;

    private ObjectAnimator mObjectAnimatorSweep;
    private ObjectAnimator mObjectAnimatorAngle;
    private ValueAnimator fractionAnimator;
    private ValueAnimator translateAnimator;

    private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator();
    private static final Interpolator SWEEP_INTERPOLATOR = new AccelerateDecelerateInterpolator();
    private static final int ANGLE_ANIMATOR_DURATION = 1000;//
    private static final int SWEEP_ANIMATOR_DURATION = 800;
    private static final int FRACTION_DURATION = 300;
    private static final int DELAY_TIME = 2000;
    private static final int START_ANGLE = 270;
    private static final int RESULT_TIME = 300;
    private static final int ALPHA_FULL = 255;
    private static final int MIN_SWEEP_ANGLE = 30;

    private float mCurrentGlobalAngleOffset;
    private float mCurrentGlobalAngle;
    private float mCurrentSweepAngle;

    public static final int STATE_DRAW_IDLE = 0;
    public static final int STATE_DRAW_ARC = 1;
    public static final int STATE_DRAW_PATH = 2;//???
    public static final int STATE_DRAW_OUT_PATH = 3;//???
    public static final int STATE_DRAW_CIRCLE = 4;
    public static final int STATE_DRAW_ARROW = 5;
    public static final int STATE_DRAW_PROGRESS = 6;
    public static final int STATE_DRAW_ERROR = 7;
    public static final int STATE_DRAW_SUCCESS = 8;
    public static final int STATE_DRAW_BACK = 9;
    private static final int STATE_TRANSLATE_PATH = 10;

    private CirclePoint p0;
    private CirclePoint p1;
    private CirclePoint p2;
    private CirclePoint p3;
    private CirclePoint p4;
    private CirclePoint p5;
    private CirclePoint p6;
    private CirclePoint p7;
    private CirclePoint p8;
    private CirclePoint p9;
    private CirclePoint p10;
    private CirclePoint p11;

    private float currentOffset;
    private int defaultOffset;
    private final Runnable finishAction = new Runnable() {
        @Override
        public void run() {
            finish();
        }
    };
    private Runnable startLoadingAction = new Runnable() {
        @Override
        public void run() {
            updateRectF();
            startLoading();
        }
    };
    private float pathMax;
    private Paint secPaint;
    private int mCurrentRadius;
    private RectF mCurrentRectf = new RectF();
    private boolean isBack;
    private float changeDy;
    private boolean abortReset;

    public int getCurrentState() {
        return currentState;
    }

    public void setCurrentState(int currentState) {
        this.currentState = currentState;
        invalidate();
    }

    private int currentState;
    private int centerY;
    private int centerX;
    private float mBorderWidth = 4;
    private float mRingCenterRadius;
    private boolean mModeAppearing;
    private float mArrowScale = 1.0f;
    private float fraction;
    private Paint mHookPaint;

    public boolean ismRunning() {
        return mRunning;
    }

    private boolean mRunning;
    private boolean isDrawTriangle;

    //?
    @SuppressWarnings("unused")
    public void setOutCirRadius(int outCirRadius) {
        this.outCirRadius = outCirRadius;
        Log.i("?", outCirRadius + "");

    }

    @SuppressWarnings("unused")
    public int getOutCirRadius() {
        return outCirRadius;
    }

    //?
    private int outCirRadius;
    //not used in the project
    private int secondRadius = (int) (outCirRadius * 1f);
    //
    private int innerCirRadius = outCirRadius - 30;
    private static int ARROW_WIDTH = 20 * 2;
    private static int ARROW_HEIGHT = 10 * 2;

    private long angle;
    private int paths;
    private long backpaths;

    public TouchCircleView(Context context) {
        this(context, null);
    }

    public TouchCircleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TouchCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        float density = context.getResources().getDisplayMetrics().density;
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HeaderProgress, defStyleAttr, 0);
        mBorderWidth = a.getDimension(R.styleable.HeaderProgress_circleBorderWidth, 2 * density);
        outCirRadius = (int) a.getDimension(R.styleable.HeaderProgress_outRadius, 18 * density);
        innerCirRadius = (int) a.getDimension(R.styleable.HeaderProgress_innerRadius, 10 * density);
        secondRadius = (int) (outCirRadius * 1.5f);
        //        innerCirRadius = (int) (outCirRadius * 0.6f);
        a.recycle();
        initView();
    }

    private void initView() {
        setupAnimations();
        innerRectf = new RectF();
        secondRectf = new RectF();
        outRectF = new RectF();
        ARROW_WIDTH = (int) (mBorderWidth * 1.5f);
        ARROW_HEIGHT = (int) (mBorderWidth * 0.75f);
        initPaintPath();
    }

    /**
     * 
     */
    private static final float CIRCLE_VALUE = 0.551915024494f;

    private void initPoints() {
        float m = mCurrentRadius * CIRCLE_VALUE;
        float centerX = mCurrentRectf.centerX();
        float centerY = mCurrentRectf.centerY();

        p0 = new CirclePoint(centerX, centerY - mCurrentRadius);
        p1 = new CirclePoint(centerX + m, centerY - mCurrentRadius);
        p2 = new CirclePoint(centerX + mCurrentRadius, centerY - m);
        p3 = new CirclePoint(centerX + mCurrentRadius, centerY);

        p4 = new CirclePoint(centerX + mCurrentRadius, centerY + m);
        p5 = new CirclePoint(centerX + m, centerY + mCurrentRadius);
        p6 = new CirclePoint(centerX, centerY + mCurrentRadius);

        p7 = new CirclePoint(centerX - m, centerY + mCurrentRadius);
        p8 = new CirclePoint(centerX - mCurrentRadius, centerY + m);
        p9 = new CirclePoint(centerX - mCurrentRadius, centerY);

        p10 = new CirclePoint(centerX - mCurrentRadius, centerY - m);
        p11 = new CirclePoint(centerX - m, centerY - mCurrentRadius);
    }

    private void resetPoints() {
        float centerX = mCurrentRectf.centerX();
        float centerY = mCurrentRectf.centerY();
        resetPoints(centerX, centerY);
    }

    private void resetPoints(float centerX, float centerY) {
        float m = mCurrentRadius * CIRCLE_VALUE;

        p0.setPoint(centerX, centerY - mCurrentRadius);
        p1.setPoint(centerX + m, centerY - mCurrentRadius);
        p2.setPoint(centerX + mCurrentRadius, centerY - m);
        p3.setPoint(centerX + mCurrentRadius, centerY);

        p4.setPoint(centerX + mCurrentRadius, centerY + m);
        p5.setPoint(centerX + m, centerY + mCurrentRadius);
        p6.setPoint(centerX, centerY + mCurrentRadius);

        p7.setPoint(centerX - m, centerY + mCurrentRadius);
        p8.setPoint(centerX - mCurrentRadius, centerY + m);
        p9.setPoint(centerX - mCurrentRadius, centerY);

        p10.setPoint(centerX - mCurrentRadius, centerY - m);
        p11.setPoint(centerX - m, centerY - mCurrentRadius);
    }

    public void handleOffset(int dy) {
        float percent;
        if (dy < 0) {
            dy = 0;
        }
        mModeAppearing = true;
        percent = dy / firstRange;
        if (dy != angle && dy >= 0 && dy <= firstRange) {
            paint.setAlpha((int) (percent * ALPHA_FULL));
            updateState(STATE_DRAW_ARC, false);
            resetAngle();
            Log.e(TAG, "handleOffset: height::" + getHeight());
            currentOffset = getHeight() - percent * defaultOffset;
            updateRectF();
            angle = (long) (percent * 360);
            Log.i(TAG, "onTouchEvent: " + angle);
            invalidate();
            return;
        }
        if (dy > firstRange && dy <= secRange) {
            innerPaint.setAlpha(ALPHA_FULL);
            outRectF.set(centerX - outCirRadius, currentOffset, centerX + outCirRadius,
                    centerY + outCirRadius + currentOffset);
            updateState(STATE_DRAW_ARROW, false);
            percent = (dy - firstRange) / (secRange - firstRange);
            mCurrentSweepAngle = mCurrentGlobalAngle = percent * 200;
            invalidate();
        }

    }

    private void updateState(int state, boolean hide) {
        updateState(state, hide, false);

    }

    private void updateState(final int state, final boolean hide, boolean delay) {
        currentState = state;
        if (listeners != null) {
            for (final OnLoadingListener listener : listeners) {
                if (STATE_DRAW_BACK == state) {
                    listener.onGoBackHome();
                }
                if (delay) {
                    postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            listener.onProgressStateChange(state, hide);
                        }
                    }, RESULT_TIME);
                } else {
                    listener.onProgressStateChange(state, hide);
                }
            }
        }
    }

    public void resetTouch() {
        if (mRunning || translateAnimator.isRunning()) {
            abortReset = true;
            return;
        }
        abortReset = false;
        if (STATE_DRAW_ARROW == currentState || STATE_DRAW_PATH == currentState
                || STATE_DRAW_OUT_PATH == currentState) {
            outRectF.set(centerX - outCirRadius, currentOffset, centerX + outCirRadius,
                    centerY + outCirRadius + currentOffset);
            innerPaint.setAlpha(ALPHA_FULL);
            updateState(STATE_DRAW_PROGRESS, false);
            start();
            return;
        }
        if (STATE_DRAW_CIRCLE == currentState) {
            updateState(STATE_DRAW_BACK, true);
        }
        stop();
        currentOffset = 0;
        updateState(STATE_DRAW_IDLE, true);
        invalidate();

    }

    private void startLoading() {
        innerPaint.setAlpha(ALPHA_FULL);
        start();
    }

    private void initPaintPath() {

        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(20);
        paint.setColor(Color.RED);
        paint.setStrokeCap(Paint.Cap.SQUARE);

        secPaint = new Paint(paint);
        secPaint.setColor(Color.GREEN);

        mixPaint = new Paint(paint);
        innerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        innerPaint.setStyle(Paint.Style.STROKE);
        innerPaint.setAntiAlias(true);
        innerPaint.setColor(Color.WHITE);
        innerPaint.setStrokeWidth(mBorderWidth);

        mHookPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHookPaint.setStyle(Paint.Style.STROKE);
        mHookPaint.setStrokeCap(Paint.Cap.ROUND);
        mHookPaint.setStrokeWidth(mBorderWidth);
        mHookPaint.setColor(Color.WHITE);

        path = new Path();
        mArrow = new Path();
        mHook = new Path();
        mError = new Path();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = outCirRadius;

        updateRectF();
        initPoints();
        mRingCenterRadius = Math.min(innerRectf.centerX() - innerRectf.left, innerRectf.centerY() - innerRectf.top)
                - mBorderWidth;

    }

    private void updateRectF() {
        outRectF.set(centerX - outCirRadius, currentOffset, centerX + outCirRadius,
                centerY + outCirRadius + currentOffset);
        innerRectf.set(centerX - innerCirRadius, centerY - innerCirRadius + currentOffset, centerX + innerCirRadius,
                centerY + innerCirRadius + currentOffset);
        secondRectf.set(centerX - secondRadius, currentOffset + secondRadius * 2, centerX + secondRadius,
                centerY + secondRadius + currentOffset + secondRadius * 2);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        defaultOffset = (int) (getHeight() * 0.4f);
        firstRange = (int) (getHeight() * 0.6f);
        secRange = getHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //
        switch (currentState) {
        case STATE_DRAW_IDLE:
            canvas.drawArc(outRectF, 0, 0, false, paint);
            break;
        case STATE_DRAW_ARC:
            canvas.drawArc(outRectF, START_ANGLE, angle, true, paint);
            break;
        case STATE_DRAW_ARROW:
            isDrawTriangle = true;
            mCurrentRectf = outRectF;
            mCurrentRadius = outCirRadius;
            mCurrentPaint = paint;
            resetPoints();
            drawCirclePath(canvas);
            drawArc(canvas);
            break;
        case STATE_DRAW_PROGRESS:
            isDrawTriangle = false;
            mCurrentRectf = outRectF;
            mCurrentPaint = paint;
            mCurrentRadius = outCirRadius;
            resetPoints();
            drawCirclePath(canvas);
            drawArc(canvas);
            break;
        case STATE_DRAW_SUCCESS:
            drawHook(canvas);
            break;
        case STATE_DRAW_ERROR:
            drawError(canvas);
            break;
        case STATE_DRAW_PATH:
            mCurrentRadius = outCirRadius;
            mCurrentRectf = outRectF;
            mCurrentPaint = paint;
            resetPoints();
            drawCirclePath(canvas, paths);
            break;
        case STATE_TRANSLATE_PATH:
            drawCirclePath(canvas);
            break;
        case STATE_DRAW_OUT_PATH:
            mCurrentRadius = secondRadius;
            mCurrentRectf = secondRectf;
            mCurrentPaint = secPaint;
            resetPoints();
            drawCirclePath(canvas, backpaths);
            break;
        case STATE_DRAW_CIRCLE:
            mCurrentRadius = secondRadius;
            mCurrentRectf = secondRectf;
            mCurrentPaint = secPaint;
            resetPoints();
            drawCirclePath(canvas);
            break;
        }
    }

    private void drawCirclePath(Canvas canvas) {
        drawCirclePath(canvas, 0);
    }

    private void drawCirclePath(Canvas canvas, float distance) {
        boolean isDrawPath = distance != 0;
        if (isDrawPath) {
            if (distance > 0) {
                p0.y -= changeDy;
                p11.y = p1.y = p0.y;//??
                // +
                p5.y += distance;
                p7.y = p6.y = p5.y;
                // +
                p8.x += changeDy / 2;
                p10.x = p9.x = p8.x;

                //? -
                p2.x -= changeDy / 2;
                p3.x = p4.x = p2.x;
            } else {
                //
                p0.y += distance;
                p11.y = p1.y = p0.y;//??
                // +
                p5.y += changeDy;
                p7.y = p6.y = p5.y;
                // +
                p8.x -= changeDy / 2;
                p10.x = p9.x = p8.x;

                //? -
                p2.x += changeDy / 2;
                p3.x = p4.x = p2.x;
            }
        }
        path.reset();
        path.moveTo(p0.x, p0.y);
        path.cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
        path.cubicTo(p4.x, p4.y, p5.x, p5.y, p6.x, p6.y);
        path.cubicTo(p7.x, p7.y, p8.x, p8.y, p9.x, p9.y);
        path.cubicTo(p10.x, p10.y, p11.x, p11.y, p0.x, p0.y);
        canvas.drawPath(path, mCurrentPaint);
    }

    private void drawArc(Canvas canvas) {
        float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset + START_ANGLE;
        float sweepAngle = mCurrentSweepAngle;
        if (mModeAppearing) {
            //            paint.setColor(gradient(mColors[mCurrentColorIndex], mColors[mNextColorIndex],
            //                    mCurrentSweepAngle / (360 - MIN_SWEEP_ANGLE * 2)));
            sweepAngle += MIN_SWEEP_ANGLE;
        } else {
            startAngle = startAngle + sweepAngle;
            sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE;
        }
        canvas.drawArc(innerRectf, startAngle, sweepAngle, false, innerPaint);
        if (isDrawTriangle) {
            drawTriangle(canvas, startAngle, sweepAngle);
        }
    }

    public void drawTriangle(Canvas c, float startAngle, float sweepAngle) {
        if (mArrow == null) {
            mArrow = new Path();
            mArrow.setFillType(Path.FillType.EVEN_ODD);
        } else {
            mArrow.reset();
        }

        // Adjust the position of the triangle so that it is inset as
        // much as the arc, but also centered on the arc.
        //        float inset = (int) mStrokeInset / 2 * mArrowScale;
        float x = (float) (mRingCenterRadius * Math.cos(0) + innerRectf.centerX());
        float y = (float) (mRingCenterRadius * Math.sin(0) + innerRectf.centerY());

        // Update the path each time. This works around an issue in SKIA
        // where concatenating a rotation matrix to a scale matrix
        // ignored a starting negative rotation. This appears to have
        // been fixed as of API 21.
        mArrow.moveTo(0, 0);
        mArrow.lineTo(ARROW_WIDTH * mArrowScale, 0);
        mArrow.lineTo((ARROW_WIDTH * mArrowScale / 2), (ARROW_HEIGHT * mArrowScale));
        mArrow.offset(x, y);
        mArrow.close();
        // draw a triangle
        c.rotate(startAngle + sweepAngle, innerRectf.centerX(), innerRectf.centerY());
        c.drawPath(mArrow, innerPaint);

    }

    /**
     * 
     */
    private void drawHook(Canvas canvas) {
        mHook.reset();
        mHook.moveTo(innerRectf.centerX() - innerRectf.width() * 0.25f * fraction, innerRectf.centerY());
        mHook.lineTo(innerRectf.centerX() - innerRectf.width() * 0.1f * fraction,
                innerRectf.centerY() + innerRectf.height() * 0.18f * fraction);
        mHook.lineTo(innerRectf.centerX() + innerRectf.width() * 0.25f * fraction,
                innerRectf.centerY() - innerRectf.height() * 0.20f * fraction);
        mCurrentRectf = outRectF;
        mCurrentPaint = paint;
        resetPoints();
        drawCirclePath(canvas);
        canvas.drawPath(mHook, mHookPaint);

    }

    /**
     * 
     */
    private void drawError(Canvas canvas) {
        mError.reset();
        mError.moveTo(innerRectf.centerX() + innerRectf.width() * 0.2f * fraction,
                innerRectf.centerY() - innerRectf.height() * 0.2f * fraction);
        mError.lineTo(innerRectf.centerX() - innerRectf.width() * 0.2f * fraction,
                innerRectf.centerY() + innerRectf.height() * 0.2f * fraction);
        mError.moveTo(innerRectf.centerX() - innerRectf.width() * 0.2f * fraction,
                innerRectf.centerY() - innerRectf.height() * 0.2f * fraction);
        mError.lineTo(innerRectf.centerX() + innerRectf.width() * 0.2f * fraction,
                innerRectf.centerY() + innerRectf.height() * 0.2f * fraction);
        mCurrentRectf = outRectF;
        mCurrentPaint = paint;
        resetPoints();
        drawCirclePath(canvas);
        canvas.drawPath(mError, mHookPaint);
    }

    private void start() {
        if (mRunning) {
            return;
        }
        mRunning = true;
        dispatchListener();
        mObjectAnimatorAngle.setFloatValues(mCurrentGlobalAngle, 360f);
        mObjectAnimatorSweep.setFloatValues(mCurrentSweepAngle, 360f - MIN_SWEEP_ANGLE * 2);
        //        mCurrentState = STATE_LOADING;
        mObjectAnimatorAngle.start();
        mObjectAnimatorSweep.start();
        //        postDelayed(finishAction, DELAY_TIME);
        invalidate();
    }

    private void dispatchListener() {
        if (listeners != null) {
            for (OnLoadingListener listener : listeners) {
                listener.onProgressStateChange(STATE_DRAW_PROGRESS, false);
                listener.onProgressLoading();
            }

        }
    }

    public void finish() {
        stop();
        mRunning = true;
        resetAngle();
        if (!fractionAnimator.isRunning()) {
            fractionAnimator.start();
        }
        updateState(currentState, false, false);
        invalidate();
    }

    private void resetAngle() {
        if (mCurrentSweepAngle != 0 || mCurrentGlobalAngle != 0 || mCurrentGlobalAngleOffset != 0) {
            mCurrentSweepAngle = 0;
            mCurrentGlobalAngle = 0;
            mCurrentGlobalAngleOffset = 0;
        }
    }

    private void stop() {
        if (!mRunning) {
            return;
        }
        //        mRunning = false;
        mObjectAnimatorAngle.cancel();
        mObjectAnimatorSweep.cancel();
        //        mRunning = fractionAnimator.isRunning();
    }

    private void toggleAppearingMode() {
        mModeAppearing = !mModeAppearing;
        if (mModeAppearing) {
            //            mCurrentColorIndex = ++mCurrentColorIndex % 4;
            //            mNextColorIndex = ++mNextColorIndex % 4;
            mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;
        }
    }

    private Property<TouchCircleView, Float> mAngleProperty = new Property<TouchCircleView, Float>(Float.class,
            "angle") {
        @Override
        public Float get(TouchCircleView object) {
            return object.getCurrentGlobalAngle();
        }

        @Override
        public void set(TouchCircleView object, Float value) {
            object.setCurrentGlobalAngle(value);
        }
    };

    private Property<TouchCircleView, Float> mSweepProperty = new Property<TouchCircleView, Float>(Float.class,
            "arc") {
        @Override
        public Float get(TouchCircleView object) {
            return object.getCurrentSweepAngle();
        }

        @Override
        public void set(TouchCircleView object, Float value) {
            object.setCurrentSweepAngle(value);
        }
    };

    private Runnable idleAction = new Runnable() {
        @Override
        public void run() {
            mRunning = false;
            updateState(STATE_DRAW_IDLE, true);
            invalidate();
        }
    };

    private void setupAnimations() {

        mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, mCurrentGlobalAngle, 360f);
        mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
        mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
        mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
        mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);
        mObjectAnimatorAngle.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationRepeat(Animator animation) {
                mObjectAnimatorAngle.setFloatValues(360f);
            }
        });

        mObjectAnimatorSweep = ObjectAnimator.ofFloat(this, mSweepProperty, mCurrentSweepAngle,
                360f - MIN_SWEEP_ANGLE * 2);
        mObjectAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR);
        mObjectAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION);
        mObjectAnimatorSweep.setRepeatMode(ValueAnimator.RESTART);
        mObjectAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE);
        mObjectAnimatorSweep.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationRepeat(Animator animation) {
                mObjectAnimatorSweep.setFloatValues(360f - MIN_SWEEP_ANGLE * 2);
                toggleAppearingMode();
            }
        });

        fractionAnimator = ValueAnimator.ofInt(0, ALPHA_FULL);
        fractionAnimator.setInterpolator(ANGLE_INTERPOLATOR);
        fractionAnimator.setDuration(FRACTION_DURATION);
        fractionAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                fraction = animation.getAnimatedFraction();
                mHookPaint.setAlpha((Integer) animation.getAnimatedValue());
                invalidate();
            }
        });
        fractionAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                mRunning = false;
                postDelayed(idleAction, 0);
            }
        });
        translateAnimator = ValueAnimator.ofFloat(0, 100);
        translateAnimator.setInterpolator(ANGLE_INTERPOLATOR);
        translateAnimator.setDuration(200);
        translateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            private float tranlateFraction;

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float animatedValue = (Float) animation.getAnimatedValue();
                updateState(STATE_TRANSLATE_PATH, false);
                tranlateFraction = animation.getAnimatedFraction();
                if (!isBack) {
                    mixPaint.setColor(ColorUtils.blendARGB(Color.RED, Color.GREEN, tranlateFraction));
                    mCurrentRadius = (int) (outCirRadius + tranlateFraction * (secondRadius - outCirRadius));
                } else {
                    mixPaint.setColor(ColorUtils.blendARGB(Color.GREEN, Color.RED, tranlateFraction));
                    mCurrentRadius = (int) (secondRadius - tranlateFraction * (secondRadius - outCirRadius));
                }
                mCurrentPaint = mixPaint;
                resetPoints(secondRectf.centerX(), animatedValue);
                invalidate();
            }
        });
        translateAnimator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                updateState(isBack ? STATE_DRAW_ARROW : abortReset ? STATE_DRAW_BACK : STATE_DRAW_CIRCLE,
                        abortReset);

                updateRectF();
                invalidate();
            }
        });
    }

    public void setCurrentGlobalAngle(float currentGlobalAngle) {
        mCurrentGlobalAngle = currentGlobalAngle;
        invalidate();
    }

    public float getCurrentGlobalAngle() {
        return mCurrentGlobalAngle;
    }

    public void setCurrentSweepAngle(float currentSweepAngle) {
        mCurrentSweepAngle = currentSweepAngle;
        invalidate();
    }

    public float getCurrentSweepAngle() {
        return mCurrentSweepAngle;
    }

    ArrayList<OnLoadingListener> listeners = new ArrayList<>();

    public void addLoadingListener(@Nullable OnLoadingListener listener) {
        if (listener != null) {
            listeners.add(listener);
        }
    }

    public boolean removeLoadingListener(@NonNull OnLoadingListener listener) {
        return listeners != null && listeners.remove(listener);
    }

    //    @Nullable
    //    OnLoadingListener listener;

    public void setRefresh(boolean mRefresh) {
        currentOffset = getHeight() * 0.5f;
        updateState(mRefresh ? STATE_DRAW_PROGRESS : STATE_DRAW_IDLE, !mRefresh);
        if (mRefresh) {
            //            currentOffset = c;
            post(startLoadingAction);
        } else {
            currentOffset = 0;
            mObjectAnimatorSweep.cancel();
            mObjectAnimatorAngle.cancel();
            updateRectF();
            invalidate();
        }

    }

    public void setRefreshError() {
        if (currentState == STATE_DRAW_ERROR) {
            return;
        }
        currentState = STATE_DRAW_ERROR;
        finish();
    }

    public void setRefreshSuccess() {
        Log.e(TAG, "setRefreshSuccess: ");
        if (currentState == STATE_DRAW_SUCCESS) {
            return;
        }
        currentState = STATE_DRAW_SUCCESS;
        finish();
    }

    public interface OnLoadingListener {
        void onProgressStateChange(int state, boolean hide);

        void onProgressLoading();

        void onGoBackHome();
    }

    @Override
    protected void onDetachedFromWindow() {
        stop();
        super.onDetachedFromWindow();
    }

    private static class CirclePoint {
        float x;
        float y;

        public CirclePoint(float x, float y) {
            this.x = x;
            this.y = y;
        }

        public void setPoint(float x, float y) {
            this.x = x;
            this.y = y;
        }
    }
}