Android实现滚动刻度尺效果

时间:2021-05-20

缘起

最近在帮人做一个计步器,其中涉及到身高、体重等信息的采集;我参考了众多app的实现,觉得"乐动力"中滑动刻度的方式比较优雅。于是乎,反编译了该app,结果发现它是采用图片的方式实现的,即ScrollView内嵌了一张带刻度的图片。
个人觉得该方式太不灵活,且对美工的依赖较大,于是便想自定义一个刻度尺控件。

需求分析

  • 绘制刻度,区分整值刻度和普通刻度
  • 红色指针始终在刻度尺的中间,表示当前的刻度
  • 刻度的最大值和最小值可动态设置
  • 刻度尺的高度或宽度可设置,设置后中间刻度不变
  • 可滑动,滑动后当前刻度随之改变
  • 涉及的知识点

  • View的机制
  • canvas绘图
  • Scroller工具类的使用
  • 自定义View的属性
  • 点击、滑动事件的处理
  • 最终效果

    由于简书上无法嵌入gif,为不影响效果,请移步github查看,如果觉得不错,帮忙给个star ^_^https://github.com/LichFaker/ScaleView

    实现过程

    1、新建一个class:HorizontalScaleScrollView, 继承自View

    2、在构造方法中获取自定义属性:

    protected void init(AttributeSet attrs) { // 获取自定义属性 TypedArray ta = getContext().obtainStyledAttributes(attrs, ATTR); mMin = ta.getInteger(LF_SCALE_MIN, 0); mMax = ta.getInteger(LF_SCALE_MAX, 200); mScaleMargin = ta.getDimensionPixelOffset(LF_SCALE_MARGIN, 15); mScaleHeight = ta.getDimensionPixelOffset(LF_SCALE_HEIGHT, 20); ta.recycle(); mScroller = new Scroller(getContext()); }

    3、重写onMeasure,计算中间刻度

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height=MeasureSpec.makeMeasureSpec(mRectHeight, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, height); mScaleScrollViewRange = getMeasuredWidth(); mTempScale = mScaleScrollViewRange / mScaleMargin / 2 + mMin; mMidCountScale = mScaleScrollViewRange / mScaleMargin / 2 + mMin;}

    4、重写onDraw,绘制刻度和指针

    protected void onDrawScale(Canvas canvas, Paint paint) { paint.setTextSize(mRectHeight / 4); for (int i = 0, k = mMin; i <= mMax - mMin; i++) { if (i % 10 == 0) { //整值 canvas.drawLine(i * mScaleMargin, mRectHeight, i * mScaleMargin, mRectHeight - mScaleMaxHeight, paint); //整值文字 canvas.drawText(String.valueOf(k), i * mScaleMargin, mRectHeight - mScaleMaxHeight - 20, paint); k += 10; } else { canvas.drawLine(i * mScaleMargin, mRectHeight, i * mScaleMargin, mRectHeight - mScaleHeight, paint); } }}protected void onDrawPointer(Canvas canvas, Paint paint) { paint.setColor(Color.RED); //每一屏幕刻度的个数/2 int countScale = mScaleScrollViewRange / mScaleMargin / 2; //根据滑动的距离,计算指针的位置【指针始终位于屏幕中间】 int finalX = mScroller.getFinalX(); //滑动的刻度 int tmpCountScale = (int) Math.rint((double) finalX / (double) mScaleMargin);//四舍五入取整 //总刻度 mCountScale = tmpCountScale + countScale + mMin; if (mScrollListener != null) { //回调方法 mScrollListener.onScaleScroll(mCountScale); } canvas.drawLine(countScale * mScaleMargin + finalX, mRectHeight, countScale * mScaleMargin + finalX, mRectHeight - mScaleMaxHeight - mScaleHeight, paint);}

    处理滑动事件

  • 在手指按下时,记录当前的x坐标(针对水平刻度尺)。
  • 在手指滑动过程中,判断当前指针所指的刻度是否已经超出了边界,如果超出,则禁止滑动,同时刷新当前界面。
  • 在手指抬起时,校正当前的刻度。
  • @Overridepublic boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (mScroller != null && !mScroller.isFinished()) { mScroller.abortAnimation(); } mScrollLastX = x; return true; case MotionEvent.ACTION_MOVE: int dataX = mScrollLastX - x; if (mCountScale - mTempScale < 0) { //向右边滑动 if (mCountScale <= mMin && dataX <= 0) //禁止继续向右滑动 return super.onTouchEvent(event); } else if (mCountScale - mTempScale > 0) { //向左边滑动 if (mCountScale >= mMax && dataX >= 0) //禁止继续向左滑动 return super.onTouchEvent(event); } smoothScrollBy(dataX, 0); mScrollLastX = x; postInvalidate(); mTempScale = mCountScale; return true; case MotionEvent.ACTION_UP: if (mCountScale < mMin) mCountScale = mMin; if (mCountScale > mMax) mCountScale = mMax; int finalX = (mCountScale - mMidCountScale) * mScaleMargin; mScroller.setFinalX(finalX); //纠正指针位置 postInvalidate(); return true; } return super.onTouchEvent(event);}

    最后的说明

    以上只是针对水平滑动刻度的实现,垂直滑动原理一致,在源码中已经实现,其中也有许多不够完善的地方,如:

  • 第一次快速滑动时,可以超出边界,之后则不会;
  • 开放的自定义属性不够(根据具体情况);
  • 可以考虑将水平和垂直的实现,在一个类中完成,因为在实现过程中发现其实有很多代码都是类似的,只是个别参数属性的不同,在坐标系中,垂直可以看成是水平旋转了90°,之后有时间可以朝这个方向尝试下。
  • 声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

    相关文章