UI:类似于汽车速度盘的仪表盘

思路

1.使用中心渐变的方式实现仪表盘底部颜色的变化,

2.上层覆盖一层圆环,随之游标的变化而变化

该view的java代码:

/**
 *	仪表盘view 
 *
 */
public class DialView extends View {
	
	private int counNum = 54;//区间分成的格子数量
	private int scaleWith = 3;//标度的宽度
	private int cursorWidth = 4; //游标的宽度

	private final int totalDegree = 270;//总的圆环角度
	private double claDegree = 135;//游标的初始位置,右端水平点作为起点,顺时针0 -> 360
	
	private boolean checkIn; //判读是否点击到了表盘
	private int cursorCount; // 游标刻度值
	
	private int mScreenWidth;
	private int mScreenHeigh;
	
	private Paint cursorPaint;
	private Paint scalePaint;
	private Paint shaderPaint;
	private Paint maskPaint;
	private Paint txtPaint;
	
	private int arcWidth;//圆环的宽度
	private int txtSize;//中心文件的大小
	private RectF arcRect; //半圆绘制的区域
	private int resultWidth;//view的宽
	private int resultHeigh;//view的高
	private int rectSide;
	private int itemDegree; //每一格所占据的角度 ,注意该角度需要是45 和 135 的公约数
	private OnChangeListener mListener;
	
	private int startNum;

	
	@SuppressWarnings("deprecation")
	public DialView(Context context, AttributeSet attrs) {
		super(context, attrs);
		
		//获取屏幕的宽高
		Display display = ((Activity)context).getWindowManager().getDefaultDisplay();
		mScreenWidth = display.getWidth();
		mScreenHeigh = display.getHeight();
		
		initPaint();
		initDegree();
	}
	
	private void initDegree() {
		itemDegree = totalDegree / counNum;
	}

	private void initPaint() {
		//绘制底部渐变的画笔
		shaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
		shaderPaint.setStyle(Style.STROKE);
		
		//绘制标度的画笔
		scalePaint = new Paint();
		scalePaint.setColor(Color.WHITE);
		scalePaint.setStrokeWidth(scaleWith);
		
		//绘制表盘游标的画笔
		cursorPaint = new Paint();
		cursorPaint.setStrokeWidth(cursorWidth);
		cursorPaint.setColor(Color.BLACK);
		
		//绘制表盘遮盖区域的画笔
		maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
		maskPaint.setColor(Color.GREEN);
		maskPaint.setStyle(Style.STROKE);
		
		//绘制文字的画笔
		txtPaint = new Paint();
		txtPaint.setColor(Color.GREEN);
		txtPaint.setStrokeCap(Cap.ROUND);
		txtPaint.setTextAlign(Align.CENTER);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		//测量view的宽
		resultWidth = 0;
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		if(widthMode == MeasureSpec.EXACTLY){
			//父view给该view的大小,那么就使用父亲给的大小吧
			resultWidth = widthSize;
		}else{//父view不知道给该view多大的大小
			//那么自view就给自己定义一个大小吧
			resultWidth = mScreenWidth; // 没有设置宽度就为屏幕的宽度
			 //如果父view给了子view的是一个限制值
			if(widthMode == MeasureSpec.AT_MOST){
				//那么该view就需要跟父view给的限制值进行比较,选择最小值
				resultWidth = Math.min(resultWidth, widthSize);
			}
		}
		
		//测量view的高
		resultHeigh = 0;
		int heighSize = MeasureSpec.getSize(heightMeasureSpec);
		int heighMode = MeasureSpec.getMode(heightMeasureSpec);
		if(heighMode == MeasureSpec.EXACTLY){
			resultHeigh = heighSize;
		}else{
			resultHeigh = mScreenHeigh ; //没有设置高度就为屏幕的高度
			resultHeigh = Math.min(resultHeigh, heighSize);
		}
		setMeasuredDimension(resultWidth, resultHeigh);
		
		getMeasureSize();
	}
	
	/**
	 * 获取view的宽高和其他绘制的宽高大小
	 */
	private void getMeasureSize() {
		//获取绘制表盘的矩形区域
		rectSide = Math.min(resultHeigh, resultWidth);
		
		//表盘的宽度
		arcWidth =(int) (rectSide * 0.25);
		shaderPaint.setStrokeWidth(arcWidth);
		maskPaint.setStrokeWidth(arcWidth);
		//中心文字大小
		txtSize = (int) (rectSide * 0.125);
		txtPaint.setTextSize(txtSize);
		
		float l = (resultWidth - rectSide)/2 + arcWidth/2;
		float t = (resultHeigh - rectSide)/2 + arcWidth/2;
		float r = l + rectSide - arcWidth;
		float b = t + rectSide - arcWidth;
		arcRect = new RectF( l, t, r , b);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		//绘制底色
		drawBottomShaderArc(canvas);
		//绘制遮蔽色
		drawMaskArc(canvas);
		//绘制标尺
		drawScale(canvas);
		//绘制游标
		drawCursor(canvas);
		//绘制中心字
		drawCenterTxt(canvas);
	}
	
	private void drawCenterTxt(Canvas canvas) {
		int bo = (int) (txtPaint.descent() + txtPaint.ascent());
		canvas.drawText(cursorCount + "格", resultWidth / 2, resultHeigh / 2 - bo / 2, txtPaint);
	}

	private void drawCursor(Canvas canvas) {
		canvas.save();
		float degrees = (float) (-(180 - claDegree));
		canvas.rotate(degrees, resultWidth / 2, resultHeigh / 2);
		canvas.drawLine(arcRect.left - arcWidth/2, 
				arcRect.top + (rectSide - arcWidth)/2, //view中心点y坐标
				arcRect.left + arcWidth/2, 
				arcRect.top + (rectSide - arcWidth)/2, cursorPaint);
		canvas.restore();
	}

	private void drawScale(Canvas canvas) {
		for(int i = 0 ; i < counNum + 1 ; i ++){
			canvas.save();
			canvas.rotate(-45 + (itemDegree) * i,resultWidth / 2, resultHeigh / 2);
			canvas.drawLine(arcRect.left - arcWidth/2, 
					arcRect.top + (rectSide - arcWidth)/2, //view中心点y坐标
					arcRect.left + arcWidth/2, 
					arcRect.top + (rectSide - arcWidth)/2,
					scalePaint);
			canvas.restore();
		}
	}

	private void drawMaskArc(Canvas canvas) {
		  //计算弧度大小
		float sweepAgree = 0;
		if(claDegree <= 45){
			sweepAgree  = (float) (45 - claDegree);
		}else{
			sweepAgree = (float) (360 + 45 - claDegree);
		}
		canvas.drawArc(arcRect, 45, -sweepAgree,false, maskPaint);
	}

	private void drawBottomShaderArc(Canvas canvas) {
		shaderPaint.setShader(new SweepGradient(resultWidth / 2, resultHeigh / 2,
				new int[]{Color.BLUE,Color.YELLOW, Color.RED},new float[]{0f,0.33333f,1.0f}));  
		canvas.save();
		canvas.rotate(135, resultWidth / 2, resultHeigh / 2);
		canvas.drawArc(arcRect, 0, 270, false, shaderPaint);
		canvas.restore();
	}

	@SuppressLint("ClickableViewAccessibility") 
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int action = event.getAction();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			float downX = event.getX();
			float downY = event.getY();
			this.startNum = cursorCount;
			checkIn = checkIn(downX,downY);
			return true;
			
		case MotionEvent.ACTION_MOVE:
			float moveX = event.getX();
			float moveY = event.getY();
			if(checkIn){
				claDegree = calculateDegree(moveX, moveY, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
				handleClaDegree();
				invalidate();
			}
			break;
		
		case MotionEvent.ACTION_UP:
			checkIn = false;
			//回调监听
			if(this.mListener != null && startNum != cursorCount){
				this.mListener.onChanged(counNum, cursorCount);
			}
			break;

		default:
			break;
		}
		
		return super.onTouchEvent(event);
	}

	/**
	 * 用来处理degree, 是其一格一格的变化
	 */
	private void handleClaDegree() {
		if(claDegree > 45 && claDegree < 135){
			if(claDegree - 90 > 0){
				claDegree = 135;
			}else{
				claDegree = 45;
			}
		}
		int count = (int) (claDegree / itemDegree);
		int cou = (int) (claDegree %  itemDegree);
		if(cou > itemDegree / 2){
			claDegree = itemDegree * (count + 1);
		}else {
			claDegree = itemDegree * count;
		}
		
		//计算有滑动的个数
		cursorCount = (int) (claDegree <= 45 ? (225+claDegree)/itemDegree : (claDegree-135)/itemDegree);
	}

	/*
	 * 判断是否点中了游标盘
	 */
	private boolean checkIn(float downX, float downY) {
		int maxRaduis = rectSide / 2;
		int minRaduis = maxRaduis - arcWidth;
		int claToCenter = calculateToCenter(downX,downY,getMeasuredWidth()/2 ,getMeasuredHeight()/2);
		claDegree = calculateDegree(downX,downY,getMeasuredWidth()/2 ,getMeasuredHeight()/2);
		
		if((claToCenter < maxRaduis && claToCenter > minRaduis - 30) && ( claDegree < 45 || claDegree > 135) ){ // -30为了增大触摸几率
			return true;
		}
		return false;
	}
	
	/*
	 * 两点间的角度,右端水平点作为起点,顺时针0 -> 360
	 */
	private double calculateDegree(float downX, float downY, int cX, int cY) {
		int x = (int) (downX - cX);
		int y = (int) (downY - cY);
		double sqrt = Math.sqrt(x*x + y*y);
		double asin = Math.asin(Math.abs(y)/sqrt);
		double degrees = Math.toDegrees(asin);
		
		if(downX > cX && downY >= cY){//右下
			return degrees;
			
		}else if(downX < cX && downY >= cY){//左下
			return 180 - degrees;
			
		}else if(downX < cX && downY <= cY){//左上
			return 180 + degrees;
			
		}else if(downX > cX && downY <= cY){//右上
			return 360 - degrees;
		}
		return degrees;
	}

	/**
	 * 计算到圆心的距离
	 * @return
	 */
	private int calculateToCenter(float dX,float dY,float cX,float cY){
		float x = Math.abs(dX - cX);
		float y = Math.abs(dY - cY);
		return (int) Math.sqrt(x*x + y*y);
	}
	
	public void setOnChangeListener(OnChangeListener listener){
		this.mListener = listener;
	}
	
	public interface OnChangeListener{
		public void onChanged(int totalNum,int curNum);
	}
}


布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
	<com.example.zz.DialView
	    android:id="@+id/tv"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent"
	    android:layout_centerInParent="true"
	    android:background="#aaaaaa"/>
</RelativeLayout>


activity中调用:

public class TestViewActivity extends Activity implements OnChangeListener {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_test_view);
		DialView tv = (DialView) findViewById(R.id.tv);
		tv.setOnChangeListener(this);
	}

	@Override
	public void onChanged(int totalNum, int curNum) {
		Toast.makeText(this, "total = " + totalNum + " , curNum = " +curNum, Toast.LENGTH_SHORT).show();
	}
}


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值