由于项目需求,个人信息的输入是以一个刻度尺滑动作为数据选择方法输入。如下图

哎呦喂,貌似看起来很简单的样子,但又好像不知从拿入手实现。实话说,确实不好做,所以~~~百度~~~哈哈哈哈。

然而,结果令我有点失望,网上的解决办法并不多,现成的工程又不符合自己的需求,改起来又耗时间,那咋办呢?

一个点子瞬间闪亮,没错,一条很长的图片加HorizontalSrcollView,重写onScrollChange(),用接口将其滑动距离暴露出来。。。。总之,可以实现。嗯,小满足~

然后,你就真的满足了?反正我可不满意。因为个人感觉太依赖图片的程序猿要不就是大牛,写多了懒得写了,直接上图;要不就是不会菜鸟,不会嘛~

所以,我在项目即将完结之前,升级了下装备,绕回来把这最终Boss解决了。

废话少说,转入正题:

首先,先上个效果图:

第一步,定义好view的属性,创建attrs属性:

attrs.xml

    <declare-styleable name="Ruler"><attr name="interval" format="dimension"/><attr name="fromValue" format="integer"/><attr name="toValue" format="integer"/><attr name="currentValue" format="integer"/><attr name="intervalsBetweenValues" format="integer"/><attr name="valuesInterval" format="integer"/><attr name="valuesTextSize" format="dimension"/><attr name="valuesTextColor" format="color"/><attr name="linesWidth" format="dimension"/><attr name="linesColor" format="color"/><attr name="isShowPointer" format="boolean"/><attr name="orientation"><enum name="vertical" value="0"/><enum name="horizontal" value="1"/></attr></declare-styleable>

上面的属性都在代码里有注释:

 //间隔,即两条刻度之间的距离private int interval;//起始值private int fromValue;//结束值private int toValue;//每两个值之间的间隔数,也指多少个最小单位,比如0cm到1cm有10个最小单位1mmprivate int intervalsBetweenValues;//相邻两个值的跳跃间隔,如上面第一张图的10000到11000,中间的跳跃值就是1000private int valuesInterval;//当前值private int currentValue;//值的文本大小private int valuesTextSize;//值的文本颜色private int valuesTextColor;//刻度的宽度private int linesWidth;//刻度的颜色private int linesColor;//刻度尺是vertical还是horizontal,上面第一张图的就是horizontalprivate int orientation;

第二步,构造体内利用TypedArray收集其属性,以及画笔,一些其他变量的初始化。

     interval=array.getDimensionPixelSize(R.styleable.Ruler_interval, dp2px(intervalsBetweenValues));fromValue=array.getInt(R.styleable.Ruler_fromValue, 0);toValue=array.getInt(R.styleable.Ruler_toValue, intervalsBetweenValues);currentValue=array.getInt(R.styleable.Ruler_currentValue, (fromValue+toValue)/2);intervalsBetweenValues=array.getInt(R.styleable.Ruler_intervalsBetweenValues, intervalsBetweenValues);valuesInterval=array.getInt(R.styleable.Ruler_valuesInterval, 1);valuesTextSize=array.getDimensionPixelSize(R.styleable.Ruler_valuesTextSize, sp2px(16));valuesTextColor=array.getColor(R.styleable.Ruler_valuesTextColor, Color.BLACK);linesWidth=array.getDimensionPixelSize(R.styleable.Ruler_linesWidth, dp2px(1));linesColor=array.getColor(R.styleable.Ruler_linesColor, Color.BLACK);orientation=array.getInt(R.styleable.Ruler_orientation, HORIZONTAL);

第三步,不需要重写onMeasure,定义布局时应固定长宽,直接重写onDraw,先画好一帧画面,后面的重绘就根据这一帧画,这里才是关键。

这里只以垂直的刻度尺为例:

     if(orientation==VERTICAL){//画中间的指针,就在中间画,很简单paint.setColor(Color.RED);paint.setStrokeWidth(dp2px(2));canvas.drawLine(getWidth(), getHeight()/2, getWidth()/4, getHeight()/2, paint);//刻度线分两部分画,一半是上部分,一半是下部分//画中间以上部分的刻度paint.setColor(linesColor);paint.setStrokeWidth(linesWidth);int height=getHeight()/2+offset;int position=currentPosition;//循环画刻度,当画到上边界或起始值时则退出循环,去画下半部分刻度while(true){      //intervalsBetweenValues/2是指两个相邻值之间距离的中间那条稍微长一点的刻度的位置if(position%(intervalsBetweenValues/2)==0){//当刻度值的位置为刻度值旁边的时候则画长一点,并在旁边画上数字,否则就按普通刻度长度画if(position%intervalsBetweenValues==0){canvas.drawLine(getWidth(), height, getWidth()/2, height, paint);String valueString=Integer.toString(position/intervalsBetweenValues*valuesInterval);paint.setColor(valuesTextColor);canvas.drawText(valueString, getWidth()/2-paint.measureText(valueString)-dp2px(5), height+textHeight/3, paint);paint.setColor(linesColor);}else{canvas.drawLine(getWidth(), height, getWidth()*3/5, height, paint);}}else{canvas.drawLine(getWidth(), height, getWidth()*4/5, height, paint);}          //每画完一条刻度则递减position和height,当position=起始值,或height低于0,即超出边界时,退出循环position--;if(position<fromValue/valuesInterval*intervalsBetweenValues) break;height-=interval;                       if(height<0-textHeight) break;}//画中间以下部分的刻度,和画上半部同理,值得注意的是先改变position和height再画,和上部分不同,因为不用再画中间那一个刻度height=getHeight()/2+offset;position=currentPosition;while(true){position++;if(position>toValue/valuesInterval*intervalsBetweenValues) break;height+=interval;                      if(height>getHeight()+textHeight) break;if(position%(intervalsBetweenValues/2)==0){if(position%intervalsBetweenValues==0){canvas.drawLine(getWidth(), height, getWidth()/2, height, paint);String valueString=Integer.toString(position/intervalsBetweenValues*valuesInterval);paint.setColor(valuesTextColor);canvas.drawText(valueString, getWidth()/2-paint.measureText(valueString)-dp2px(5), height+textHeight/3, paint);paint.setColor(linesColor);}else{canvas.drawLine(getWidth(), height, getWidth()*3/5, height, paint);}}else{canvas.drawLine(getWidth(), height, getWidth()*4/5, height, paint);}}

第四步,重写onTouchEvent,处理滑动事件,这也是重点。

 @Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubswitch (event.getAction()) {case MotionEvent.ACTION_DOWN://记录初始位置if(orientation==HORIZONTAL){oldX=(int)event.getX();}else{oldY=(int)event.getY();}          break;case MotionEvent.ACTION_MOVE:if(orientation==HORIZONTAL){//滑动的距离offset=(int)(event.getX()-oldX);//滑动的距离除以每个刻度间隔,得出滑动了多少个间隔,即在相应的刻度position上加上或减去该间隔数             if(Math.abs(offset)>=interval){currentPosition-=offset/interval;if(currentPosition>toValue/valuesInterval*intervalsBetweenValues) currentPosition=toValue/valuesInterval*intervalsBetweenValues;if(currentPosition<fromValue/valuesInterval*intervalsBetweenValues) currentPosition=fromValue/valuesInterval*intervalsBetweenValues;//记录好当前的偏移位置作为下次滑动偏移量的起始位置oldXoldX=(int)event.getX();}        //取余算好不够一个间隔的偏移量,用于ACTION_UP计算四舍五入offset%=interval;}else{//同HORIZONTAL模式offset=(int)(event.getY()-oldY);if(Math.abs(offset)>=interval){currentPosition-=offset/interval;if(currentPosition>toValue/valuesInterval*intervalsBetweenValues) currentPosition=toValue/valuesInterval*intervalsBetweenValues;if(currentPosition<fromValue/valuesInterval*intervalsBetweenValues)currentPosition=fromValue/valuesInterval*intervalsBetweenValues;oldY=(int)event.getY();}      offset%=interval;}         //重绘,达到滑动动画效果invalidate();if(listener!=null){//通过一个接口将数据暴露出去listener.onValueChange(currentPosition*valuesInterval/intervalsBetweenValues);}break;case MotionEvent.ACTION_UP://根据offset处理,若大于间隔的一半,那么+1,否则-1if(offset>0 && offset>interval/2){currentPosition--;if(currentPosition<fromValue/valuesInterval*intervalsBetweenValues){currentPosition=fromValue/valuesInterval*intervalsBetweenValues;}}else if(offset<0 && Math.abs(offset)>interval/2){currentPosition++;                if(currentPosition>toValue/valuesInterval*intervalsBetweenValues){currentPosition=toValue/valuesInterval*intervalsBetweenValues;                    }}//偏移量置0offset=0; invalidate();if(listener!=null){//通过一个接口将数据暴露出去listener.onValueChange(currentPosition*valuesInterval/intervalsBetweenValues);}break;}return true;}

上面涉及到一个接口类,很简单,直接创建一个接口就OK了,这个接口可以将刻度的位置暴露出去,进行相应的业务处理:

OnValueChangeListener.java

public interface OnValueChangeListener {void onValueChange(int value);
}

至此,一个可以实现滑动的刻度尺就实现了,代码确实也不多也算少,要不就自己耐心画一个,要不就耐心看完,哈哈哈,但基本的原理很好理解,先按中间刻度循环递增或递减刻度位置画好一半,再同理画好另一半,再增加触摸事件更新刻度位置,重绘,通过接口输出数据。

直接贴出代码,这里的attrs.xml上面有了不贴了:

Ruler.java

public class Ruler extends View {private final int VERTICAL=0;private final int HORIZONTAL=1;//间隔,即两条刻度之间的距离private int interval;//起始值private int fromValue;//结束值private int toValue;//每两个值之间的间隔数,也指多少个最小单位,比如0cm到1cm有10个最小单位1mmprivate int intervalsBetweenValues;//相邻两个值的跳跃间隔,如上面第一张图的10000到11000,中间的跳跃值就是1000private int valuesInterval;//当前值private int currentValue;//值的文本大小private int valuesTextSize;//值的文本颜色private int valuesTextColor;//刻度的宽度private int linesWidth;//刻度的颜色private int linesColor;//刻度尺是vertical还是horizontal,上面第一张图的就是horizontalprivate int orientation;private Paint paint;private OnValueChangeListener listener;private int currentPosition;private int textHeight;private int offset;private int oldX;private int oldY;public Ruler(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// TODO Auto-generated constructor stubTypedArray array=context.obtainStyledAttributes(attrs, R.styleable.Ruler);interval=array.getDimensionPixelSize(R.styleable.Ruler_interval, dp2px(intervalsBetweenValues));fromValue=array.getInt(R.styleable.Ruler_fromValue, 0);toValue=array.getInt(R.styleable.Ruler_toValue, intervalsBetweenValues);currentValue=array.getInt(R.styleable.Ruler_currentValue, (fromValue+toValue)/2);intervalsBetweenValues=array.getInt(R.styleable.Ruler_intervalsBetweenValues, intervalsBetweenValues);valuesInterval=array.getInt(R.styleable.Ruler_valuesInterval, 1);valuesTextSize=array.getDimensionPixelSize(R.styleable.Ruler_valuesTextSize, sp2px(16));valuesTextColor=array.getColor(R.styleable.Ruler_valuesTextColor, Color.BLACK);linesWidth=array.getDimensionPixelSize(R.styleable.Ruler_linesWidth, dp2px(1));linesColor=array.getColor(R.styleable.Ruler_linesColor, Color.BLACK);orientation=array.getInt(R.styleable.Ruler_orientation, HORIZONTAL);array.recycle();paint=new Paint();paint.setTextSize(valuesTextSize);//文本高度FontMetrics fm=paint.getFontMetrics();textHeight=(int)(fm.bottom-fm.top);//当前所指的刻度位置,即中间红色指针指向的值currentPosition=currentValue/valuesInterval*intervalsBetweenValues;}public Ruler(Context context, AttributeSet attrs) {this(context, attrs, 0);// TODO Auto-generated constructor stub}public Ruler(Context context) {this(context, null);// TODO Auto-generated constructor stub}@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stubif(orientation==VERTICAL){//画中间的指针,就在中间画,很简单paint.setColor(Color.RED);paint.setStrokeWidth(dp2px(2));canvas.drawLine(getWidth(), getHeight()/2, getWidth()/4, getHeight()/2, paint);//刻度线分两部分画,一半是上部分,一半是下部分//画中间以上部分的刻度paint.setColor(linesColor);paint.setStrokeWidth(linesWidth);int height=getHeight()/2+offset;int position=currentPosition;//循环画刻度,当画到上边界或起始值时则退出循环,去画下半部分刻度while(true){      //intervalsBetweenValues/2是指两个相邻值之间距离的中间那条稍微长一点的刻度的位置if(position%(intervalsBetweenValues/2)==0){//当刻度值的位置为刻度值旁边的时候则画长一点,并在旁边画上数字,否则就按普通刻度长度画if(position%intervalsBetweenValues==0){canvas.drawLine(getWidth(), height, getWidth()/2, height, paint);String valueString=Integer.toString(position/intervalsBetweenValues*valuesInterval);paint.setColor(valuesTextColor);canvas.drawText(valueString, getWidth()/2-paint.measureText(valueString)-dp2px(5), height+textHeight/3, paint);paint.setColor(linesColor);}else{canvas.drawLine(getWidth(), height, getWidth()*3/5, height, paint);}}else{canvas.drawLine(getWidth(), height, getWidth()*4/5, height, paint);}          //每画完一条刻度则递减position和height,当position=起始值,或height低于0,即超出边界时,退出循环position--;if(position<fromValue/valuesInterval*intervalsBetweenValues) break;height-=interval;                       if(height<0-textHeight) break;}//画中间以下部分的刻度,和画上半部同理height=getHeight()/2+offset;position=currentPosition;while(true){position++;if(position>toValue/valuesInterval*intervalsBetweenValues) break;height+=interval;                       if(height>getHeight()+textHeight) break;if(position%(intervalsBetweenValues/2)==0){if(position%intervalsBetweenValues==0){canvas.drawLine(getWidth(), height, getWidth()/2, height, paint);String valueString=Integer.toString(position/intervalsBetweenValues*valuesInterval);paint.setColor(valuesTextColor);canvas.drawText(valueString, getWidth()/2-paint.measureText(valueString)-dp2px(5), height+textHeight/3, paint);paint.setColor(linesColor);}else{canvas.drawLine(getWidth(), height, getWidth()*3/5, height, paint);}}else{canvas.drawLine(getWidth(), height, getWidth()*4/5, height, paint);}}}else{//画中间的指针paint.setColor(Color.RED);paint.setStrokeWidth(dp2px(2));canvas.drawLine(getWidth()/2, getHeight(), getWidth()/2, getHeight()/2, paint);//画中间左边部分的刻度paint.setColor(linesColor);paint.setStrokeWidth(linesWidth);int width=getWidth()/2+offset;int position=currentPosition;while(true){                            if(position%(intervalsBetweenValues/2)==0){if(position%intervalsBetweenValues==0){canvas.drawLine(width, getHeight(), width, getHeight()/2, paint);String valueString=Integer.toString(position/intervalsBetweenValues*valuesInterval);paint.setColor(valuesTextColor);canvas.drawText(valueString, width-paint.measureText(valueString)/2, getHeight()/2-textHeight/2, paint);paint.setColor(linesColor);}else{canvas.drawLine(width, getHeight(), width, getHeight()*3/5, paint);}}else{canvas.drawLine(width, getHeight(), width, getHeight()*4/5, paint);}         position--;if(position<fromValue/valuesInterval*intervalsBetweenValues) break;width-=interval;                      //这里需要额外加上以文本“10000”的长度作为偏移量,防止值文本很长的时候,文本还没完全退出边界就消失了if(width<0-paint.measureText("10000")) break;}//画中间右边部分的刻度width=getWidth()/2+offset;position=currentPosition;while(true){position++;if(position>toValue/valuesInterval*intervalsBetweenValues) break;width+=interval;                        if(width>getWidth()+paint.measureText("10000")) break;if(position%(intervalsBetweenValues/2)==0){if(position%intervalsBetweenValues==0){canvas.drawLine(width, getHeight(), width, getHeight()/2, paint);String valueString=Integer.toString(position/intervalsBetweenValues*valuesInterval);paint.setColor(valuesTextColor);canvas.drawText(valueString, width-paint.measureText(valueString)/2, getHeight()/2-textHeight/2, paint);paint.setColor(linesColor);}else{canvas.drawLine(width, getHeight(), width, getHeight()*3/5, paint);}}else{canvas.drawLine(width, getHeight(), width, getHeight()*4/5, paint);}}}}

布局文件,记得加上区域定义:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:custom="http://schemas.android.com/apk/res/com.samuelzhan.ruler"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#00aced"><RelativeLayoutandroid:id="@+id/ruler_layout"android:layout_width="70dp"android:layout_height="290dp"android:layout_marginTop="40dp"android:layout_alignParentRight="true"android:layout_marginRight="80dp"android:background="@drawable/shape_ruler"><com.samuelzhan.ruler.Rulerandroid:id="@+id/ruler_height"android:layout_width="60dp"android:layout_height="280dp"android:layout_centerInParent="true"android:background="#ffffff"custom:orientation="vertical"custom:fromValue="130"custom:toValue="220"custom:intervalsBetweenValues="10"custom:valuesInterval="10"custom:interval="5dp"custom:linesColor="#33000000"custom:linesWidth="4px"custom:valuesTextSize="12sp"/></RelativeLayout><RelativeLayoutandroid:layout_width="250dp"android:layout_height="60dp"android:layout_marginTop="60dp"android:layout_below="@id/ruler_layout"android:layout_centerHorizontal="true"android:background="@drawable/shape_ruler"><com.samuelzhan.ruler.Rulerandroid:id="@+id/ruler_weight"android:layout_width="240dp"android:layout_height="50dp"android:layout_centerInParent="true"android:background="#ffffff"custom:fromValue="30"custom:toValue="150"custom:intervalsBetweenValues="10"custom:valuesInterval="10"custom:interval="5dp"custom:linesColor="#33000000"custom:linesWidth="4px"custom:valuesTextSize="12sp"/></RelativeLayout>     </RelativeLayout>

这里还需要添加一个刻度尺的周边的shape修饰一下边界

shape_ruler.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" ><corners android:radius="8dp"/><stroke android:width="5dp" android:color="#EEEEEE"/><solid android:color="#ffffff"/>
</shape>

代码下载: github

自定义view之刻度尺相关推荐

  1. (转载)自定义View——弹性滑动

    滑动是Android开发中非常重要的UI效果,几乎所有应用都包含了滑动效果,而本文将对滑动的使用以及原理进行介绍. 一.scrollTo与ScrollBy View提供了专门的方法用于实现滑动效果,分 ...

  2. Android自定义View实现方位刻度尺(类似于吃鸡手游)

    Android自定义View实现方位刻度尺(类似于吃鸡手游) 先上效果图 gif可能看不清,我下面放几张图片 原理解析 首先,我们应该把看得到的内容从上至下分成三部分:最上面的文字.中间的竖线和最下面 ...

  3. Android自定义view之(刻度尺view)

    前言: 最近一直在做h5,感觉学的东西多了还真有点混淆了,再来看anroid的时候,觉得有点点陌生了,难道真的是鱼与熊掌不可兼得吗? 好吧,也罢- 在技术群中看到一个小伙伴有一个这样的需求,所以在不是 ...

  4. 自定义View -- 刻度尺

    [图片] 这次在自定义View时主要通过以下几个步骤: 1.准备阶段(在构造方法处) 初始化各种默认的Paint,图片资源.( 其中NinePatch资源需要通过Bitmap生成,绘制时调用nineP ...

  5. android自定义起止时间的时间刻度尺,Android 自定义View篇(六)实现时钟表盘效果...

    前言 Android 自定义 View 是高级进阶不可或缺的内容,日常工作中,经常会遇到产品.UI 设计出花里胡哨的界面.当系统自带的控件不能满足开发需求时,就只能自己动手撸一个效果. 本文就带自定义 ...

  6. 安卓自定义view之——可滑动时间轴(时间刻度尺)

    如果需要解决在viewpager,horizontalscrollview中滑动事件的冲突,可注入viewpager,horizontalscrollview对象,在down move的时候调用req ...

  7. android标尺自定义view,android尺子的自定义view——RulerView详解

    项目中用到自定义尺子的样式: 原效果为 因为跟自己要使用的view稍有不同 所以做了一些修改,修改的注释都放在代码中了,特此记录一下. 首先是一个自定义View: public class RuleV ...

  8. 自定义View----滑动刻度尺与流式布局 实例(四)

    2019独角兽企业重金招聘Python工程师标准>>> 近在系统学习自定义View这一块的知识,前面几篇基本都是理论知识,这篇博客着重从实战来加强对自定义View的理解与运用.实现的 ...

  9. Android 自定义View合集

    http://blog.csdn.net/u011507982/article/details/51199644 自定义控件学习  https://github.com/GcsSloop/Androi ...

  10. Android自定义View教你一步一步实现薄荷健康滑动卷尺

    前言 前几天写了一篇一步一步教你实现即刻点赞效果后,实现点赞效果主要是自己对自定义View的一些canvas绘制,缩放知识,位移的理解.而朋友说HenCoder还有给出薄荷健康滑动卷尺,小米运动记录界 ...

最新文章

  1. 网关和BFF是如何演进出来的?
  2. python3默认编码_python3的url编码和解码,自定义gbk、utf-8的例子
  3. android--系统jar包引用
  4. DUBBO:前端调用后端服务返回类名
  5. linux下启动tomcat,Cannot find setclasspath.sh
  6. 机器智能芯片 10 大新秀!华为抢占一席,Google 占比最多!
  7. 2015年计算机维修记录表,2015年 主题教学记录表.doc
  8. CCF CSP 201604-1 折点计数
  9. SVM分类器(matlab)
  10. C程序设计案例(牛顿迭代法求高次方程的根)
  11. android 获取录音时长_Android、iOS录音时音量大小计算
  12. 阿里云服务器添加CDN
  13. 优秀架构师必须掌握的架构思维 - 菜鸟架构(转载)
  14. C语言编程从键盘输入n值
  15. 使用wireshark找不到“捕获接口”问题的解决
  16. Excel工作表探密
  17. react如何获取option的下标和值_select选中获取索引三种写法
  18. Git冲突:Your local changes would be overwritten by merge. Commit, stash or revert them to proceed.
  19. 日语自学学习网站汇总
  20. 用qt做网易云音乐--01标题栏实现

热门文章

  1. el-cascader级联选择器当子节点的children为空数组的话,有bug(前端解决办法)
  2. 文明IV模组(MOD)制作指南
  3. 矩阵的特征值、特征向量、特征子空间
  4. Android 自定义圆形进度条带图片旋转
  5. PageObject(PO)设计模式在 UI 自动化中的实践总结(以 QQ 邮箱登陆为例)
  6. Justinmind使用教程(6)——Justinmind的切换事件toggle
  7. C语言的函数到底是什么
  8. 6.misc类设备与蜂鸣器驱动
  9. EUI学习之自定义皮肤
  10. C语言中 1%3,算术什么意思啊 算数什么意思