效果图预览

android 自定义卡尺游标 游标卡尺改装_刻度尺

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. 小结和源码下载

小结:
    绘制并不难,主要还是需要计算边界范围,实时计算游标拨动过程中的数据值,然后绘制带背景的数据展示

源码下载:
    最后统一提供下载地址