前言
這個是年前就寫好的demo,現在是整理後放了上來,至於我爲什麼要寫這個,因爲要寫別的了:)
標題的描述可能不夠準確,所以具體的樣式還是看效果圖。
關於這個效果其實是可以擴展成我想玩的一個遊戲的,類似淘寶裏的貓咪咖啡館,等我週日補上來。
效果圖
思路
- 有兩個list,一個位置固定的,另一個位置不固定的(即手指滑動哪一個就更改哪一個的座標)。
- 在手勢的ACTION_MOVE的時候,位置是及時更新的,但是不允許越過手機屏幕的邊界。
- 在手勢爲ACTION_UP的時候,需要判斷是否是靠近了最近的一個Square,如果是,直接讓這個item進入到Square裏。如果該Square內的item不爲空,則交換兩個item的位置。
- 尺寸相關的適配。
代碼
ExchangeCirclesView.java
public class ExchangeCirclesView extends SurfaceView {
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:暫時未傳上去,等我夜晚傳上去吧。)
有不對的地方歡迎留言指正,不勝感激.。