怎样用自定义View实现一个直尺

773 阅读2分钟

实现效果

image.png

实现逻辑

利用TypeValue.applyDimension方法将1mm转化成像素,以此为步长循环绘制刻度,中间需要处理一下整值和半值的线条长度以及对应的数字标识。

TypeValue.applyDimension方法说明

/** 
     * Converts an unpacked complex data value holding a dimension to its final floating 
     * point value. The two parameters <var>unit</var> and <var>value</var>
     * are as in {@link #TYPE_DIMENSION}.
     *  
     * @param unit The unit to convert from.
     * @param value The value to apply the unit to.
     * @param metrics Current display metrics to use in the conversion -- 
     *                supplies display density and scaling information.
     * 
     * @return The complex floating point value multiplied by the appropriate 
     * metrics depending on its unit. 
     */
    public static float applyDimension(int unit, float value,
                                       DisplayMetrics metrics)
    {
        switch (unit) {
        case COMPLEX_UNIT_PX:
            return value;
        case COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f/72);
        case COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f/25.4f);
        }
        return 0;
    }

毫米转像素

TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1f, resources.displayMetrics)

实现代码

class RulerView @JvmOverloads constructor(context: Context,
                                          attrs: AttributeSet? = null,
                                          defStyle: Int = 0
) : View(context, attrs, defStyle) {
    /** 步长,为每毫米对应的像素值 **/
    private var mStep: Float = 0f

    /** 当前刻度所在的位置 **/
    private var mIndex: Float = 0f

    /** 当前刻度的索引值 **/
    private var mLineCount: Int = 0

    /** 普通刻度的高度 **/
    private var mNormalLineHeight = 10f.toDp()

    /** 半值刻度的高度,如5mm,15mm **/
    private var mHalfLineHeight = 15f.toDp()

    /** 整值刻度的高度,如0mm,10mm,20mm **/
    private var mIntegerLineHeight = 20f.toDp()

    /** 刻度值线宽 **/
    private var mCurrentLineHeight: Float = mNormalLineHeight
    private var mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)

    init {
        mStep = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1f, resources.displayMetrics)
        mPaint.color = Color.BLACK
        mPaint.strokeWidth = 2f
        mPaint.textSize = 14f.toDp()
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        // 初始化第一个刻度所在的位置,为一个文字的高度,这里主要是为了让0mm的刻度值显示出来
        mIndex = mPaint.getFontMetrics(null)
        mLineCount = 0
        // 同理,最大值的后面也加了一个文字的间隔
        while (mIndex <= measuredHeight - mPaint.getFontMetrics(null)) {
            mCurrentLineHeight = when {
                // 整值
                (mLineCount % 10) == 0 || mLineCount == 0 -> mIntegerLineHeight
                // 半值
                (mLineCount) % 5 == 0 -> mHalfLineHeight
                // 普通值
                else -> mNormalLineHeight
            }
            canvas?.drawLine(mCurrentLineHeight, mIndex, 0f, mIndex, mPaint)
            // 绘制刻度值文字
            if ((mLineCount % 10) == 0 || mLineCount == 0) {
                canvas?.drawText("${mLineCount / 10}cm", mCurrentLineHeight + 5, mIndex + mPaint.getFontMetrics(null) / 3, mPaint)
            }
            mIndex += mStep
            mLineCount++
        }
    }

    private fun Float.toDp(): Float {
        return (this * Resources.getSystem().displayMetrics.density + 0.5f)
    }
}

需要注意的是,为了能让刻度0和最大的刻度值显示出来,这里手动加了个上下值为刻度值字体行高的间距。 至于类似于触摸指针获取当前厘米值的功能,有待后续开发,不过也不难,只是过多了个了值的计算过程而已。

代码不多,欢迎指正。