由于项目需求,个人信息的输入是以一个刻度尺滑动作为数据选择方法输入。如下图
哎呦喂,貌似看起来很简单的样子,但又好像不知从拿入手实现。实话说,确实不好做,所以~~~百度~~~哈哈哈哈。
然而,结果令我有点失望,网上的解决办法并不多,现成的工程又不符合自己的需求,改起来又耗时间,那咋办呢?
一个点子瞬间闪亮,没错,一条很长的图片加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个最小单位1mm private int intervalsBetweenValues; //相邻两个值的跳跃间隔,如上面第一张图的10000到11000,中间的跳跃值就是1000 private int valuesInterval; //当前值 private int currentValue; //值的文本大小 private int valuesTextSize; //值的文本颜色 private int valuesTextColor; //刻度的宽度 private int linesWidth; //刻度的颜色 private int linesColor; //刻度尺是vertical还是horizontal,上面第一张图的就是horizontal private 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);
这里只以垂直的刻度尺为例:
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,处理滑动事件,这也是重点。
@Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub switch (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; //记录好当前的偏移位置作为下次滑动偏移量的起始位置oldX oldX=(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,否则-1 if(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; } } //偏移量置0 offset=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个最小单位1mm private int intervalsBetweenValues; //相邻两个值的跳跃间隔,如上面第一张图的10000到11000,中间的跳跃值就是1000 private int valuesInterval; //当前值 private int currentValue; //值的文本大小 private int valuesTextSize; //值的文本颜色 private int valuesTextColor; //刻度的宽度 private int linesWidth; //刻度的颜色 private int linesColor; //刻度尺是vertical还是horizontal,上面第一张图的就是horizontal private 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 stub TypedArray 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 } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub 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; } //画中间以下部分的刻度,和画上半部同理 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"> <RelativeLayout android: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.Ruler android: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> <RelativeLayout android: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.Ruler android: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_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>