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