Android自定义View实现可拖拽的进度条
目录

效果展示

实现步骤
1、计算出控件宽度的直线路径
在onSizeChanged方法中进行计算,这时可以得到一条与控件宽度相同的直线,并把路径设置给PathMeasure
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);//进度条绘制在控件中央,宽度为控件宽度(mProgressHeight/2是为了显示出左右两边的圆角)mPathProgressBg.moveTo(mProgressHeight / 2,h / 2f);mPathProgressBg.lineTo(w - mProgressHeight / 2,h / 2f);//将进度条路径设置给PathMeasuremPathMeasure.setPath(mPathProgressBg,false);invalidate();}

2、计算当前进度的路径
使用PathMeasure得出当前进度的路径并进行绘制,这里我将上一步的绘制放在了一起
private void drawProgress(Canvas canvas) {mPathProgressFg.reset();mPaintProgress.setColor(mColorProgressBg);//绘制进度背景canvas.drawPath(mPathProgressBg, mPaintProgress);//计算进度条的进度float stop = mPathMeasure.getLength() * mProgress;//得到与进度对应的路径mPathMeasure.getSegment(0,stop,mPathProgressFg,true);mPaintProgress.setColor(mColorProgressFg);//绘制进度canvas.drawPath(mPathProgressFg, mPaintProgress);}

3、计算显示进度的圆角矩形
这个矩形的宽度需要我们用绘制最长的文字来确定其宽高

另外矩形的显示位置也是以当前进度所在的点为中心点

private void drawShowProgressRoundRect(Canvas canvas) {float stop = mPathMeasure.getLength() * mProgress;//计算进度条的进度//根据要绘制的文字的最大长宽来计算要绘制的圆角矩形的长宽Rect rect = new Rect();mPaintProgressText.getTextBounds(mProgressMaxText,0, mProgressMaxText.length(),rect);//要绘制矩形的宽、高float rectWidth = rect.width() + (mProgressStrMarginH * 2);float rectHeight = rect.height() + (mProgressStrMarginV * 2);//计算边界值(为了不让矩形在左右两边超出边界)if(stop < rectWidth / 2f){stop = rectWidth / 2f;}else if(stop > (getWidth() - rectWidth / 2f)){stop = getWidth() - rectWidth / 2f;}//定义绘制的矩形float left = stop - rectWidth / 2f;float right = stop + rectWidth / 2f;float top = getHeight() / 2f - rectHeight / 2f;float bottom = getHeight() / 2f + rectHeight / 2f;mProgressRoundRectF = new RectF(left,top,right,bottom);//绘制为圆角矩形canvas.drawRoundRect(mProgressRoundRectF, mRoundRectRadius, mRoundRectRadius,mPaintRoundRect);}

4、计算文字的显示位置
文字显示的位置计算起来就比较简单了,直接用上一步算出的矩形的中心点即可,不过这里需要调整文字绘制的垂直的偏移,这样才能实现文字垂直居中
private void drawProgressText(Canvas canvas) {String progressText = (int)Math.floor(100 * mProgress) + "%";//让文字垂直居中的偏移int offsetY = (mFontMetricsInt.bottom - mFontMetricsInt.ascent) / 2 - mFontMetricsInt.bottom;//将文字绘制在矩形的中央canvas.drawText(progressText,mProgressRoundRectF.centerX(),mProgressRoundRectF.centerY() + offsetY,mPaintProgressText);}

5、实现拖拽
实现拖拽需要对onTouchEvent方法进行处理,也就是当手指触摸矩形区域的时候,根据手指横向滑动的偏移来设置当前的进度,具体如下:
@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_DOWN://判断手指是否触摸了显示进度的圆角矩形块,这样才可以拖拽if(mProgressRoundRectF != null && mProgressRoundRectF.contains(event.getX(),event.getY())){//记录手指刚接触屏幕的X轴坐标(因为只需要在X轴上平移)mStartTouchX = event.getX();mIsTouchSeek = true;}break;case MotionEvent.ACTION_MOVE:if(mIsTouchSeek){//计算横向移动的距离float moveX = event.getX() - mStartTouchX;//计算出当前进度的X轴所显示的进度长度float currentProgressWidth = mPathMeasure.getLength() * mProgress;//计算进度条的进度//计算滑动后的X轴的坐标float showProgressWidth = currentProgressWidth + moveX;//计算边界值if(showProgressWidth < 0){showProgressWidth = 0;}else if(showProgressWidth > mPathMeasure.getLength()){showProgressWidth = mPathMeasure.getLength();}//计算滑动后的进度mProgress = showProgressWidth / mPathMeasure.getLength();//重绘invalidate();//刷新用于计算移动的X轴坐标mStartTouchX = event.getX();}break;case MotionEvent.ACTION_UP:mIsTouchSeek = false;break;}return mIsTouchSeek;}
6、计算当前自定义View的宽高
为了适配高度的wrap_content属性,我们需要计算出控件最小需要显示的高度

这里我们是用显示进度的矩形的高度作为控件最小的高度的,因为矩形的高度是所有图形最高的一个

而矩形的高度又是文字的大小与边距之和

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(measureSizeWidth(widthMeasureSpec), measureSizeHeight(heightMeasureSpec));}//计算宽度private int measureSizeWidth(int size) {int mode = MeasureSpec.getMode(size);int s = MeasureSpec.getSize(size);if (mode == MeasureSpec.EXACTLY) {return s;} else{return Math.min(s, 200);}}//计算高度private int measureSizeHeight(int size) {int mode = MeasureSpec.getMode(size);int s = MeasureSpec.getSize(size);if (mode == MeasureSpec.EXACTLY) {return s;}else {//自适应模式,返回所需的最小高度return (int) (mTextSize + mProgressStrMarginV * 2);}}
源码地址:
https://gitee.com/itfitness/seek-progress-bar
评论
