欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android实现滚动刻度尺效果

程序员文章站 2023-12-02 19:06:52
缘起 最近在帮人做一个计步器,其中涉及到身高、体重等信息的采集;我参考了众多app的实现,觉得"乐动力"中滑动刻度的方式比较优雅。于是乎,反编译了该app,结果发现它是采...

缘起

最近在帮人做一个计步器,其中涉及到身高、体重等信息的采集;我参考了众多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、新建一个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,计算中间刻度

@override
protected 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);
}

处理滑动事件

  1. 在手指按下时,记录当前的x坐标(针对水平刻度尺)。
  2. 在手指滑动过程中,判断当前指针所指的刻度是否已经超出了边界,如果超出,则禁止滑动,同时刷新当前界面。
  3. 在手指抬起时,校正当前的刻度。
@override
public 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°,之后有时间可以朝这个方向尝试下。