欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

带刻度的安卓 SEEKBAR 和安卓定制刻度

最编程 2024-04-05 16:25:39
...


最近在做一个时间设置功能,之前都是简单的用一个EditText来实现,并设置输入范围或者输入错误的警告信息,这样的方法虽然简单,但用户使用起来,显得繁琐,而且还动不动的蹦出来俩提示,一点都不友好。
因此这次换个新的设计吧——卡尺选择。

主要介绍一下这个View的主要几个绘制点:
1. 坐标轴:需要绘制X轴(横向卡尺)或Y轴(纵向卡尺)drawLine(Canvas canvas, Paint paint);
2. 刻度:需要绘制坐标轴上的刻度。drawScale(Canvas canvas, Paint paint);绘制刻度时,需要的参数有坐标轴宽度以及刻度之间的间隔;
3. 当前刻度指针:drawScalePointer(Canvas canvas, Paint paint); 需要绘制在当前卡尺显示区域的中点。由于卡尺的这个指针每次卡尺滚动都需要重新绘制,所以每次都需要计算当前显示区域的中点位置,而这个中间位置是相对于卡尺起点位置的坐标;
4. 滑动卡尺支持scroll以及fling方式,且注意滑动的边界处理。

其他的就不多说了,直接上代码吧,代码中都有注释:
首先是卡尺绘制的基类,

package rkhy.com.ecg.view.scale;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.Scroller;

import rkhy.com.ecg.R;

/**
 * **************************************************
 *
 * @ 日        期:2018/1/5 15:33
 * @ 作        者:shangming
 * 卡尺基类
 * **************************************************
 */
public abstract class BaseScaleView extends View {
    protected int mScaleMin; // 刻度最小值
    protected int mScaleMax; // 刻度最大值
    protected int mScaleHeight; // 刻度高度
    protected int mScaleNumHeight; // 整数刻度高度
    protected int mScaleSpace; // 刻度间隔

    protected int mScaleCount; //相对刻度起点滑动的刻度
    protected int mViewWidth; //宽度
    protected int mViewHeight; //高度
    protected int mScrollPreX;
    protected int mInitMiddleScalePointer; // 初始中间刻度指针
    protected int mScrollViewWidth; // 滚动的View宽度
    protected int mScaleMiddle; // 屏幕中间的刻度偏移量

    protected Scroller mScroller;
    protected OnScaleScrollListener mOnScaleScrollListener;
    protected VelocityTracker mVelocityTracker;

    public BaseScaleView(Context context) {
        super(context);
        init(context, null);
    }

    public BaseScaleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public BaseScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.scale_attrs);
        mScaleMin = ta.getInteger(R.styleable.scale_attrs_scale_min, 10);
        mScaleMax = ta.getInteger(R.styleable.scale_attrs_scale_max, 100);
        mScaleHeight = ta.getDimensionPixelOffset(R.styleable.scale_attrs_scale_height, 20);
        mScaleNumHeight = ta.getDimensionPixelOffset(R.styleable.scale_attrs_scale_number_height, 30);
        mScaleSpace = ta.getDimensionPixelOffset(R.styleable.scale_attrs_scale_space, 15);
        ta.recycle();

        mScroller = new Scroller(context);
        initView();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(getResources().getColor(R.color.headBgColor));
        paint.setAntiAlias(true);
        // 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
        paint.setDither(true);
        paint.setStyle(Paint.Style.STROKE);
        // 文字居中
        paint.setTextAlign(Paint.Align.CENTER);

        drawLine(canvas, paint);
        drawScale(canvas, paint);
        drawScalePointer(canvas, paint);
        super.onDraw(canvas);
    }

    public void setCurrent(int scale) {
        if (scale >= mScaleMin && scale <= mScaleMax) {
            smoothTo(scale);
            this.postInvalidate();
        }
    }

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

    @Override
    public void computeScroll() {
        super.computeScroll();
        // 判断Scroller是否执行完毕
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            // 通过重绘来不断调用computeScroll
            invalidate();
        }
    }

    public void setOnScaleScrollListener(OnScaleScrollListener onScaleScrollListener) {
        this.mOnScaleScrollListener = onScaleScrollListener;
    }

    /**
     * 初始化参数
     */
    protected abstract void initView();

    /**
     * 绘制刻度线
     *
     * @param canvas
     * @param paint
     */
    protected abstract void drawLine(Canvas canvas, Paint paint);

    /**
     * 绘制刻度
     *
     * @param canvas
     * @param paint
     */
    protected abstract void drawScale(Canvas canvas, Paint paint);

    /**
     * 绘制刻度指针
     *
     * @param canvas
     * @param paint
     */
    protected abstract void drawScalePointer(Canvas canvas, Paint paint);

    /**
     * 滑动到指定刻度
     *
     * @param scale
     */
    public abstract void smoothTo(int scale);

    public interface OnScaleScrollListener {
        void onScaleScroll(int scale);
    }
}

横向卡尺的实现:

package rkhy.com.ecg.view.scale;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewGroup;

import rkhy.com.ecg.R;

/**
 * **************************************************
 *
 * @ 日        期:2018/1/5 16:58
 * @ 作        者:shangming
 * 横向卡尺实现
 * **************************************************
 */
public class HorizontalScaleView extends BaseScaleView {
    public HorizontalScaleView(Context context) {
        super(context);
    }

    public HorizontalScaleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public HorizontalScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void initView() {
        mViewWidth = (mScaleMax - mScaleMin) * mScaleSpace;
        mViewHeight = mScaleHeight * 8 - 4;

        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(mViewWidth, mViewHeight);
        this.setLayoutParams(layoutParams);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = MeasureSpec.makeMeasureSpec(mViewHeight, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, height);
        mScrollViewWidth = getMeasuredWidth();
        mScaleMiddle = mScrollViewWidth / mScaleSpace / 2;
        mInitMiddleScalePointer = mScaleMiddle + mScaleMin;
    }

    @Override
    protected void drawLine(Canvas canvas, Paint paint) {
        canvas.drawLine(0, mViewHeight, mViewWidth, mViewHeight, paint);
    }

    @Override
    protected void drawScale(Canvas canvas, Paint paint) {
        paint.setTextSize(mViewHeight / 4); // 刻度数字大小
        paint.setStrokeWidth(2.0f);
        for (int i = 0, num = mScaleMin; i <= mScaleMax - mScaleMin; i++) {
            int x = i * mScaleSpace;
            if (0 == (i % 10)) { // 10的整数倍
                canvas.drawLine(x, mViewHeight, x, mViewHeight - mScaleNumHeight, paint);
                canvas.drawText(num + "", x, mViewHeight - mScaleNumHeight - mScaleHeight - 2, paint); // 绘制整数数字
                num += 10;
            } else if (0 == (i % 5)) { // 5的整数倍
                int height = mViewHeight - (mScaleHeight + (mScaleNumHeight - mScaleHeight) / 2);
                canvas.drawLine(x, mViewHeight, x, height, paint);
            } else { // 非整数
                canvas.drawLine(x, mViewHeight, x, mViewHeight - mScaleHeight, paint);
            }
        }
    }

    @Override
    protected void drawScalePointer(Canvas canvas, Paint paint) {
        paint.setColor(getResources().getColor(R.color.red));
        paint.setStrokeWidth(5.0f);
        //根据滑动的距离,计算指针的位置【指针始终位于刻度中间】
        int currentX = mScroller.getCurrX();
        //滑动的刻度
        mScaleCount = mInitMiddleScalePointer + (int) Math.rint((double) currentX / (double) mScaleSpace);
        // 刻度越界处理
        if (mScaleCount >= mScaleMax) {
            mScaleCount = mScaleMax;
        } else if (mScaleCount <= mScaleMin) {
            mScaleCount = mScaleMin;
        }
        if (mOnScaleScrollListener != null) { //回调方法
            mOnScaleScrollListener.onScaleScroll(mScaleCount);
        }
        int x = mScaleSpace * (mScaleMiddle + mScaleCount - mInitMiddleScalePointer);
        // 滑动的距离越界处理
        if (x >= mViewWidth) {
            x = mViewWidth;
        } else if (x <= 0) {
            x = 0;
        }
        canvas.drawLine(x, mViewHeight, x, mViewHeight - mScaleNumHeight - mScaleHeight, paint);
    }

    @Override
    public void smoothTo(int scale) {
        if (scale < mScaleMin || scale > mScaleMax) {
            return;
        }
        int dx = (scale - mScaleCount) * mScaleSpace;
        smoothScrollBy(dx, 0);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        if (mVelocityTracker == null) {
            //检查速度测量器,如果为null,获得一个
            mVelocityTracker = VelocityTracker.obtain();
        }
        int index = event.getActionIndex();
        int pointerId = event.getPointerId(index);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mVelocityTracker.addMovement(event);
                if (mScroller != null && !mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                mScrollPreX = x;
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                int dx = mScrollPreX - x;
                if (dx < 0) {
                    if (mScaleCount <= mScaleMin) { // 滑到右边界
                        return super.onTouchEvent(event);
                    }
                } else {
                    if (mScaleCount >= mScaleMax) { // 滑到左边界
                        return super.onTouchEvent(event);
                    }
                }
                smoothScrollBy(dx, 0);
                mScrollPreX = x;
                postInvalidate();
                break;
            case MotionEvent.ACTION_UP:
                //设置速度单位:/50ms
                mVelocityTracker.computeCurrentVelocity(50);
                // 初始速度:*px/50ms
                int initialVelocity = (int) mVelocityTracker.getXVelocity(pointerId);
                if (Math.abs(initialVelocity) > 150) {
                    // 由于坐标轴正方向问题,要加负号。
                    doFling(-initialVelocity);
                } else {
                    if (mScaleCount < mScaleMin) mScaleCount = mScaleMin;
                    if (mScaleCount > mScaleMax) mScaleCount = mScaleMax;
                    int finalX = (mScaleCount - mInitMiddleScalePointer) * mScaleSpace;
                    mScroller.setFinalX(finalX); //纠正指针位置
                    invalidate();
                }
                break;
        }
        return true;
    }

    private void doFling(int speed) {
        if (mScroller == null) {
            return;
        }
        mScroller.fling(this.getScrollX(), 0, speed, 0,
                -(mScrollViewWidth / 2), mViewWidth - (mScrollViewWidth / 2),
                0, 0);
    }
}

最终效果:

Android 带刻度的SEEKBAR android自定义刻度尺_ci