一、DragScroller
DragScroller是一個接口,顧名思義是控制滑動的接口,它定義了4個方法
public interface DragScroller {
/**
* 左滑
*/
void scrollLeft();
/**
* 右滑
*/
void scrollRight();
/**
*進入滑動區域
*/
boolean onEnterScrollArea(int x, int y, int direction);
/**
*退出滑動區域
*/
boolean onExitScrollArea();
}
想具體分析它還是要結合WorkSpace和DragController,二、DragController
DragController是一個類,不繼承任何父類,內部提供了一個接口DragListener
interface DragListener {
void onDragStart(DragSource source, Object info, int dragAction);
void onDragEnd();
}
onDragStart————拖動圖標開始時執行的方法
onDragEnd———— 拖動圖標結束時執行的方法
DragController是處理拖動圖標的類,那麼我們自然得關注它的觸摸事件.拖動圖標的流程是這樣的
Launcher和WorkSpace後面會詳解,大家目前只要知道這個流程即可.Launcher的onLongClick事件會調用WorkSpace的startDrag方法,startDrag方法會調用beginDragShared方法, beginDragShared方法最終調用DragController的startDrag方法.好,我們先看下這個startDrag方法.
/**
* @param b 拖動對象的圖像,有可能會被縮放
* @param dragLayerX 拖動對象的圖像x座標
* @param dragLayerY 拖動對象的圖像y座標
* @param source 發起拖動的對象
* @param dragInfo 被拖動對象的數據
* @param dragAction 拖放的動作:移動或者複製
* @param dragRegion 拖動對象bitmap的區域
*/
public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
float initialDragViewScale) {
if (PROFILE_DRAWING_DURING_DRAG) {
android.os.Debug.startMethodTracing("Launcher");
}
// 隱藏軟鍵盤
if(mInputMethodManager == null) {
mInputMethodManager = (InputMethodManager)
mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
}
mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
for (DragListener listener : mListeners) {
listener.onDragStart(source, dragInfo, dragAction);
}
//記住手指點擊位置與屏幕左上角位置偏差
final int registrationX = mMotionDownX - dragLayerX;
final int registrationY = mMotionDownY - dragLayerY;
final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
mDragging = true;
//創建DragObject對象
mDragObject = new DropTarget.DragObject();
mDragObject.dragComplete = false;
mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
mDragObject.dragSource = source;
mDragObject.dragInfo = dragInfo;
//創建DragView對象
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
if (dragOffset != null) {
dragView.setDragVisualizeOffset(new Point(dragOffset));
}
if (dragRegion != null) {
dragView.setDragRegion(new Rect(dragRegion));
}
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
//顯示Dragview對象
dragView.show(mMotionDownX, mMotionDownY);
handleMoveEvent(mMotionDownX, mMotionDownY);
}
事實上還有一個startDrag的重載方法,不過我不知道它是什麼時候執行的,這個重載方法最終還是進入到圖中的startDrag方法.
根據傳入的一些參數創建了一個DragView,DragView就是我們拖動圖標的時候顯示的圖片,爲了驗證一下,在這裏我做一個修改
Bitmap mbBitmap=BitmapFactory.decodeResource(mLauncher.getResources(), R.drawable.hand);
//創建DragView對象
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, mbBitmap, registrationX,
registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
我傳入了一張手指的Bitmap,其他參數不變,我們看下效果.
最後一行代碼調用了handleMoveEventd方法,它是處理移動圖標的方法
private void handleMoveEvent(int x, int y) {
//跟隨手指移動dragview
mDragObject.dragView.move(x, y);
final int[] coordinates = mCoordinatesTemp;
// 根據手指所在屏幕座標獲取目前所在的拖放view的容器
DropTarget dropTarget = findDropTarget(x, y, coordinates);
mDragObject.x = coordinates[0];
mDragObject.y = coordinates[1];
checkTouchMove(dropTarget);
// Check if we are hovering over the scroll areas
mDistanceSinceScroll +=Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
mLastTouch[0] = x;
mLastTouch[1] = y;
checkScrollState(x, y);
}
根據傳入的座標會調用mDragObject.dragView.move方法移動dragview,然後獲取Dragview當前處於的容器DropTarget,然後調用checkTouchMove方法 private void checkTouchMove(DropTarget dropTarget) {
if (dropTarget != null) {
// 如果還沒有設置dropTarget或者dropTarget已經發生了改變,則設置dropTarget,調用onDragEnter
if (mLastDropTarget != dropTarget) {
if (mLastDropTarget != null) {
//退出這個DropTarget
mLastDropTarget.onDragExit(mDragObject);
}
dropTarget.onDragEnter(mDragObject);
}
dropTarget.onDragOver(mDragObject);
} else {
// 如果沒有dropTarget並且上次處理的mLastDropTarget不爲空則mLastDropTarget調用移出事件
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragObject);
}
}
mLastDropTarget = dropTarget;
}
如果傳入的容器DropTarget不爲空,那麼如果當前的容器DropTarget與上一次的DropTarget不同並且上一次的DropTarget不爲空,則退出上一次的DropTarget,進入當前的DropTarget.
如果傳入的容器DropTarget爲空,上一次的DropTarget不爲空,那麼上一次的DropTarget移除當前DragObject
最後把mLastDropTarge重新t賦值爲當前的dropTarget.
好了,checkTouchMove方法分析完了,回到handleMoveEventd方法往下看,會繼續調用checkScrollState方法.
private void checkScrollState(int x, int y) {
final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
final DragLayer dragLayer = mLauncher.getDragLayer();
//是否從右到左
final boolean isRtl = (dragLayer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
final int forwardDirection = isRtl ? SCROLL_RIGHT : SCROLL_LEFT;
final int backwardsDirection = isRtl ? SCROLL_LEFT : SCROLL_RIGHT;
//如果從左往右拖動到滾動區域
if (x < mScrollZone) {
// 進入可滾動區域後仍然要等待一會兒以後再滾動。
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
Toast.makeText(mLauncher, "向左邊滑動", 0).show();
dragLayer.onEnterScrollArea(forwardDirection);
mScrollRunnable.setDirection(forwardDirection);
mHandler.postDelayed(mScrollRunnable, delay);
}
}
}
//如果從右往左拖動到滾動區域
else if (x > mScrollView.getWidth() - mScrollZone) {
System.out.println("...................區域外");
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
Toast.makeText(mLauncher, "向右邊滑動", 0).show();
dragLayer.onEnterScrollArea(backwardsDirection);
mScrollRunnable.setDirection(backwardsDirection);
mHandler.postDelayed(mScrollRunnable, delay);
}
}
}
// 在滾動之前進入了不滾動區域,則取消滾動
else {
clearScrollRunnable();
}
}
ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop()是獲取可以觸發移動事件的最短距離,賦值給slop.mDistanceSinceScroll是上一次手指移動的距離,如果mDistanceSinceScroll小於slop,那麼delay取值RESCROLL_DELAY,默認是900,否則取SCROLL_DELAY,默認值爲500.
isRtl是判斷是否從右到左移動,is Right To Left的意思.然後根據isRtl的值取forwardDirection和backwardsDirection,這兩個是往前和往後的標誌.
如果x座標小於mScrollZone,說明是從左到右拖動圖標,那麼進行滑動到右邊屏幕的操作
如果x座標大於當前屏幕寬度和可滑動區域的差值,說明是從右到左拖動圖標,那麼進行滑動到左邊屏幕的操作
否則取消滾動.
checkTouchMove方法執行完後,會執行onTouchEvent方法,我覺得奇怪的是onTouchEvent的down事件無法觸發,不知道什麼原因.
move事件裏又調用了handleMoveEvent方法.
up事件裏就停止拖動,做一些恢復操作.
cancel事件裏清除handler消息隊列,取消拖動
public boolean onTouchEvent(MotionEvent ev) {
if (!mDragging) {
return false;
}
// Update the velocity tracker
acquireVelocityTrackerAndAddMovement(ev);
final int action = ev.getAction();
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
final int dragLayerX = dragLayerPos[0];
final int dragLayerY = dragLayerPos[1];
switch (action) {
case MotionEvent.ACTION_DOWN:
// Remember where the motion event started
mMotionDownX = dragLayerX;
mMotionDownY = dragLayerY;
if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
mScrollState = SCROLL_WAITING_IN_ZONE;
mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
} else {
mScrollState = SCROLL_OUTSIDE_ZONE;
}
handleMoveEvent(dragLayerX, dragLayerY);
break;
case MotionEvent.ACTION_MOVE:
handleMoveEvent(dragLayerX, dragLayerY);
break;
case MotionEvent.ACTION_UP:
// 確保我們在擡起點處理了事件
handleMoveEvent(dragLayerX, dragLayerY);
mHandler.removeCallbacks(mScrollRunnable);
if (mDragging) {
PointF vec = isFlingingToDelete(mDragObject.dragSource);
if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {
vec = null;
}
if (vec != null) {
dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
} else {
drop(dragLayerX, dragLayerY);
}
}
endDrag();
break;
case MotionEvent.ACTION_CANCEL:
mHandler.removeCallbacks(mScrollRunnable);
cancelDrag();
break;
}
return true;
}
DragController的分析就到此爲止了.