Android自定义View第三弹(反人类尺子)

个人开发中自定义View系列(有需要的可以点击查看收藏)

  1. Android自定义view第一弹(防小米计步)
  2. Android自定义View第二弹(旋转的体重)
  3. Android自定义View第三弹(反人类尺子)
  4. Android自定义View第四弹(Kotlin流式布局)

都说尺子很好写,但是反人类的尺子也不好写,吐槽下产品(此处省略1000字),为啥说反人类呢,因为数字在上刻度在下,有没有很反人类,好吧,你说没有,那是因为你看了我的文章。

废话不多说,先上图:

这里写图片描述

一.给你个自定义View代码

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(getResources().getColor(R.color.colorGreen));
			paint.setStrokeWidth(dp2px(2));
			canvas.drawLine(getWidth(), getHeight()/2, getWidth()/4, getHeight()/5, 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(getResources().getColor(R.color.colorGreen));
			paint.setStrokeWidth(dp2px(2));
			canvas.drawLine(getWidth()/2, getHeight(), getWidth()/2, getHeight()/5, 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);
				}
			}
		}
	
	}

	@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;
	}
	
	private int sp2px(int sp){
		return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
	}
	
	private int dp2px(int dp){
		return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
	}
	
	public void setOnValueChangeListener(OnValueChangeListener listener){
		this.listener=listener;
	}
	
	public void setValue(int value){
		currentPosition=value/valuesInterval*intervalsBetweenValues;
		invalidate();
	}
	
	public void setFromValue(int fromValue){
		this.fromValue=fromValue;
		invalidate();
	}
	
	public void setToValue(int toValue){
		this.toValue=toValue;
		invalidate();
	}

}

二.给你个attr.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>

三.给你个原谅色

  <color name="colorGreen">#19CA89</color>

四.给你个用法

    Ruler ruler_shuzhang;
    //记得findViewById();
    ruler.setOnValueChangeListener(new OnValueChangeListener() {
            @Override
            public void onValueChange(int value) {
                tv_pressure_1.setText(value+"");
                diastolicPressure=value;
            }
        });

五.给你个布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
 <RelativeLayout
            android:id="@+id/rl_rule1"
            android:layout_width="350dp"
            android:layout_height="60dp"
            android:layout_marginTop="5dp"
            android:layout_below="@id/ll_edit_1"
            android:layout_centerHorizontal="true"
            android:background="@drawable/shape_ruler">
			<!--替换成自己的路径-->
            <com.****.*****.widgets.Ruler
                android:id="@+id/ruler_shuzhang"
                android:layout_width="345dp"
                android:layout_height="55dp"
                android:layout_centerInParent="true"
                android:background="#ffffff"
                custom:fromValue="30"
                custom:interval="5dp"
                custom:intervalsBetweenValues="10"
                custom:linesColor="#33000000"
                custom:linesWidth="4px"
                custom:toValue="150"
                custom:valuesInterval="10"
                custom:valuesTextSize="12sp" />

        </RelativeLayout>
</LinearLayout>

六.给你个Shape,具有美颜效果的Shape : shape_ruler.xml

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

哈哈,好看的尺子给你了,点个赞呗,求五星好评

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值