热门标签 | HotTags
当前位置:  开发笔记 > 开发工具 > 正文

Android实现滚动刻度尺效果

缘起 最近在帮人做一个计步器,其中涉及到身高、体重等信息的采集;我参考了众多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  mMax) mCountScale = mMax;
      int finalX = (mCountScale - mMidCountScale) * mScaleMargin;
      mScroller.setFinalX(finalX); //纠正指针位置
      postInvalidate();
      return true;
  }
  return super.onTouchEvent(event);
}

最后的说明

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

  1. 第一次快速滑动时,可以超出边界,之后则不会;
  2. 开放的自定义属性不够(根据具体情况);
  3. 可以考虑将水平和垂直的实现,在一个类中完成,因为在实现过程中发现其实有很多代码都是类似的,只是个别参数属性的不同,在坐标系中,垂直可以看成是水平旋转了90°,之后有时间可以朝这个方向尝试下。

推荐阅读
  • 探索将Python Spyder与GitHub连接的方法,了解当前的技术状态及未来可能的发展方向。 ... [详细]
  • 本文档提供了在Windows 10操作系统中安装Python 3及Scrapy框架的完整指南,包括必要的依赖库如wheel、lxml、pyOpenSSL、Twisted和pywin32的安装方法。 ... [详细]
  • 本文深入探讨了微信小程序直播中点赞动画的实现方法,特别是如何利用三阶贝塞尔曲线使点赞图标沿预设路径移动,以及相关的数学计算与代码实现。 ... [详细]
  • 2023年1月28日网络安全热点
    涵盖最新的网络安全动态,包括OpenSSH和WordPress的安全更新、VirtualBox提权漏洞、以及谷歌推出的新证书验证机制等内容。 ... [详细]
  • 本文介绍了如何利用 Apache NiFi 的灵活性和扩展性,通过自定义组件来解决标准组件无法满足的特定业务需求。文章不仅涵盖了自定义处理器的基本步骤,还讨论了调试自定义组件时可能遇到的问题及解决方案。 ... [详细]
  • 页面预渲染适用于主要包含静态内容的页面。对于依赖大量API调用的动态页面,建议采用SSR(服务器端渲染),如Nuxt等框架。更多优化策略可参见:https://github.com/HaoChuan9421/vue-cli3-optimization ... [详细]
  • 本文旨在探讨Swift中的Closure与Objective-C中的Block之间的区别与联系,通过定义、使用方式以及外部变量捕获等方面的比较,帮助开发者更好地理解这两种机制的特点及应用场景。 ... [详细]
  • 汇总了2023年7月7日最新的网络安全新闻和技术更新,包括最新的漏洞披露、工具发布及安全事件。 ... [详细]
  • 深入理解iOS中的链式编程:以Masonry为例
    本文通过介绍Masonry这一轻量级布局框架,探讨链式编程在iOS开发中的应用。Masonry不仅简化了Auto Layout的使用,还提高了代码的可读性和维护性。 ... [详细]
  • WebBenchmark:强大的Web API性能测试工具
    本文介绍了一款名为WebBenchmark的Web API性能测试工具,该工具不仅支持HTTP和HTTPS服务的测试,还提供了丰富的功能来帮助开发者进行高效的性能评估。 ... [详细]
  • ArcBlock 发布 ABT 节点 1.0.31 版本更新
    2020年11月9日,ArcBlock 区块链基础平台发布了 ABT 节点开发平台的1.0.31版本更新,此次更新带来了多项功能增强与性能优化。 ... [详细]
  • 本文详细介绍了Socket在Linux内核中的实现机制,包括基本的Socket结构、协议操作集以及不同协议下的具体实现。通过这些内容,读者可以更好地理解Socket的工作原理。 ... [详细]
  • 探讨多种方法来确定Java对象的实际类型,包括使用instanceof关键字、getClass()方法等。 ... [详细]
  • HDU 2537 键盘输入处理
    题目描述了一个名叫Pirates的男孩想要开发一款键盘输入软件,遇到了大小写字母判断的问题。本文提供了该问题的解决方案及实现方法。 ... [详细]
  • 本文详细介绍了如何在Java Swing中使用`JButton.registerKeyboardAction()`方法来为按钮设置键盘快捷键,并提供了多个实用的代码示例。 ... [详细]
author-avatar
mobiledu2502883463
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有