自定义View之滚动刻度尺,2018/1/14 05

自定义刻度尺,通过Scroller实现的滚动效果,一般用于金额选择,样子为:
刻度尺
1,创建自定view,并在values文件下创建attrs文件


<resources>
    <declare-styleable name="ScrollDividingRuleView">
        <attr name="line_height" format="dimension|reference" />
        <attr name="dividing_text_size" format="dimension|reference" />
    declare-styleable>
resources>

ScrollDividingRuleView为创建的自定义view 的名
line_height为刻度线的高度
dividing_text_size 刻度显示文字的大小
2,初始化获取view的属性

private void init(Context context, AttributeSet attrs) {
        for (int i = 0; i < attrs.getAttributeCount(); i++) {
            String name = attrs.getAttributeName(i);
            if ("layout_width".equals(name)) {
                String value = attrs.getAttributeValue(i);
                if (value.length() > 2) {
                    if (value.endsWith("dp")) {
                        mScaleWidth = Utils.dp2px(context, Float.valueOf(value.substring(0, value.length() - 2)));
                    } else {
                        mScaleWidth = Float.valueOf(value.substring(0, value.length() - 2));
                    }
                } else if (value.equals("-1") || value.equals("-2") || value.equals("0")) {
                    mScaleWidth = 0;
                }
            }else if ("line_height".equals(name)) {
                String value = attrs.getAttributeValue(i);
                if (value.length() > 2) {
                    if (value.endsWith("dp")) {
                        mLineHeight = Utils.dp2px(context, Float.valueOf(value.substring(0, value.length() - 2)));
                    } else {
                        mLineHeight = Float.valueOf(value.substring(0, value.length() - 2));
                    }
                } else {
                    mLineHeight = 50;
                }
            } else if ("dividing_text_size".equals(name)) {
                String value = attrs.getAttributeValue(i);
                if (value.length() > 2) {
                    if (value.endsWith("sp")) {
                        mTextSize = Utils.sp2px(context, Float.valueOf(value.substring(0, value.length() - 2)));
                    } else {
                        mTextSize = Float.valueOf(value.substring(0, value.length() - 2));
                    }
                } else {
                    mTextSize = 32;
                }
            }
        }
        // 画笔
        mPaint = new Paint();
        //总的高度,因为text的高度和设置的textSize会有误差所以加上20的高度
        mRectHeight = (int) (mLineHeight +mTextSize+mTextLineMargin+20);
        //初始设置每个刻度间距为30px
        mScaleMargin = 30;
        mTextList = new ArrayList<>();
        //计算宽度
        mScaleWidth = (mTextList.size() * 5 - 5) * mScaleMargin;
        mScroller = new Scroller(context);
        mVelocityTracker = VelocityTracker.obtain();
    }

其中VelocityTracker类是为了计算滚动速度,VelocityTracke具体介绍参见Android 官方文档VelocityTracke链接
3,重写onMeasure,onDraw方法

@Override
    protected void onDraw(Canvas canvas) {
        mPaint.setColor(Color.GRAY);
        // 抗锯齿
        mPaint.setAntiAlias(true);
        // 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
        mPaint.setDither(true);
        // 空心
        mPaint.setStyle(Paint.Style.STROKE);
        // 文字居中
        mPaint.setTextAlign(Paint.Align.CENTER);
        onDrawScale(canvas, mPaint); //画刻度
        onDrawLine(canvas, mPaint);//画刻度中间横线
        onDrawCenter(canvas, mPaint);//画中心远点
        super.onDraw(canvas);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = View.MeasureSpec.makeMeasureSpec(mRectHeight, View.MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, height);
        //view宽度
        mScaleRange = getMeasuredWidth();
        //初始化开始位置
        mInitDistance = mScaleRange / 2-mInitPosition*mScaleMargin*5;
    }

    private void onDrawScale(Canvas canvas, Paint paint) {
        paint.setColor(Color.GRAY);
        paint.setStrokeWidth(2);
        paint.setTextSize(mTextSize);
        for (int i = 0, k = 0; i < mTextList.size() * 5 - 4; i++) {
            if (i % 5 == 0) { //整值
                canvas.drawLine(i * mScaleMargin + mInitDistance, mRectHeight, i * mScaleMargin + mInitDistance, mRectHeight - mLineHeight, paint);
                //整值文字
                canvas.drawText(mTextList.get(k), i * mScaleMargin + mInitDistance, mRectHeight -mLineHeight-mTextLineMargin, paint);
                k++;
            } else {
                canvas.drawLine(i * mScaleMargin + mInitDistance, mRectHeight - mLineHeight / 4, i * mScaleMargin + mInitDistance, mRectHeight - mLineHeight + mLineHeight / 4, paint);
            }
        }
    }

    private void onDrawLine(Canvas canvas, Paint paint) {
        paint.setColor(Color.GRAY);
        paint.setStrokeWidth(2);
        canvas.drawLine(mInitDistance, mRectHeight - mLineHeight / 2, mScaleWidth + mInitDistance, mRectHeight - mLineHeight / 2, paint);
    }


    private void onDrawCenter(Canvas canvas, Paint paint) {
        paint.setColor(Color.GRAY);
        paint.setStrokeWidth(6);
        for (int i = 0; i < mTextList.size() * 5 - 4; i++) {
            if (i % 5 == 0) { //整值
                canvas.drawCircle(i * mScaleMargin + mInitDistance, mRectHeight-mLineHeight/2, 4, paint);
            }
        }
    }

mInitDistance 是为了计算中间点显示初始化中间点为哪个位置
4,处理触摸事件
这里就涉及到了一些计算,当手指抬起如果为快速滑动则滑动到开始或者结尾,慢速滑动则会滚动到比较近的主刻度,如果滑动超过也会回到起点或者重点

@Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mScroller != null && !mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                mScrollLastX = x;
                mStartX = x;
                return true;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.computeCurrentVelocity(1000);
                int dataX = mScrollLastX - x;
                smoothScrollBy(dataX, 0);
                mScrollLastX = x;
                return true;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                int moveX = mStartX - x;
                //如果快速滑动则滚动到滑动方向结尾的位置
                if(Math.abs(mVelocityTracker.getXVelocity())>4000) {
                    if(moveX>0) {
                        mListener.onScaleScrollChanged(mTextList.size()-1);
                        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), (int) (mScaleWidth - mScaleRange / 2 + mInitDistance)-mScroller.getFinalX(), 0, 800);
                    }else{
                        mListener.onScaleScrollChanged(0);
                        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -mScaleRange / 2 + mInitDistance-mScroller.getFinalX(), 0, 800);
                    }
                }else {
                    dealActionUp(moveX);
                }
                return true;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 处理手势抬起之后的操作
     */
    private void dealActionUp(int moveX) {
        int finalX = mScroller.getFinalX();//当前位置
        //计算获取手指抬起之后要回到的位置
        if (moveX >= 0 && finalX > mScaleWidth - mScaleRange / 2 + mInitDistance) {
            finalX = (int) (mScaleWidth - mScaleRange / 2 + mInitDistance);
        } else if (moveX <= 0 && finalX <= -mScaleRange / 2 + mInitDistance) {
            finalX = -mScaleRange / 2 + mInitDistance;
        } else {
            int round = Math.abs(Math.round((float) finalX / mScaleMargin));
            if (round % 5 > 2) {
                if(finalX>0) {
                    if (finalX / mScaleMargin < round) {
                        finalX = finalX + (5 - round % 5) * mScaleMargin - finalX % mScaleMargin + mScaleMargin;
                    } else {
                        finalX = finalX + (5 - round % 5) * mScaleMargin - finalX % mScaleMargin;
                    }
                }else{
                    if (finalX / mScaleMargin > -round) {
                        finalX = finalX + ( round % 5-5) * mScaleMargin - finalX % mScaleMargin - mScaleMargin;
                    } else {
                        finalX = finalX + (round % 5-5) * mScaleMargin - finalX % mScaleMargin;
                    }
                }
            } else {
                if(finalX>0) {
                    if (finalX / mScaleMargin < round) {
                        finalX = finalX - (round % 5) * mScaleMargin + (mScaleMargin - finalX % mScaleMargin);
                    } else {
                        finalX = finalX - (round % 5) * mScaleMargin - finalX % mScaleMargin;
                    }
                }else{
                    if (finalX / mScaleMargin > -round) {
                        finalX = finalX - (-round % 5) * mScaleMargin - finalX % mScaleMargin-mScaleMargin;
                    } else {
                        finalX = finalX - (-round % 5) * mScaleMargin - finalX % mScaleMargin;
                    }
                }
            }
        }
        mListener.onScaleScrollChanged(finalX/ mScaleMargin / 5+mInitPosition);//返回滚动选中的位置
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), finalX - mScroller.getFinalX(), 0, 800);//纠正指针位置
        postInvalidate();
    }

    /**
     * 处理手势抬起之后的操作
     */
    private void dealActionUp(int moveX) {
        int finalX = mScroller.getFinalX();//当前位置
        //计算获取手指抬起之后要回到的位置
        if (moveX >= 0 && finalX > mScaleWidth - mScaleRange / 2 + mInitDistance) {
            finalX = (int) (mScaleWidth - mScaleRange / 2 + mInitDistance);
        } else if (moveX <= 0 && finalX <= -mScaleRange / 2 + mInitDistance) {
            finalX = -mScaleRange / 2 + mInitDistance;
        } else {
            int round = Math.abs(Math.round((float) finalX / mScaleMargin));
            if (round % 5 > 2) {
                if(finalX>0) {
                    if (finalX / mScaleMargin < round) {
                        finalX = finalX + (5 - round % 5) * mScaleMargin - finalX % mScaleMargin + mScaleMargin;
                    } else {
                        finalX = finalX + (5 - round % 5) * mScaleMargin - finalX % mScaleMargin;
                    }
                }else{
                    if (finalX / mScaleMargin > -round) {
                        finalX = finalX + ( round % 5-5) * mScaleMargin - finalX % mScaleMargin - mScaleMargin;
                    } else {
                        finalX = finalX + (round % 5-5) * mScaleMargin - finalX % mScaleMargin;
                    }
                }
            } else {
                if(finalX>0) {
                    if (finalX / mScaleMargin < round) {
                        finalX = finalX - (round % 5) * mScaleMargin + (mScaleMargin - finalX % mScaleMargin);
                    } else {
                        finalX = finalX - (round % 5) * mScaleMargin - finalX % mScaleMargin;
                    }
                }else{
                    if (finalX / mScaleMargin > -round) {
                        finalX = finalX - (-round % 5) * mScaleMargin - finalX % mScaleMargin-mScaleMargin;
                    } else {
                        finalX = finalX - (-round % 5) * mScaleMargin - finalX % mScaleMargin;
                    }
                }
            }
        }
        mListener.onScaleScrollChanged(finalX/ mScaleMargin / 5+mInitPosition);//返回滚动选中的位置
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), finalX - mScroller.getFinalX(), 0, 800);//纠正指针位置
        postInvalidate();
    }

    private void smoothScrollBy(int dx, int dy) {
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
        postInvalidate();
    }

5,传值回调方法
使用Scroller 必须实现的方法

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), 0);
            postInvalidate();
        }
    }
 public interface OnScrollListener {
        void onScaleScrollChanged(int scale);
    }

    /**
     * 初始化数据
     * @param textList 刻度尺显示文字
     * @param initPosition 初始位置
     * @param listener 滚动监听
     */
    public void bindDataAndListener(ArrayList textList,int initPosition,OnScrollListener listener){
        mInitPosition=initPosition;
        mTextList = textList;
        mScaleWidth = (mTextList.size() * 5 - 5) * mScaleMargin;
        mListener=listener;
    }

    /**
     * 设置每个刻度间距
     * @param margin 间距
     * @return 返回当前view 链式编程
     */
    public ScrollDividingRuleView setScaleMargin(int margin){
        mScaleMargin=margin;
        mRectHeight = (int) (mLineHeight +mTextSize+mTextLineMargin+20);
        return this;
    }

    /**
     * 设置文字和刻度线的间距
     */
    public ScrollDividingRuleView setTextLineMargin(int textLineMargin){
        mTextLineMargin=textLineMargin;
        mRectHeight = (int) (mLineHeight +mTextSize+mTextLineMargin+20);
        return this;
    }

最后附上github 上的地址ScrollDividingRule

你可能感兴趣的:(Android)