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

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

概述

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

看到这个效果以后估计好多新手会觉得不知道如何入手,但是要是大神看到了就会想用什么方式实现才是最好的。这就是差距啊,没办法像我这个菜鸟只好参考下其他实现方法,写了这个demo,让我们一起来看看实现思路。

我们来分步骤一步一步来实现:

1、绘制刻度尺及刻度值(高,中,低刻度)。

2、绘制底部线。

3、绘制中间箭头。

4、监听手势处理(处理范围越界,及选中)。



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

 <declare-styleable name="RulerView">
        <!--最大刻度的颜色-->
        <attr name="mMaxScaleColor" format="color"/>
        <!--中间刻度的颜色-->
        <attr name="mMidScaleColor" format="color"/>
        <!--最小刻度的颜色-->
        <attr name="mMinScaleColor" format="color"/>
        <!--底线的颜色-->
        <attr name="mBottomLineColor" format="color"/>
        <!--最大刻度的宽度-->
        <attr name="mMaxScaleWidth" format="dimension"/>
        <!--中间刻度的宽度-->
        <attr name="mMidScaleWidth" format="dimension"/>
        <!--最小刻度的宽度-->
        <attr name="mMinScaleWidth" format="dimension"/>
        <!--底线的宽度-->
        <attr name="mBottomLineWidth" format="dimension"/>
        <!--最大刻度的高度占控件的高度比例-->
        <attr name="mMaxScaleHeightRatio" format="float"/>
        <!--中间刻度的高度占控件的高度比例-->
        <attr name="mMidScaleHeightRatio" format="float"/>
        <!--最小刻度的高度占控件的高度比例-->
        <attr name="mMinScaleHeightRatio" format="float"/>
        <!--是否显示刻度值-->
        <attr name="isShowScaleValue" format="boolean"/>
        <!--是否刻度渐变 包括刻度值和刻度线及下面的线-->
        <attr name="isScaleGradient" format="boolean"/>
        <!--刻度值颜色-->
        <attr name="mScaleValueColor" format="color"/>
        <!--刻度值文字大小-->
        <attr name="mScaleValueSize" format="dimension"/>
        <!--刻度值间隔-->
        <attr name="mScaleSpace" format="dimension"/>
        <!-- 当前值-->
        <attr name="mCurrentValue" format="integer"/>
        <!--最大值-->
        <attr name="mMaxValue" format="integer"/>
        <!--最小值-->
        <attr name="mMinValue" format="integer"/>
        <!--刻度基数-->
        <attr name="mScaleBase" format="integer"/>
        <!--中间图标-->
        <attr name="mMiddleImg" format="reference"/>
    </declare-styleable>

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


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

 <com.dalong.rulerview.RulerView
        android:id="@+id/ruler2"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:mMaxValue="5000"
        app:mMinValue="1000"
        app:mScaleBase="100"
        app:mScaleSpace="10dp"
        app:mMaxScaleColor="@color/colorAccent"
        app:mMidScaleColor="@color/colorPrimary"
        app:mMinScaleColor="@color/colorPrimary"
        app:mBottomLineColor="@color/colorAccent"
        app:mMaxScaleHeightRatio="0.5"
        app:mMidScaleHeightRatio="0.3"
        app:mMinScaleHeightRatio="0.2"
        app:mMaxScaleWidth="2.5dp"
        app:mMidScaleWidth="2dp"
        app:mMinScaleWidth="2dp"
        app:mBottomLineWidth="2.5dp"
        app:mCurrentValue="1000"
        app:mScaleValueColor="@color/colorAccent"
        app:mScaleValueSize="12sp"
        app:mMiddleImg="@mipmap/icon_arrow"
        app:isScaleGradient="false"
        />

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<scaleNum;i++){
            // 右面
            xPosition=mDrawWidth/2f+i*mScaleSpace+distanceX;
            value=currValue+i;
            if(xPosition<=mDrawWidth && value>=(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);
    }

以上写完就已经可以实现刻度尺了,但是刻度尺是无法拖动的,效果如下:


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


这里单独创建一个滑动控制类:

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<T> {
        /**
         * 当更改选择的时候回调方法
         * @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);
    }

其中有些参考了网上实现方式并进行拓展以后就实现了下面的效果。


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


相关文章
|
12月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
687 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
12月前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
223 0
|
12月前
|
Android开发 开发者
Android自定义view之利用drawArc方法实现动态效果
本文介绍了如何通过Android自定义View实现动态效果,重点使用`drawArc`方法完成圆弧动画。首先通过`onSizeChanged`进行测量,初始化画笔属性,设置圆弧相关参数。核心思路是不断改变圆弧扫过角度`sweepAngle`,并调用`invalidate()`刷新View以实现动态旋转效果。最后附上完整代码与效果图,帮助开发者快速理解并实践这一动画实现方式。
266 0
|
12月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
238 0
|
12月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
488 65
Android自定义view之网易云推荐歌单界面
|
12月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
875 84
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
266 1
|
12月前
|
Android开发 开发者
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
本文详细介绍了如何通过自定义 `attrs.xml` 文件实现 Android 自定义 View 的属性配置。以一个包含 TextView 和 ImageView 的 DemoView 为例,讲解了如何使用自定义属性动态改变文字内容和控制图片显示隐藏。同时,通过设置布尔值和点击事件,实现了图片状态的切换功能。代码中展示了如何在构造函数中解析自定义属性,并通过方法 `setSetting0n` 和 `setbackeguang` 实现功能逻辑的优化与封装。此示例帮助开发者更好地理解自定义 View 的开发流程与 attrs.xml 的实际应用。
345 2
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
164 3
|
12月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
319 3

热门文章

最新文章