Android实现滚动刻度尺效果

发布时间 - 2026-01-11 01:25:01    点击率:

缘起

最近在帮人做一个计步器,其中涉及到身高、体重等信息的采集;我参考了众多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°,之后有时间可以朝这个方向尝试下。


# Android滚动刻度尺  # Android实现刻度尺  # Android  # 刻度尺  # Android自定义控件之刻度尺控件  # Android实现滑动刻度尺效果  # Android实现自定义滑动刻度尺方法示例  # Android自定义RecyclerView实现不固定刻度的刻度尺  # 自定义  # 重写  # 过程中  # 都是  # 也有  # 有很多  # 它是  # 做一个  # 按下  # 涉及到  # 给个  # 帮人  # 回调  # 书上  # 太不  # 内嵌  # 新建一个  # 类中  # 觉得该  # 最小值 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 香港服务器网站生成指南:免费资源整合与高速稳定配置方案  网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?  Laravel软删除怎么实现_Laravel Eloquent SoftDeletes功能使用教程  Laravel如何使用Eloquent进行子查询  中国移动官方网站首页入口 中国移动官网网页登录  Laravel Facade的原理是什么_深入理解Laravel门面及其工作机制  mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?  网易LOFTER官网链接 老福特网页版登录地址  ChatGPT常用指令模板大全 新手快速上手的万能Prompt合集  宙斯浏览器怎么屏蔽图片浏览 节省手机流量使用设置方法  Laravel的路由模型绑定怎么用_Laravel Route Model Binding简化控制器逻辑  西安专业网站制作公司有哪些,陕西省建行官方网站?  详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)  如何用ChatGPT准备面试 模拟面试问答与职场话术练习教程  Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道  HTML5空格和margin有啥区别_空格与外边距的使用场景【说明】  如何在阿里云完成域名注册与建站?  Laravel怎么实现支付功能_Laravel集成支付宝微信支付  如何在IIS服务器上快速部署高效网站?  魔方云NAT建站如何实现端口转发?  如何获取免费开源的自助建站系统源码?  东莞专业网站制作公司有哪些,东莞招聘网站哪个好?  ChatGPT怎么生成Excel公式_ChatGPT公式生成方法【指南】  html5的keygen标签为什么废弃_替代方案说明【解答】  iOS验证手机号的正则表达式  Laravel如何处理CORS跨域问题_Laravel项目CORS配置与解决方案  详解Android图表 MPAndroidChart折线图  Laravel如何发送系统通知?(Notification渠道示例)  HTML透明颜色代码在Angular里怎么设置_Angular透明颜色使用指南【详解】  如何快速查询域名建站关键信息?  Win11怎么设置虚拟桌面 Win11新建多桌面切换操作【技巧】  品牌网站制作公司有哪些,买正品品牌一般去哪个网站买?  详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)  Laravel怎么使用Blade模板引擎_Laravel模板继承与Component组件复用【手册】  Laravel如何正确地在控制器和模型之间分配逻辑_Laravel代码职责分离与架构建议  如何快速辨别茅台真假?关键步骤解析  高性价比服务器租赁——企业级配置与24小时运维服务  如何快速搭建支持数据库操作的智能建站平台?  如何确保西部建站助手FTP传输的安全性?  Laravel用户认证怎么做_Laravel Breeze脚手架快速实现登录注册功能  Laravel如何升级到最新的版本_Laravel版本升级流程与兼容性处理  Swift开发中switch语句值绑定模式  南京网站制作费用,南京远驱官方网站?  Android 常见的图片加载框架详细介绍  如何在 Python 中将列表项按字母顺序编号(a.、b.、c. …)  Laravel如何清理系统缓存命令_Laravel清除路由配置及视图缓存的方法【总结】  在线制作视频的网站有哪些,电脑如何制作视频短片?  Laravel队列任务超时怎么办_Laravel Queue Timeout设置详解  浅析上传头像示例及其注意事项  Laravel如何使用Telescope进行调试?(安装和使用教程)