基于ArcSeekBar(圆弧拖动条)的修改

原作者地址:ArcSeekBar(圆弧拖动条)

原作者的效果是这样的。

我的需求是 移动滑块,前景色要变动。

我看到作者的GitHub上,也有人提这样的需求。

于是我自己在原作者的基础上,进行了二次修改

先放效果图:

前景色渐变 + 跟着滑块一起动。

背景色渐变 + 静止

废话不多说,直接上代码。

ArcSeekBar.java
/**
 * 作用: 圆弧形 SeekBar
 * 作者: GcsSloop
 * 摘要: 该 SeekBar 实现的核心原理非常简单,其绘制内容实际上为一个圆弧,只用了一条 Path,
 * 尽管核心原理简单,但仍有以下细节需要进行注意:
 * 1. 画布旋转导致的坐标系问题.
 * -  为了让开口方向可控,并且实现起来简单,进行了画布旋转,因此实际显示的坐标系下的坐标和绘制坐标系坐标是不同的,尤其需要注意.
 * 2.当前进度确定问题
 * -  进度是由角度确定的, 先根据当前点击位置和中心点连线与水平线的夹角表示当前角度,
 * -  根据该角度和实际圆弧的角度相关信息推算出当前进度百分比.
 * -  根据百分比和最大数值确定当前实际的进度.
 * 3. 拖动与点击
 * -  为了防止误触, 只有手指按下位置在拖动按钮附近时才可以执行拖动.
 * -  但是用户单击时,可以可以直接跳转进度, 当然,只有点击在圆弧进度条的区域内才允许设置新的进度.
 * -  如何判断是否点击在圆弧区域内,使用了 Region 的相关方法,该 Region 区域是从 Paint 的 getFillPath 得到的.
 * 4. 进度回调去重
 * -  用户拖动时,判断是否和上次进度相同,如果相同,则不发送回调.
 * 5. 防止突变
 * -  由于进度条时圆弧形状的,因此进度可能会从 0.0 直接突变到 1.0 或者相反,因此在计算进度与当前进度差异过大时,禁止改变当前进度.
 */
public class ArcSeekBar extends View {
    private static final int DEFAULT_EDGE_LENGTH = 250;              // 默认宽高

    private static final float CIRCLE_ANGLE = 360;                  // 圆周角
    private static final int DEFAULT_ARC_WIDTH = 40;                // 默认宽度 dp
    private static final float DEFAULT_OPEN_ANGLE = 120;            // 开口角度
    private static final float DEFAULT_ROTATE_ANGLE = 90;           // 旋转角度
    private static final int DEFAULT_BORDER_WIDTH = 0;              // 默认描边宽度
    private static final int DEFAULT_BORDER_COLOR = 0xffffffff;     // 默认描边颜色

    private static final int DEFAULT_THUMB_COLOR = 0xffffffff;      // 拖动按钮颜色
    private static final int DEFAULT_THUMB_WIDTH = 2;               // 拖动按钮描边宽度 dp
    private static final int DEFAULT_THUMB_RADIUS = 15;             // 拖动按钮半径 dp
    private static final int DEFAULT_THUMB_SHADOW_RADIUS = 0;       // 拖动按钮阴影半径 dp
    private static final int DEFAULT_THUMB_SHADOW_COLOR = 0xFF000000; // 拖动按钮阴影颜色

    private static final int DEFAULT_SHADOW_RADIUS = 0;             // 默认阴影半径 dp

    private static final int THUMB_MODE_STROKE = 0;                 // 拖动按钮模式 - 描边
    private static final int THUMB_MODE_FILL = 1;                   // 拖动按钮模式 - 填充
    private static final int THUMB_MODE_FILL_STROKE = 2;            // 拖动按钮模式 - 填充+描边

    private static final int DEFAULT_MAX_VALUE = 100;               // 默认最大数值
    private static final int DEFAULT_MIN_VALUE = 0;                 // 默认最小数值

    private static final String KEY_PROGRESS_PRESENT = "PRESENT";   // 用于存储和获取当前百分比

    // 可配置数据
    private int[] mArcColors;       // Seek 颜色
    private int[] mProgressColors;  // progress的颜色
    private float mArcWidth;        // Seek 宽度
    private float mOpenAngle;       // 开口的角度大小 0 - 360
    private float mRotateAngle;     // 旋转角度
    private int mBorderWidth;       // 描边宽度
    private int mBorderColor;       // 描边颜色

    private int mThumbColor;        // 拖动按钮颜色
    private float mThumbWidth;      // 拖动按钮宽度
    private float mThumbRadius;     // 拖动按钮半径
    private float mThumbShadowRadius;// 拖动按钮阴影半径
    private int mThumbShadowColor;// 拖动按钮阴影颜色
    private int mThumbMode;         // 拖动按钮模式

    private int mShadowRadius;      // 阴影半径

    private int mMaxValue;          // 最大数值
    private int mMinValue;          // 最小数值
    private int progress;           // 进度值

    private float mCenterX;         // 圆弧 SeekBar 中心点 X
    private float mCenterY;         // 圆弧 SeekBar 中心点 Y

    private float mThumbX;         // 拖动按钮 中心点 X
    private float mThumbY;         // 拖动按钮 中心点 Y

    private int mOffsetX;           //偏移量 X
    private int mOffsetY;           //偏移量 Y

    private Path mSeekPath;         // seekBar 绘制路径
    private Path mProgressPath;     // seekBar progress 绘制路径
    private Path mBorderPath;
    private Paint mArcPaint;
    private Paint mProgressPaint;   // seekBar progress 画笔
    private Paint mThumbPaint;
    private Paint mBorderPaint;
    private Paint mShadowPaint;

    private RectF content;

    private float[] mTempPos;
    private float[] mTempTan;
    private PathMeasure mSeekPathMeasure;
    private PathMeasure mProgressPathMeasure;

    private float mProgressPresent = 0;         // 当前进度百分比
    private boolean mCanDrag = false;           // 是否允许拖动
    private boolean mAllowTouchSkip = false;    // 是否允许越过边界
    private GestureDetector mDetector;
    private Matrix mInvertMatrix;               // 逆向 Matrix, 用于计算触摸坐标和绘制坐标的转换
    private Region mArcRegion;                  // ArcPath的实际区域大小,用于判定单击事件


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

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

    public ArcSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setSaveEnabled(true);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        initAttrs(context, attrs);
        initPaint();
        initData();
    }

    //--- 初始化 -----------------------------------------------------------------------------------

    // 初始化各种属性
    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ArcSeekBar);
        mArcColors = getArcColors(context, ta);
        mProgressColors = getProgressColors(context, ta);
        mArcWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_width, dp2px(DEFAULT_ARC_WIDTH));
        mOpenAngle = ta.getFloat(R.styleable.ArcSeekBar_arc_open_angle, DEFAULT_OPEN_ANGLE);
        mRotateAngle = ta.getFloat(R.styleable.ArcSeekBar_arc_rotate_angle, DEFAULT_ROTATE_ANGLE);
        mMaxValue = ta.getInt(R.styleable.ArcSeekBar_arc_max, DEFAULT_MAX_VALUE);
        mMinValue = ta.getInt(R.styleable.ArcSeekBar_arc_min, DEFAULT_MIN_VALUE);
        // 如果用户设置的最大值和最小值不合理,则直接按照默认进行处理
        if (mMaxValue <= mMinValue) {
            mMaxValue = DEFAULT_MAX_VALUE;
            mMinValue = DEFAULT_MIN_VALUE;
        }
        progress = ta.getInt(R.styleable.ArcSeekBar_arc_progress, mMinValue);

        mBorderWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_border_width, dp2px(DEFAULT_BORDER_WIDTH));
        mBorderColor = ta.getColor(R.styleable.ArcSeekBar_arc_border_color, DEFAULT_BORDER_COLOR);

        mThumbColor = ta.getColor(R.styleable.ArcSeekBar_arc_thumb_color, DEFAULT_THUMB_COLOR);
        mThumbRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_radius, dp2px(DEFAULT_THUMB_RADIUS));
        mThumbShadowRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_shadow_radius, dp2px(DEFAULT_THUMB_SHADOW_RADIUS));
        mThumbShadowColor = ta.getColor(R.styleable.ArcSeekBar_arc_thumb_shadow_color, DEFAULT_THUMB_SHADOW_COLOR);
        mThumbWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_width, dp2px(DEFAULT_THUMB_WIDTH));
        mThumbMode = ta.getInt(R.styleable.ArcSeekBar_arc_thumb_mode, THUMB_MODE_STROKE);

        mShadowRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_shadow_radius, dp2px(DEFAULT_SHADOW_RADIUS));
        mOffsetX = ta.getInt(R.styleable.ArcSeekBar_arc_offset_x, 0);
        mOffsetY = ta.getInt(R.styleable.ArcSeekBar_arc_offset_y, 0);
        ta.recycle();
    }

    // 获取 Arc 颜色数组
    private int[] getArcColors(Context context, TypedArray ta) {
        int[] ret;
        int resId = ta.getResourceId(R.styleable.ArcSeekBar_arc_progress_background, 0);
        if (0 == resId) {
            resId = R.array.arc_colors_default;
        }
        ret = getColorsByArrayResId(context, resId);
        return ret;
    }
    private int[] getProgressColors(Context context, TypedArray ta) {
        int[] ret;
        int resId = ta.getResourceId(R.styleable.ArcSeekBar_arc_progress_front, 0);
        if (0 == resId) {
            resId = R.array.arc_colors_default;
        }
        ret = getColorsByArrayResId(context, resId);
        return ret;
    }

    // 根据 resId 获取颜色数组
    private int[] getColorsByArrayResId(Context context, int resId) {
        int[] ret;
        TypedArray colorArray = context.getResources().obtainTypedArray(resId);
        ret = new int[colorArray.length()];
        for (int i = 0; i < colorArray.length(); i++) {
            ret[i] = colorArray.getColor(i, 0);
        }
        return ret;
    }

    // 初始化数据
    private void initData() {
        mSeekPath = new Path();
        mProgressPath = new Path();
        mBorderPath = new Path();
        mSeekPathMeasure = new PathMeasure();
        mProgressPathMeasure = new PathMeasure();
        mTempPos = new float[2];
        mTempTan = new float[2];

        mDetector = new GestureDetector(getContext(), new OnClickListener());
        mInvertMatrix = new Matrix();
        mArcRegion = new Region();
        setProgress(getProgress());
    }

    // 初始化画笔
    private void initPaint() {
        initArcPaint();
        initProgressPaint();
        initThumbPaint();
        initBorderPaint();
        initShadowPaint();
    }

    // 初始化圆弧画笔
    private void initArcPaint() {
        mArcPaint = new Paint();
        mArcPaint.setAntiAlias(true);
        mArcPaint.setStrokeWidth(mArcWidth);
        mArcPaint.setStyle(Paint.Style.STROKE);
        mArcPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    private void initProgressPaint() {
        mProgressPaint = new Paint();
        mProgressPaint.setAntiAlias(true);
        mProgressPaint.setStrokeWidth(mArcWidth);
        mProgressPaint.setStyle(Paint.Style.STROKE);
        mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    // 初始化拖动按钮画笔
    private void initThumbPaint() {
        mThumbPaint = new Paint();
        mThumbPaint.setAntiAlias(true);
        mThumbPaint.setColor(mThumbColor);
        mThumbPaint.setStrokeWidth(mThumbWidth);
        mThumbPaint.setStrokeCap(Paint.Cap.ROUND);
        if (mThumbMode == THUMB_MODE_FILL) {
            mThumbPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        } else if (mThumbMode == THUMB_MODE_FILL_STROKE) {
            mThumbPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        } else {
            mThumbPaint.setStyle(Paint.Style.STROKE);
        }
        mThumbPaint.setTextSize(56);
    }

    // 初始化拖动按钮画笔
    private void initBorderPaint() {
        mBorderPaint = new Paint();
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);
        mBorderPaint.setStyle(Paint.Style.STROKE);
    }

    // 初始化阴影画笔
    private void initShadowPaint() {
        mShadowPaint = new Paint();
        mShadowPaint.setAntiAlias(true);
        mShadowPaint.setStrokeWidth(mBorderWidth);
        mShadowPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    }

    //--- 初始化结束 -------------------------------------------------------------------------------

    //--- 状态存储 ---------------------------------------------------------------------------------

    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable("superState", super.onSaveInstanceState());
        bundle.putFloat(KEY_PROGRESS_PRESENT, mProgressPresent);
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            this.mProgressPresent = bundle.getFloat(KEY_PROGRESS_PRESENT);
            state = bundle.getParcelable("superState");
        }
        if (null != mOnProgressChangeListener) {
            mOnProgressChangeListener.onProgressChanged(this, getProgress(), false);
        }
        super.onRestoreInstanceState(state);
    }

    //--- 状态存储结束 -----------------------------------------------------------------------------

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int ws = MeasureSpec.getSize(widthMeasureSpec);     //取出宽度的确切数值
        int wm = MeasureSpec.getMode(widthMeasureSpec);     //取出宽度的测量模式
        int hs = MeasureSpec.getSize(heightMeasureSpec);    //取出高度的确切数值
        int hm = MeasureSpec.getMode(heightMeasureSpec);    //取出高度的测量模

        if (wm == MeasureSpec.UNSPECIFIED) {
            wm = MeasureSpec.EXACTLY;
            ws = dp2px(DEFAULT_EDGE_LENGTH);
        } else if (wm == MeasureSpec.AT_MOST) {
            wm = MeasureSpec.EXACTLY;
            ws = Math.min(dp2px(DEFAULT_EDGE_LENGTH), ws);
        }
        if (hm == MeasureSpec.UNSPECIFIED) {
            hm = MeasureSpec.EXACTLY;
            hs = dp2px(DEFAULT_EDGE_LENGTH);
        } else if (hm == MeasureSpec.AT_MOST) {
            hm = MeasureSpec.EXACTLY;
            hs = Math.min(dp2px(DEFAULT_EDGE_LENGTH), hs);
        }
        setMeasuredDimension(MeasureSpec.makeMeasureSpec(ws, wm), MeasureSpec.makeMeasureSpec(hs, hm));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 计算在当前大小下,内容应该显示的大小和起始位置
        int safeW = w - getPaddingLeft() - getPaddingRight();
        int safeH = h - getPaddingTop() - getPaddingBottom();
        float edgeLength, startX, startY;
        float fix = mArcWidth / 2 + mBorderWidth + mShadowRadius * 2;  // 修正距离,画笔宽度的修正
        if (safeW < safeH) {
            // 宽度小于高度,以宽度为准
            edgeLength = safeW - fix;
            startX = getPaddingLeft();
            startY = (safeH - safeW) / 2.0f + getPaddingTop();
        } else {
            // 宽度大于高度,以高度为准
            edgeLength = safeH - fix;
            startX = (safeW - safeH) / 2.0f + getPaddingLeft();
            startY = getPaddingTop();
        }

        // 得到显示区域和中心的
        content = new RectF(startX + fix, startY + fix, startX + edgeLength, startY + edgeLength);
        mCenterX = content.centerX();
        mCenterY = content.centerY();

        // 得到路径
        mSeekPath.reset();
        mSeekPath.addArc(content, mOpenAngle / 2, CIRCLE_ANGLE - mOpenAngle);
        mSeekPathMeasure.setPath(mSeekPath, false);
        computeThumbPos(mProgressPresent);
        resetShaderColor();

        // 绘制progress路径
        mProgressPath.reset();
        float total = CIRCLE_ANGLE - mOpenAngle;//100%进度的扇形角度
        float percent = ((float) getProgress() / (mMaxValue - mMinValue));
        mProgressPath.addArc(content, mOpenAngle / 2, total * percent);
        mProgressPathMeasure.setPath(mProgressPath, false);//是否闭合开口,设置成false
        resetProgressColor();


        mInvertMatrix.reset();
        mInvertMatrix.preRotate(-mRotateAngle, mCenterX, mCenterY);

        mArcPaint.getFillPath(mSeekPath, mBorderPath);
        mBorderPath.close();
        mArcRegion.setPath(mBorderPath, new Region(0, 0, w, h));
    }

    // 重置 shader 颜色
    private void resetShaderColor() {
        // 计算渐变数组
        float startPos = (mOpenAngle / 2) / CIRCLE_ANGLE;
        float stopPos = (CIRCLE_ANGLE - (mOpenAngle / 2)) / CIRCLE_ANGLE;

        int len = mArcColors.length - 1;
        float distance = (stopPos - startPos) / len;
        float pos[] = new float[mArcColors.length];
        for (int i = 0; i < mArcColors.length; i++) {
            pos[i] = startPos + (distance * i);
        }
        SweepGradient gradient = new SweepGradient(mCenterX, mCenterY, mArcColors, pos);
        mArcPaint.setShader(gradient);
    }

    private void resetProgressColor() {
        // 计算渐变数组
        float startPos = (mOpenAngle / 2) / CIRCLE_ANGLE;
        float stopPos = (CIRCLE_ANGLE - (mOpenAngle / 2)) / CIRCLE_ANGLE;

        int len = mProgressColors.length - 1;
        float distance = (stopPos - startPos) / len;
        float pos[] = new float[mProgressColors.length];
        for (int i = 0; i < mProgressColors.length; i++) {
            pos[i] = startPos + (distance * i);
        }
        SweepGradient gradient = new SweepGradient(mCenterX, mCenterY, mProgressColors, pos);
        mProgressPaint.setShader(gradient);
        invalidate();
    }

    // 具体绘制
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.save();
        canvas.rotate(mRotateAngle, mCenterX + mOffsetX, mCenterY + mOffsetY);
        mShadowPaint.setShadowLayer(mShadowRadius * 2, 0, 0, getColor());
        canvas.drawPath(mBorderPath, mShadowPaint);
        canvas.drawPath(mSeekPath, mArcPaint);

        mProgressPath.reset();
        float total = CIRCLE_ANGLE - mOpenAngle;//100%进度的扇形角度
        float percent = ((float) getProgress() / (mMaxValue - mMinValue));
        mProgressPath.addArc(content, mOpenAngle / 2, total * percent);
        mProgressPathMeasure.setPath(mProgressPath, false);//是否闭合开口,设置成false
        resetProgressColor();

        canvas.drawPath(mProgressPath, mProgressPaint);
        if (mBorderWidth > 0) {
            canvas.drawPath(mBorderPath, mBorderPaint);
        }
        if (mThumbShadowRadius > 0) {
            mThumbPaint.setShadowLayer(mThumbShadowRadius, 0, 0, mThumbShadowColor);
            canvas.drawCircle(mThumbX, mThumbY, mThumbRadius, mThumbPaint);
            mThumbPaint.clearShadowLayer();
        }
        canvas.drawCircle(mThumbX, mThumbY, mThumbRadius, mThumbPaint);
        canvas.restore();
    }

    private boolean moved = false;
    private int lastProgress = -1;

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        int action = event.getActionMasked();
        switch (action) {
            case ACTION_DOWN:
                moved = false;
                judgeCanDrag(event);
                if (null != mOnProgressChangeListener) {
                    mOnProgressChangeListener.onStartTrackingTouch(this);
                }
                break;
            case ACTION_MOVE:
                if (!mCanDrag) {
                    break;
                }
                float tempProgressPresent = getCurrentProgress(event.getX(), event.getY());
                if (!mAllowTouchSkip) {
                    // 不允许突变
                    if (Math.abs(tempProgressPresent - mProgressPresent) > 0.5f) {
                        break;
                    }
                }
                // 允许突变 或者非突变
                mProgressPresent = tempProgressPresent;
                computeThumbPos(mProgressPresent);
                // 事件回调
                if (null != mOnProgressChangeListener && getProgress() != lastProgress) {
                    mOnProgressChangeListener.onProgressChanged(this, getProgress(), true);
                    lastProgress = getProgress();
                }
                moved = true;
                resetProgressColor();
                break;
            case ACTION_UP:
            case ACTION_CANCEL:
                if (null != mOnProgressChangeListener && moved) {
                    mOnProgressChangeListener.onStopTrackingTouch(this);
                }
                break;
        }
        mDetector.onTouchEvent(event);
        invalidate();
        return true;
    }

    // 判断是否允许拖动
    private void judgeCanDrag(MotionEvent event) {
        float[] pos = {event.getX(), event.getY()};
        mInvertMatrix.mapPoints(pos);
        if (getDistance(pos[0], pos[1]) <= mThumbRadius * 1.5) {
            mCanDrag = true;
        } else {
            mCanDrag = false;
        }
    }

    private class OnClickListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            // 判断是否点击在了进度区域
            if (!isInArcProgress(e.getX(), e.getY())) return false;
            // 点击允许突变
            mProgressPresent = getCurrentProgress(e.getX(), e.getY());
            computeThumbPos(mProgressPresent);
            // 事件回调
            if (null != mOnProgressChangeListener) {
                mOnProgressChangeListener.onProgressChanged(ArcSeekBar.this, getProgress(), true);
                mOnProgressChangeListener.onStopTrackingTouch(ArcSeekBar.this);
            }
            return true;
        }
    }

    // 判断该点是否在进度条上面
    private boolean isInArcProgress(float px, float py) {
        float[] pos = {px, py};
        mInvertMatrix.mapPoints(pos);
        return mArcRegion.contains((int) pos[0], (int) pos[1]);
    }

    // 获取当前进度理论进度数值
    private float getCurrentProgress(float px, float py) {
        float diffAngle = getDiffAngle(px, py);
        float progress = diffAngle / (CIRCLE_ANGLE - mOpenAngle);
        if (progress < 0) progress = 0;
        if (progress > 1) progress = 1;
        return progress;
    }

    // 获得当前点击位置所成角度与开始角度之间的数值差
    private float getDiffAngle(float px, float py) {
        float angle = getAngle(px, py);
        float diffAngle;
        diffAngle = angle - mRotateAngle;
        if (diffAngle < 0) {
            diffAngle = (diffAngle + CIRCLE_ANGLE) % CIRCLE_ANGLE;
        }
        diffAngle = diffAngle - mOpenAngle / 2;
        return diffAngle;
    }

    // 计算指定位置与内容区域中心点的夹角
    private float getAngle(float px, float py) {
        float angle = (float) ((Math.atan2(py - mCenterY, px - mCenterX)) * 180 / 3.14f);
        if (angle < 0) {
            angle += 360;
        }
        return angle;
    }

    // 计算指定位置与上次位置的距离
    private float getDistance(float px, float py) {
        return (float) Math.sqrt((px - mThumbX) * (px - mThumbX) + (py - mThumbY) * (py - mThumbY));
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
    }

    // 计算拖动块应该显示的位置
    private void computeThumbPos(float present) {
        if (present < 0) present = 0;
        if (present > 1) present = 1;
        if (null == mSeekPathMeasure) return;
        float distance = mSeekPathMeasure.getLength() * present;
        mSeekPathMeasure.getPosTan(distance, mTempPos, mTempTan);
        mThumbX = mTempPos[0];
        mThumbY = mTempPos[1];
    }

    //--- 线性取色 ---------------------------------------------------------------------------------

    /**
     * 获取当前进度的具体颜色
     *
     * @return 当前进度在渐变中的颜色
     */
    public int getColor() {
        return getColor(mProgressPresent);
    }

    /**
     * 获取某个百分比位置的颜色
     *
     * @param radio 取值[0,1]
     * @return 最终颜色
     */
    private int getColor(float radio) {
        float diatance = 1.0f / (mArcColors.length - 1);
        int startColor;
        int endColor;
        if (radio >= 1) {
            return mArcColors[mArcColors.length - 1];
        }
        for (int i = 0; i < mArcColors.length; i++) {
            if (radio <= i * diatance) {
                if (i == 0) {
                    return mArcColors[0];
                }
                startColor = mArcColors[i - 1];
                endColor = mArcColors[i];
                float areaRadio = getAreaRadio(radio, diatance * (i - 1), diatance * i);
                return getColorFrom(startColor, endColor, areaRadio);
            }
        }
        return -1;
    }

    /**
     * 计算当前比例在子区间的比例
     *
     * @param radio         总比例
     * @param startPosition 子区间开始位置
     * @param endPosition   子区间结束位置
     * @return 自区间比例[0, 1]
     */
    private float getAreaRadio(float radio, float startPosition, float endPosition) {
        return (radio - startPosition) / (endPosition - startPosition);
    }

    /**
     * 取两个颜色间的渐变区间 中的某一点的颜色
     *
     * @param startColor 开始的颜色
     * @param endColor   结束的颜色
     * @param radio      比例 [0, 1]
     * @return 选中点的颜色
     */
    private int getColorFrom(int startColor, int endColor, float radio) {
        int redStart = Color.red(startColor);
        int blueStart = Color.blue(startColor);
        int greenStart = Color.green(startColor);
        int redEnd = Color.red(endColor);
        int blueEnd = Color.blue(endColor);
        int greenEnd = Color.green(endColor);

        int red = (int) (redStart + ((redEnd - redStart) * radio + 0.5));
        int greed = (int) (greenStart + ((greenEnd - greenStart) * radio + 0.5));
        int blue = (int) (blueStart + ((blueEnd - blueStart) * radio + 0.5));
        return Color.argb(255, red, greed, blue);
    }


    //region 对外接口 -------------------------------------------------------------------------------

    /**
     * 设置进度
     *
     * @param progress 进度值
     */
    public void setProgress(int progress) {
        System.out.println("setProgress = " + progress);
        if (progress > mMaxValue) progress = mMaxValue;
        if (progress < mMinValue) progress = mMinValue;
        mProgressPresent = (progress - mMinValue) * 1.0f / (mMaxValue - mMinValue);
        System.out.println("setProgress present = " + mProgressPresent);
        if (null != mOnProgressChangeListener) {
            mOnProgressChangeListener.onProgressChanged(this, progress, false);
        }
        computeThumbPos(mProgressPresent);
        resetProgressColor();
        postInvalidate();
    }
    public void setProgress() {
        setProgress(getProgress());
    }

    /**
     * 获取当前进度数值
     *
     * @return 当前进度数值
     */
    public int getProgress() {
        return (int) (mProgressPresent * (mMaxValue - mMinValue)) + mMinValue;
    }

    /**
     * 设置颜色
     *
     * @param colors 颜色
     */
    public void setArcColors(int[] colors) {
        mArcColors = colors;
        resetShaderColor();
        resetProgressColor();
        postInvalidate();
    }

    /**
     * 设置最大数值
     * @param max 最大数值
     */
    public void setMaxValue(int max) {
        mMaxValue = max;
    }

    /**
     * 设置最小数值
     * @param min 最小数值
     */
    public void setMinValue(int min) {
        mMinValue = min;
    }

    /**
     * 设置颜色
     *
     * @param colorArrayRes 颜色资源 R.array.arc_color
     */
    public void setArcColors(int colorArrayRes) {
        setArcColors(getColorsByArrayResId(getContext(), colorArrayRes));
    }

    // endregion -----------------------------------------------------------------------------------
    // region 状态回调 ------------------------------------------------------------------------------

    private OnProgressChangeListener mOnProgressChangeListener;

    public void setOnProgressChangeListener(OnProgressChangeListener onProgressChangeListener) {
        mOnProgressChangeListener = onProgressChangeListener;
    }

    public interface OnProgressChangeListener {
        /**
         * 进度发生变化
         *
         * @param seekBar  拖动条
         * @param progress 当前进度数值
         * @param isUser   是否是用户操作, true 表示用户拖动, false 表示通过代码设置
         */
        void onProgressChanged(ArcSeekBar seekBar, int progress, boolean isUser);

        /**
         * 用户开始拖动
         *
         * @param seekBar 拖动条
         */
        void onStartTrackingTouch(ArcSeekBar seekBar);

        /**
         * 用户结束拖动
         *
         * @param seekBar 拖动条
         */
        void onStopTrackingTouch(ArcSeekBar seekBar);
    }
}

attrs.xml

<declare-styleable name="ArcSeekBar">
        <attr name="arc_width" format="dimension|reference" />
        <attr name="arc_open_angle" format="float" />
        <attr name="arc_rotate_angle" format="float" />
<!--        <attr name="arc_colors" format="reference" />-->
        <attr name="arc_border_width" format="dimension|reference" />
        <attr name="arc_border_color" format="color|reference" />
        <attr name="arc_max" format="integer|reference" />
        <attr name="arc_min" format="integer|reference" />
        <attr name="arc_progress" format="integer|reference" />
        <attr name="arc_progress_background" format="reference" />
        <attr name="arc_progress_front" format="reference" />
        <attr name="arc_thumb_width" format="dimension|reference" />
        <attr name="arc_thumb_color" format="color|reference" />
        <attr name="arc_thumb_radius" format="dimension|reference" />
        <attr name="arc_thumb_shadow_radius" format="dimension|reference" />
        <attr name="arc_thumb_shadow_color" format="color|reference" />
        <attr name="arc_thumb_mode" format="integer|dimension">
            <enum name="STROKE" value="0" />
            <enum name="FILL" value="1" />
            <enum name="FILL_STROKE" value="2" />
        </attr>
        <attr name="arc_shadow_radius" format="dimension|reference" />
        <attr name="arc_offset_x" format="integer" />
        <attr name="arc_offset_y" format="integer" />
    </declare-styleable>

color颜色值:

<array name="arc_colors_default">
        <item>#343434</item>
        <item>#626262</item>
        <item>#fff</item>
    </array>
    <array name="arc_colors_1">
        <item>#FFE881</item>
        <item>#fefefe</item>
        <item>#68D1F4</item>
    </array>

动图中 我的应用实例:

<ArcSeekBar
                android:id="@+id/arc_seek_bar"
                android:layout_width="280dp"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                app:arc_max="100"
                app:arc_open_angle="90"
                app:arc_progress="0"
                app:arc_progress_background="@array/arc_colors_default"
                app:arc_progress_front="@array/arc_colors_1"
                app:arc_rotate_angle="90"
                app:arc_thumb_color="#fff"
                app:arc_thumb_mode="FILL"
                app:arc_thumb_radius="16dp"
                app:arc_thumb_shadow_color="#000000"
                app:arc_thumb_shadow_radius="3dp"
                app:arc_thumb_width="3dp"
                app:arc_width="20dp"
                app:arc_offset_x="-20"
                app:arc_offset_y="20"
                />

 我修改的属性

namedescformatdefault
arc_progress_background背景色
reference
arc_colors_default
arc_progress_front前景色
reference
arc_colors_default
arc_offset_x圆环X轴偏移量int0
arc_offset_y圆环Y轴偏移量int0

 

 前景色和后景色是从作者的arc_colors属性演变而来的

偏移量的目的:因为圆环宽度如果小于滑块宽度,会出现 滑块被裁剪的现象默认为0

功能简单,我就不放GitHub了。有心人可以去给原作者一个star

再次感谢作者:GcsSloop

作者的GitHub:GitHub - GcsSloop/arc-seekbar: Android 圆弧形 SeekBar。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值