Android自定义View实现可拖拽的进度条
2021-09-01 本文已影响0人
itfitness
目录
效果展示
实现步骤
1.计算出控件宽度的直线路径
在onSizeChanged方法中进行计算,这时可以得到一条与控件宽度相同的直线,并把路径设置给PathMeasure
@Override
protected 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);
//将进度条路径设置给PathMeasure
mPathMeasure.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方法进行处理,也就是当手指触摸矩形区域的时候,根据手指横向滑动的偏移来设置当前的进度,具体如下
@Override
public 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属性,我们需要计算出控件最小需要显示的高度
这里我们是用显示进度的矩形的高度作为控件最小的高度的,因为矩形的高度是所有图形最高的一个
而矩形的高度又是文字的大小与边距之和
@Override
protected 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);
}
}