Android Launcher浅析(一)

2023-05-16

Launcher桌面的一大功能就是支持左右滑动,这样的功能在现在的应用中使用非常广泛,并且有很多实现的方式,可以通过使用Fragment来实现也可以通过自定义的控件来实现。Launcher采用了后者,这一功能的实现在Workspace来完成。首先来看一下Workspace的继承关系:
继承关系

从图中可以看出Workspace是PagedView的子类,而实际上滑动功能的实现是在PagedView中实现的。在Launcher中,Workspace中有五个CellLayout,分别代表五个分屏。

当左右拖动CellLayout时,就能实现滑动的效果。但实际上CellLayout中还有其他的子View,PagedView是如何避免了来自子View的干扰的呢?这里就需要讨论另一个问题,Android对touch事件的拦截制度。而拦截发生在ViewGroup的onInterceptTouchEvent()和onTouchEvent()以及View的onTouchEvent中。当发生touch事件时,系统会产生一个MotionEvent并且沿着View Tree开始传递。首先获取MotionEvent是View Tree的根节点,根节点通常是一个ViewGroup,ViewGroup将在onInterceptTouchEvent()中获取MotionEvent并决定是否继续向下传递。当在ViewGroup.onInterceptEvent()中返回true时,将截获MotionEvent,View Tree下面的View将无法获得MotionEvent,转而交给当前ViewGroup的onTouchEvent()方法。如果onTouchEvent中返回false,那么MotionEvent将沿着View Tree向上传给上一层。拦截的过程草图如下:

事件拦截

有了touch事件的拦截机制之后,View tree中的各个层之间的分工也就更加明确了。在Launcher的View tree中,从上到下的主要的节点有,DragLayer,Workspace,CellLayout。DragLayer层的主要任务是负责对图标和AppWidget进行拖拽,Workspace则主要负责左右滑动,CellLayout则用于容纳各种桌面的快捷方式。大概的分工如下:

大概的分工

那么现在就进入PagedView中去了解实现的过程吧。
滑动功能主要分两步:1、在onInterceptTouchEvent中进行拦截。2、在onTouchEvent中进行滑动。
1,onInterceptTouchEvent(MotionEvent en)
在这个方法中,决定了什么时候截获MotionEvent来实现滑动,避免了子View的其他事件的影响(如点击事件)。


    public boolean onInterceptTouchEvent(MotionEvent ev) {  
         /**  
         * This method JUST determines whether we want to intercept the motion. 
         * If we return true, onTouchEvent will be called and we do the actual 
         * scrolling there. 
         **/  

        //获取速度跟踪器,记录各个时刻的速度。并且添加当前的MotionEvent以记录更行速度值。  
        acquireVelocityTrackerAndAddMovement(ev);  
        ......  
         /** 
         * Shortcut the most recurring case: the user is in the dragging 
         * state and he is moving his finger.  We want to intercept this 
         * motion. 
         * 最常见的需要拦截的情况:用户已经进入滑动状态,并且正在滑动手指。 
         * 对这种情况直接进行拦截,执行onTouchEvent()继续执行滑动操作。 
         **/  
        final int action = ev.getAction();  
        if ((action == MotionEvent.ACTION_MOVE) &&  
                (mTouchState == TOUCH_STATE_SCROLLING)) {  
            return true;  
        }  

        switch (action & MotionEvent.ACTION_MASK) {  
            case MotionEvent.ACTION_MOVE: {  

                /** 
                 *  mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 
                 * whether the user has moved far enough from his original down touch. 
                 */  
                /** 
                 * 当在这里接受到ACTION_MOVE时,说明mTouchState!=TOUCH_STATE_SCROLLING并且mIsBeingDragged的值应该为false, 
                 * 否则DragLayer就应该截获了MotionEvent用于实现拖拽。 
                 * 此时还没有进入滑动状态,当mActivePointerId == INVALID_POINTER时,也就是在此之前没有接收到任何touch事件。 
                 * 这种情况发生在Workspace变小时,也就是之前Workspace处于SPRING_LOADED状态。当出现这种情况时直接把当前的事件当作ACTION_DOWN进行处理。 
                 * 反之,则通过determineScrollingStart()尝试能够进入滑动状态。 
                 */  
                if (mActivePointerId != INVALID_POINTER) {  
                    determineScrollingStart(ev);  
                    break;  
                }  
                // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN  
                // event. in that case, treat the first occurence of a move event as a ACTION_DOWN  
                // i.e. fall through to the next case (don't break)  
                // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events  
                // while it's small- this was causing a crash before we checked for INVALID_POINTER)  
            }  

            case MotionEvent.ACTION_DOWN: {  
                final float x = ev.getX();  
                final float y = ev.getY();  
                // Remember location of down touch  
                //记录按下的x的坐标值  
                mDownMotionX = x;  
                //记录前次发生touch时的坐标  
                mLastMotionX = x;  
                mLastMotionY = y;  
                //因为在ScrollBy时只能使用int,而记录的x和y都是float,会产生误差,故这里用mLastMotionXRemainder记录余数  
                //用于消除误差  
                mLastMotionXRemainder = 0;  
                //x方向上的总位移  
                mTotalMotionX = 0;  
                mActivePointerId = ev.getPointerId(0);  

                //设置mAllowLongPress=true,允许LongClick事件发生。LongClick事件定义在Launcher中  
                //处理的内容包括启动对shortcut的拖拽或弹出壁纸选择的对话框,若mAllowLongPress=false,  
                //则不会响应以上事件。  
                mAllowLongPress = true;  

                /**  
                 * If being flinged and user touches the screen, initiate drag; 
                 * otherwise don't.  mScroller.isFinished should be false when 
                 * being flinged. 
                 * 当屏幕处于flinged状态(快速滑动)时,若此时用户触摸了屏幕,需要使滑动停止。 
                 * 并且初始化拖拽的条件 
                 **/  
                final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());  
                final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);  
                if (finishedScrolling) {  
                    mTouchState = TOUCH_STATE_REST;  
                    mScroller.abortAnimation();  
                } else {  
                    mTouchState = TOUCH_STATE_SCROLLING;  
                }  

                // check if this can be the beginning of a tap on the side of the pages  
                // to scroll the current page  
                if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {  
                    if (getChildCount() > 0) {  
                        if (hitsPreviousPage(x, y)) {  
                            mTouchState = TOUCH_STATE_PREV_PAGE;  
                        } else if (hitsNextPage(x, y)) {  
                            mTouchState = TOUCH_STATE_NEXT_PAGE;  
                        }  
                    }  
                }  
                break;  
            }  

            case MotionEvent.ACTION_UP:  
            case MotionEvent.ACTION_CANCEL:  
                mTouchState = TOUCH_STATE_REST;  
                mAllowLongPress = false;  
                mActivePointerId = INVALID_POINTER;  
                releaseVelocityTracker();  
                break;  
            case MotionEvent.ACTION_POINTER_UP:  
                onSecondaryPointerUp(ev);  
                releaseVelocityTracker();  
                break;  
        }  

        /**  
         * The only time we want to intercept motion events is if we are in the 
         * drag mode. 
         * 只有进入了滑动状态,才进行拦截,进入onTouchEvent执行滑动操作。当mTouchState != TOUCH_STATE_REST 
         * 时,就说明没有进入滑动状态。 
         **/  
        return mTouchState != TOUCH_STATE_REST;  
    }  

代码的主要工作是根据MotionEvent和当前的状态,在各个状态中切换mTouchState。其中一共有四个状态值,TOUCH_STATE_REST、TOUCH_STATE_SCROLLING、TOUCH_STATE_PREV_PAGE、TOUCH_STATE_NEXT_PAGE。状态之间的切换可以用如下的草图表示:

这里写图片描述

当mTouchState==TOUCH_STATE_REST时,不需要任何滑动操作,将MotionEvent向子View传递。这里,分析一种最简单的情况:初始桌面静止,手指按下,然后开始左右滑动。通过这种简单情况的分析,来帮助理解onInterceptTouceEvent的作用。开始桌面静止,则mTouchState==TOUCH_STATE_REST,触发switch分支中MotionEvent.ACTION_DOWN的代码。记录按下点的坐标,设置mAllowLongPress=true。由于mTouchState=TOUCH_STATE_REST,所以动作被传向了子View。接下来,在长按事件被触发之前移动手指则会在代码中调用determineScrollingStart()来决定是否进入滑动状态。进入滑动状态之后mTouchState的值就变为TOUCH_STATE_SCROLLING,然后onTouchEvent中的操作就会被用,开始滑动。

总结一下onInterceptTouchEvent()的工作就是:切换PagedView的状态,如果处于滑动状态就拦截。2,onTouchEvent(MotionEvent en)

在这个方法中,执行各种关于滑动的工作的计算,界面的刷新等工作。


    public boolean onTouchEvent(MotionEvent ev) {  
            ......  
            switch (action & MotionEvent.ACTION_MASK) {  
            case MotionEvent.ACTION_DOWN:  
                /* 
                 * If being flinged and user touches, stop the fling. isFinished 
                 * will be false if being flinged. 
                 */  
                /** 
                 * 如果Workspace此时已经被“掷出去”(靠惯性滑动)。 
                 * 此时发生ACTION_DOWN则需要停止滑动。 
                 */  
                if (!mScroller.isFinished()) {  
                    mScroller.abortAnimation();  
                }  

                // Remember where the motion event started  
                mDownMotionX = mLastMotionX = ev.getX();  
                mLastMotionXRemainder = 0;  
                mTotalMotionX = 0;  
                mActivePointerId = ev.getPointerId(0);  
                if (mTouchState == TOUCH_STATE_SCROLLING) {  
                    pageBeginMoving();  
                }  
                break;  

            case MotionEvent.ACTION_MOVE:  
                if (mTouchState == TOUCH_STATE_SCROLLING) {  
                    ......  
                    if (Math.abs(deltaX) >= 1.0f) {  
                        ......  
                        if (!mDeferScrollUpdate) {  
                            //调用scrollBy滑动桌面  
                            scrollBy((int) deltaX, 0);  
                            ......  
                        } else {  
                            ......  
                        }  
                        mLastMotionX = x;  
                        mLastMotionXRemainder = deltaX - (int) deltaX;  
                    } else {  
                        awakenScrollBars();  
                    }  
                } else {  
                    /** 
                     * 如果条件满足,则进入滑动状态,开始滑动。 
                     */  
                    determineScrollingStart(ev);  
                }  
                break;  
            case MotionEvent.ACTION_UP:  
                if (mTouchState == TOUCH_STATE_SCROLLING) {  
                    ......  
                    boolean isSignificantMove = Math.abs(deltaX) > MIN_LENGTH_FOR_MOVE;  

                    boolean returnToOriginalPage = false;  
                    final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));  
                    if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&  
                            Math.signum(velocityX) != Math.signum(deltaX)) {  
                        returnToOriginalPage = true;  
                    }  

                    //当速率超过snapVelocity或者总的移动距离超过MIN_LENGTH_FOR_FLING  
                    //则判定isFling=true  
                    boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&  
                            Math.abs(velocityX) > snapVelocity;  

                    int finalPage;  

                    //判断拿起手指之后应该进入哪个分屏  
                    if (((isSignificantMove && deltaX > 0 && !isFling) ||  
                            (isFling && velocityX > 0)) && mCurrentPage > 0) {  
                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;  
                        snapToPageWithVelocity(finalPage, velocityX);  
                    } else if (((isSignificantMove && deltaX < 0 && !isFling) ||  
                            (isFling && velocityX < 0)) &&  
                            mCurrentPage < getChildCount() - 1) {  
                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;  
                        snapToPageWithVelocity(finalPage, velocityX);  
                    } else {  
                        snapToDestination();  
                    }  
                } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {  
                    // at this point we have not moved beyond the touch slop  
                    // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so  
                    // we can just page  
                    //直接进入前一屏  
                    int nextPage = Math.max(0, mCurrentPage - 1);  
                    if (nextPage != mCurrentPage) {  
                        snapToPage(nextPage);  
                    } else {  
                        snapToDestination();  
                    }  
                } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {  
                    // at this point we have not moved beyond the touch slop  
                    // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so  
                    // we can just page  
                    //直接进入后一屏  
                    int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);  
                    if (nextPage != mCurrentPage) {  
                        snapToPage(nextPage);  
                    } else {  
                        snapToDestination();  
                    }  
                } else {  
                    onUnhandledTap(ev);  
                }  
                mTouchState = TOUCH_STATE_REST;  
                mActivePointerId = INVALID_POINTER;  
                releaseVelocityTracker();  
                break;  

            case MotionEvent.ACTION_CANCEL:  
                if (mTouchState == TOUCH_STATE_SCROLLING) {  
                    snapToDestination();  
                }  
                mTouchState = TOUCH_STATE_REST;  
                mActivePointerId = INVALID_POINTER;  
                releaseVelocityTracker();  
                break;  
            case MotionEvent.ACTION_POINTER_UP:  
                onSecondaryPointerUp(ev);  
                break;  
            }  
            return true;  
        }  
在onTouchEvent中,对接受到的不同的事件进行了分类的处理,大致可以将功能分类为:1、当接受到ACTION_DOWN时,若滑动正在进行,则停止。

2、当接受到ACTION_MOVE时,根据当前的状态调用scrollBy进行滑动或则调用determineScrollingStart准备开始滑动。

3、当接受到ACTION_UP时,根据当前所滑动的位移和速度,判断松手后进入到哪一个分屏。

到这里,Workspace滑动过程的基本流程就介绍完毕了。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Launcher浅析(一) 的相关文章

随机推荐

  • 算法之路(四)----汉诺塔(又称河内之塔)

    汉诺塔是很简单也很经典的算法之一 汉诺塔是根据一个传说形成的数学问题 xff1a 有三根杆子A xff0c B xff0c C A杆上有N个 N gt 1 穿孔圆盘 xff0c 盘的尺寸由下到上依次变小 要求按下列规则将所有圆盘移至C杆 x
  • 相位一致性——利用频域检测边缘

    相位一致性 利用频域检测边缘 一 相位一致性提出的背景 相位一致性的提出是基于科学家发现了人感觉图像的关键不在于图像的长度差或者高度差之类的因素 xff0c 关键在于图像信号的相位大小 xff0c 例如人知道一个方波的边缘 xff0c 并不
  • 图像分割——掩膜法

    电路板是用掩膜法制作而成的 xff0c 现在电路板表面涂上一层抗腐蚀的材料 xff0c 然后再进行处理 xff0c 最后洗去材料就得到了电路 掩膜法在图像处理中的应用 xff1a 可用于分割图像中的特定部分 xff0c 关键在于怎么取膜 例
  • C++ 值传递、指针传递、引用传递详解

    具体内容源自 xff1a http www cnblogs com yanlingyin archive 2011 12 07 2278961 html 以下是简介 xff1a 值传递 xff1a 形参是实参的拷贝 xff0c 改变形参的值
  • MATLAB: 读取同一目录下的所有文件名并按时间排序

    用MATLAB测试图像处理算法的过程中 通常需要读入一个目录下的多张测试图片 可以根据文件命名规则来读入某个特定目录下的所有文件 但是相对比较麻烦 通过利用MATLAB自带的dir 可以先读入所有的文件名字 知道文件数量 而且不用知道文件的
  • 相机模型(Camera Model)

    Perspective Camera Model Perspective Camera Model 或 Pinhole Camera Model都是简单但是应用广泛的模型 xff0c 描述了将物体从3D世界坐标系转换 xff08 World
  • Solid Compression

    定义 Solid Compression是一种多文件的数据压缩方式 xff0c 其中所有未被压缩的文件是一个整体 xff0c 视为一个独立的data block 这样的文件称为solid archive 7z RAR压缩格式和tar bas
  • 区分AR、VR、MR、CR

    终极扫盲贴 xff1a VR AR MR CR到底如何区分 xff1f
  • (2016/02/19)多传感器数据融合算法---9轴惯性传感器

    2016年2月18日 传感器的原理 加速度计 xff1a 加速度计 我们可以把它想作一个圆球在一个方盒子中 假定这个盒子不在重力场中或者其他任何会影响球的位置的场中 xff0c 球处于盒子的正中央 你可以想象盒子在外太空中 xff0c 或远
  • 【tx2】——NVIDIA TX2--3--NVIDIA Jetson TX2 查看系统版本参数状态及重要指令

    NVIDIA Jetson TX2 查看系统参数状态 当前博主的TX2更新的版本为 xff1a Jetpack 3 3 cuda 9 0 252 cudnn7 0 opencv3 3 1 TensorRT4 0 2 系统内核 xff1a t
  • 训练深度学习模型时电脑自动重启

    文章目录 问题可能原因解决方案 问题 前面用自己的台式机利用GPU训练模型的时候 xff0c 电脑老是自动重启 xff0c 当时试了各种方法 xff0c 找了各种原因 电脑配置 电脑买的是二手的 xff0c xff08 强烈建议买新的 xf
  • 华为机试题[2017.8.23]

    题目 xff1a 给定一个正整数 xff0c 给出消除重复数字以后最大的整数 输入描述 xff1a 正整数 xff0c 注意考虑长整数 输出描述 xff1a 消除重复数字以后的最大整数 下面的好像有问题 xff0c 当输入是4325432时
  • Kubernetes(k8s)中dashboard的汉化

    1 访问服务器的http 192 168 110 133 8080 ui地址 xff0c 如下所示 xff1a 使用dashboard版本registry cn hangzhou aliyuncs com google containers
  • docker + Rancher + guacamole 容器环境搭建并配置vnc连接

    Rancher 43 guacamole 容器环境搭建 准备环境 xff1a docker ce 17 01 43 43 centos7 x 43 guacamole 最新版0 9 14 43 Rancher 搭建完成效果 xff1a 1
  • C语言习题(1)——字符串拷贝,去空格,奇偶抽取字符串

    1 字符串拷贝 作者 xff1a 一叶扁舟 作用 xff1a 字符串的拷贝 时间 xff1a 18 25 2017 5 1 include lt stdio h gt include lt string h gt include lt st
  • 基于安卓平台的滤镜功能相机

    1 1需求背景 爱美之心 xff0c 人皆有之 我们拍照是为了留住一个美好的瞬间 Android自带的相机拍照效果满足不了人们的爱美心理 xff0c 而且比较单一 xff1b 因此为了解决这个问题我们研 发 滤镜功能相机 滤镜功能相机主要基
  • 威廉·巴特勒·叶芝:“我们是最后的浪漫主义者”

    喜欢叶芝是一件很文艺的事情 叶芝的诗滋润了无数少男少女的情怀 在叶芝被茅德 冈 嫌弃的这一生中 我们不知道他是否曾经后悔 我感动了全世界 却感动不了你 但至少 他的诗 感动了后世无数人 题记 多少人曾爱你青春欢畅的时辰 爱慕你的美丽 假意或
  • Javassist即时编译技术,热修复核心与原理

    Java 字节码以二进制的形式存储在 class 文件中 xff0c 每一个 class 文件包含一个Java类或接口 Javaassist 框架就是一个用来处理 Java 字节码的类库 它可以在一个已经编译好的类中添加新的方法 xff0c
  • ubuntu系统编译安装

    Ubuntu程序安装是个很好理解的 xff0c 这里我发表一下个人理解 xff0c 下面就这就来讲术Ubuntu编译安装 Ubuntu编译程序新手指导Ubuntu 团队对它的使用者公开的承诺 Ubuntu 永远免费 并且对于 34 企业版本
  • Android Launcher浅析(一)

    Launcher桌面的一大功能就是支持左右滑动 xff0c 这样的功能在现在的应用中使用非常广泛 xff0c 并且有很多实现的方式 xff0c 可以通过使用Fragment来实现也可以通过自定义的控件来实现 Launcher采用了后者 xf