效果图预览
1. 分析
1. 游标卡尺嘛首先得绘制一个尺子吧
2. 有了尺子不得绘制刻度线
3. 绘制具体刻度值
4. 还要绘制能拨动的按钮,不然怎么叫游标卡尺
5. 拨动尺子的时候应该是能实时看到数据
2. 技术实现原理
1. 尺子组成是一个长长的进度条加上带刻度线和刻度值的数据
2. 进度条可以用canvas.drawRect画一个矩形
3. 刻度线和刻度值分别用canvas.drawLine和canvas.drawText实现
4. onTouchEvent处理手指拖动游标按钮拨动事件
5. 按钮拨动的过程根据x轴的偏移值实时绘制拨动会的数据值
3.初始化一些东西
//刻度尺进度条的高度
private int mPbHeight;
//刻度长线的高度
private float mLineMaxHeight;
//刻度短线的高度
private float mLineMinHeight;
//每一小份刻度的宽度
private float mPartWidth;
//图片左右最小最大值
private int mLeftMin;
private int mRightMax;
//刻度的左右间距
private int mLeftMargin;
private int mRightMargin;
private Paint mValuePaint;
//左右两边图片id
private static final int LEFT_ICON_ID = 0x166666;
private static final int RIGHT_ICON_ID = 0x166667;
//文字大小
private float mTextSize;
//进度值的背景图片高度
private int mValueBitmapHeight;
//y轴上偏差值 防止高度太紧凑
private int mOffsetY = dip2px(4);
//控件的宽度
private int mViewWidth;
3. onMeasure和onLayout处理
(一)onMeasure
//测量所有子控件
measureChildren(widthMeasureSpec,heightMeasureSpec);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量所有子控件
measureChildren(widthMeasureSpec,heightMeasureSpec);
//得到当前控件的测量模式和测量大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//wrap_content
int width = widthSize;
int height = dip2px(120);
width = widthMode == MeasureSpec.AT_MOST ? width : widthSize;
height = heightMode == MeasureSpec.AT_MOST ? height : heightSize;
setMeasuredDimension(width,height);
this.mViewWidth = width;
mLeftMin = mLeftMargin;
mRightMax = width - mRightMargin;
}
(二) onLayout
设置左右波动按钮的极限值
mLeftIcon.setLeftRightLimit(mLeftMargin,mRightMax);
mRightIcon.setLeftRightLimit(mLeftMargin,mRightMax);
//设置左右波动按钮的初始化中心点坐标
mLeftIcon.setCenterX(mLeftMargin + leftWidth / 2);
mRightIcon.setCenterX(mRightMax - rightWidth / 2 );
布局左右两个子View波动图片按钮的left,top,right,bottom值,放置好子View的位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int cWidth = childView.getMeasuredWidth();
int cHeight = childView.getMeasuredHeight();
int left = 0;
//左右可以拖动的图片距上面的高度=进度条的高度 + 最长刻度线的高度
// + 刻度值字体大小的高度
// + 拖动显示刻度值背景图片的高度 + 偏移量
int top = (int) (mPbHeight + mLineMaxHeight + mTextSize
+ mValueBitmapHeight + mOffsetY);
int right = 0;
//距底部的高度 = top + 图片的高度 - 距离底部的padding值
int bottom = top + cHeight - getPaddingBottom();
if(i == 0){
left = mLeftMargin + getPaddingLeft();
right = left + cWidth;
}else if(i == 1){
left = (mViewWidth - mRightMargin - cWidth - getPaddingRight());
right = left + cWidth;
}
childView.layout(left,top,right,bottom);
}
int leftWidth = mLeftIcon.getMeasuredWidth();
int rightWidth = mRightIcon.getMeasuredWidth();
mPartWidth = (mViewWidth - (mLeftMargin + leftWidth / 2)
- (mRightMargin + rightWidth / 2)) / (float) mRulerMax;
mLeftIcon.setLeftRightLimit(mLeftMargin,mRightMax);
mRightIcon.setLeftRightLimit(mLeftMargin,mRightMax);
mLeftIcon.setCenterX(mLeftMargin + leftWidth / 2);
mRightIcon.setCenterX(mRightMax - rightWidth / 2 );
}
4. 绘制尺子进度条
mLeftIcon.getCenterX: 获取左边拨动按钮的x轴中心坐标值
mRightIcon.getCenterX(): 获取右边拨动按钮的x轴中心坐标值
//画进度条
private void drawProgressBar(Canvas canvas) {
//画进度条
mPbPaint.setColor(Color.GRAY);
int left = 0;
int top = (int) (mLineMaxHeight + mTextSize + mValueBitmapHeight + mOffsetY);
int right = mViewWidth;
int bottom = top + mPbHeight;
Rect rect = new Rect(left ,top,right,bottom);
canvas.drawRect(rect, mPbPaint);
//游标卡尺拨动过程中 实时进度条的绘制
mPbPaint.setColor(Color.YELLOW);
rect = new Rect(mLeftIcon.getCenterX(),top,mRightIcon.getCenterX(),bottom);
canvas.drawRect(rect, mPbPaint);
}
5. 画刻度线和刻度值
mRulerMax:代表最多画多少根刻度线
i % 10 == 0 & i+=2:代表每5根线画一个刻值
每10根线绘制一个具体数值
canvas.drawText(String.valueOf(i),startX,startY - mOffsetY,mRulerPaint);
//画刻度尺和刻度值
private void drawRuler(Canvas canvas) {
for (int i = 0; i <= mRulerMax; i+=2) {
float startX = mLeftMargin + mLeftIcon.getMeasuredWidth() / 2 + i * mPartWidth;
float startY,stopY;
if(i % 10 == 0){
mRulerPaint.setColor(ContextCompat.getColor(getContext(),R.color.color_333333));
startY = mValueBitmapHeight + mTextSize + mOffsetY;
stopY = startY + mLineMaxHeight;
canvas.drawText(String.valueOf(i),startX,startY - mOffsetY,mRulerPaint);
}else{
startY = mValueBitmapHeight + (mLineMaxHeight - mLineMinHeight) + mTextSize + mOffsetY;
stopY = startY + mLineMinHeight;
}
mRulerPaint.setColor(ContextCompat.getColor(getContext(),R.color.color_666666));
canvas.drawLine(startX,startY,startX,stopY,mRulerPaint);
}
}
6. 绘制游标拨动过程中带背景图片实时刻度值的文字
private void drawRulerValue(Canvas canvas,ThumbImageView imageView) {
int centerX = imageView.getCenterX();
Bitmap valueBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rod_place_icon);
int top = 0;
canvas.drawBitmap(valueBitmap,centerX - valueBitmap.getWidth() / 2,top,mValuePaint);
canvas.drawText(String.valueOf(getValue(imageView)),centerX,top + valueBitmap.getHeight() / 2 + dip2px(3),mValuePaint);
}
7. onTouchEvent触摸事件处理
根据手指的拖动范围实时计算波动图片按钮的中心点坐标,从而实时刷新具体刻度值范围,显示实时具体刻度值
private int mDownX = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mDownX = (int) event.getX();
mIsMoving=false;
break;
case MotionEvent.ACTION_MOVE:
mIsMoving = true;
int moveX = (int) event.getX();
int distanceX = moveX - mDownX;
int left = mRect.left + distanceX;
int right = mRect.right + distanceX;
setCenterX((left + right) / 2);
break;
case MotionEvent.ACTION_UP:
mIsMoving = false;
mRangeSeekBar.postInvalidate();
break;
default:
}
return true;
}
/**
* 设置图片中心位置,不超过左右的limit,就刷新整个控件,并且回调计算value值
* @param centerX
*/
public void setCenterX(int centerX) {
int left = centerX - mWidth / 2;
int right = centerX + mWidth / 2;
//左边极限值处理
if(centerX < mLeftMin + mWidth / 2){
Log.e("setCenterX",centerX+" : "+mLeftMin);
left = mLeftMin;
right = left + mWidth;
}
//右边极限值处理
if(centerX > mRightMax - mWidth / 2){
Log.e("setCenterX",centerX+" : "+mRightMax);
left = mRightMax - mWidth;
right = left + mWidth;
}
this.mCenterX = (left + right) / 2;
//确保拨动过才刷新控件
if(left != mRect.left || right != mRect.right){
mRect.union(left,mRect.top,right,mRect.bottom);
layout(left, mRect.top, right, mRect.bottom);
mRangeSeekBar.postInvalidate();
//回调处理 计算value值
if(mOnScrollListener != null){
int value = 100 * (mCenterX - mLeftMin - mWidth / 2) / (mRightMax - mLeftMin - mWidth);
mOnScrollListener.onValue(value);
}
}
}
//回调接口定义
//滑动监听接口
public interface OnScrollListener{
void onValue(int value);
}
private OnScrollListener mOnScrollListener;
public void setOnScrollListener(OnScrollListener onScrollListener) {
mOnScrollListener = onScrollListener;
}
8. 小结和源码下载
小结:
绘制并不难,主要还是需要计算边界范围,实时计算游标拨动过程中的数据值,然后绘制带背景的数据展示
源码下载:
最后统一提供下载地址