一個可拖動item的九宮格自定義View

 

一、寫在前面的話

也不知道給這個取個啥名,就隨便取了一個,主要功能就如上圖顯示的那樣,待選區view可以拖拽進九宮格中,九宮格可以按照順時針輪播。

二、分析需要的元素

1.待選區的item

選中底色有改變,item可以拖動到九宮格中,在即將進入九宮格的時候有一個吸附的效果。

2.九宮格

點擊中間的開始按鈕順時針輪播九宮格

3.拖動的View

在底部待選欄中的item選中的時候繪製,模擬從待選欄拖入九宮格的效果。

三、動畫效果

1、拖動的view(思路)

在手指觸碰到備選欄中的item一定時間後(500ms左右),在手指的位置繪製一個選中的item並且跟隨手指的位置移動。九宮格的輪播位置,在拖動的view進入到其範圍(手指位置的座標與九宮格item中心的直線距離小於九宮格item的內切圓半徑)之後,在九宮格中繪製一個拖動的item,手指的位置取消繪製,模擬吸附效果。

2、九宮格的輪播動畫

九宮格對於的位置爲

123
456
789

輪播的時候順序爲12369874,在對應的位置顏色變爲高亮色,再在該位置畫上一個稍小的,圓角一致的矩形。(時至今日我都覺得這個方式有點蠢,等後面再優化了)

四、開始畫(繪製)

1、準備工作

首先準備三個List去存放九宮格中item的位置,待選欄view的位置,待選欄view中的圖片的位置。


    private List<RectF> mItemRectFList;

    private List<RectF> mSmallRectFList;

    private List<Bitmap> mBitmapList;
 private void initItemRectf() {
        if (mItemRectFList == null) {
            mItemRectFList = new ArrayList<>();
        }
        mItemRectFList.clear();
        int fristX = (int) (mWidth * 0.2);
        //第一排
        mItemRectFList.add(new RectF(fristX + mItemRectMagin, top + mItemRectMagin,
                fristX + mItemRectMagin + mItemRectA, top + mItemRectMagin + mItemRectA));

        mItemRectFList.add(new RectF(fristX + 2 * mItemRectMagin + mItemRectA, top + mItemRectMagin,
                fristX + 2 * mItemRectMagin + 2 * mItemRectA, top + mItemRectMagin + mItemRectA));

        mItemRectFList.add(new RectF(fristX + 3 * mItemRectMagin + 2 * mItemRectA, top + mItemRectMagin,
                fristX + 3 * mItemRectMagin + 3 * mItemRectA, top + mItemRectMagin + mItemRectA));
        //第二排
        mItemRectFList.add(new RectF(fristX + mItemRectMagin, top + 2 * mItemRectMagin + mItemRectA,
                fristX + mItemRectMagin + mItemRectA, top + 2 * mItemRectMagin + 2 * mItemRectA));
        mItemRectFList.add(new RectF(fristX + 2 * mItemRectMagin + mItemRectA, top + 2 * mItemRectMagin + mItemRectA,
                fristX + 2 * mItemRectMagin + 2 * mItemRectA, top + 2 * mItemRectMagin + 2 * mItemRectA));
        mItemRectFList.add(new RectF(fristX + 3 * mItemRectMagin + 2 * mItemRectA, top + 2 * mItemRectMagin + mItemRectA,
                fristX + 3 * mItemRectMagin + 3 * mItemRectA, top + 2 * mItemRectMagin + 2 * mItemRectA));
        //第三排
        mItemRectFList.add(new RectF(fristX + mItemRectMagin, top + mItemRectMagin * 3 + mItemRectA * 2,
                fristX + mItemRectMagin + mItemRectA, top + mItemRectMagin * 3 + mItemRectA * 3));

        mItemRectFList.add(new RectF(fristX + 2 * mItemRectMagin + mItemRectA, top + mItemRectMagin * 3 + mItemRectA * 2,
                fristX + 2 * mItemRectMagin + 2 * mItemRectA, top + mItemRectMagin * 3 + mItemRectA * 3));

        mItemRectFList.add(new RectF(fristX + 3 * mItemRectMagin + 2 * mItemRectA, top + mItemRectMagin * 3 + mItemRectA * 2,
                fristX + 3 * mItemRectMagin + 3 * mItemRectA, top + mItemRectMagin * 3 + mItemRectA * 3));

    }

對九宮格item的位置、大小等信息進行初始化

private void initSmallRectf() {
        if (mSmallRectFList == null) {
            mSmallRectFList = new ArrayList<>();
        }
        mSmallRectFList.clear();
        for (int i = 0; i < 8; i++) {
                if (i<5) {
                    mSmallRectFList.add(new RectF((i + 1) * mSmallRectMagin +  mSmallRectA / 2 + i * mSmallRectA, mSmallRectTop,
                            (i + 1) * mSmallRectMagin + (i + 1) * mSmallRectA +  mSmallRectA / 2, mSmallRectTop + mSmallRectA));
                }else {
                    mSmallRectFList.add(new RectF((i + 1) * mSmallRectMagin +  mSmallRectA*3/2  + i * mSmallRectA, mSmallRectTop,
                            (i + 1) * mSmallRectMagin + (i + 1) * mSmallRectA +  mSmallRectA*3/2 , mSmallRectTop + mSmallRectA));
                }
            }

    }

待選欄的item的位置、大小進行初始化。這裏我一排顯示5個,所以對大於5位置上進行了一些許的微調。

        Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.mipmap.food1);
...

圖片的初始化。

private void initPath() {
        int centerX =  mSmallRectMagin;
        int centerY = (int) mSmallRectFList.get(0).centerY();
        mLeftPath = new Path();
        mLeftPath.moveTo(centerX+20,centerY+20);
        mLeftPath.lineTo(centerX,centerY);
        mLeftPath.lineTo(centerX+20,centerY-20);
        mLeftPath.close();

        mRightPath = new Path();
        mRightPath.moveTo(mWidth-mSmallRectMagin-20,centerY+20);
        mRightPath.lineTo(mWidth-mSmallRectMagin,centerY);
        mRightPath.lineTo(mWidth-mSmallRectMagin-20,centerY-20);
        mRightPath.close();

    }

待選欄的兩個小三角,用path畫一下,找不到適合的圖片了。

2、繪製(onDraw)

下面的順序就是在onDraw中的繪製順序。

for (int i = 0; i < mItemRectFList.size(); i++) {
            if (i == 4) {
                canvas.drawRoundRect(mItemRectFList.get(i), 20, 20, mRectFItemSelectPaint);
                //畫圖片
                if (isPaly) {
                    //畫圖片
                    canvas.drawBitmap(mBitmapStop, mSrcPlay, mdesPlay, mTextPaint);
                } else {
                    canvas.drawBitmap(mBitmapStart, mSrcPlay, mdesPlay, mTextPaint);

                }

            } else {
                canvas.drawRoundRect(mItemRectFList.get(i), 20, 20, mRectFItemPaint);
            }

        }

畫九宮格,位置4上爲九宮格的中間那格,根據輪播狀態畫上不同的圖片。

for (int i = 0; i < mSmallRectFList.size(); i++) {
            canvas.drawCircle(mSmallRectFList.get(i).centerX() + offset, mSmallRectFList.get(i).centerY(), mSmallRectA / 2, mRectFItemPaint);
            //畫圖片
            Rect des = new Rect((int) mSmallRectFList.get(i).left + offset + mSmallRectA / 4, (int) mSmallRectFList.get(i).top + mSmallRectA / 4
                    , (int) mSmallRectFList.get(i).right + offset - mSmallRectA / 4, (int) mSmallRectFList.get(i).bottom - mSmallRectA / 4);
            canvas.drawBitmap(mBitmapList.get(i), mSrcItem, des, mRectFItemPaint);
            des = null;
            //畫text
            canvas.drawText(modeTextArray[i], mSmallRectFList.get(i).centerX() + offset - mTextSize, (float) (mSmallRectFList.get(i).bottom + 1.5 * mTextSize), mTextPaint);
            if (mDragIndex != -1 && mDragIndex == i && isDrag) {
                mRectFItemSelectPaint.setAlpha(128);
                canvas.drawCircle(mSmallRectFList.get(i).centerX() + offset, mSmallRectFList.get(i).centerY(), mSmallRectA / 2, mRectFItemSelectPaint);
                //畫圖片
                Rect desAlpha = new Rect((int) mSmallRectFList.get(i).left + offset + mSmallRectA / 4, (int) mSmallRectFList.get(i).top + mSmallRectA / 4
                        , (int) mSmallRectFList.get(i).right + offset - mSmallRectA / 4, (int) mSmallRectFList.get(i).bottom - mSmallRectA / 4);
                canvas.drawBitmap(mBitmapList.get(i), mSrcItem, desAlpha, mRectFItemPaint);
                mRectFItemSelectPaint.setAlpha(255);

            }
        }
 //畫箭頭
        if (offset < 0) {
            canvas.drawPath(mLeftPath, mTitlePaint);
            canvas.drawPath(mRightPath, mTextPaint);

        }else {
            canvas.drawPath(mLeftPath, mTextPaint);
            canvas.drawPath(mRightPath, mTitlePaint);

        }

待選欄的繪製,因爲有不同的兩頁,所以小箭頭的顏色有些許的區別。

 //畫選中效果
        canvas.drawRoundRect(mItemRectFList.get(selectIndex), 20, 20, mRectFSelectPaint);
        for (int z = 0; z < seletIndexArray.length; z++) {
            if (seletIndexArray[z] != -1) {
                canvas.drawCircle(mItemRectFList.get(z).centerX(), mItemRectFList.get(z).centerY(), (float) (mItemRectA / 2.2), mRectFItemSelectPaint);
                Rect des = new Rect((int) mItemRectFList.get(z).left + mItemRectA / 4, (int) mItemRectFList.get(z).top + mItemRectA / 4
                        , (int) mItemRectFList.get(z).right - mItemRectA / 4, (int) mItemRectFList.get(z).bottom - mItemRectA / 4);
                canvas.drawBitmap(mBitmapList.get(seletIndexArray[z]), mSrcItem, des, mRectFItemSelectPaint);
            }
        }

繪製選中效果以及從待選欄拖入的item。

   //畫拖動中的view
        if (isDrag && mDragX != 0) {
            canvas.drawCircle(mDragX, mDragY, (float) (mItemRectA / 2.2), mRectFItemSelectPaint);
            matrix.setTranslate(mDragX - mBitmapList.get(mDragIndex).getWidth() / 2, mDragY - mBitmapList.get(mDragIndex).getHeight() / 2);
            Rect des = new Rect(mDragX - mBitmapList.get(mDragIndex).getWidth() / 2, mDragY - mBitmapList.get(mDragIndex).getHeight() / 2
                    , mDragX + mBitmapList.get(mDragIndex).getWidth() / 2, mDragY + mBitmapList.get(mDragIndex).getHeight() / 2);
            canvas.drawBitmap(mBitmapList.get(mDragIndex), mSrcItem, des, mRectFItemSelectPaint);
            des = null;
        }

繪製拖動中的item

五、觸摸事件處理

1、判斷觸摸的有效範圍

  //判斷點擊的是不是九宮格中的item
    private int checkItem(int x, int y) {
        int r = (int) ((mItemRectFList.get(0).right - mItemRectFList.get(0).left) / 2);
        for (int i = 0; i < mItemRectFList.size(); i++) {

            if (checkPoint(x, y, mItemRectFList.get(i).centerX(), mItemRectFList.get(i).centerY(), r)) {
                return i;
            }
        }
        return -1;
    }

判斷觸摸的是不是九宮格中的item

//判斷點擊的是不是待選欄的item
    private int checkSmallItem(int x, int y) {
        Log.d(TAG, "x=" + x + ",y=" + y);
        int r = (int) ((mSmallRectFList.get(0).right - mSmallRectFList.get(0).left) / 2);
        x = x - offset;
        Log.d(TAG, "x2=" + x);

        for (int i = 0; i < mSmallRectFList.size(); i++) {
            if (checkPoint(x, y, mSmallRectFList.get(i).centerX(), mSmallRectFList.get(i).centerY(), r)) {
                Log.d(TAG, "index=" + i);
                return i;
            }
        }

        return -1;
    }

判斷觸摸的是不是待選欄

 //計算兩點間的距離
    private boolean checkPoint(float x1, float y1, float x2, float y2, int r) {
        double c;
        double i = Math.pow((x1 - x2), 2.0);
        double j = Math.pow((y1 - y2), 2.0);
        c = Math.sqrt(i + j);
        return c < r;

    }

兩點間的距離公式

  public interface OnExpLoreViewEventListener {
        void onPlayIndexChange(int index);

        void onCenterClick();

    }

準備好監聽器

if (checkItem(x, y) != -1 && event.getAction() == MotionEvent.ACTION_UP && !isDrag && !isMove) {
            //點擊的是上面大正方形
            if (checkItem(x, y) == 4) {
                //點擊播放按鈕
                if (mEventListener != null) {
                    mEventListener.onCenterClick();
                }
            } else if (!isPaly) {
                selectIndex = checkItem(x, y);
                invalidate();
            }

        }

首先是九宮格的事件處理,如果點擊的是中間控制item則回調事件,如果是其餘的item則選中該item,這裏的邏輯在拖拽view和移動時不可用。

 

 switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mHandler.removeMessages(MSG_DRAG);
                mDragIndex = checkSmallItem(x, y);
                if (y > mSmallRectFList.get(0).top  && mDragIndex != -1) {

                    mHandler.sendEmptyMessageDelayed(MSG_DRAG, delayMillis);

                } else if (y < bgRectA + top && x > mWidth * 0.2 && x < mWidth * 0.8) {
                    int selectIndex = checkItem(x, y);
                    if (selectIndex != -1) {
                        Message m = Message.obtain();
                        m.what = MSG_DRAG;
                        m.obj = selectIndex;
                        mHandler.sendMessageDelayed(m, delayMillis);
                    }

                }

                if (y > mSmallRectFList.get(0).top && x < mSmallRectMagin +  mSmallRectA / 2){
                    offset = 0;
                    invalidate();
                }else if(y > mSmallRectFList.get(0).top && x > mWidth-(mSmallRectMagin +  mSmallRectA / 2)) {
                    offset = -mWidth+30;
                    invalidate();
                }


                break;
            case MotionEvent.ACTION_UP:
//                if (offset < 0 - (mSmallRectA * 3 + mSmallRectMagin * 3)) {
//                    offset = 0 - (mSmallRectA * 3 + mSmallRectMagin * 3);
//                } else if (offset > 0) {
//                    offset = 0;
//                }

                mHandler.removeMessages(MSG_DRAG);
                if (isDrag) {
                    if (y < bgRectA + top) {
                        int mSelectItem = checkItem(x, y);
                        if (mSelectItem != -1 && mSelectItem != 4) {
                            seletIndexArray[mSelectItem] = mDragIndex;
                        }
                    }
                }
                isDrag = false;
                mDragIndex = -1;
                mDragX = 0;
                mDragY = 0;
                isMove = false;

                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:

                mMomentX = x;
                mMomentY = y;
                if (isDrag) {
                    mDragX = x;
                    mDragY = y;
                    if (y < bgRectA + top) {
                        int mSelectItem = checkItem(x, y);
                        if (mSelectItem != -1 && mSelectItem != 4) {
                            mDragX = (int) mItemRectFList.get(mSelectItem).centerX();
                            mDragY = (int) mItemRectFList.get(mSelectItem).centerY();
                        }
                    }
                    invalidate();
                }else {
                    if (!isMove) {
                        mDragIndex = checkSmallItem(x, y);
                        if (y > mSmallRectFList.get(0).top && mDragIndex != -1) {

                            mHandler.sendEmptyMessageDelayed(MSG_DRAG, delayMillis);

                        }
                    }
                }

                break;
        }
  case MSG_DRAG:
                    //判斷出髮長按的點與當前手指所處座標的距離,在按鈕上才觸發長按
                    if (mDragIndex != -1 && checkPoint(mMomentX - offset, mMomentY, mSmallRectFList.get(mDragIndex).centerX(), mSmallRectFList.get(mDragIndex).centerY(), mSmallRectA / 2)) {
                        isDrag = true;
                        vibrator.vibrate(10);
                        invalidate();
                    }
                    if (msg.obj != null) {
                        int s = (int) msg.obj;
                        if (seletIndexArray[s] != -1) {
                            isDrag = true;
                            vibrator.vibrate(10);
                            mDragIndex = seletIndexArray[s];
                            seletIndexArray[s] = -1;
                            invalidate();
                        }
                    }

                    break;

這裏我們在DOWN事件中做處理,如果Y的值在待選欄的返回內,激活isDrag變量,進入拖拽模式。

在MOVE事件中,如果isDrag被激活,就根據手指的移動位置,進行view的刷新。

接着在up事件中根據點的位置去判斷是都需要在九宮格中添加item以及對一些控制變量進行重置,這樣便完成一次拖拽流程。

六、總結

總之,這種複雜一點的控件還是需要一邊做一邊優化。由於代碼寫的有點亂,整理後再貼github的鏈接了,就這樣。

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