您的位置:首页 > 移动开发 > Android开发

Android4.4-Launcher源码分析系列之关键的类和接口之DragLayer

2016-05-31 19:36 465 查看
一、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源码的.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息