自定义View之九宫格Item位置*交换
前言
这个是年前就写好的demo,现在是整理后放了上来,至于我为什么要写这个,因为要写别的了:)
标题的描述可能不够准确,所以具体的样式还是看效果图。
关于这个效果其实是可以扩展成我想玩的一个游戏的,类似淘宝里的猫咪咖啡馆,等我周日补上来。
效果图
思路 1
-
有两个list,一个位置固定的,另一个位置不固定的(即手指滑动哪一个就更改哪一个的坐标)。
-
在手势的ACTION_MOVE的时候,位置是及时更新的,但是不允许越过手机屏幕的边界。
2.1 判断这个item是否处于已经从Square中拖动的状态,如果是,直接更新位置,如果不是则走2.2的逻辑。
2.2 根据手指实际对应的位置找出不固定位置的列表对应的item,并及时更新位置信息。 -
在手势为ACTION_UP的时候,判断是否是靠近了最近的一个Square,如果是,直接让这个item进入到Square里。如果该Square内的item不为空,则交换两个item的位置。
-
尺寸相关的适配。
思路 2 —2019/02/18更新
我又思考了一下,可以考虑只做一个list,
但是在拖动item的坐标是一个单独的item对象,这样在拖动item值不为空的情况下,
绘制list的item的坐标的时候,跳过拖动的item,(拖动的item需要单独绘制)
因为最终的item的坐标终归会回到初始值.所以最后只需要在up的时候,替换list中的两个item的坐标即可.
代码
ExchangeCirclesView.java
public class ExchangeCirclesView extends View {
private final int radiusCircle2 = 150; //直径
private Paint paint;
private float circleX = radiusCircle2, circleY = radiusCircle2;
private int[] arrays;
private int width;
private List<Point> circlePointList = new ArrayList<>(); //圆圈的实际PointList,坐标位置可变
private List<Point> finalSquarePointList = new ArrayList<>(); //固定Square区域的Point,坐标位置不可变
private float heightPadding;
private int height;
private Point touchPoint; //实际触摸的那个Point(圆圈)
private int textColor;
public ExchangeCirclesView(Context context) {
this(context, null);
}
public ExchangeCirclesView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ExchangeCirclesView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
private void initPaint() {
int textSize = radiusCircle2 / 5;
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
paint.setTextSize(textSize * 3 / 2);
paint.setTextAlign(Paint.Align.CENTER);
touchPoint = new Point(-1, -1);
textColor = Color.WHITE;
arrays = new int[]{Color.MAGENTA, Color.GREEN, Color.YELLOW, Color.RED, Color.BLUE, Color.GRAY, Color.DKGRAY,
ContextCompat.getColor(getContext(), R.color.colorPrimaryDark), Color.LTGRAY, Color.CYAN};
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
initList();
}
private void initList() {
int xLines = 4;
int yLines = 3;
clear();
int padding = (width - radiusCircle2 * yLines) / (yLines + 1);
heightPadding = 0;
float paceHeight = height - (padding * xLines + radiusCircle2 * 5);
int index = 0;
for (int j = 0; j < xLines; j++) {
int endX = padding + radiusCircle2;
float endY = radiusCircle2 * (yLines - 1) + j * (radiusCircle2 + padding) + (j > 0 ? paceHeight : 0);
if (j == (xLines - 1))
heightPadding = endY;
for (int i = 0; i < yLines; i++) {
if (i == 0 && j == 0) {
circleX = endX - radiusCircle2 / 2;
circleY = endY - radiusCircle2 / 2;
}
circlePointList.add(new Point(endX - radiusCircle2 / 2, endY - radiusCircle2 / 2, j > 0 ? index : -1, index, j == 0));
finalSquarePointList.add(new Point(endX - radiusCircle2 / 2, endY - radiusCircle2 / 2)); //固定坐标,不可移动.
index++;
endX += padding + radiusCircle2;
}
}
}
public void clear() {
circlePointList.clear();
finalSquarePointList.clear();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
circleX = event.getRawX();
circleY = event.getRawY() - radiusCircle2 / 2;
//不允许越过X,Y的最大边界
circleX = circleX < radiusCircle2 ? radiusCircle2 : circleX > width - radiusCircle2 ? width - radiusCircle2 : circleX;
circleY = circleY < radiusCircle2 ? radiusCircle2 : circleY > heightPadding ? heightPadding : circleY;
updateMovePointPos();
break;
case MotionEvent.ACTION_UP:
updateUpPointPos();
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
drawFinalSquares(canvas);
paint.setStyle(Paint.Style.FILL);
int j = 0;
for (int i = 0; i < circlePointList.size(); i++) {
Point point = circlePointList.get(i);
if (point.isEmpty) //没有填充的内容
continue;
drawCircles(canvas, point, j);
drawText(canvas, point);
j++;
}
}
/**
* 绘制固定的正方形背景(12个)
*
* @param canvas
*/
private void drawFinalSquares(Canvas canvas) {
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
for (Point point : finalSquarePointList) {
canvas.drawRect(point.endX - radiusCircle2 / 2, point.endY - radiusCircle2 / 2,
point.endX + radiusCircle2 / 2, point.endY + radiusCircle2 / 2, paint);
}
}
/**
* 绘制所有的圆圈(9个)
*
* @param canvas
* @param point
* @param j
*/
private void drawCircles(Canvas canvas, Point point, int j) {
paint.setColor(arrays[j]);
circleX = point.endX;
circleY = point.endY;
canvas.drawCircle(circleX, circleY, radiusCircle2 / 2, paint);
}
/**
* 辅助文字来查看实际以及原来的位置(S:Start Position,R:Real Position)
*
* @param canvas
* @param point
*/
private void drawText(Canvas canvas, Point point) {
paint.setColor(textColor);
canvas.drawText("S: " + String.valueOf(point.startIndex), circleX, circleY - 10, paint);
canvas.drawText("R: " + String.valueOf(point.realIndex), circleX, circleY + 40, paint);
}
/**
* 手势为ACTION_MOVE的时候,及时更新被Move的Point的位置
*/
private void updateMovePointPos() {
if (touchPoint.startIndex >= 0) { //已经处于拖动中了
int touchIndex = touchPoint.startIndex;
Point point = circlePointList.get(touchIndex);
updatePointListRealItem(point, touchIndex, point.realIndex);
touchPoint = new Point(point.endX, point.endY, point.realIndex, point.startIndex, true);
postInvalidate();
return;
}
for (int i = 0; i < circlePointList.size(); i++) {
Point point = circlePointList.get(i);
if (point.realIndex < 0) //只有原来状态是有圆圈的时候,才允许交换
continue;
if (isCircleRange(point, false)) {
//只有上一个圆圈结束了非正方形内的Move周期,才允许新的圆圈移动
if (touchPoint.realIndex >= 0 && touchPoint.realIndex != point.realIndex)
continue;
updatePointListRealItem(point, i, point.realIndex);
touchPoint = new Point(point.endX, point.endY, point.realIndex, point.startIndex, true);
postInvalidate();
break;
}
}
}
/**
* 手势为ACTION_UP的时候,更新被UP的Point的位置(需要检查是否是靠近Square的四周,如果是,则直接更新Point位置为固定的Square)
*/
private void updateUpPointPos() {
if (!circlePointList.isEmpty() && touchPoint.startIndex >= 0) {
Point point, point2, point3;
for (int i = 0; i < finalSquarePointList.size(); i++) {
point = finalSquarePointList.get(i);
if (isCircleRange(point, true)) { //已经靠近Rect区域周围
circleX = point.endX;
circleY = point.endY;
int currentIndex = touchPoint.startIndex;
for (int j = 0; j < circlePointList.size(); j++) {
point2 = circlePointList.get(j);
if (point2.realIndex == i && touchPoint.realIndex >= 0) {
point3 = finalSquarePointList.get(touchPoint.realIndex); //这个是point1变更前的list内的index的值
point2.endX = point3.endX;
point2.endY = point3.endY;
point2.realIndex = touchPoint.realIndex;
circlePointList.set(j, point2);
break;
}
}
updatePointListRealItem(circlePointList.get(currentIndex), currentIndex, i); //更新实际位于list位置的Point
touchPoint.realIndex = -1;
postInvalidate();
break;
}
}
}
}
/**
* Point是否处于可被拖动
* @param point
* @param isFinal
* @return
*/
public boolean isCircleRange(Point point, boolean isFinal) {
int range = isFinal ? radiusCircle2 : radiusCircle2 / 2;//radiusCircle2 / 2
return point.endX - range < circleX && circleX < point.endX + range &&
point.endY - range < circleY && circleY < point.endY + range;
}
/**
* 更新Point的位置
*
* @param point 要更新的Point
* @param index 实际list的位置
* @param realIndex 在view的显示的位置,在手指抬起的时候可能会有变更
*/
private void updatePointListRealItem(Point point, int index, int realIndex) {
point.endX = circleX;
point.endY = circleY;
point.realIndex = realIndex;
circlePointList.set(index, point);
}
}
Point.java
public class Point {
public float endY;
public float endX;
public int realIndex = -1; //实际的index位置
public int startIndex = -1; //初始的index的位置
public boolean isEmpty = true;
public Point(float endX, float endY) {
this(endX, endY, -1, -1, true);
}
public Point(float endX, float endY, int realIndex, int startIndex, boolean isEmpty) {
this.endY = endY;
this.endX = endX;
this.realIndex = realIndex;
this.startIndex = startIndex;
this.isEmpty = isEmpty;
}
@Override
public String toString() {
return "Point{" +
", realIndex=" + realIndex +
", startIndex=" + startIndex +
'}';
}
}
补充
一个扩展效果
实现方式 与上述代码类似
最后
Github 项目地址(PS:暂时未传上去,等我夜晚传上去吧。)
有不对的地方欢迎留言指正,不胜感激.。