有朋自远方来,不亦乐乎。
如果本篇文章对您有帮助,请动动您的小手点赞、评论、收藏。
话不多少,先上效果图。
主要功能
1. 扁平化的控件样式。
2. 实现上、下、左、右点击功能,并实现拖拽中间圆形进行八个方向的控制。
实现思路
1. 由于此功能基于自定义View实现,因此先实现View类,重写onSeasure、onDraw函数。
2. 在onSeasure函数中,计算此view的宽高,后面基于此宽高计算控件中其他元素的宽高和位置信息。
3. 由于自定义View需使用Canvas绘制控件的所有元素,因此,提前定义好不同元素所使用的画笔。
4. 由于是方向控件,需要实现按钮的点击事件和中间圆的拖拽事件,在自定义View中,是使用onTouchListener实现的,在onTouch事件回调函数中,处理不同功能的操作逻辑。
5. 在处理点击事件时,根据用户点击的部位判断点击的是哪个按钮,如果点击的是中间,则可以拖拽。处理好点击事件后,调用invalidate()进行重绘。
6. 最后,在onDraw函数中重绘控件的相关部位。
实现代码见下面,复制可用,有缺失的类是我自己的业务逻辑,删除即可。
public class DirectionView extends View implements View.OnTouchListener {
private int width;
private int height;
private int halfWidth;
private int halfHeight;
private int smallCircleRadius;
private int sideWidth = 4;//外层边框宽度
private Paint paintCircleSide;
private Paint paintCircleBg;
private Paint paintCircleCenter;
private Paint paintDirection;
private int pressDirectionH = 0;
private int pressDirectionV = 0;
private int smallCircleCenterX = -1;
private int smallCircleCenterY = -1;
private int halfTouchWidth = 65;
private BlurMaskFilter blurMaskFilter;
private Path path = new Path();
public DirectionView(Context context) {
super(context);
init(context);
}
public DirectionView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DirectionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context)
{
paintCircleSide = createPaintWithCommon(sideWidth, "#66ffffff", Paint.Style.STROKE);
paintCircleBg = createPaintWithCommon(2, "#662a3845", Paint.Style.FILL);
paintCircleCenter = createPaintWithCommon(2, "#ffffff", Paint.Style.FILL);
paintDirection = createPaintWithCommon(6, "#ffffff", Paint.Style.STROKE);
blurMaskFilter = new BlurMaskFilter(20, BlurMaskFilter.Blur.SOLID);
this.setOnTouchListener(this);
}
private Paint createPaintWithCommon(int strokeWidth, String color, Paint.Style style)
{
Paint paint = new Paint();
paint.setStrokeWidth(strokeWidth);
paint.setColor(Color.parseColor(color));
paint.setStyle(style);
paint.setAntiAlias(true);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
return paint;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(halfWidth, halfHeight, halfWidth - sideWidth / 2, paintCircleBg);
canvas.drawCircle(halfWidth, halfHeight, halfWidth - sideWidth, paintCircleSide);
if(this.smallCircleCenterY != -1)
{
// paintCircleCenter.setMaskFilter(blurMaskFilter);
canvas.drawCircle(smallCircleCenterX, smallCircleCenterY, smallCircleRadius, paintCircleCenter);
}
else
{
// paintCircleCenter.setMaskFilter(null);
canvas.drawCircle(halfWidth, halfHeight, smallCircleRadius, paintCircleCenter);
}
int offsetSize = 25;
int offsetPosition = 20;
//上
if(pressDirectionV == PtzAction.Direction.Up)
paintDirection.setColor(Color.parseColor("#0000ff"));
else
paintDirection.setColor(Color.parseColor("#ffffff"));
path.reset();
path.moveTo(halfWidth - offsetSize, halfHeight / 2 - offsetPosition);
path.lineTo(halfWidth, halfHeight / 2 - offsetSize - offsetPosition);
path.lineTo(halfWidth + offsetSize, halfHeight / 2 - offsetPosition);
canvas.drawPath(path, paintDirection);
//右
if(pressDirectionH == PtzAction.Direction.Right)
paintDirection.setColor(Color.parseColor("#0000ff"));
else
paintDirection.setColor(Color.parseColor("#ffffff"));
path.reset();
path.moveTo(halfWidth + halfWidth / 2 + offsetPosition, halfHeight - offsetSize);
path.lineTo(halfWidth + halfWidth / 2 + offsetPosition + offsetSize, halfHeight);
path.lineTo(halfWidth + halfWidth / 2 + offsetPosition, halfHeight + offsetSize);
canvas.drawPath(path, paintDirection);
//下
if(pressDirectionV == PtzAction.Direction.Down)
paintDirection.setColor(Color.parseColor("#0000ff"));
else
paintDirection.setColor(Color.parseColor("#ffffff"));
path.reset();
path.moveTo(halfWidth - offsetSize, halfHeight + halfHeight / 2 + offsetPosition);
path.lineTo(halfWidth, halfHeight + halfHeight / 2 + offsetSize + offsetPosition);
path.lineTo(halfWidth + offsetSize, halfHeight + halfHeight / 2 + offsetPosition);
canvas.drawPath(path, paintDirection);
//左
if(pressDirectionH == PtzAction.Direction.Left)
paintDirection.setColor(Color.parseColor("#0000ff"));
else
paintDirection.setColor(Color.parseColor("#ffffff"));
path.reset();
path.moveTo(halfWidth / 2 - offsetPosition, halfHeight - offsetSize);
path.lineTo(halfWidth / 2 - offsetPosition - offsetSize, halfHeight);
path.lineTo(halfWidth / 2 - offsetPosition, halfHeight + offsetSize);
canvas.drawPath(path, paintDirection);
}
@Override
public boolean onTouch(View v, MotionEvent event)
{
try
{
int action = event.getAction();
int x = (int) event.getX();
int y = (int) event.getY();
if(action == MotionEvent.ACTION_DOWN)
{
//判断按下的是不是中间
if(x > halfWidth - smallCircleRadius && x < halfWidth + smallCircleRadius && y > halfHeight - smallCircleRadius && y < halfHeight + smallCircleRadius)
{
this.smallCircleCenterX = x;
this.smallCircleCenterY = y;
return true;
}
int offset = 30;
if(x > halfWidth - halfTouchWidth && x < halfWidth + halfTouchWidth)
{
if(y > halfHeight / 2 - halfTouchWidth - offset && y < halfHeight / 2 + halfTouchWidth - offset)
{
setPtzDirection(PtzAction.Direction.None, PtzAction.Direction.Up);
}
else if (y > halfHeight + halfHeight / 2 + offset - halfTouchWidth && y < halfHeight + halfHeight / 2 + offset + halfTouchWidth)
{
setPtzDirection(PtzAction.Direction.None, PtzAction.Direction.Down);
}
}
if(y > halfHeight - halfTouchWidth && y < halfHeight + halfTouchWidth)
{
if(x > halfWidth / 2 - halfTouchWidth - offset && x < halfWidth / 2 + halfTouchWidth - offset)
{
setPtzDirection(PtzAction.Direction.Left, PtzAction.Direction.None);
}
else if(x > halfWidth + halfWidth / 2 + offset - halfTouchWidth && x < halfWidth + halfWidth / 2 + offset + halfTouchWidth)
{
setPtzDirection(PtzAction.Direction.Right, PtzAction.Direction.None);
}
}
}
else if (action == MotionEvent.ACTION_MOVE)
{
//说明按下的是中间小圆
if(this.smallCircleCenterX != -1)
{
//小圆拖动时圆心所在的圆的最大半径
int maxRadius = halfHeight - smallCircleRadius - 35;
int minRadius = 70;
int deltaX = x - halfWidth;
int deltaY = y - halfHeight;
int length = (int) Math.hypot(deltaX, deltaY);
if(length > maxRadius)
{
// LOGUtil.d("在圆外");
this.smallCircleCenterX = halfWidth + maxRadius * deltaX / length;
this.smallCircleCenterY = halfHeight + maxRadius * deltaY / length;
}
else
{
// LOGUtil.d("在圆内");
this.smallCircleCenterX = x;
this.smallCircleCenterY = y;
if(length < minRadius)
{
setPtzDirection(PtzAction.Direction.None, PtzAction.Direction.None);
invalidate();
return true;
}
}
double atan = Math.atan((this.smallCircleCenterY - halfHeight) * 1.0 / (this.smallCircleCenterX - halfWidth));
double degree = Math.toDegrees(atan);
if(this.smallCircleCenterX < halfWidth)
{
if(degree > 60)
setPtzDirection(PtzAction.Direction.None, PtzAction.Direction.Up);
else if(degree > 30)
setPtzDirection(PtzAction.Direction.Left, PtzAction.Direction.Up);
else if(degree > -30)
setPtzDirection(PtzAction.Direction.Left, PtzAction.Direction.None);
else if(degree > -60)
setPtzDirection(PtzAction.Direction.Left, PtzAction.Direction.Down);
else
setPtzDirection(PtzAction.Direction.None, PtzAction.Direction.Down);
}
else
{
if(degree > 60)
setPtzDirection(PtzAction.Direction.None, PtzAction.Direction.Down);
else if(degree > 30)
setPtzDirection(PtzAction.Direction.Right, PtzAction.Direction.Down);
else if(degree > -30)
setPtzDirection(PtzAction.Direction.Right, PtzAction.Direction.None);
else if(degree > -60)
setPtzDirection(PtzAction.Direction.Right, PtzAction.Direction.Up);
else
setPtzDirection(PtzAction.Direction.None, PtzAction.Direction.Up);
}
// LOGUtil.d("atan = " + atan + ", degree = " + Math.toDegrees(atan));
}
}
else if(action == MotionEvent.ACTION_UP)
{
setPtzDirection(PtzAction.Direction.None, PtzAction.Direction.None);
this.smallCircleCenterX = -1;
this.smallCircleCenterY = -1;
}
invalidate();
}
catch (Exception e)
{
LOGUtil.e(e.getMessage());
}
//一定要return true, 否则UP事件不会被监听到。
return true;
}
private void setPtzDirection(int directionH, int directionV)
{
PtzAction ptzAction = ApplicationContext.getInstance().getPtzAction();
ptzAction.setDirectionH(directionH);
ptzAction.setDirectionV(directionV);
this.pressDirectionH = directionH;
this.pressDirectionV= directionV;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(widthMeasureSpec);
width = getSize(widthMode, widthMeasureSpec);
height = getSize(heightMode, heightMeasureSpec);
halfWidth = width / 2;
halfHeight = height / 2;
smallCircleRadius = halfWidth / 4;
}
private int getSize(int mode, int sizeMeasureSpec)
{
if(mode == MeasureSpec.EXACTLY || mode == MeasureSpec.AT_MOST)
return MeasureSpec.getSize(sizeMeasureSpec);
else if (mode == MeasureSpec.UNSPECIFIED)
return sizeMeasureSpec;
else
return 0;
}
}