打造一个简单好用的滑动刻度尺控件

572 阅读3分钟

打造一个简单易用的刻度尺控件

废话不多说先上效果图:

tutieshi_576x280_16s.gif tutieshi_580x180_13s.gif tutieshi_580x200_14s.gif tutieshi_580x188_8s.gif

可以看出通过刻度线和刻度值的位置配置共实现了四种显示模式。

  1. 刻度线在上刻度在下模式
  2. 刻度线在上刻度在下靠拢模式
  3. 刻度在上刻度线在下模式
  4. 刻度在上刻度线下靠拢模式

接下来讲学习如何实现刻度在上刻度线在下方并靠拢的显示模式。

tutieshi_580x200_14s.gif

自定义View

测量

首先我们要配置画布大小修改onMeasure方法。先上代码

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int heightModel = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = 0;
    if (heightModel == MeasureSpec.AT_MOST) {
            heightSize = (int) (textHeight + (showHeightScaleLine ? heightScaleHeight : showMiddleScaleLine ? middleScaleHeight : lowScaleHeight) + valueScaleSpace + getPaddingTop() + getPaddingBottom());
    } else if (heightModel == MeasureSpec.EXACTLY) {
        heightSize = MeasureSpec.getSize(heightMeasureSpec) + getPaddingTop() + getPaddingBottom();
    }
    setMeasuredDimension(widthSize, heightSize);
}

这里主要对高度做处理,使用者配置为wrap_content 我们需要明确设置一个高度便于后面绘制的Y轴坐标计算。 当用户设置为wrap_content 画布高度为 文字高度+文字和刻度间隔高度+最大刻度高度+padding 如果不是wrap_content模式则测量多少就是多少高度。

获取最终的宽高

获取最终的宽高 可以在onMeasure 方法setMessureDimension 后获取。当时笔者更愿意在 onSizeChanged方法中获取。

protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = w;
    mHeight = h;
}

开始绘制

绘制刻度

先上代码

//绘制刻度线从中间开始绘制,向两边依次绘制
private void drawScale(Canvas canvas) {
    int currentValue;
    canvas.save();
    canvas.translate(mWidth / 2, mHeight / 2);
    // 向绘制左边 <----
    float currentValueXAxis = 0;
    currentValue = initValue;
    //最左边的坐标 计算边界使用。
    while (currentValueXAxis >= -(mWidth / 2) && currentValue >= startValue) {
        drawScaleLineItem(currentValueXAxis, currentValue, canvas);
        currentValueXAxis = (int) (currentValueXAxis - spacing);
        currentValue = currentValue - 1;
    }
    Log.d(TAG, "drawScale: " + leftXAxis);
    //向左绘制 ----->
    currentValueXAxis = 0;
    currentValue = initValue;
    while (currentValueXAxis <= mWidth && currentValue <= endValue) {
        drawScaleLineItem(currentValueXAxis, currentValue, canvas);
        currentValueXAxis = (int) (currentValueXAxis + spacing);
        currentValue = currentValue + 1;
    }
    Log.d(TAG, "drawScale: " + rightXAxis);
    canvas.restore();
}

首先将画布坐标移到View的中心位置:canvas.translate(mWidth / 2, mHeight / 2) 刻度的绘制由中间(0,0)位置开始向X轴负方向(左边)和X轴正方向(右边)开始绘制。

先绘制左边

设置当X点坐标为0. 开始刻度为我们设置的开始刻度。

// 向绘制左边 <----
float currentValueXAxis = 0 + offsetX;
currentValue = initValue;

根据开始刻度向最小刻度开始循环绘制,当刻度最小刻度切坐标超出View 的显示范围时停止绘制。绘制完一个刻度线后重置X点坐标坐标为当前X坐标减去相邻的刻度间距值,重置当前的刻度值:当前刻度-1

//最左边的坐标 计算边界使用。
while (currentValueXAxis >= -(mWidth / 2) && currentValue >= startValue) {
    drawScaleLineItem(currentValueXAxis, currentValue, canvas);
    currentValueXAxis = (int) (currentValueXAxis - spacing);
    currentValue = currentValue - 1;
}

绘制刻度线

//绘制刻度线
private void drawScaleLineItem(float currentValueXAxis, int currentValue, Canvas canvas) {
    float startCenterX = 0,endCenterX = 0,startCenterY =0,endCenterY=0;
    float startLowX=0, endLowX=0, startLowY=0, endLowY=0;
    float startMiddleX=0, endMiddleX=0, startMiddleY=0, endMiddleY=0;
    float startHighX=0, endHighX=0, startHighY=0, endHighY=0;
    startCenterX = -mWidth;
    startCenterY = valueScaleSpace / 2;
    endCenterX = mWidth;
    endCenterY = valueScaleSpace / 2;
    startLowX = currentValueXAxis;
    startLowY = 0 + valueScaleSpace / 2;
    endLowX = currentValueXAxis;
    endLowY = lowScaleHeight + valueScaleSpace / 2;
    startMiddleX = currentValueXAxis;
    startMiddleY = 0 + valueScaleSpace / 2;
    endMiddleX = currentValueXAxis;
    endMiddleY = middleScaleHeight + valueScaleSpace / 2;
    startHighX = currentValueXAxis;
    startHighY = 0 + valueScaleSpace / 2;
    endHighX = currentValueXAxis;
    endHighY = heightScaleHeight + valueScaleSpace / 2;
    canvas.drawLine(startCenterX, startCenterY, endCenterX, endCenterY, centerLinePaint);
    }
   canvas.drawLine(startLowX, startLowY, endLowX, endLowY, lowScalePaint);
   if (currentValue % middleSpaceValueSpace == 0) {
            canvas.drawLine(startMiddleX, startMiddleY, endMiddleX, endMiddleY, middleScalePaint);
        }
   if (currentValue % highSpaceValeSpace == 0) {
            canvas.drawLine(startHighX, startHighY, endHighX, endHighY, highScalePaint);
        }
}

X坐标不会变化,主要考虑Y轴的最低点坐标,应为刻度分为高中低。这里加了一个valueScaleSpace,valueScaleSpace 值是数值和刻度之间的间隔距离。 只是向左边绘制,当然向右绘制也是一样的只不过刻度值是+1,X轴坐标点每次循环都是加上间距。

currentValueXAxis = (int) (currentValueXAxis + spacing);
currentValue = currentValue + 1;

接下来绘制刻度值

//绘制刻度值从中间开始绘制,向两边依次绘制
private void drawScaleNumber(Canvas canvas) {
    int currentValue;
    canvas.save();
    canvas.translate(mWidth / 2, mHeight / 2);
    // 向绘制左边 <----
    float currentValueXAxis = 0;
    currentValue = initValue;
    while (currentValueXAxis >= -(mWidth / 2) && currentValue >= startValue) {
        drawScaleNumberItem(canvas, currentValue, (int) currentValueXAxis);
        currentValueXAxis = (int) (currentValueXAxis - spacing);
        currentValue = currentValue - 1;
    }
    //向左绘制 ----->
    currentValueXAxis = 0;
    currentValue = initValue;
    while (currentValueXAxis <= mWidth && currentValue <= endValue) {
        drawScaleNumberItem(canvas, currentValue, (int) currentValueXAxis);
        currentValueXAxis = (int) (currentValueXAxis + spacing);
        currentValue = currentValue + 1;
    }
    canvas.restore();
}

这里逻辑和绘制刻度是一样的,直接上代码看吧。

//绘制刻度值
private void drawScaleNumberItem(Canvas canvas, int currentValue, int currentValueXAxis) {
        drawText(canvas, String.valueOf(currentValue), currentValueXAxis, 0);
}
private void drawText(Canvas canvas, String text, int x, int y) {
    Rect rect = new Rect();
    Log.d(TAG, "drawText: x :" + x);
    Log.d(TAG, "drawText: text :" + text);
    textPaint.getTextBounds(text, 0, text.length(), rect);
    canvas.drawText(text, 0, text.length(), (float) (x + ((rect.left * 1.0 - rect.right * 1.0) / 2)), y - valueScaleSpace / 2, textPaint);
}

绘制完了怎么让画面动起来呢

直接上代码

float onTouchLocationX = -1;

float offsetX = 0;

float lastMoveX = 0;

@SuppressLint("ClickableViewAccessibility")
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            onTouchLocationX = event.getX();
            break;
        case MotionEvent.ACTION_MOVE:
            offsetX = event.getX() - onTouchLocationX + lastMoveX;
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            lastMoveX = offsetX;
            invalidate();
            startAnimation();
            break;
    }
    return true;
}

这里通过监听手指的滑动时间计算出来偏移量,这个偏移量叠加到绘制的x坐标上不就可以滑动了吗。

// 向绘制左边 <----
float currentValueXAxis = 0 + offsetX;

绘制的时候将偏移量加到X轴坐标上。

好了最基础的刻度尺算是完成了。

当然了你也可以访问gitHub查看完整功能的实现。 Andihu/ruler (github.com)

当然也可以留言:1641926972@qq.com