Android自定義View之刻度尺

gl7080 8年前發布 | 23K 次閱讀 Android Android開發 移動開發 view

背景

項目中之前用的縱向滾輪用于選擇身高體重之類的需求,新版設計要求用橫向刻度尺控件來實現,效果圖,上面的字不在刻度尺范圍內,是一個TextView

刻度尺

自定義控件對于Android開發者來說是必備技能,這篇文章就不講自定義View的基礎知識了,主要談談繪制邏輯。

實現

遵循自定義View的開發流程,onMeasure() –> onSizeChanged() –> onLayout() –> onDraw()。由于我們要自定義的是View而不是ViewGroup,所以onLayout()就不用實現了。

onMeasure() 測量

onMeasure()用于測量View的大小,View的大小不僅由自身決定,同時也受父控件的影響,為了我們的控件能更好的適應各種情況,一般會自己進行測量。刻度尺View左右是滿屏的,偷個懶寬度就不適配了,只做高度測試就好了。高度包括長刻度的高度,加上字和底部間距

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}

private int measureHeight(int heightMeasure) { int measureMode = View.MeasureSpec.getMode(heightMeasure); int measureSize = View.MeasureSpec.getSize(heightMeasure); int result = (int) (bottomPadding + longLineHeight 2); switch (measureMode) { case View.MeasureSpec.EXACTLY: result = Math.max(result, measureSize); break; case View.MeasureSpec.AT_MOST: result = Math.min(result, measureSize); break; default: break; } height = result; return result; } </code></pre> </td> </tr> </tbody> </table>

onDraw() 繪制

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.setDrawFilter(pfdf);
    drawBg(canvas);
    drawIndicator(canvas);
    drawRuler(canvas);
}

繪制做了三件事情:

  1. 繪制背景色
  2. 繪制指示器
  3. 繪制刻度

只看下怎么畫刻度,難點在于怎么確定刻度的坐標。首先高度的坐標是一樣的,刻度指示器是在屏幕正中,指示某個值,那么該刻度值的x坐標就是確定的。根據這個值去畫其他坐標,包括長刻度和短刻度。

首先要確定每個長刻度(和短刻度,如果有的話)的坐標寬度和單位,確定基礎單位和基礎單位寬度(如果有短刻度以短刻度為基礎單位)。那么

  • 第i個刻度x坐標 = 中間刻度x坐標 + i  基礎單位寬度
    </code></pre> </td> 
        </tr> 
       </tbody> 
      </table> 
      

    其中i的取值范圍在正負屏幕可繪制多少個基礎單位,第0位就是屏幕正中的刻度值。以該值為基礎一次畫出可以在屏幕中顯示的剩余刻度,如果是長刻度單位的整數倍就畫長刻度,刻度值只在長刻度下畫。
    這樣就有一個問題,正中刻度值必須是可以整除基礎單位,比如,長刻度 = 1,中間兩個短刻度,這樣基礎單位值就是0.5,currentValue = 0.3,那么下一個值就是0.8,但是這樣顯示并不是我們想要的,我們想要0、0.5、1、1.5這樣的值。所以就是在初始化的時候格式化這些值,使得所有可顯示的值都可以整除基礎單位值,也就是余數為0。
    由于使用float計算,所以要用到float精確計算,否則取余操作會出現不等于0的誤差導致畫不出長刻度。

    //精度支持2位小數
       private float format(float vallue) {
           float result = 0;
           if (getBaseUnit() < 0.1) {
               //0.01
               result = ArithmeticUtil.round(vallue, 2);
               //float精確計算 取余
               if (ArithmeticUtil.remainder(result, getBaseUnit(), 2) != 0) {
                   result += 0.01;
                   result = format(result);
               }
           } else if (getBaseUnit() < 1) {
               //0.1
               result = ArithmeticUtil.round(vallue, 1);
               if (ArithmeticUtil.remainder(result, getBaseUnit(), 1) != 0) {
                   result += 0.1;
                   result = format(result);
               }
           } else if (getBaseUnit() < 10) {
               //1
               result = ArithmeticUtil.round(vallue, 0);
               if (ArithmeticUtil.remainder(result, getBaseUnit(), 0) != 0) {
                   result += 1;
                   result = format(result);
               }
           }
           return result;
       }
    

    處理滑動操作

    滑動處理比較簡單,以初始化為基礎,每次move操作累加x坐標,以此值繪制偏移量,停止滑動時以基礎單位寬度為基準四舍五入,開始動畫滑動到相應的刻度值上。
    主要方法

    private void drawRuler(Canvas canvas) {
            if (moveX < maxRightOffset) {
                moveX = maxRightOffset;
            }
            if (moveX > maxLeftOffset) {
                moveX = maxLeftOffset;
            }
            int halfCount = (int) (width / 2 / getBaseUnitWidth());
            float moveValue = (int) (moveX / getBaseUnitWidth())  getBaseUnit();
            currentValue = originValue - moveValue;
            //剩余偏移量
            offset = moveX - (int) (moveX / getBaseUnitWidth())  getBaseUnitWidth();

        for (int i = -halfCount - 1; i <= halfCount + 1; i++) {
            float value = ArithmeticUtil.addWithScale(currentValue, ArithmeticUtil.mulWithScale(i, getBaseUnit(), 2), 2);
            //只繪出范圍內的圖形
            if (value >= startValue && value <= endValue) {
                //畫長的刻度
                float startx = width / 2 + offset + i * getBaseUnitWidth();
                if (startx > 0 && startx < width) {
                    if (microUnitCount != 0) {
                        if (ArithmeticUtil.remainder(value, unit, 2) == 0) {
                            drawLongLine(canvas, i, value);
                        } else {
                            //畫短線
                            drawShortLine(canvas, i);
                        }
                    } else {
                        //畫長線
                        drawLongLine(canvas, i, value);
                    }
                }
            }
        }
        //通知結果
        notifyValueChange();
    }
    

    </code></pre> </td> </tr> </tbody> </table>

    關于刻度的單位,需要給出長刻度單位和中間的短刻度個數,這樣中間的短刻度單位就確定了,所以理論上不管中間有幾個短刻度計算都是一樣的。我在里面封裝了三個常用的,2、5、10三種。
    支持的styleable

    <declare-styleable name="BooheeRulerView">
        <attr name="ruler_bg_color" format="color|reference"/>
        <attr name="ruler_line_color" format="color|reference"/>
        <attr name="ruler_text_size" format="dimension"/>
        <attr name="ruler_text_color" format="color|reference"/>
        <attr name="ruler_width_per_unit" format="dimension"/>
    </declare-styleable>
    

    Android自定義View之刻度尺

    Android自定義View之刻度尺

    代碼在這里

    總結

    實現的效果比較單一,沒有做太多的擴展,有時間再完善下。

    via:http://w4lle.github.io/2016/05/15/Android%E8%87%AA%E5%AE%9A%E4%B9%89View%E4%B9%8B%E5%88%BB%E5%BA%A6%E5%B0%BA/

     本文由用戶 gl7080 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
  • sesese色