public class GestureView extends View {
/**
* 默认格子数
*/
private static final int POINT_COUNT = 9;
/**
* 默认中心点的半径
*/
private static final int POINT_RADIUS = 2;
/**
* 默认外圈半径
*/
private static final int CIRCLE_RADIUS = 25;
/**
* 默认最少连接的点数,否则提示错误
*/
private static final int MIN_POINT_COUNT = 4;
private static final int DEF_LINE_WIDTH = 4;
private static final int DEF_CIRCLE_BORDER_WIDTH = 1;
/**
* 中心点颜色
*/
private int pointColor;
/**
* 中心点选择态颜色
*/
private int pointSelectColor;
private int pointNormalSelectedColor;
private int pointErrorSelectedColor;
/**
* 圆圈的颜色
*/
private int circleColor;
/**
* 圆圈的选择态颜色
*/
private int circleSelectedColor;
private int circleNormalSelectedColor;
private int circleErrorSelectedColor;
/**
* 条目外圆框的颜色
*/
private int circleBorderColor;
private float circleBorderWidth;
/**
* 条目外圆框的选择态颜色
*/
private int circleBorderSelectedColor;
private int circleBorderNormalSelectedColor;
private int circleBorderErrorSelectedColor;
private Paint mPaint;
private Paint mLinePaint;
private Map pointMap = new HashMap<>(10);
/**
* 记录选择过的点下标
*/
private ArrayList selectList = new ArrayList<>();
private GestureListener listener;
private int pointNum;
private int linkMinNum;
private float lineWidth;
private int lineColor;
private int lineErrorColor;
private float pointRadius;
private float circleRadius;
private Path linePath;
public void setListener(GestureListener listener) {
this.listener = listener;
}
public GestureView(Context context) {
this(context, null);
}
public GestureView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttr(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mLinePaint = new Paint();
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(lineWidth);
mLinePaint.setPathEffect(new CornerPathEffect(5));
linePath = new Path();
setSelectedColor(false);
}
private void initAttr(Context context, AttributeSet attrs) {
TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.GestureView);
pointNum = attr.getInt(R.styleable.GestureView_item_num, POINT_COUNT);
linkMinNum = attr.getInt(R.styleable.GestureView_link_min_num, MIN_POINT_COUNT);
circleColor = attr.getColor(R.styleable.GestureView_circle_Color, ResourcesUtils.getColor(R.color.gesture_circle_color));
circleNormalSelectedColor = attr.getColor(R.styleable.GestureView_circle_selected_Color, ResourcesUtils.getColor(R.color.gesture_circle_select_color));
circleErrorSelectedColor = attr.getColor(R.styleable.GestureView_circle_error_Color, ResourcesUtils.getColor(R.color.gesture_error_circle_color));
pointColor = attr.getColor(R.styleable.GestureView_point_color, ResourcesUtils.getColor(R.color.gesture_point_color));
pointNormalSelectedColor = attr.getColor(R.styleable.GestureView_point_selected_color, ResourcesUtils.getColor(R.color.gesture_point_select_color));
pointErrorSelectedColor = attr.getColor(R.styleable.GestureView_point_error_color, ResourcesUtils.getColor(R.color.gesture_error_point_select_color));
pointRadius = attr.getDimension(R.styleable.GestureView_point_radius, BaseUtils.dp2px(POINT_RADIUS));
lineWidth = attr.getDimension(R.styleable.GestureView_line_width, DEF_LINE_WIDTH);
lineColor = attr.getColor(R.styleable.GestureView_line_color, ResourcesUtils.getColor(R.color.gesture_point_select_color));
lineErrorColor = attr.getColor(R.styleable.GestureView_line_error_color, ResourcesUtils.getColor(R.color.gesture_error_point_select_color));
circleRadius = attr.getDimension(R.styleable.GestureView_circle_radius, BaseUtils.dp2px(CIRCLE_RADIUS));
circleBorderColor = attr.getColor(R.styleable.GestureView_circle_border_Color, ResourcesUtils.getColor(R.color.gesture_circle_color));
circleBorderNormalSelectedColor = attr.getColor(R.styleable.GestureView_circle_border_selected_Color, ResourcesUtils.getColor(R.color.gesture_point_select_color));
circleBorderErrorSelectedColor = attr.getColor(R.styleable.GestureView_circle_border_error_Color, ResourcesUtils.getColor(R.color.gesture_error_point_select_color));
circleBorderWidth = attr.getDimension(R.styleable.GestureView_circle_border_width, DEF_CIRCLE_BORDER_WIDTH);
attr.recycle();
}
public void setItemCount(@IntRange(from = 1, to = 25) int count) {
pointNum = count;
initPointMap();
requestLayout();
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initPointMap();
}
/**
* 保存每个点对应的坐标到Map中
*/
private void initPointMap() {
pointMap.clear();
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
int width = Math.min(measuredWidth, measuredHeight);
if (width == 0) {
return;
}
int columns = (int) Math.round(Math.sqrt(pointNum));//列columns
int rows = (pointNum > (columns * columns)) ? columns + 1 : columns;//rows
float columnW = width * 1.0f / columns;//列宽
float rowH = width * 1.0f / (rows);//行高
int offset = measuredWidth - measuredHeight;
float abs = Math.abs(offset / 2f);
float offsetX = (offset > 0) ? abs : 0;//控件宽度大于高度 则锚点需水平居中
float offsetY = (offset < 0) ? abs : 0;//控件高度大于宽度 则锚点需垂直居中
for (int index = 0; index < pointNum; index++) {
float[] location = new float[2];
if (columns > 0) {
int rowLocation = (index) / columns + 1;//当前锚点所在行位置
int columnLocation = (index) % columns + 1;//当前锚点所在列位置
if (rowLocation == rows && (pointNum % columns) != 0) {//最后一行 && 锚点总数不平方数
columnW = width * 1.0f / (pointNum % columns);
}
location[0] = (columnLocation - 0.5f) * (columnW) + offsetX;
location[1] = (rowLocation - 0.5f) * (rowH) + offsetY;
}
pointMap.put(index + 1, location);
}
}
/**
* 设置点,圈,和连接线的颜色
*/
private void setSelectedColor(boolean isError) {
pointSelectColor = isError ? pointErrorSelectedColor : pointNormalSelectedColor;
circleSelectedColor = isError ? circleErrorSelectedColor : circleNormalSelectedColor;
circleBorderSelectedColor = isError ? circleBorderErrorSelectedColor : circleBorderNormalSelectedColor;
if (mLinePaint != null) {
mLinePaint.setColor(isError ? lineErrorColor : lineColor);
}
}
/**
* 恢复初始未选择的状态
*/
public void reset() {
try {
resetImpl();
invalidate();
} catch (Exception e) {
e.printStackTrace();
}
}
private void resetImpl() {
setSelectedColor(false);
lastPosition[0] = 0;
lastPosition[1] = 0;
selectList.clear();
}
/**
* 显示错误,比如连结的点过少
*/
public void showError() {
setSelectedColor(true);
invalidate();
}
private boolean isStart = false;
private float currentX = 0f;
private float currentY = 0f;
private float[] lastPosition = new float[2];
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (isStart) {
isStart = false;
if (getSelectPointCount() < linkMinNum) {
showError();
if (null != listener) {
listener.onError();
}
} else {
if (null != listener) {
listener.onFinish(selectList);
}
}
postDelayed(this::reset, 300);
}
break;
case MotionEvent.ACTION_DOWN:
isStart = true;
if (null != listener) {
listener.onDrawStart();
}
resetImpl();
case MotionEvent.ACTION_MOVE:
currentX = event.getX();
currentY = event.getY();
addSelectPoint(currentX, currentY);
invalidate();
break;
default:
break;
}
return true;
}
/**
* 获取当前触摸的点
*/
private void addSelectPoint(float touchX, float touchY) {
for (Map.Entry entry : pointMap.entrySet()) {
float[] value = entry.getValue();
if (value == null) {
return;
}
if (lastPositionNoValue()) {
if (isInBounds(touchX, touchY, value)) {
addSelected(entry, value);
}
} else if (SutherlandUtils.isInBounds(lastPosition[0], lastPosition[1], touchX, touchY, value, (int) circleRadius)) {
addSelected(entry, value);
}
}
}
private boolean lastPositionNoValue() {
return lastPosition[0] == 0 && lastPosition[1] == 0;
}
private void addSelected(Map.Entry entry, float[] value) {
int selectIndex = entry.getKey();
if (selectIndex > 0 && !selectList.contains(selectIndex)) {
lastPosition[0] = value[0];
lastPosition[1] = value[1];
selectList.add(selectIndex);
}
}
private boolean isInBounds(float x, float y, float[] location) {
return isInBounds(x, location[0]) && isInBounds(y, location[1]);
}
private boolean isInBounds(float touch, float location) {
return touch > location - circleRadius && touch < location + circleRadius;
}
/**
* 获取已经选择的点数量
*/
private int getSelectPointCount() {
return (null == selectList || selectList.isEmpty()) ? 0 : selectList.size();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画连线
linePath.reset();
int size = selectList.size();
if (size > 0 && !lastPositionNoValue()) {
for (int i = 0; i < size; i++) {
float[] point = pointMap.get(selectList.get(i));
if (point == null) {
return;
}
if (i == 0) {
linePath.moveTo(point[0], point[1]);
} else {
linePath.lineTo(point[0], point[1]);
}
}
linePath.lineTo(currentX, currentY);
canvas.drawPath(linePath, mLinePaint);
}
for (int i = 1; i <= pointNum; i++) {
float[] location = pointMap.get(i);
if (null == location) {
return;
}
if (selectList.contains(i)) {//是否选中
drawItem(canvas, location, pointSelectColor, circleSelectedColor, circleBorderSelectedColor);
if (null != listener) {
listener.onDraw(i);
}
} else {
drawItem(canvas, location, pointColor, circleColor, circleBorderColor);
}
}
}
private void drawItem(Canvas canvas, float[] location, @ColorInt int pointColor, @ColorInt int circleColor, @ColorInt int borderColor) {
if (circleColor != 0) {
//画圆圈
mPaint.setColor(circleColor);
canvas.drawCircle(location[0], location[1], circleRadius, mPaint);
}
if (borderColor != 0 && circleBorderWidth != 0) {
//画圆圈边框
mPaint.setColor(borderColor);
mPaint.setStrokeWidth(circleBorderWidth);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(location[0], location[1], circleRadius - circleBorderWidth / 2, mPaint);
mPaint.setStyle(Paint.Style.FILL);
}
mPaint.setColor(pointColor);
//画未选择的点
canvas.drawCircle(location[0], location[1], pointRadius, mPaint);
}
}
Cohen-Sutherland算法实现求线段是否过区间
#FFD6D4
#ff0000
#000000
#16BC83
#00000000
#3316BC83