近期公司需要做一个控制面板对摄像头进行方向控制,之前用一个圆形背景加4个圆形Button做的,同事可能感觉做的太敷衍了,而且点击效果都被手指挡住了,所以他给领导打小报告说让我重做,既然领导说做,哪有不做的道理
先看效果,左边的是按下时的效果, 没有按键时,颜色和上下右边的颜色是一致的,按键灵敏,使用简单
下面我们看看怎么用画布画出这个Panel(当然,颜色什么的可以可以换)
首先我们需要继承一个View,给View初始化一个isTouch数组,这个数组在你点击的时候记录你点击的那个方向,然后重绘面板的时候,会将点击的方向颜色变深;
然后初始化画笔,和一些默认尺寸,arrowLeft 和 arrowLeftDP 都是表示尺寸的, 为了自适应不同的屏幕大小,我们用arrowLeftDP来记录默认dp尺寸的大小,在初始化的时候将arrowLeftDP转化成PX(像素)赋给arrowLeft,所以下面所有的带DP和不带DP的尺寸变量都是这个原理
初始化的时候我们顺便设置一下触摸事件,后面我们要点击用到
public class PanelView extends View implements View.OnTouchListener {
private final String TAG = "PanelView";
boolean[] isTouch = new boolean[]{false,false,false,false};
private Paint paint;
private int smallCircleRadius = 180;
private final int strokeWidth = 12;
private final int halfStrokeWidth = strokeWidth/2;
private int arrowLeft = -70;
private int arrowTop = 280;
private int arrowRight = 70;
private int arrowBottom = 450;
private final int smallCircleRadiusDP = 51;
private int arrowLeftDP = -20;
private int arrowTopDP = 80;
private int arrowRightDP = 20;
private int arrowBottomDP = 125;
public PanelView(Context context) {
super(context);
setOnTouchListener(this);
dpTopx(context);
}
public PanelView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setOnTouchListener(this);
dpTopx(context);
}
public PanelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnTouchListener(this);
dpTopx(context);
}
public PanelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setOnTouchListener(this);
dpTopx(context);
}
private void dpTopx(Context context){
Log.i(TAG,"small Circle Radius " + DensityUtil.px2dip(context,smallCircleRadius) + "\r"
+ "stroke Width " + DensityUtil.px2dip(context,strokeWidth) + "\r"
+ "arrow Left " + DensityUtil.px2dip(context,arrowLeft)+ "\r"
+ "arrow Top " + DensityUtil.px2dip(context,arrowTop)+ "\r"
+ "arrow Right " + DensityUtil.px2dip(context,arrowRight)+ "\r"
+ "arrow Bottom " + DensityUtil.px2dip(context,arrowBottom));
smallCircleRadius = DensityUtil.dip2px(context,smallCircleRadiusDP);
arrowLeft = DensityUtil.dip2px(context,arrowLeftDP);
arrowTop = DensityUtil.dip2px(context,arrowTopDP);
arrowRight = DensityUtil.dip2px(context,arrowRightDP);
arrowBottom = DensityUtil.dip2px(context,arrowBottomDP);
}
}
接下来我们要画出面板的图形,整个绘制都在Ondraw里面
第一步我们先要画出1个扇形,扇形在画的时候要判断下是不是点击状态,根据状态的不同我们画扇形时的颜色不一样,rectF是画扇形时用的矩形框
再画出扇形的边框,rectFStroke时画边框的矩形,因为画笔有宽度,所以设置矩形时,需要将矩形的大小减少画笔宽度的一半,才能将边框完整的显示出来,
然后每画一个扇形就旋转90度,直到4个扇形全部画完
画完扇形将箭头旋转画到画布上,箭头的大小和位置是根据类初始化的时候设定的数值去绘制的
最后我们在中心画一个小圆就完成了我们的面板了
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint = new Paint();
int width = getWidth();
int height = getHeight();
int radius = Math.min(width,height);
paint.setAntiAlias(true);
@SuppressLint("DrawAllocation")
RectF rectF = new RectF((width-radius)/2.0f,
(height-radius)/2.0f,
width - (width-radius)/2.0f,
height - (height-radius)/2.0f);
@SuppressLint("DrawAllocation")
RectF rectFStroke = new RectF((width-radius)/2.0f + halfStrokeWidth,
(height-radius)/2.0f + halfStrokeWidth,
width - (width-radius)/2.0f - halfStrokeWidth,
height - (height-radius)/2.0f - halfStrokeWidth);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.jiantou);
float startAngle = 45.0f;
for(int i = 0; i< 4; i++) {
if(isTouch[i])
paint.setColor(0xff8a8a8a);
else
paint.setColor(0xffcdcdcd);
paint.setStyle(Paint.Style.FILL);
canvas.drawArc(rectF, startAngle, 90.0f, true, paint);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0xffbfbfbf);
canvas.drawArc(rectFStroke, startAngle, 90.0f, true, paint);
startAngle += 90;
}
float bitmapStartAngle = 90f;
Rect rectFBitmap = new Rect(0,
0,
bitmap.getWidth(),
bitmap.getHeight());
//RectF rect = new RectF(- 80,200,80,radius/2-40);
RectF rect = new RectF(arrowLeft,arrowTop,arrowRight,arrowBottom);
Log.i(TAG, rect.toString());
canvas.save();
canvas.translate(width/2.0f,height/2.0f);
for(int i = 0; i< 4; i++){
canvas.rotate(bitmapStartAngle);
canvas.drawBitmap(bitmap,rectFBitmap,rect,paint);
}
canvas.restore();
paint.setColor(0xffe6e6e6);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(width/2.0f,height/2.0f,smallCircleRadius,paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0xffbfbfbf);
canvas.drawCircle(width/2.0f,height/2.0f,smallCircleRadius-halfStrokeWidth,paint);
}
这时候面板是没有点击效果的,我们需要给面板添加点击事件
当点击面板的时候,我们首先判断是不是在圆内(中间的小圆点击不算在范围内),如果不在有效范围内,则点击无效,然后判断点击的是哪个区域,根据点击区域设置对应的isTouch标记为true,并重绘面板,当手指抬起时,将所有的isTouch全置为false,并重绘面板,让面板恢复初始状态
判断是不是在圆内,根据勾股定理计算半径,如果半径小于大圆的半径且大于小圆的半径即在圆内
判断在哪个方向也很简单,只需要判断X>Y还是X<Y即可,因为我们的面板像是一个圆上在45度和-45度的地方画了2条直经,当我们手指点在45度的分割线上, X==Y;但是稍微偏一点的话 X要么大于Y,要么小于Y,根据这个原理判断我们点击的范围
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG,"Motion Event is "+event.getAction());
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
boolean isInCircle = IsPointInCircle(event.getX(),event.getY());
if(!isInCircle) break;
int area = inWitchArea(event.getX(),event.getY());
if(area == -1) break;
isTouch[area] = true;
invalidate();
return true;
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
isTouch[0] = false;
isTouch[1] = false;
isTouch[2] = false;
isTouch[3] = false;
invalidate();
break;
}
return false;
}
private int inWitchArea(float x1,float y1){
int width = getWidth();
int height = getHeight();
float x = width/2.0f;
float y = height/2.0f;
boolean XGreaterThanY = Math.abs((x - x1)) > Math.abs((y - y1));
if(x1 > x && y1 < y){
//第一象限
if(XGreaterThanY)
return 3;
else
return 2;
}else if(x1 > x && y1 > y){
if(XGreaterThanY)
return 3;
else
return 0;
//第四象限
}else if(x1 < x && y1 > y){
if(XGreaterThanY)
return 1;
else
return 0;
//第三象限
}else if(x1 < x && y1 < y){
//第二象限
if(XGreaterThanY)
return 1;
else
return 2;
}
return -1;
}
private boolean IsPointInCircle(float x1,float y1)
{
int width = getWidth();
int height = getHeight();
int radius = Math.min(width,height);
//到圆心的距离 是否大于半径。半径是R
//如O(x,y)点圆心,任意一点P(x1,y1) (x-x1)*(x-x1)+(y-y1)*(y-y1)>R*R 那么在圆外 反之在圆内
float x = width/2.0f;
float y = height/2.0f;
float r = radius/2.0f;
if(!((x - x1)*(x - x1) + (y - y1)*(y - y1) > smallCircleRadius*smallCircleRadius))
return false;
else if (!((x - x1)*(x - x1) + (y - y1)*(y - y1) > r*r))
return true; //当前点在圆内
else
return false; //当前点在圆外
}
现在我们的面板画完了,你可以愉快的在你的布局文件中引用它了,如果觉得文章有用,请你点个赞吧