一、DragLayer佈局
上一篇文章分析過Launcher的佈局,它是最外層的佈局
<!-- Full screen view projects under the status bar and contains the background -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
android:id="@+id/launcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/workspace_bg" >
<com.android.launcher3.DragLayer
android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- The workspace contains 5 screens of cells -->
它繼承自FrameLayout,它對應的類是DragLayer.
二、DragLayer代碼分析
官方解釋爲:A ViewGroup that coordinates dragging across its descendants.意思是說它是一個協調處理它的子view拖動的容器.那麼很明顯,我們需要關注它對屏幕觸摸事件的處理.它有三個方法onTouchEvent,onInterceptTouchEvent和onInterceptHoverEvent.在分析這三個方法之前,先看下DragLayer的構造函數,它重寫了FrameLayout的一些屬性.
public DragLayer(Context context, AttributeSet attrs) {
super(context, attrs);
// 禁止多點觸控
setMotionEventSplittingEnabled(false);
// 按指定順序渲染子控件
setChildrenDrawingOrderEnabled(true);
// 偵聽子view的add和remove事件
setOnHierarchyChangeListener(this);
//拖動圖標到屏幕左邊緣的圖片
mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo);
//拖動圖標到屏幕右邊緣的圖片
mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo);
}
我已經在代碼寫上了註釋,mLeftHoverDrawable是拖動桌面上的圖標到屏幕左側邊緣時顯示的圖片,默認的圖片實現的是高亮的效果,這裏我把它替換爲手指的圖片,便於觀看效果,如下圖
不瞭解android屏幕事件傳遞機制的可以先去了解下.接下來講上面提到的三個方法.
當點擊屏幕時,會先進入onInterceptTouchEvent方法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN) {
if (handleTouchDown(ev, true)) {
return true;
}
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
if (mTouchCompleteListener != null) {
mTouchCompleteListener.onTouchComplete();
}
mTouchCompleteListener = null;
}
clearAllResizeFrames();
return mDragController.onInterceptTouchEvent(ev);
}
先判斷action是不是down,點擊事件必然會執行down,那麼繼續判斷handleTouchDown(ev, true)是否爲true,爲true則攔截,這個方法是當正在縮放插件、打開文件夾、編輯文件夾名稱的時候返回true.
private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
Rect hitRect = new Rect();
int x = (int) ev.getX();
int y = (int) ev.getY();
for (AppWidgetResizeFrame child: mResizeFrames) {
// 獲得widget縮放框佔據的矩形區域的矩形座標
child.getHitRect(hitRect);
// 動作如果碰到了子控件。檢查如果座標處於縮放框可以對widget進行縮放操作的區域中,則禁止父控件再響應觸控事件
if (hitRect.contains(x, y)) {
if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
mCurrentResizeFrame = child;
mXDown = x;
mYDown = y;
requestDisallowInterceptTouchEvent(true);
return true;
}
}
}
// 獲取當前打開的文件夾。
Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
// 如果點擊的是文件夾內區域並且正處於重命名狀態,則取消重命名
if (currentFolder.isEditingName()) {
if (!isEventOverFolderTextRegion(currentFolder, ev)) {
currentFolder.dismissEditingName();
return true;
}
}
getDescendantRectRelativeToSelf(currentFolder, hitRect);
// 如果文件夾正在打開狀態。那麼如果點擊的是文件夾以外區域則關閉文件夾。
if (!isEventOverFolder(currentFolder, ev)) {
mLauncher.closeFolder();
return true;
}
}
return false;
}
判斷觸控事件是否經過了文件夾圖標的文字區域的方法
private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
return true;
}
return false;
}
判斷觸控事件是否經過了文件夾圖標區域的方法
private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
getDescendantRectRelativeToSelf(folder, mHitRect);
if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
return true;
}
return false;
}
你們可以在裏面加上打印或者Toast驗證下,想出現文件夾,就把一個圖標移動到另一個圖標上面,自動出現文件夾.
在down下之後,如果handleTouchDown(ev, true)返回爲false,則進入到clearAllResizeFrames方法,這個方法是清除widget的所有縮放框,
widget不是可以調整大小嗎,當點擊下去的時候就重置widget大小,該方法如下
/**
* 清除widget的所有縮放框
*/
public void clearAllResizeFrames() {
if (mResizeFrames.size() > 0) {
for (AppWidgetResizeFrame frame: mResizeFrames) {
frame.commitResize();
removeView(frame);
}
mResizeFrames.clear();
}
}
執行完這個方法後會返回DragController的onInterceptTouchEvent值
return mDragController.onInterceptTouchEvent(ev);
如果你點擊查看會發現,如果正在拖動圖標則返回true,否則返回false.當你點擊下圖標就鬆開,那麼只執行onInterceptTouchEvent方法.當你點擊圖標之後拖動,那麼接下來會執行onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = false;
int action = ev.getAction();
int x = (int) ev.getX();
int y = (int) ev.getY();
if (action == MotionEvent.ACTION_DOWN) {
if (handleTouchDown(ev, false)) {
return true;
}
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
System.out.println("307");
if (mTouchCompleteListener != null) {
mTouchCompleteListener.onTouchComplete();
}
mTouchCompleteListener = null;
}
if (mCurrentResizeFrame != null) {
handled = true;
switch (action) {
case MotionEvent.ACTION_MOVE:
mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
mCurrentResizeFrame.onTouchUp();
mCurrentResizeFrame = null;
}
}
// 如果沒有正在調節widget大小也沒有打開文件夾,則進入正題,開始拖動
if (handled) return true;
return mDragController.onTouchEvent(ev);
}
它會調用DragController的onTouchEvent方法.交給子view處理.
最後是這個onInterceptHoverEvent方法,意思是如果沒有正在縮放插件、沒有打開文件夾、沒有編輯文件夾名稱。 DragLayer將處理觸控動作,
並取消一切widget的Resize動作(如果有)
@Override
public boolean onInterceptHoverEvent(MotionEvent ev) {
if (mLauncher == null || mLauncher.getWorkspace() == null) {
return false;
}
Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
if (currentFolder == null) {
return false;
} else {
AccessibilityManager accessibilityManager = (AccessibilityManager)
getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
if (accessibilityManager.isTouchExplorationEnabled()) {
final int action = ev.getAction();
boolean isOverFolder;
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
isOverFolder = isEventOverFolder(currentFolder, ev);
if (!isOverFolder) {
sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
mHoverPointClosesFolder = true;
return true;
} else if (isOverFolder) {
mHoverPointClosesFolder = false;
} else {
return true;
}
case MotionEvent.ACTION_HOVER_MOVE:
isOverFolder = isEventOverFolder(currentFolder, ev);
if (!isOverFolder && !mHoverPointClosesFolder) {
sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
mHoverPointClosesFolder = true;
return true;
} else if (isOverFolder) {
mHoverPointClosesFolder = false;
} else {
return true;
}
}
}
}
return false;
}
好了,關於DragLayer的介紹就這麼多了,關於其它的一些細節,我會在後面上傳註釋後的Launcher源碼的.