自定義view,可拖拽進度和吸附效果的圓形進度條
阿新 • • 發佈:2018-12-31
前言
最近接到一個需求,第一眼看到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); } }