Swift51.com
麦子学院 头像
麦子学院  2017-06-12 12:06

Android滚动刻度尺实现

回复:0  查看:2352  

本文和大家分享的主要是android中滚动刻度尺的实现,一起来看看吧,希望对大家学习android有所帮助。

  最近在帮人做一个计步器,其中涉及到身高、体重等信息的采集;我参考了众多app的实现,觉得"乐动力"中滑动刻度的方式比较优雅。于是乎,反编译了该app,结果发现它是采用图片的方式实现的,即ScrollView内嵌了一张带刻度的图片。

  个人觉得该方式太不灵活,且对美工的依赖较大,于是便想自定义一个刻度尺控件。

  需求分析

  1. 绘制刻度,区分整值刻度和普通刻度

  2. 红色指针始终在刻度尺的中间,表示当前的刻度

  3. 刻度的最大值和最小值可动态设置

  4. 刻度尺的高度或宽度可设置,设置后中间刻度不变

  5. 可滑动,滑动后当前刻度随之改变

  涉及的知识点

  1. View的机制

  2. canvas绘图

  3. Scroller工具类的使用

  4. 自定义View的属性

  5. 点击、滑动事件的处理

  最终效果

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

  https://github.com/LichFaker/ScaleView

  实现过程

  1. 新建一个classHorizontalScaleScrollView, 继承自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.

  4. 重写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;

  }

  5.

  6.

  重写onDraw,绘制刻度和指针

  7.

  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);

  }

  }

  }

  8.

  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);

  }

  9.

  10.

  处理滑动事件

  11.

  1. 在手指按下时,记录当前的x坐标(针对水平刻度尺)。

  2. 在手指滑动过程中,判断当前指针所指的刻度是否已经超出了边界,如果超出,则禁止滑动,同时刷新当前界面。

  3. 在手指抬起时,校正当前的刻度。

  @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);

  }

  最后的说明

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

  1. 第一次快速滑动时,可以超出边界,之后则不会;

  2. 开放的自定义属性不够(根据具体情况);

  3. 可以考虑将水平和垂直的实现,在一个类中完成,因为在实现过程中发现其实有很多代码都是类似的,只是个别参数属性的不同,在坐标系中,垂直可以看成是水平旋转了90°,之后有时间可以朝这个方向尝试下。

 

 

来源:简书