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

原创 2015年11月19日 15:46:29

思路

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();
	}
}


 

版权声明:本文为博主原创文章,未经博主允许不得转载。

基于Java Swing的仪表盘实现

基于Java Swing的仪表盘实现
  • jacke121
  • jacke121
  • 2017年05月15日 22:23
  • 435

自定义View实战(一) 汽车速度仪表盘

自定义View实战(一) 汽车速度仪表盘  转载请以链接形式标明出处:  http://blog.csdn.net/lxk_1993/article/details/5137326...
  • lxk_1993
  • lxk_1993
  • 2016年05月11日 13:02
  • 8868

Qt Qwdget 汽车仪表知识点拆解1 速度表示

先贴上效果图,注意,没有写逻辑,所以这些都是乱动的 这里线主要说一下中间显示速度的显示制作的方式,在这里,自己专门写了一个数字的仪表 考虑的一般的汽车是没有办法把瞬时速度提升到四位...
  • z609932088
  • z609932088
  • 2017年01月06日 16:29
  • 908

android 仿汽车仪表盘

android 仿汽车仪表盘
  • coderinchina
  • coderinchina
  • 2016年08月28日 20:04
  • 1924

手把手带你画一个 时尚仪表盘 Android 自定义View

拿到美工效果图,咱们程序员就得画得一模一样。 为了不被老板喷,只能多练啊。 听说你觉得前面几篇都so easy,那今天就带你做个相对比较复杂的。 今天的效果图如下(左边是ui图 右边是实现...
  • linfan591
  • linfan591
  • 2016年01月14日 15:33
  • 1305

Android模拟汽车仪表盘

界面酷炫的Android模拟汽车仪表盘
  • guokehello
  • guokehello
  • 2015年07月15日 16:56
  • 1807

用swift开发仪表盘控件(二)

swift开发的仪表控件
  • ynmaoyong
  • ynmaoyong
  • 2014年10月28日 16:11
  • 1676

Qt 汽车仪表 QWidget

今天是2016年的最后一个工作日,在这个最后工作日里面,和以为网友要了一点练手的素材文件,经过网友确认,不涉及商业机密,在这里分享一下,如侵权,请联系我删除、 先上程序运行图 ...
  • z609932088
  • z609932088
  • 2016年12月30日 20:53
  • 1716

Qt Qwdget 汽车仪表知识点拆解4 另类进度条实现

先贴上效果图,注意,没有写逻辑,都是乱动的 注意看一下,右面的这两个进度条,有瑕疵,就是我没有把图片处理干净,这里犹豫我不知道这个具体的弧度,也没法绘制,就偷懒了 现在上面放一个UI,把两...
  • z609932088
  • z609932088
  • 2017年01月06日 16:56
  • 571

自定义View----仿仪表盘的水柱进度条(高度定制化,已通过JitPack发布)

仪表盘进度条—–已通过JitPack发布 可以自定义的属性: 1.仪表盘半径 2.仪表盘宽度 3.指针大小 4.刻度的密度 5.可触发触摸事件对应设置进度(可选择) 6.进度动画(可选择...
  • sdfdzx
  • sdfdzx
  • 2017年03月29日 12:35
  • 249
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:UI:类似于汽车速度盘的仪表盘
举报原因:
原因补充:

(最多只允许输入30个字)