关闭

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

标签: androidLauncher4.4源码Launcher3
1365人阅读 评论(0) 收藏 举报
分类:

一、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源码的.


1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:114983次
    • 积分:1552
    • 等级:
    • 排名:千里之外
    • 原创:43篇
    • 转载:0篇
    • 译文:0篇
    • 评论:129条
    博客专栏
    最新评论