Android资源整理自定义view

Android自定义View之滑动刻度尺

2017-03-12  本文已影响586人  愚蠢的高小星

纵向滑动刻度尺

VerticalScaleView.png

横向滑动刻度尺

HorizontalScaleView.png

实现思路:
onDraw方法中画出整个刻度尺和指针。重写onTouchEvent,根据手指的滑动不断改变坐标,并重新计算出刻度对应的坐标以及指针所指向坐标对应的刻度,然后重绘。具体的绘制方法参考源码,大致就是找到每个点的坐标然后用canvas的相应方法绘制,计算比较多但不复杂~

这里没有用Scroller实现滑动,因为Scroller滑动时整个View都会滑动,而我希望的效果是刻度滑动,中间的指针不动,尝试了几种滑动方法后发现还是重绘的效果最好。即通过手指滑动的偏移量计算新的坐标,然后根据新坐标重绘视图。

另外,使用VelocityTracker记录手指抬起的速度,实现惯性滑动的效果。源码如下:

public class VerticalScaleView extends View {

    private final int SCALE_WIDTH_BIG = 4;//大刻度线宽度
    private final int SCALE_WIDTH_SMALL = 2;//小刻度线宽度
    private final int LINE_WIDTH = 6;//指针线宽度

    private int rectPadding = 20;//圆角矩形间距
    private int rectHeight;//圆角矩形高

    private int maxScaleLength;//大刻度长度
    private int midScaleLength;//中刻度长度
    private int minScaleLength;//小刻度长度
    private int scaleSpace;//刻度间距
    private int scaleSpaceUnit;//每大格刻度间距
    private int height, width;//view高宽
    private int ruleWidth;//刻度尺宽

    private int max;//最大刻度
    private int min;//最小刻度
    private int borderUp, borderDown;//上下边界值坐标
    private float midY;//当前中心刻度y坐标
    private float originMidY;//初始中心刻度y坐标
    private float minY;//最小刻度y坐标,从最小刻度开始画刻度

    private float lastY;

    private float originValue;//初始刻度对应的值
    private float currentValue;//当前刻度对应的值

    private Paint paint;//画笔

    private Context context;

    private String descri = "身高";//描述
    private String unit = "cm";//刻度单位

    private VelocityTracker velocityTracker;//速度监测
    private float velocity;//当前滑动速度
    private float a = 1000000;//加速度
    private boolean continueScroll;//是否继续滑动

    private boolean isMeasured;

    private OnValueChangeListener onValueChangeListener;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (null != onValueChangeListener) {
                float v = (float) (Math.round(currentValue * 10)) / 10;//保留一位小数
                onValueChangeListener.onValueChanged(v);
            }
        }
    };

    public VerticalScaleView(Context context) {
        super(context);
        this.context = context;
        init();
    }

    public VerticalScaleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    public VerticalScaleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        init();
    }

    private void init() {
        //初始化画笔
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);

    }

    //设置刻度范围
    public void setRange(int min, int max) {
        this.min = min;
        this.max = max;
        originValue = (max + min) / 2;
        currentValue = originValue;
    }

    //设置刻度单位
    public void setUnit(String unit) {
        this.unit = unit;
    }

    //设置刻度描述
    public void setDescri(String descri) {
        this.descri = descri;
    }

    //设置value变化监听
    public void setOnValueChangeListener(OnValueChangeListener onValueChangeListener) {
        this.onValueChangeListener = onValueChangeListener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!isMeasured) {
            width = getMeasuredWidth();
            height = getMeasuredHeight();
            ruleWidth = width * 2 / 3;
            maxScaleLength = width / 5;
            midScaleLength = width / 6;
            minScaleLength = maxScaleLength / 2;
            scaleSpace = height / 80 > 8 ? height / 80 : 8;
            scaleSpaceUnit = scaleSpace * 10 + SCALE_WIDTH_BIG + SCALE_WIDTH_SMALL * 9;
            rectHeight = scaleSpaceUnit / 2;

            borderUp = height / 2 - ((min + max) / 2 - min) * scaleSpaceUnit;
            borderDown = height / 2 + ((min + max) / 2 - min) * scaleSpaceUnit;
            midY = (borderUp + borderDown) / 2;
            originMidY = midY;
            minY = borderDown;
            isMeasured = true;
        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //画刻度线
        for (int i = min; i <= max; i++) {
            //画刻度数字
            canvas.rotate(90);
            Rect rect = new Rect();
            String str = String.valueOf(i);
            paint.setColor(getResources().getColor(R.color.black));
            paint.setTextSize(40);
            paint.getTextBounds(str, 0, str.length(), rect);
            int w = rect.width();
            int h = rect.height();
            canvas.drawText(str, minY - (i - min) * scaleSpaceUnit - w / 2 - SCALE_WIDTH_BIG / 2, -(ruleWidth - maxScaleLength - h - minScaleLength / 2), paint);
            canvas.rotate(-90);
            //画大刻度线
            paint.setStrokeWidth(SCALE_WIDTH_BIG);
            canvas.drawLine(ruleWidth, minY - (i - min) * scaleSpaceUnit, ruleWidth - maxScaleLength, minY - (i - min) * scaleSpaceUnit, paint);

            if (i == min) {
                continue;//最后一条不画中小刻度线
            }
            //画中刻度线
            paint.setStrokeWidth(SCALE_WIDTH_SMALL);
            canvas.drawLine(ruleWidth, minY - (i - min) * scaleSpaceUnit + scaleSpaceUnit / 2, ruleWidth - midScaleLength, minY - (i - min) * scaleSpaceUnit + scaleSpaceUnit / 2, paint);
            //画小刻度线
            for (int j = 1; j < 10; j++) {
                if (j == 5) {
                    continue;
                }
                canvas.drawLine(ruleWidth, minY - (i - min) * scaleSpaceUnit + (SCALE_WIDTH_SMALL + scaleSpace) * j, ruleWidth - minScaleLength, minY - (i - min) * scaleSpaceUnit + (SCALE_WIDTH_SMALL + scaleSpace) * j, paint);
            }

        }

        //画竖线
        paint.setStrokeWidth(LINE_WIDTH);
        paint.setColor(getResources().getColor(R.color.gray));
        canvas.drawLine(ruleWidth + LINE_WIDTH / 2, minY + SCALE_WIDTH_BIG / 2, ruleWidth + LINE_WIDTH / 2, minY - (max - min) * scaleSpaceUnit - SCALE_WIDTH_BIG / 2, paint);
        //画指针线
        paint.setColor(getResources().getColor(R.color.colorPrimaryDark));
        canvas.drawLine(0, height / 2, ruleWidth, height / 2, paint);
        //画圆角矩形
        paint.setStyle(Paint.Style.FILL);
        RectF r = new RectF();
        r.left = ruleWidth + rectPadding;
        r.top = height / 2 - rectHeight / 2;
        r.right = width;
        r.bottom = height / 2 + rectHeight / 2;
        canvas.drawRoundRect(r, 10, 10, paint);
        //画小三角形指针
        Path path = new Path();
        path.moveTo(ruleWidth + rectPadding, height / 2 - scaleSpace);
        path.lineTo(ruleWidth + rectPadding - 8, height / 2);
        path.lineTo(ruleWidth + rectPadding, height / 2 + scaleSpace);
        path.close();
        canvas.drawPath(path, paint);
        //绘制文字
        paint.setColor(getResources().getColor(R.color.black));
        Rect rect1 = new Rect();
        paint.getTextBounds(descri, 0, descri.length(), rect1);
        int w1 = rect1.width();
        canvas.drawText(descri, width - (width - ruleWidth - 20) / 2 - w1 / 2, height / 2 - rectHeight / 2 - 10, paint);
        //绘制当前刻度值数字
        paint.setColor(getResources().getColor(R.color.white));
        float v = (float) (Math.round(currentValue * 10)) / 10;//保留一位小数
        String value = String.valueOf(v) + unit;
        Rect rect2 = new Rect();
        paint.getTextBounds(value, 0, value.length(), rect2);
        int w2 = rect2.width();
        int h2 = rect2.height();
        canvas.drawText(value, width - (width - ruleWidth - 20) / 2 - w2 / 2, height / 2 + h2 / 2, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastY = y;
                continueScroll = false;
                //初始化速度追踪
                if (velocityTracker == null) {
                    velocityTracker = VelocityTracker.obtain();
                } else {
                    velocityTracker.clear();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                velocityTracker.addMovement(event);
                int offsetY = (int) (lastY - y);
                minY -= offsetY;
                midY -= offsetY;
                calculateCurrentScale();
                invalidate();
                lastY = y;
                break;
            case MotionEvent.ACTION_UP:
                confirmBorder();
                //当前滑动速度
                velocityTracker.computeCurrentVelocity(1000);
                velocity = velocityTracker.getYVelocity();
                float minVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
                if (Math.abs(velocity) > minVelocity) {
                    continueScroll = true;
                    continueScroll();
                } else {
                    velocityTracker.recycle();
                    velocityTracker = null;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                velocityTracker.recycle();
                velocityTracker = null;
                break;
        }
        return true;
    }

    //计算当前刻度
    private void calculateCurrentScale() {
        float offsetTotal = originMidY - midY;
        int offsetBig = (int) (offsetTotal / scaleSpaceUnit);//移动的大刻度数
        float offsetS = offsetTotal % scaleSpaceUnit;
        int offsetSmall = (new BigDecimal(offsetS / (scaleSpace + SCALE_WIDTH_SMALL)).setScale(0, BigDecimal.ROUND_HALF_UP)).intValue();//移动的小刻度数 四舍五入取整
        float offset = offsetBig + offsetSmall * 0.1f;
        if (originValue - offset > max) {
            currentValue = max;
        } else if (originValue - offset < min) {
            currentValue = min;
        } else {
            currentValue = originValue - offset;
        }
        mHandler.sendEmptyMessage(0);
    }

    //指针线超出范围时 重置回边界处
    private void confirmBorder() {
        if (midY < borderUp) {
            midY = borderUp;
            minY = borderDown + (borderUp - borderDown) / 2;
            postInvalidate();
        } else if (midY > borderDown) {
            midY = borderDown;
            minY = borderDown - (borderUp - borderDown) / 2;
            postInvalidate();
        }
    }

    //手指抬起后继续惯性滑动
    private void continueScroll() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                float velocityAbs = 0;//速度绝对值
                if (velocity > 0 && continueScroll) {
                    velocity -= 50;
                    minY += velocity * velocity / a;
                    midY += velocity * velocity / a;
                    velocityAbs = velocity;
                } else if (velocity < 0 && continueScroll) {
                    velocity += 50;
                    minY -= velocity * velocity / a;
                    midY -= velocity * velocity / a;
                    velocityAbs = -velocity;
                }
                calculateCurrentScale();
                confirmBorder();
                postInvalidate();
                if (continueScroll && velocityAbs > 0) {
                    post(this);
                } else {
                    continueScroll = false;
                }
            }
        }).start();
    }
}

源码下载
https://github.com/GITbiubiubiu/ScaleView

上一篇下一篇

猜你喜欢

热点阅读