自定義View之九宮格Item位置自由交換

前言

這個是年前就寫好的demo,現在是整理後放了上來,至於我爲什麼要寫這個,因爲要寫別的了:)
標題的描述可能不夠準確,所以具體的樣式還是看效果圖。
關於這個效果其實是可以擴展成我想玩的一個遊戲的,類似淘寶裏的貓咪咖啡館,等我週日補上來。

效果圖

image

思路

  1. 有兩個list,一個位置固定的,另一個位置不固定的(即手指滑動哪一個就更改哪一個的座標)。
  2. 在手勢的ACTION_MOVE的時候,位置是及時更新的,但是不允許越過手機屏幕的邊界。
  3. 在手勢爲ACTION_UP的時候,需要判斷是否是靠近了最近的一個Square,如果是,直接讓這個item進入到Square裏。如果該Square內的item不爲空,則交換兩個item的位置。
  4. 尺寸相關的適配。

代碼

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 +
                '}';
    }
}

補充

一個擴展效果

實現方式 與上述代碼類似
images

最後

Github 項目地址(PS:暫時未傳上去,等我夜晚傳上去吧。)
有不對的地方歡迎留言指正,不勝感激.。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章