1. 程式人生 > >自定義view,可拖拽進度和吸附效果的圓形進度條

自定義view,可拖拽進度和吸附效果的圓形進度條

前言

最近接到一個需求,第一眼看到ui互動效果時,瞬間想對產品小哥說“尼瑪,這麼會玩,你咋不上天”。確認了具體互動細節,喝了兩口農夫三拳,開始了兩耳不聞窗外事,一心只想擼程式碼的過程。

先上ui效果

這裡寫圖片描述
說明:

  • 外圈弧形上面是進度的標記點,預設在12點位置,也是progress 0
  • 在圓環範圍內,可以任意拖拽進度的標記點,當拖拽結束鬆手的時候,會自動吸附在外圈弧形對應的progress位置
  • 如果進度標記點拖拽的座標位置在圓環以外,那麼標記點的位置自動限制在外圈弧形上面

下面是整個自定義view的程式碼:

public class RoundProgressView extends View {

    /**context*/
    private Context mContext;

    /**整個view的寬度*/
    private int mViewWidth;
    /**整個view的高度*/
    private int mViewHeight;
    /**整個view的中心X座標*/
    private float mCenterX;
    /**整個view的中心y座標*/
    private float mCenterY;
	/**圓環距離view的間距*/
    private float mOuterMargin;
    
    /**外層弧線畫布*/
    private RectF mDashRect = new RectF();
    /**外層弧線的畫筆*/
    private Paint mDashPaint;

	/**外層圓的畫筆*/
    private Paint mOuterPaint;
    /**外層圓半徑*/
    private float mOuterRadius;
   
    /**內層圓的畫筆*/
    private Paint mInnerPaint;
    /**內層圓半徑*/
    private float mInnerRadius;
    
    /**中心點畫布*/
    private RectF mCenterRect = new RectF();
    /**中心點的畫筆*/
    private Paint mCenterPaint;
    /**中心點背景圖*/
    private Bitmap mCenterBitmap;

    /**進度標記icon的半徑*/
    private static final int PROGRESS_RADIUS = 15;
    /**進度標記畫布*/
    private RectF mProgressRect = new RectF();
    /**進度標記畫筆*/
    private Paint mProgressPaint;
    /**進度標記*/
    private Bitmap mProgressBitmap;
    /**進度標記的半徑*/
    private  int mProgressRadius;
    /**進度條最大值*/
    private static final int MAX_PROGRESS = 100;
    /**當前的百分值*/
    private int mProgress;
    /**進度條標誌移動後的角度, 0 ~ 360*/
    private int mAngle = 0;
    /**進度標記開始位置的x座標*/
    private float mProgressPointX;
    /**進度標記開始位置的y座標*/
    private float mProgressPointY;

    /**進度標記到圓心的距離*/
    private float mDistance;

    /**進度條變化監聽*/
    private OnProgressChangeListener mOnProgressChangeListener;

    /**是否手指按下的標誌位*/
    private boolean IS_PRESSED = false;

    public RoundProgressView(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public RoundProgressView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    public RoundProgressView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        init();
    }

    private void init() {
        mDashPaint = new Paint();
        mDashPaint.setColor(Color.parseColor("#ff7690"));
        mDashPaint.setAntiAlias(true);
        mDashPaint.setStrokeWidth(3f);
        mDashPaint.setStyle(Paint.Style.STROKE);

        mOuterPaint = new Paint();
        mOuterPaint.setColor(Color.parseColor("#201617"));
        mOuterPaint.setAntiAlias(true);

        mInnerPaint = new Paint();
        mInnerPaint.setColor(Color.parseColor("#402328"));
        mInnerPaint.setAntiAlias(true);

        //進度標記
        mProgressBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.progress_mark);
        mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mProgressRadius = dp2px(mContext, PROGRESS_RADIUS);

        //中心圖示
        mCenterBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.center_icon);
        mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //整個view的高度
        mViewWidth = getWidth();
        //整個view的寬度
        mViewHeight = getHeight();

        //x軸中心點
        mCenterX = mViewWidth / 2;
        //y軸中心點
        mCenterY = mViewHeight / 2;

        //整個view的大小
        int viewSize = ((mViewWidth > mViewHeight) ? mViewHeight : mViewWidth) / 2;

        //外圈半徑
        mOuterRadius = viewSize * 70 / 100;

        //外圈距離view的間距
        mOuterMargin = viewSize - mOuterRadius;

        //外圈虛線
        float dashLeft = mCenterX - mOuterRadius;
        float dashTop = mCenterY - mOuterRadius;
        float dashRight = mCenterX + mOuterRadius;
        float dashBottom = mCenterY + mOuterRadius;
        mDashRect.set(dashLeft, dashTop, dashRight, dashBottom);

        //內層圈半徑
        mInnerRadius = mOuterRadius * 30 / 100;
        //中心點圖示
        float centerLeft = mCenterX - (mInnerRadius / 2);
        float centerTop = mCenterY - (mInnerRadius / 2);
        float centerRight = mCenterX + (mInnerRadius / 2);
        float centerBottom = mCenterY + (mInnerRadius / 2);
        mCenterRect.set(centerLeft, centerTop, centerRight, centerBottom);

        //進度條標誌開始位置X軸座標
        mProgressPointX = getXByProgress(mProgress);
        //進度條標誌開始位置X軸座標
        mProgressPointY = getYByProgress(mProgress);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawCircle(mCenterX, mCenterY, mOuterRadius, mOuterPaint);
        canvas.drawCircle(mCenterX, mCenterY, mInnerRadius, mInnerPaint);
        canvas.drawArc(mDashRect, 0, 360, false, mDashPaint);

        //繪製中心點
        canvas.drawBitmap(mCenterBitmap, null, mCenterRect, mCenterPaint);

        //繪製Progress
        float progressLeft = mProgressPointX - mProgressRadius;
        float progressTop = mProgressPointY - mProgressRadius;
        float progressRight = mProgressPointX + mProgressRadius;
        float progressBottom = mProgressPointY + mProgressRadius;
        //進度標記座標
        mProgressRect.set(progressLeft, progressTop, progressRight, progressBottom);
        canvas.drawBitmap(mProgressBitmap, null, mProgressRect, mProgressPaint);

        super.onDraw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        boolean up = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                drawProgress(x, y, up);
                break;
            case MotionEvent.ACTION_MOVE:
                drawProgress(x, y, up);
                break;
            case MotionEvent.ACTION_UP:
                up = true;
                drawProgress(x, y, up);
                break;
        }
        return true;
    }

    private float getXByProgress(int progress) {
        float x = 0;
        float angle = (float) (2 * progress * Math.PI / 100);
        x = (float) (mCenterX + mOuterRadius * Math.cos(angle - Math.PI / 2));
        return x;
    }

    private float getYByProgress(int progress) {
        float y = 0;
        float angle = (float) (2 * progress * Math.PI / 100);
        y = (float) (mCenterY + mOuterRadius * Math.sin(angle - Math.PI / 2));
        return y;
    }

    /**
     * 繪製進度標記
     *
     * @param x  the x
     * @param y  the y
     * @param up the up
     */
    private void drawProgress(float x, float y, boolean up) {
        //觸控點到圓心的間距
        mDistance = (float) Math.sqrt(Math.pow((x - mCenterX), 2) + Math.pow((y - mCenterY), 2));

        if (mDistance < mOuterRadius + mOuterMargin && !up) {
            IS_PRESSED = true;

            mProgressPointX = x;
            mProgressPointY = y;

            float degrees = (float) ((float) ((Math.toDegrees(Math.atan2(x - mCenterX, mCenterY - y)) + 360.0)) % 360.0);
            if (degrees < 0) {
                degrees += 2 * Math.PI;
            }
            setAngle(Math.round(degrees));
        } else {
            IS_PRESSED = false;
            //計算進度標記在外圈軌道上X,Y的座標,(ACTION_UP時自動回彈到外圈軌道對應角度的座標)
            mProgressPointX = (float) (mCenterX + mOuterRadius * Math.cos(Math.atan2(x - mCenterX, mCenterY - y) - (Math.PI / 2)));
            mProgressPointY = (float) (mCenterY + mOuterRadius * Math.sin(Math.atan2(x - mCenterX, mCenterY - y) - (Math.PI / 2)));

        }
        invalidate();
    }

    /**
     * 設定圓弧的角度
     *
     * @param angle
     */
    private void setAngle(int angle) {
        mAngle = angle;
        float donePercent = (((float) mAngle) / 360) * 100;
        //通過角度計算當前進度,範圍:0 ~ 100
        float progress = (donePercent / 100) * 100;
        setProgressLoca(Math.round(progress));
    }

    private void setProgressLoca(int progress) {
        mProgress = progress;
        if (!IS_PRESSED) {
            int newPercent = (mProgress * 100) / MAX_PROGRESS;
            int newAngle = (newPercent * 360) / 100;
            setAngle(newAngle);
        }
        mOnProgressChangeListener.onProgressChange(getProgress(), getDistance());
    }

    /**
     * 獲取progress
     *
     * @return
     */
    public int getProgress() {
        return mProgress;
    }

    /**
     * 獲取progress
     *
     * @return
     */
    public void setProgress(int progress) {
        mProgress = progress;
    }

    /**
     * 獲取progress標記點到圓心的距離
     *
     * @return
     */
    private float getDistance() {
        return mDistance;
    }

    public void setProgressChangeListener(OnProgressChangeListener listener) {
        mOnProgressChangeListener = listener;
    }

    interface OnProgressChangeListener {
        /**
         * 進度改變的回撥方法
         * @param progress  當前進度
         * @param distance  當前進度座標點到圓心的距離
         */
        void onProgressChange(int progress, float distance);
    }

    private int dp2px(Context context,float dpValue){
        float scale=context.getResources().getDisplayMetrics().density;
        return (int)(dpValue * scale + 0.5f);
    }
}