一个可拖动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的链接了,就这样。

发布了14 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qwe749082787/article/details/104005508