一、寫在前面的話
也不知道給這個取個啥名,就隨便取了一個,主要功能就如上圖顯示的那樣,待選區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的鏈接了,就這樣。