本来打算分析CellLayout的源码,不过因为它们之间是容器包含关系,所以打算先把PagedView分析。PagedView代码很多,今天主要是分析跟核心功能相关的代码。PagedView主要实现一个功能——页面滑动。

  PagedView继承了ViewGroup类,是一个容器类,可以包含第三方的View,实际上Launcher里面的PagedView主要就是包含了CellLayout的显示。对于一个View类来说,我们触摸屏幕界面,首先会触发View类的onInterceptTouchEvent()回调函数。这个函数负责处理原始的消息驱动,决定是拦截消息还是传给上层的View。这个涉及Android的事件驱动的原理。不了解的朋友可以查阅相关资料,这里不多说。

下面是PagedView的消息流程传递流程图:


//Edited by mythou
//http://www.cnblogs.com/mythou/
 /*
* 这个方法主要是决定触摸消息如何是否需要往上层传递。
* 如果返回为true,则会调用PagedView类的onTouchEvent方法,处理触摸事件。否则传给上层View
*/
  public boolean onInterceptTouchEvent(MotionEvent ev)
{
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent Enter chlid="+getChildCount());
//获取速度跟踪器,记录各个时刻的速度。并且添加当前的MotionEvent以记录更新速度值。 OWL
acquireVelocityTrackerAndAddMovement(ev); //没有页面,直接跳过给父类处理。
if (getChildCount() <= ) return super.onInterceptTouchEvent(ev); /*
* 最常见的需要拦截的情况:用户已经进入滑动状态,而且正在移动手指滑动
* 对这种情况直接进行拦截,调用PagedView的onTouchEvent()
*/

final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) &&
(mTouchState == TOUCH_STATE_SCROLLING))
{
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent move scrolling...");
//截断触摸反馈,直接触发onToucEvent,滑动过程中。
return true;
} switch (action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_MOVE:
{
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent ACTION_MOVE...");
/*
* 当在这里接受到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) //已经发生触摸,获取到触点OWL
{
determineScrollingStart(ev);
break;
}
} case MotionEvent.ACTION_DOWN:
{
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent ACTION_DOWN...");
final float x = ev.getX();
final float y = ev.getY();
//记录点击的位置的坐标以及触点的记录(多点触摸识别)
mDownMotionX = x;
mLastMotionX = x;
mLastMotionY = y;
mLastMotionXRemainder = ;
mTotalMotionX = ;
mActivePointerId = ev.getPointerId(); //第一个触点,返回0
mAllowLongPress = true; if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent mDownMotionX="+mDownMotionX+"
mLastMotionY="+mLastMotionY +" mActivePointerId="+mActivePointerId);
/*
* 判断目前是真正滑动页面还是已经滑动结束,如果是真正滑动会拦截消息
*/

final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
if (finishedScrolling)
{
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent finishedScrolling..");
mTouchState = TOUCH_STATE_REST;
mScroller.abortAnimation();
}
else //按下拖动应该属于滑动状态
{
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent TOUCH_STATE_SCROLLING..");
mTouchState = TOUCH_STATE_SCROLLING;
} // 识别是否触摸状态是否是直接翻页状态,如果是直接翻页,在onTouchEvent里面会直接调用
// snapToPage()方法,跳到目标页面

if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE)
{
if (getChildCount() > )
{
if (hitsPreviousPage(x, y))
{
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent hitsPreviousPage true...");
//点击上一页
mTouchState = TOUCH_STATE_PREV_PAGE;
}
else if (hitsNextPage(x, y))
{
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent hitsNextPage true...");
mTouchState = TOUCH_STATE_NEXT_PAGE;
}
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent no next or pre page..");
}
}
break;
}
       //不需要拦截触摸消息
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent ACTION_UP || ACTION_CANCEL");
mTouchState = TOUCH_STATE_REST;
mAllowLongPress = false;
mActivePointerId = INVALID_POINTER;
releaseVelocityTracker();
break; case MotionEvent.ACTION_POINTER_UP:
if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent ACTION_POINTER_UP");
onSecondaryPointerUp(ev);
releaseVelocityTracker();
break;
} if(OWL_DEBUG) Log.d(OWL, "onInterceptTouchEvent mTouchState="+mTouchState);
/*.
* 只要是mTouchState的状态不为TOUCH_STATE_REST,那么就进行事件拦截,调用onTouchEvent
*/

return mTouchState != TOUCH_STATE_REST;
}

  上面是onInterceptTouchEvent的代码,上面已经给了很详细的注释。整个函数主要就是判断TouchState是什么状态。PagedView类定义了4种TouchEvent的状态。


//Edited by mythou
//http://www.cnblogs.com/mythou/
//滑动结束状态
protected final static int TOUCH_STATE_REST = ;
//正在滑动
protected final static int TOUCH_STATE_SCROLLING = ;
//滑动到上一页
protected final static int TOUCH_STATE_PREV_PAGE = ;
//滑动到下一页
protected final static int TOUCH_STATE_NEXT_PAGE = ;

  除非是TOUCH_STATE_REST状态,否则都会进行消息拦截。拦截后的消息会在onInterceptTouchEvent的onTouchEvent里面处理。主要都是有关滑动状态的消息。其他点击按下的消息在会传递到上一层的View控件。再进行类似的处理。下面我们看看onTouchEvent处理的事情,因为PagedView的onTouchEvent代码比较多,我这里就不全部贴上来,只把关键处理的部分代码贴出来,主要是页面切换。所有的最终操作都是在ACTION_UP里面进行处理。也就是说需要等你把手指离开了屏幕,才会最后决定是否需要切换页面。

下面给出ACTION_UP的关键代码:

//Edited by mythou
//http://www.cnblogs.com/mythou/
 public boolean onTouchEvent(MotionEvent ev)
{
if(OWL_DEBUG) Log.d(OWL, "onTouchEvent entering..");
     //........switch (action & MotionEvent.ACTION_MASK)
{
     //.........此处省略ACTION_DOWN和ACTION_MOVE
case MotionEvent.ACTION_UP:
if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP..");
if (mTouchState == TOUCH_STATE_SCROLLING)
{
if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING");
final int activePointerId = mActivePointerId;
final int pointerIndex = ev.findPointerIndex(activePointerId);
final float x = ev.getX(pointerIndex);
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(, mMaximumVelocity);
//横向速率
int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
//移动距离
final int deltaX = (int) (x - mDownMotionX);
//页面宽
final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));
//移动距离超过页面宽40%
boolean isSignificantMove = Math.abs(deltaX) > pageWidth *SIGNIFICANT_MOVE_THRESHOLD;
final int snapVelocity = mSnapVelocity; mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
//根据滑动距离和速率,判断是否是滑动
boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && Math.abs(velocityX) > snapVelocity;
// 这钟情况是页面朝一个方向移动了一段距离,然后又弹回去了。
//
我们使用一个阀值来判断是进行翻页还是返回到初始页面
boolean returnToOriginalPage = false;
//Math.signum 函数将正数转换为 1.0,将负数转换为 -1.0,0 仍然是 0。 实际上,它只是提取一个数的符号
if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
Math.signum(velocityX) != Math.signum(deltaX) && isFling)
{
//返回当前页面
returnToOriginalPage = true;
} int finalPage;//页面朝左移动
if (((isSignificantMove && deltaX > && !isFling) ||
(isFling && velocityX > )) && mCurrentPage > )
{
if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING LeftSnap");
finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - ;
snapToPageWithVelocity(finalPage, velocityX);
}
//页面朝右移动
else if (((isSignificantMove && deltaX < && !isFling) ||
(isFling && velocityX < )) && mCurrentPage < getChildCount() - )
{
if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING RightSnap");
finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + ;
snapToPageWithVelocity(finalPage, velocityX);
}
else
{
if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_SCROLLING Back Origation");
//寻找离屏幕中心最近的页面移动
snapToDestination();
}
}
else if (mTouchState == TOUCH_STATE_PREV_PAGE)
{
if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_PREV_PAGE");
//直接切换到上一页,没有进行滑动
int nextPage = Math.max(, mCurrentPage - );
if (nextPage != mCurrentPage) {
snapToPage(nextPage);
} else {
snapToDestination();
}
}
else if (mTouchState == TOUCH_STATE_NEXT_PAGE)
{
if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. TOUCH_STATE_NEXT_PAGE");
//直接切换到下一页
int nextPage = Math.min(getChildCount() - , mCurrentPage + );
if (nextPage != mCurrentPage) {
snapToPage(nextPage);
} else {
snapToDestination();
}
}
else
{
if(OWL_DEBUG) Log.d(OWL, "onTouchEvent ACTION_UP.. onUnhandledTap()");
         //这里是空回调,继承PagedView的方法,会重载这方法处理一些操作。
onUnhandledTap(ev);
}
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
releaseVelocityTracker();
break;return true;
}

  从上面我们可以看到,onTouchEvent的ACTION_UP主要是处理了3种情况,分别对应TOUCH_STATE_SCROLLING、

TOUCH_STATE_PREV_PAGE和TOUCH_STATE_NEXT_PAGE 3种不同的滑动情况。

我们接下来看看但我们滑动界面的时候,触摸消息如何分配:

  上面是我向右滑动的时候,Logcat打印的消息,上面代码都加了打印消息,可以对应着看。我们可以看到,首先触发的是onInterceptTouchEvent()方法,而且触发过程中,mTouchState的状态发生变化,从TOUCH_STATE_REST到TOUCH_STATE_SCROLLING。也就是说从刚开始不会触发onTouchEvent到后面会直接拦截触摸消息,除非PagedView的onTouchEvent。onTouchEvent也是经历了几种状态变化,最后在ACTION_UP的时候,按照TOUCH_STATE_SCROLLING的状态进行处理,最后调用snapToPageWithVelocity()方法,跳转到下一页。
下面我们在看看snapToPageWithVelocity如何跳转到其他页面:
//Edited by mythou
//http://www.cnblogs.com/mythou/
  protected void snapToPageWithVelocity(int whichPage, int velocity)
{
//..............
//根据滑动的速度,计算一个切换动画的显示时间,速度越快,动画显示时间越短。
duration = * Math.round( * Math.abs(distance / velocity));
//跳转到指定页面。
snapToPage(whichPage, delta, duration);
}

  里面还是调用了snapToPage的方法,只是多了一个处理滑动速度的方法,滑动速度在onTouchEvent里面启动了一个记录滑动速度的类VelocityTracker类实现记录滑动的速度。这里会根据用户滑动的速度急速页面切换的动画时间。

//Edited by mythou
//http://www.cnblogs.com/mythou/
 protected void snapToPage(int whichPage, int delta, int duration)
  {
mNextPage = whichPage;
//............
     //切换的时候调用Scroller的startScroll进行切换
if (!mScroller.isFinished()) mScroller.abortAnimation();
mScroller.startScroll(mUnboundedScrollX, , delta, , duration);
notifyPageSwitchListener();
invalidate();
}

当系统调用startScroll()的时候,会自动回调computeScroll()方法,而在computeScroll()方法里面,会调用scrollTo()方法进行切换,并且会调用invalidate()方法一直刷新界面,形成动画效果和页面切换效果。

//Edited by mythou
//http://www.cnblogs.com/mythou/
  protected boolean computeScrollHelper()
{
     //computeScrolloffset用来计算滑动是否结束,当你调用startScroll()方法的时候这个方法返回的值一直都为true,
     //如果采用其它方式移动视图比如:scrollTo()或 scrollBy时那么这个方法返回false   
if (mScroller.computeScrollOffset())
{
if (mScrollX != mScroller.getCurrX() || mScrollY != mScroller.getCurrY())
{
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
}
       //刷新界面
invalidate();
return true;
}
//.............
return false;
}

  到这里基本就追踪到页面最后滑动切换的实际操作过程。scrollTo是View类的方法,PagedView类把它重写了,主要是做了一些位置的判断操作。更新X、Y坐标,然后调用父类的scrollTo方法。除了这些页面操作外,PagedView类还有很多其他的辅助方法,后面有时间,我会再分析,不过掌握Launcher的PagedView类,你刚开始只需要弄清楚滑动切换的功能就可以,毕竟这个类最主要的功能就是这个。

今天就说到这里,下一篇文章会分析workspace或者cellLayout。到时候看情况分析!

系列文章:

Android Launcher分析和修改1——Launcher默认界面配置(default_workspace)

Android Launcher分析和修改2——Icon修改、界面布局调整、壁纸设置

Android Launcher分析和修改3——Launcher启动和初始化

Android Launcher分析和修改4——初始化加载数据

Android Launcher分析和修改5——HotSeat分析

Edited by mythou

原创博文,转载请标明出处:http://www.cnblogs.com/mythou/p/3174032.html 

												

Android Launcher分析和修改6——页面滑动(PagedView)的更多相关文章

  1. Android Launcher分析和修改13——实现Launcher编辑模式(1) 壁纸更换

    已经很久没更新Launcher系列文章,今天不分析源码,讲讲如何在Launcher里面添加桌面设置的功能.目前很多第三方Launcher或者定制Rom都有简单易用的桌面设置功能.例如小米MIUI的La ...

  2. Android Launcher分析和修改9——Launcher启动APP流程

    本来想分析AppsCustomizePagedView类,不过今天突然接到一个临时任务.客户反馈说机器界面的图标很难点击启动程序,经常点击了没有反应,Boss说要优先解决这问题.没办法,只能看看是怎么 ...

  3. Android Launcher分析和修改10——HotSeat深入进阶

    前面已经写过Hotseat分析的文章,主要是讲解如何在Launcher里面配置以及修改Hotseat的参数.今天主要是讲解一下如何在Hotseat里面的Item显示名称.这个小问题昨天折腾了半天,最后 ...

  4. Android Launcher分析和修改7——AllApp全部应用列表(AppsCustomizeTabHost)

    今天主要是分析一下Launcher里面的所有应用列表.Android4.0 Launcher的所有应用列表跟2.X比较大的区别就是多了Widget的显示.下面会详细分析Launcher里面有关所有应用 ...

  5. Android Launcher分析和修改8——AllAPP界面拖拽元素(PagedViewWithDraggableItems)

    接着上一篇文章,继续分析AllAPP列表界面.上一篇文章分析了所有应用列表的界面构成以及如何通过配置文件修改属性.今天主要是分析PagedViewWithDraggableItems类,因为在我们分析 ...

  6. Android Launcher分析和修改11——自定义分页指示器(paged_view_indicator)

    Android4.0的Launcher自带了一个简单的分页指示器,就是Hotseat上面那个线段,这个本质上是一个ImageView利用.9.png图片做,效果实在是不太美观,用测试人员的话,太丑了. ...

  7. Android Launcher分析和修改12——Widget列表信息收集

    很久没写Launcher分析的文章,最近实在太忙.今天七夕本来是想陪女朋友逛街 ,碰巧打台风呆在家里,就继续写一篇文章.今天主要是讲一下Launcher里面的Widget列表,这方面信息比较多,今天重 ...

  8. Android Launcher分析和修改4——初始化加载数据

    上面一篇文章说了Launcher是如何被启动的,Launcher启动的过程主要是加载界面数据然后显示出来, 界面数据都是系统APP有关的数据,都是从Launcher的数据库读取,下面我们详细分析Lau ...

  9. Android Launcher分析和修改3——Launcher启动和初始化

    前面两篇文章都是写有关Launcher配置文件的修改,代码方面涉及不多,今天开始进入Launcher代码分析. 我们开机启动Launcher,Launcher是由Activity Manager启动的 ...

随机推荐

  1. P1279 字串距离

    P1279 字串距离一看就是字符串dp,然而并不会,骗分之后爆零了.以后dp题要好好想想转移方程.f[i][j]表示是a串选了前i个字符,b串选了前j个字符的距离.显然(QAQ)f[i][j]=min ...

  2. 008.Docker Flannel+Etcd分布式网络部署

    一 环境准备 1.1 Flannel概述 Flannel是一种基于overlay网络的跨主机容器网络解决方案,即将TCP数据包封装在另一种网络包里面进行路由转发和通信,Flannel是CoreOS开发 ...

  3. 开启mysql的远程访问权限

    改表法 1.登陆mysql mysql -u root -p 2.修改mysql库的user表,将host项,从localhost改为%.%这里表示的是允许任意host访问,如果只允许某一个ip访问, ...

  4. python新手总结(二)

    random模块 随机小数 random uniform 随机整数 randint randrange 随机抽取 choice sample 打乱顺序 shuffle random.random() ...

  5. Object类浅析

    Object类的方法有: hashCode(): 返回该对象的哈希码值 hashCode 的常规协定是: 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回 ...

  6. Android Firebase Android google-cloud-tools

    Firebase 让不懂服务端的开发者也可以快速写出实时性的Web端和移动端应用. firebase的功能包括推送通知,云存储,活动监视,远程部署 针对国内三方推送,只能在国内使用,到了国外就不支持了 ...

  7. HDU.5909.Tree Cutting(树形DP FWT/点分治)

    题目链接 \(Description\) 给定一棵树,每个点有权值,在\([0,m-1]\)之间.求异或和为\(0,1,...,m-1\)的非空连通块各有多少个. \(n\leq 1000,m\leq ...

  8. Chrome for Mac键盘快捷键!来自Google Chrome官网!

    ⌘-N 打开新窗口. ⌘-T 打开新标签页. ⌘-Shift-N 在隐身模式下打开新窗口. 按 ⌘-O,然后选择文件. 在 Google Chrome 浏览器中打开计算机中的文件. 按住 ⌘ 键,然后 ...

  9. 前端性能优化 —— 减少HTTP请求

    简要:对于影响页面呈选 的因素有3个地方:服务器连接数据库并计算返回数据 , http请求以及数据(文件)经过网络传输 , 文件在浏览器中计算渲染呈选: 其中大约80%的时间都耗在了http请求上,所 ...

  10. 使用Date和SimpleDateFormat类表示时间

    Date类: 使用 Date 类的默认无参构造方法创建出的对象就代表当前时间,我们可以直接输出 Date 对象显示当前的时间,显示的结果如下: Date d = new Date(); System. ...