效果图:
完整代码:
/**
* Created by QiuLong on 2020/11/16.
*/
public class CircleMenuLayout extends View {
public final static int CLICK_AREA_NONE = 0;// 不做任何点击事件处理
public final static int CLICK_AREA_LEFT = 1;
public final static int CLICK_AREA_TOP = 2;
public final static int CLICK_AREA_RIGHT = 3;
public final static int CLICK_AREA_BOTTOM = 4;
public final static int CLICK_AREA_CENTER = 5;
private int mClickArea = CLICK_AREA_NONE;
private final Rect mRealBounds;
private final Rect mCenterBounds;
private final PointF mVector1 = new PointF();
private final PointF mVector2 = new PointF();
private Drawable mLeftDrawable;
private Drawable mTopDrawable;
private Drawable mRightDrawable;
private Drawable mBottomDrawable;
private Drawable mCenterDrawable;
private OnCircleMenuClickListener mOnCircleMenuClickListener;
public CircleMenuLayout(Context context) {
this(context, null);
}
public CircleMenuLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleMenuLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mCenterBounds = new Rect();
mRealBounds = new Rect();
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleMenuLayout);
int thumbLeft = typedArray.getResourceId(R.styleable.CircleMenuLayout_circleThumbLeft, 0);
int thumbTop = typedArray.getResourceId(R.styleable.CircleMenuLayout_circleThumbTop, 0);
int thumbRight = typedArray.getResourceId(R.styleable.CircleMenuLayout_circleThumbRight, 0);
int thumbBottom = typedArray.getResourceId(R.styleable.CircleMenuLayout_circleThumbBottom, 0);
int thumbCenter = typedArray.getResourceId(R.styleable.CircleMenuLayout_circleThumbCenter, 0);
if (thumbLeft != 0) {
mLeftDrawable = AppCompatResources.getDrawable(context, thumbLeft);
}
if (thumbTop != 0) {
mTopDrawable = AppCompatResources.getDrawable(context, thumbTop);
}
if (thumbRight != 0) {
mRightDrawable = AppCompatResources.getDrawable(context, thumbRight);
}
if (thumbBottom != 0) {
mBottomDrawable = AppCompatResources.getDrawable(context, thumbBottom);
}
if (thumbCenter != 0) {
mCenterDrawable = AppCompatResources.getDrawable(context, thumbCenter);
}
}
}
public void setThumbDrawable(int[] thumbRes) {
if (thumbRes != null) {
mLeftDrawable = AppCompatResources.getDrawable(getContext(), thumbRes[0]);
mTopDrawable = AppCompatResources.getDrawable(getContext(), thumbRes[1]);
mRightDrawable = AppCompatResources.getDrawable(getContext(), thumbRes[2]);
mBottomDrawable = AppCompatResources.getDrawable(getContext(), thumbRes[3]);
mCenterDrawable = AppCompatResources.getDrawable(getContext(), thumbRes[4]);
invalidate();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w <= 0 || h <= 0) {
return;
}
// 去掉padding后的实际宽高
int width = w - getPaddingLeft() - getPaddingRight();
int height = h - getPaddingTop() - getPaddingBottom();
int size = Math.min(width, height);
mRealBounds.set(0, 0, size, size);
mRealBounds.offsetTo(getPaddingLeft() + (width - size) / 2, getPaddingTop() + (height - size) / 2);
mCenterBounds.set(mRealBounds);
int inset = (int) (mRealBounds.width() * (1 - 5F / 9) / 2);
mCenterBounds.inset(inset, inset);
}
@Override
protected void onDraw(Canvas canvas) {
if (mLeftDrawable != null) {
setDrawableAlpha(mLeftDrawable, CLICK_AREA_LEFT);
mLeftDrawable.setBounds(mRealBounds);
mLeftDrawable.draw(canvas);
}
if (mTopDrawable != null) {
setDrawableAlpha(mTopDrawable, CLICK_AREA_TOP);
mTopDrawable.setBounds(mRealBounds);
mTopDrawable.draw(canvas);
}
if (mRightDrawable != null) {
setDrawableAlpha(mRightDrawable, CLICK_AREA_RIGHT);
mRightDrawable.setBounds(mRealBounds);
mRightDrawable.draw(canvas);
}
if (mBottomDrawable != null) {
setDrawableAlpha(mBottomDrawable, CLICK_AREA_BOTTOM);
mBottomDrawable.setBounds(mRealBounds);
mBottomDrawable.draw(canvas);
}
if (mCenterDrawable != null) {
setDrawableAlpha(mCenterDrawable, CLICK_AREA_CENTER);
mCenterDrawable.setBounds(mCenterBounds);
mCenterDrawable.draw(canvas);
}
}
// 绘制点击效果
private void setDrawableAlpha(Drawable drawable, int area) {
drawable.setAlpha(mClickArea == area ? 0xCC : 0xFF);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mClickArea = calculateArea(event.getX(), event.getY());
invalidate();
break;
case MotionEvent.ACTION_UP:
int clickArea = mClickArea;
mClickArea = CLICK_AREA_NONE;
invalidate();
if (clickArea != CLICK_AREA_NONE) {
notifyTouchChanged(clickArea);
}
break;
}
return true;
}
// 计算焦点范围
private int calculateArea(float x, float y) {
// 中心按钮
boolean innerCircle = isInCircle(mRealBounds.centerX(), mRealBounds.centerY(), x, y, mCenterBounds.width() / 2F);
if (innerCircle) {
return CLICK_AREA_CENTER;
}
// 超出外圆焦点范围
boolean outerCircle = isInCircle(mRealBounds.centerX(), mRealBounds.centerY(), x, y, mRealBounds.width() / 2F);
if (!outerCircle) {
return CLICK_AREA_NONE;
}
// 上下左右四个按钮
float degrees = angle(mRealBounds.centerX(), mRealBounds.centerY(), x, y, mRealBounds.centerX() + 100, mRealBounds.centerY());
if (degrees < -135F || degrees > 135F) {
return CLICK_AREA_LEFT;
}
if (degrees < -45F) {
return CLICK_AREA_BOTTOM;
}
if (degrees < 45F) {
return CLICK_AREA_RIGHT;
}
return CLICK_AREA_TOP;
}
// 计算焦点是否在圆内的范围
private boolean isInCircle(float centerX, float centerY, float x, float y, float radius) {
// 点击位置x坐标与圆心x坐标的距离
int distanceX = (int) Math.abs(centerX - x);
// 点击位置y坐标与圆心y坐标的距离
int distanceY = (int) Math.abs(centerY - y);
// 点击位置与圆心的直线距离
int distanceZ = (int) Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
// 如果点击位置与圆心的距离小于或等于圆的半径,则点击位置在圆内
return distanceZ <= radius;
}
// 计算夹角
public float angle(float centX, float centY, float x1, float y1, float x2, float y2) {
float dx1 = x1 - centX;
float dx2 = x2 - centX;
float dy1 = y1 - centY;
float dy2 = y2 - centY;
float angle;
float c = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1) * (float) Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (c == 0)
return -180;
angle = (float) Math.acos((dx1 * dx2 + dy1 * dy2) / c);
angle = (float) Math.toDegrees(angle);
// center -> proMove的向量, 使用PointF来实现
mVector1.set((x1 - centX), (y1 - centY));
// center -> curMove 的向量
mVector2.set((x2 - centX), (y2 - centY));
// 向量叉乘结果, 如果结果为负数, 表示为逆时针, 结果为正数表示顺时针
float result = mVector1.x * mVector2.y - mVector1.y * mVector2.x;
if (result < 0) {
angle = -angle;
}
return angle;
}
private void notifyTouchChanged(int areaType) {
if (mOnCircleMenuClickListener != null) {
mOnCircleMenuClickListener.onMenuClick(areaType);
}
}
public void setOnCircleMenuClickListener(OnCircleMenuClickListener listener) {
this.mOnCircleMenuClickListener = listener;
}
public interface OnCircleMenuClickListener {
void onMenuClick(int areaType);
}
}