Android自定义一个属于自己的刻度尺

概述

      本文只要说的是自定义一个刻度尺,正好学习下android自定义控件,之前写过一篇《Android自定义一个属于自己的时间钟表》,大家如果感兴趣可以去看下,好了不扯淡了,直接上效果:
Android自定义一个属于自己的刻度尺_第1张图片


看到这个效果以后估计好多新手会觉得不知道如何入手,但是要是大神看到了就会想用什么方式实现才是最好的。这就是差距啊,没办法像我这个菜鸟只好参考下其他实现方法,写了这个demo,让我们一起来看看实现思路。
我们来分步骤一步一步来实现:
1、绘制刻度尺及刻度值(高,中,低刻度)。
2、绘制底部线。
3、绘制中间箭头。
4、监听手势处理(处理范围越界,及选中)。


第一步:1、自定义View的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

 
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

通过这个attrs大家也看到,我们设置了还是蛮细的,所有的刻度及刻度值颜色大小都设定了,让控件设置更加的灵活。

2、下面创建一个class类为rulerview.class ,并设置main.xml中:
 

3、在自定义View的构造方法中,获得我们的自定义的样式

 // 默认刻度模式
    public static final int MOD_TYPE_SCALE = 5;
    //刻度基数  每个刻度代表多少 默认为1
    public int mScaleBase=1;
    //最大刻度的颜色
    public int mMaxScaleColor;
    //中间刻度的颜色
    public int mMidScaleColor;
    //最小刻度的颜色
    public int mMinScaleColor;
    //底部线的颜色
    public int mBottomLineColor;
    //最大刻度的宽度
    public float mMaxScaleWidth;
    //中间刻度的宽度
    public float mMidScaleWidth;
    //最小刻度的宽度
    public float mMinScaleWidth;
    //底线的宽度
    public float mBottomLineWidth;
    //最大刻度的高度占控件的高度比例
    public float mMaxScaleHeightRatio;
    //中间刻度的高度占控件的高度比例
    public float mMidScaleHeightRatio;
    //最小刻度的高度占控件的高度比例
    public float mMinScaleHeightRatio;
    //是否显示刻度值
    public boolean isShowScaleValue;
    //是否刻度渐变
    public boolean isScaleGradient;
    //刻度值颜色
    public int  mScaleValueColor;
    //刻度值文字大小
    public float  mScaleValueSize;
    //当前值
    public int mCurrentValue;
    //最大值
    public int mMaxValue;
    //最小值
    public int mMinValue;
    //中间图片
    private Bitmap mMiddleImg;
    //刻度线画笔
    private  Paint mScalePaint;
    // 刻度值画笔
    private  TextPaint mScaleValuePaint;
    //中间图片画笔
    private  Paint mMiddleImgPaint;
    private  float mTpDesiredWidth;
    //最大刻度高度
    private int mMaxScaleHeight;
    //中间刻度高度
    private int mMidScaleHeight;
    //最小刻度高度
    private int mMinScaleHeight;
    // 滚动偏移量
    private int scrollingOffset;
    //间隔S
    private int mScaleSpace=20;
    // 滚动器
    private RulerViewScroller scroller;
    // 是否执行滚动
    private boolean isScrollingPerformed;

    public RulerView(Context context) {
        this(context,null);
    }

    public RulerView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public RulerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.RulerView);

        mMaxScaleColor=typedArray.getColor(R.styleable.RulerView_mMaxScaleColor, Color.BLACK);
        mMidScaleColor=typedArray.getColor(R.styleable.RulerView_mMidScaleColor, Color.BLACK);
        mMinScaleColor=typedArray.getColor(R.styleable.RulerView_mMinScaleColor, Color.BLACK);
        mMaxScaleColor=typedArray.getColor(R.styleable.RulerView_mMaxScaleColor, Color.BLACK);
        mScaleValueColor=typedArray.getColor(R.styleable.RulerView_mScaleValueColor, Color.BLACK);
        mBottomLineColor=typedArray.getColor(R.styleable.RulerView_mBottomLineColor, Color.BLACK);

        mMaxScaleWidth = typedArray.getDimensionPixelSize(R.styleable.RulerView_mMaxScaleWidth, 15);
        mMidScaleWidth = typedArray.getDimensionPixelSize(R.styleable.RulerView_mMidScaleWidth, 12);
        mMinScaleWidth = typedArray.getDimensionPixelSize(R.styleable.RulerView_mMinScaleWidth, 10);
        mBottomLineWidth = typedArray.getDimensionPixelSize(R.styleable.RulerView_mBottomLineWidth, 15);
        mScaleValueSize = typedArray.getDimensionPixelSize(R.styleable.RulerView_mScaleValueSize, 12);
        mScaleSpace = typedArray.getDimensionPixelSize(R.styleable.RulerView_mScaleSpace, 20);

        mMaxScaleHeightRatio = typedArray.getFloat(R.styleable.RulerView_mMaxScaleHeightRatio, 0.3f);
        mMidScaleHeightRatio = typedArray.getFloat(R.styleable.RulerView_mMidScaleHeightRatio, 0.2f);
        mMinScaleHeightRatio = typedArray.getFloat(R.styleable.RulerView_mMinScaleHeightRatio, 0.1f);

        isShowScaleValue = typedArray.getBoolean(R.styleable.RulerView_isShowScaleValue, true);
        isScaleGradient = typedArray.getBoolean(R.styleable.RulerView_isScaleGradient, true);

        mMaxValue = typedArray.getInteger(R.styleable.RulerView_mMaxValue, 100);
        mMinValue = typedArray.getInteger(R.styleable.RulerView_mMinValue, 0);
        mScaleBase = typedArray.getInteger(R.styleable.RulerView_mScaleBase, 1);
        mCurrentValue = typedArray.getInteger(R.styleable.RulerView_mCurrentValue, 0);
        setCurrentValue(mCurrentValue);

        mMiddleImg = BitmapFactory.decodeResource(getResources(),
                typedArray.getResourceId(R.styleable.RulerView_mMiddleImg,R.drawable.ruler_mid_arraw));
        typedArray.recycle();

        mScalePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        mScalePaint.setStyle(Paint.Style.STROKE);
        mScalePaint.setAntiAlias(true);

        mScaleValuePaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mScaleValuePaint.setColor(mScaleValueColor);
        mScaleValuePaint.setTextSize(mScaleValueSize);
        mScaleValuePaint.setTextAlign(Paint.Align.CENTER);
        mTpDesiredWidth = Layout.getDesiredWidth("0", mScaleValuePaint);

        mMiddleImgPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
        mMiddleImgPaint.setStyle(Paint.Style.STROKE);
        mMiddleImgPaint.setAntiAlias(true);

        scroller=new RulerViewScroller(context,scrollingListener);
    }

4、我们重写onMesure:
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidthSize(widthMeasureSpec),measureHeightSize(heightMeasureSpec));
    }

    private int measureHeightSize(int heightMeasureSpec) {
        int result;
        int mode=MeasureSpec.getMode(heightMeasureSpec);
        int size=MeasureSpec.getSize(heightMeasureSpec);
        if(mode==MeasureSpec.EXACTLY){
            result=size;
        }else{
            result=(int) (mMiddleImg.getHeight() + getPaddingTop() + getPaddingBottom() + 2 * mScaleValuePaint.getTextSize());
            if(mode==MeasureSpec.AT_MOST){
                result=Math.min(result,size);
            }
        }
        return  result;
    }

    private int measureWidthSize(int widthMeasureSpec) {
        int result;
        int mode=MeasureSpec.getMode(widthMeasureSpec);
        int size=MeasureSpec.getSize(widthMeasureSpec);
        if(mode==MeasureSpec.EXACTLY){
            result=size;
        }else{
            result=400;
            if(mode==MeasureSpec.AT_MOST){
                result=Math.min(result,size);
            }
        }
        return  result;
    }

5、设置下三种刻度尺的高度
  @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w == 0 || h == 0)
            return;
        /**
         * 在这里根据控件高度设置三中刻度线的高度
         */
        int mHeight = h - getPaddingTop() - getPaddingBottom();
        mMaxScaleHeight = (int) (mHeight*mMaxScaleHeightRatio);
        mMidScaleHeight = (int) (mHeight*mMidScaleHeightRatio);
        mMinScaleHeight = (int) (mHeight*mMinScaleHeightRatio);
    }

6、绘制刻度尺

 /**
     * 绘制刻度线
     * @param canvas
     * @param mDrawWidth
     * @param mDrawHeight
     */
    private void drawScaleLine(Canvas canvas, int mDrawWidth, int mDrawHeight) {
        int scaleNum= (int) (Math.ceil(mDrawWidth/2f/mScaleSpace))+2;
        int distanceX = scrollingOffset;
        int currValue = mCurrentValue;
        drawScaleLine(canvas,scaleNum,distanceX,currValue,mDrawWidth,mDrawHeight);
    }

    /**
     * 绘制刻度线
     * @param canvas
     * @param scaleNum
     * @param distanceX
     * @param currValue
     * @param mDrawWidth
     * @param mDrawHeight
     */
    private void drawScaleLine(Canvas canvas, int scaleNum, int distanceX, int currValue, int mDrawWidth, int mDrawHeight) {
        int dy = (int) (mDrawHeight - mTpDesiredWidth - mScaleValuePaint.getTextSize()) - getPaddingBottom();
        int value;
        float xPosition;
        for (int i=0;i=(mMinValue/mScaleBase)&&value<=(mMaxValue/mScaleBase)){
                drawScaleLine(canvas, value,  xPosition, dy, scaleNum, i, mDrawHeight);
            }
            //绘制右面线
            if(value<(mMaxValue/mScaleBase)&&value>=(mMinValue/mScaleBase))
                drawBottomLine(canvas,getAlpha(scaleNum, i),xPosition-mMaxScaleWidth/2, dy, xPosition+mScaleSpace+mMaxScaleWidth/2, dy);

            //左面
            xPosition=mDrawWidth/2f-i*mScaleSpace+distanceX;
            value=currValue-i;
            if(xPosition>getPaddingLeft() && value>=(mMinValue/mScaleBase)&&value<=(mMaxValue/mScaleBase)){
                drawScaleLine( canvas, value,  xPosition, dy, scaleNum, i, mDrawHeight);
            }
            //绘制左面线
            if(value>=(mMinValue/mScaleBase) && value<(mMaxValue/mScaleBase))
                drawBottomLine(canvas,getAlpha(scaleNum, i),xPosition-mMaxScaleWidth/2, dy, xPosition+mScaleSpace+mMaxScaleWidth/2, dy);
        }
    }

    /**
     * 绘制底部线
     * @param canvas
     * @param alpha
     * @param sx
     * @param sy
     * @param ex
     * @param ey
     */
    private void drawBottomLine(Canvas canvas,int alpha,float sx,float sy,float ex,float ey){
        mScalePaint.setColor(mBottomLineColor);
        mScalePaint.setStrokeWidth(mBottomLineWidth);
        mScalePaint.setAlpha(alpha);
        canvas.drawLine(sx, sy, ex, ey, mScalePaint);
    }
    /**
     * 绘制刻度尺  左  右
     * @param canvas
     * @param value
     * @param xPosition
     * @param dy
     * @param scaleNum
     * @param i
     * @param mDrawHeight
     */
    public void drawScaleLine(Canvas canvas,int value, float xPosition,int dy,int scaleNum,int i,int mDrawHeight){
        if (value % MOD_TYPE_SCALE == 0) {
            if(value % (MOD_TYPE_SCALE*2)==0){//大刻度
                drawScaleLine(canvas,mMaxScaleWidth,mMaxScaleColor,getAlpha(scaleNum, i),
                        xPosition,dy,xPosition,dy - mMaxScaleHeight);
                if (isShowScaleValue) {
                    mScaleValuePaint.setAlpha(getAlpha(scaleNum, i));
                    canvas.drawText(String.valueOf(value*mScaleBase), xPosition, mDrawHeight - mTpDesiredWidth, mScaleValuePaint);
                }
            }else{//中刻度
                drawScaleLine(canvas,mMidScaleWidth,mMidScaleColor,getAlpha(scaleNum, i),
                        xPosition,dy,xPosition,dy-mMidScaleHeight);
            }
        }else{// 小刻度
            drawScaleLine(canvas,mMinScaleWidth,mMinScaleColor,getAlpha(scaleNum, i),
                    xPosition,dy,xPosition,dy-mMinScaleHeight);
        }

    }
    /**
     * 绘制刻度尺刻度
     * @param canvas
     * @param strokeWidth
     * @param scaleColor
     * @param alpha
     * @param sx
     * @param sy
     * @param ex
     * @param ey
     */
    private void drawScaleLine(Canvas canvas,float strokeWidth,int scaleColor,int alpha,float sx,float sy,float ex,float ey){
        mScalePaint.setStrokeWidth(strokeWidth);
        mScalePaint.setColor(scaleColor);
        mScalePaint.setAlpha(alpha);
        canvas.drawLine(sx, sy, ex, ey, mScalePaint);
    }

其实上面就是绘制刻度尺的核心办法,主要实现思想就是以中心为开始点向左右绘制刻度线。其中也有一些细节需要大家慢慢去思索的,比如这里包含高,中 ,低的三种刻度线,他们在绘制的时候高度,颜色,宽度都是不一样的设置。怎么区分,我这里也写的比较的清楚。这里我就不在提了。
7、绘制中间箭头图片
    /**
     * 绘制中间图片
     * @param canvas
     * @param mDrawWidth
     * @param mDrawHeight
     */
    private void drawMiddleImg(Canvas canvas, int mDrawWidth, int mDrawHeight) {
        int left = (mDrawWidth - mMiddleImg.getWidth()) / 2;
        int top = (int) (mScaleValuePaint.getTextSize() / 2);
        canvas.drawBitmap(mMiddleImg, left, top, mMiddleImgPaint);
    }

以上写完就已经可以实现刻度尺了,但是刻度尺是无法拖动的,效果如下:
Android自定义一个属于自己的刻度尺_第2张图片

下面主要就是需要如何实现拖动的效果,其实这个才是最难的。

这里单独创建一个滑动控制类:
public class RulerViewScroller {

    //滚动的时间
    public static final int SCROLLING_DURATION = 400;

    //用于滚动的最小增量
    public static final int MIN_DELTA_FOR_SCROLLING = 1;

    //Listener
    private ScrollingListener listener;

    //上下文
    private Context context;

    // Scrolling
    private GestureDetector gestureDetector;
    private Scroller scroller;
    private int lastScrollX;
    private float lastTouchedX;
    private boolean isScrollingPerformed;

    private final int MESSAGE_SCROLL = 0;
    private final int MESSAGE_JUSTIFY = 1;


    public RulerViewScroller(Context context, ScrollingListener listener) {
        this.listener = listener;
        this.context = context;
        gestureDetector = new GestureDetector(context, gestureListener);
        gestureDetector.setIsLongpressEnabled(false);
        scroller = new Scroller(context);
        scroller.setFriction(0.05f);

    }


    /**
     * 手势监听
      */
    private GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return true;
        }

        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            lastScrollX = 0;
            scroller.fling(0, lastScrollX, (int) -velocityX, 0, -0x7FFFFFFF, 0x7FFFFFFF, 0, 0);
            setNextMessage(MESSAGE_SCROLL);
            return true;
        }
    };


    /**
     * 手势处理
     * @param event
     * @return
     */
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastTouchedX = event.getX();
                scroller.forceFinished(true);
                clearMessages();
                break;

            case MotionEvent.ACTION_MOVE:
                int distanceX = (int) (event.getX() - lastTouchedX);
                if (distanceX != 0) {
                    startScrolling();
                    listener.onScroll(distanceX);
                    lastTouchedX = event.getX();
                }
                break;
        }
        //当手指离开控件时
        if (!gestureDetector.onTouchEvent(event) && event.getAction() == MotionEvent.ACTION_UP) {
            justify();
        }

        return true;
    }
    /**
     * 发送下一步消息,清楚之前的消息
     * @param message
     */
    private void setNextMessage(int message) {
        clearMessages();
        animationHandler.sendEmptyMessage(message);
    }

    /**
     * 清楚所有的what的消息列表
     */
    private void clearMessages() {
        animationHandler.removeMessages(MESSAGE_SCROLL);
        animationHandler.removeMessages(MESSAGE_JUSTIFY);
    }


    /**
     * 滚动
     * @param distance 距离
     * @param time     时间
     */
    public void scroll(int distance, int time) {
        scroller.forceFinished(true);
        lastScrollX = 0;
        scroller.startScroll(0, 0, distance, 0, time != 0 ? time : SCROLLING_DURATION);
        setNextMessage(MESSAGE_SCROLL);
        startScrolling();
    }

    /**
     * 动画处理handler
     */
    private Handler animationHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            scroller.computeScrollOffset();
            int currX = scroller.getCurrX();
            int delta = lastScrollX - currX;
            lastScrollX = currX;
            if (delta != 0) {
                listener.onScroll(delta);
            }
            // 滚动是不是完成时,涉及到最终Y,所以手动完成
            if (Math.abs(currX - scroller.getFinalX()) < MIN_DELTA_FOR_SCROLLING) {
                lastScrollX = scroller.getFinalX();
                scroller.forceFinished(true);
            }
            if (!scroller.isFinished()) {
                animationHandler.sendEmptyMessage(msg.what);
            } else if (msg.what == MESSAGE_SCROLL) {
                justify();
            } else {
                finishScrolling();
            }
            return true;
        }
    });


    /**
     * 滚动停止时待校验
     */
    private void justify() {
        listener.onJustify();
        setNextMessage(MESSAGE_JUSTIFY);
    }

    /**
     * 开始滚动
     */
    private void startScrolling() {
        if (!isScrollingPerformed) {
            isScrollingPerformed = true;
            listener.onStarted();
        }
    }

    /**
     * 滚动结束
     */
    void finishScrolling() {
        if (isScrollingPerformed) {
            listener.onFinished();
            isScrollingPerformed = false;
        }
    }

    /**
     *  滚动监听器接口
     */
    public interface ScrollingListener {
        /**
         * 正在滚动中回调
         * @param distance 滚动的距离
         */
        void onScroll(int distance);

        /**
         * 启动滚动时调用的回调函数
         */
        void onStarted();

        /**
         * 校验完成后 执行完毕后回调
         */
        void onFinished();

        /**
         * 滚动停止时待校验
         */
        void onJustify();
    }


然后在activity中:

 /**
     * 滚动回调接口
     */
    RulerViewScroller.ScrollingListener scrollingListener = new RulerViewScroller.ScrollingListener() {

        /**
         * 滚动开始
         */
        @Override
        public void onStarted() {
            isScrollingPerformed = true;
            //滚动开始
            if (null != onWheelListener) {
                onWheelListener.onScrollingStarted(RulerView.this);
            }
        }

        /**
         * 滚动中
         * @param distance 滚动的距离
         */
        @Override
        public void onScroll(int distance) {
            doScroll(distance);
        }

        /**
         * 滚动结束
         */
        @Override
        public void onFinished() {
            if (outOfRange()) {
                return;
            }
            if (isScrollingPerformed) {
                //滚动结束
                if (null != onWheelListener) {
                    onWheelListener.onScrollingFinished(RulerView.this);
                }
                isScrollingPerformed = false;
            }
            scrollingOffset = 0;
            invalidate();
        }

        /**
         * 验证滚动是否在正确位置
         */
        @Override
        public void onJustify() {
            if (outOfRange()) {
                return;
            }
            if (Math.abs(scrollingOffset) > RulerViewScroller.MIN_DELTA_FOR_SCROLLING) {
                if (scrollingOffset < -mScaleSpace / 2) {
                    scroller.scroll(mScaleSpace + scrollingOffset, 0);
                } else if (scrollingOffset > mScaleSpace / 2) {
                    scroller.scroll(scrollingOffset - mScaleSpace, 0);
                } else {
                    scroller.scroll(scrollingOffset, 0);
                }
            }
        }
    };



    /**
     * 超出左右范围
     * @return
     */
    private boolean outOfRange() {
        //这个是越界后需要回滚的大小值
        int outRange = 0;
        if (mCurrentValue < mMinValue/mScaleBase) {
            outRange = (mCurrentValue - mMinValue/mScaleBase) * mScaleSpace;
        } else if (mCurrentValue > mMaxValue/mScaleBase) {
            outRange = (mCurrentValue - mMaxValue/mScaleBase) * mScaleSpace;
        }
        if (0 != outRange) {
            scrollingOffset = 0;
            scroller.scroll(-outRange, 100);
            return true;
        }
        return false;
    }

    /**
     * 滚动中回调最新值
     * @param delta
     */
    private void doScroll(int delta) {
        scrollingOffset += delta;
        int offsetCount = scrollingOffset / mScaleSpace;
        if (0 != offsetCount) {
            // 显示在范围内
            int oldValueIndex = Math.min(Math.max(mMinValue, mCurrentValue*mScaleBase), mMaxValue);
            mCurrentValue -= offsetCount;
            scrollingOffset -= offsetCount * mScaleSpace;
            if (null != onWheelListener) {
                //回调通知最新的值
                int valueIndex = Math.min(Math.max(mMinValue, mCurrentValue*mScaleBase), mMaxValue);
                onWheelListener.onChanged(this, oldValueIndex + "",valueIndex+"");
            }
        }
        invalidate();
    }

    private float mDownFocusX;
    private float mDownFocusY;
    private boolean isDisallowIntercept;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            return true;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownFocusX = event.getX();
                mDownFocusY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isDisallowIntercept && Math.abs(event.getY() - mDownFocusY) < Math.abs(event.getX() - mDownFocusX)) {
                    isDisallowIntercept = true;
                    if (getParent() != null) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (getParent() != null) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                isDisallowIntercept = false;
                break;
        }
        return scroller.onTouchEvent(event);
    }

    private OnRulerViewScrollListener onWheelListener;

    /**
     * 添加滚动回调
     * @param listener the listener
     */
    public void setScrollingListener(OnRulerViewScrollListener listener) {
        onWheelListener = listener;
    }


    public interface OnRulerViewScrollListener {
        /**
         * 当更改选择的时候回调方法
         * @param rulerView 状态更改的view
         * @param oldValue  当前item的旧值
         * @param newValue  当前item的新值
         */
        void onChanged(RulerView rulerView, T oldValue, T newValue);

        /**
         * 滚动启动时调用的回调方法
         * @param rulerView
         */
        void onScrollingStarted(RulerView rulerView);

        /**
         * 滚动结束时调用的回调方法
         * @param rulerView
         */
        void onScrollingFinished(RulerView rulerView);
    }

其中有些参考了网上实现方式并进行拓展以后就实现了下面的效果。
Android自定义一个属于自己的刻度尺_第3张图片

这里附上github:https://github.com/dalong982242260/AndroidRulerView

你可能感兴趣的:(Android,自定义控件)