Android Launcher分析和修改6——页面滑动(PagedView)
本来打算分析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)的更多相关文章
- Android Launcher分析和修改13——实现Launcher编辑模式(1) 壁纸更换
已经很久没更新Launcher系列文章,今天不分析源码,讲讲如何在Launcher里面添加桌面设置的功能.目前很多第三方Launcher或者定制Rom都有简单易用的桌面设置功能.例如小米MIUI的La ...
- Android Launcher分析和修改9——Launcher启动APP流程
本来想分析AppsCustomizePagedView类,不过今天突然接到一个临时任务.客户反馈说机器界面的图标很难点击启动程序,经常点击了没有反应,Boss说要优先解决这问题.没办法,只能看看是怎么 ...
- Android Launcher分析和修改10——HotSeat深入进阶
前面已经写过Hotseat分析的文章,主要是讲解如何在Launcher里面配置以及修改Hotseat的参数.今天主要是讲解一下如何在Hotseat里面的Item显示名称.这个小问题昨天折腾了半天,最后 ...
- Android Launcher分析和修改7——AllApp全部应用列表(AppsCustomizeTabHost)
今天主要是分析一下Launcher里面的所有应用列表.Android4.0 Launcher的所有应用列表跟2.X比较大的区别就是多了Widget的显示.下面会详细分析Launcher里面有关所有应用 ...
- Android Launcher分析和修改8——AllAPP界面拖拽元素(PagedViewWithDraggableItems)
接着上一篇文章,继续分析AllAPP列表界面.上一篇文章分析了所有应用列表的界面构成以及如何通过配置文件修改属性.今天主要是分析PagedViewWithDraggableItems类,因为在我们分析 ...
- Android Launcher分析和修改11——自定义分页指示器(paged_view_indicator)
Android4.0的Launcher自带了一个简单的分页指示器,就是Hotseat上面那个线段,这个本质上是一个ImageView利用.9.png图片做,效果实在是不太美观,用测试人员的话,太丑了. ...
- Android Launcher分析和修改12——Widget列表信息收集
很久没写Launcher分析的文章,最近实在太忙.今天七夕本来是想陪女朋友逛街 ,碰巧打台风呆在家里,就继续写一篇文章.今天主要是讲一下Launcher里面的Widget列表,这方面信息比较多,今天重 ...
- Android Launcher分析和修改4——初始化加载数据
上面一篇文章说了Launcher是如何被启动的,Launcher启动的过程主要是加载界面数据然后显示出来, 界面数据都是系统APP有关的数据,都是从Launcher的数据库读取,下面我们详细分析Lau ...
- Android Launcher分析和修改3——Launcher启动和初始化
前面两篇文章都是写有关Launcher配置文件的修改,代码方面涉及不多,今天开始进入Launcher代码分析. 我们开机启动Launcher,Launcher是由Activity Manager启动的 ...
随机推荐
- C# 中删除控件的事件的方法类
方法一: 代码 /// <summary> /// 删除指定控件的指定事件 /// </summary> /// <param name="control&qu ...
- node+express+mongodb初体验
从去年11月份到现在,一直想去学习nodejs,在这段时间体验了gulp.grunt.yeomen,fis,但是对于nodejs深入的去学习,去开发项目总是断断续续. 今天花了一天的时间,去了解整理整 ...
- 模拟页面获取的php数据(一)
<?php return array( "aData" => array(//通勤方式 "trafficType" => array( 0 = ...
- [ 原创 ] centos安装tomcat,启动成功 无法访问
https://blog.csdn.net/realjh/article/details/82048492 Linux下Centos7对外开放端口 2018年08月25日 09:53:42 jeter ...
- HDU.4352.XHXJ's LIS(数位DP 状压 LIS)
题目链接 \(Description\) 求\([l,r]\)中有多少个数,满足把这个数的每一位从高位到低位写下来,其LIS长度为\(k\). \(Solution\) 数位DP. 至于怎么求LIS, ...
- ARC 101E.Ribbons on Tree(容斥 DP 树形背包)
题目链接 \(Description\) 给定一棵\(n\)个点的树.将这\(n\)个点两两配对,并对每一对点的最短路径染色.求有多少种配对方案使得所有边都至少被染色一次. \(n\leq5000\) ...
- 潭州课堂25班:Ph201805201 并发(协程) 第十五课 (课堂笔记)
#斐波那契 def fid(n): res = [] indx = 0 a = 0 b = 1 while indx < n : res.append(b) a,b = b,a+b indx + ...
- linux ulimit具体修改服务器配置
ulimit -a 显示当前用户的各种限制. ulimit -n 的数值表示每个进程可以打开的文件数目. 一般情况下, ulimit -n 的数值是1024. 当进程打开的文件数目超过此限 ...
- android: 实现强制下线功能
强制下线功能应该算是比较常见的了,很多的应用程序都具备这个功能,比如你的 QQ 号在别处登录了,就会将你强制挤下线.其实实现强制下线功能的思路也比较简单,只需要 在界面上弹出一个对话框,让用户无法进行 ...
- C++ - 定义无双引号的字符串宏
在某些特殊场合下,我们可能需要定义一个字符串宏,但又不能用双引号 比如像这样 #define HELLO hello world 如果我们只是简单的展开HELLO,肯定会无法编译 std::cout ...