转载自:http://mobile.51cto.com/hot-312129.htm


Android 4.0 Launcher源码分析系列(一)

从今天起傻蛋打算做一个系列文章,对最新的Android4.0系统中的Launcher,也就是Android4.0原生的桌面程序,进行一个深入浅出的分析,从而引领Android系统的编程爱好者对Launcher的设计思想,实现方式来做一个研究,从而能够通过这个实例最掌握到目前世界领先的设计方法,同时在程序中加入我们的一些新的实现。众所周知,对一些优秀源代码的分析,是提高编程水平的一条便捷的方式,希望本系列文章能够给大家带来一定的启发,同时欢迎大家和作者一起讨论,作者的微博是:http://weibo.com/zuiniuwang/

先从整体上对Launcher布局作一个分析,让我们通过查看Launcher.xml和使用hierarchyviewer布局查看工具两者结合的方法来对Launcher的整体结构有个了解。通过hierarchyviewer来对整个桌面做个截图,如下:

android launcher源码分析_第1张图片

放大后如下所示:可以看到整个桌面包含的元素,最上面是Google的搜索框,下面是一个始终插件,然后是图标,再有就是一个分隔线,最后是dock。请注意,桌面程序其实并不包含桌面壁纸,桌面壁纸其实是由WallpaperManagerService来提供,整个桌面其实是叠加在整个桌面壁纸上的另外一个层。

android launcher源码分析_第2张图片

点击查看大图

整个Launcher.xml布局文件如下:

        
  1. <com.android.launcher2.DragLayer
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
  4. android:id="@+id/drag_layer"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent">
  7. <!--Keepthesebehindtheworkspacesothattheyarenotvisiblewhen
  8. wegointoAllApps-->
  9. <include
  10. android:id="@+id/dock_divider"
  11. layout="@layout/workspace_divider"
  12. android:layout_width="match_parent"
  13. android:layout_height="wrap_content"
  14. android:layout_marginBottom="@dimen/button_bar_height"
  15. android:layout_gravity="bottom"/>
  16. <include
  17. android:id="@+id/paged_view_indicator"
  18. layout="@layout/scroll_indicator"
  19. android:layout_width="wrap_content"
  20. android:layout_height="wrap_content"
  21. android:layout_gravity="bottom"
  22. android:layout_marginBottom="@dimen/button_bar_height"/>
  23. <!--Theworkspacecontains5screensofcells-->
  24. <com.android.launcher2.Workspace
  25. android:id="@+id/workspace"
  26. android:layout_width="match_parent"
  27. android:layout_height="match_parent"
  28. android:paddingTop="@dimen/qsb_bar_height_inset"
  29. android:paddingBottom="@dimen/button_bar_height"
  30. launcher:defaultScreen="2"
  31. launcher:cellCountX="4"
  32. launcher:cellCountY="4"
  33. launcher:pageSpacing="@dimen/workspace_page_spacing"
  34. launcher:scrollIndicatorPaddingLeft="@dimen/workspace_divider_padding_left"
  35. launcher:scrollIndicatorPaddingRight="@dimen/workspace_divider_padding_right">
  36. <includeandroid:id="@+id/cell1"layout="@layout/workspace_screen"/>
  37. <includeandroid:id="@+id/cell2"layout="@layout/workspace_screen"/>
  38. <includeandroid:id="@+id/cell3"layout="@layout/workspace_screen"/>
  39. <includeandroid:id="@+id/cell4"layout="@layout/workspace_screen"/>
  40. <includeandroid:id="@+id/cell5"layout="@layout/workspace_screen"/>
  41. </com.android.launcher2.Workspace>
  42. <includelayout="@layout/hotseat"
  43. android:id="@+id/hotseat"
  44. android:layout_width="match_parent"
  45. android:layout_height="@dimen/button_bar_height_plus_padding"
  46. android:layout_gravity="bottom"/>
  47. <include
  48. android:id="@+id/qsb_bar"
  49. layout="@layout/qsb_bar"/>
  50. <includelayout="@layout/apps_customize_pane"
  51. android:id="@+id/apps_customize_pane"
  52. android:layout_width="match_parent"
  53. android:layout_height="match_parent"
  54. android:visibility="invisible"/>
  55. <includelayout="@layout/workspace_cling"
  56. android:id="@+id/workspace_cling"
  57. android:layout_width="match_parent"
  58. android:layout_height="match_parent"
  59. android:visibility="gone"/>
  60. <includelayout="@layout/folder_cling"
  61. android:id="@+id/folder_cling"
  62. android:layout_width="match_parent"
  63. android:layout_height="match_parent"
  64. android:visibility="gone"/>
  65. </com.android.launcher2.DragLayer>

Launcher整个布局的根是DragLayer,DragLayer继承了FrameLayout,所以DragLayer本身可以看作是一个FrameLayout。下面是dock_divider,它通过include关键字包含了另外一个布局文件workspace_divider.xml,而这个workspace_divider.xml包含了一ImageView,其实dock_divider就是dock区域上面的那条直线。

再下面是paged_view_indicator,同样它包含了scroll_indicator.xml,其中包含了一个ImageView,显示的是一个.9的png文件。实际上就是当Launcher滚动翻页的时候,那个淡蓝色的页面指示条。

然后桌面的核心容器WorkSpace,如下图所示,当然你看到的只是Workspace的一部分,其实是一个workspace_screen,通过Launcher.xml可以看到,整个workspace由5个workspace_screen组成,每个workspace_screen其实就是对应桌面一页。而每个workspace_screen包含了一个CellLayout,这是一个自定义控件,继承自ViewGroup,所以它算是一个用来布局的控件,在这里主要用来承载我们每页的桌面图标、widget和文件夹。

android launcher源码分析_第3张图片

点击查看大图

通过查看如下的布局结构(由于图太大只截取了一部分)可以看到,Workspace包含了序号从0到4的5个CellLayout。

android launcher源码分析_第4张图片

接下来是一个Hotseat,其实就是这块dock区域了。如图所示:

android launcher源码分析_第5张图片

点击查看大图

从如下的布局图我们可以看到,这个Hotseat其实还是包含了一个CellLayout,用来承载4个图标和中间启动所有程序的按钮。

android launcher源码分析_第6张图片

再下来就是那个qsb_bar,就是屏幕最顶端的Google搜索框。这个搜索框是独立于图标界面的,所以当我们对桌面进行翻页的时候,这个搜索框会巍然不动滴固定在最顶端,如下所示:

最顶端的Google搜索框

紧接着是3个初始化时被隐藏的界面。

apps_customize_pane,点击dock中显示所有应用程序的按钮后才会从隐藏状态转换为显示状态,如下图所示,显示了所有应用程序和所有插件的界面。

android launcher源码分析_第7张图片

点击查看大图

通过查看apps_customize_pane.xml,我们可以看到apps_customize_pane主要由两部分组成:tabs_container和tabcontent。tabs部分,用来让我们选择是添加应用程序还是widget,如下图所示:

android launcher源码分析_第8张图片

点击查看大图

tabcontent,选择了相应的tab之后,下面的部分就会相应的显示应用程序或是widget了,如下图所示:

android launcher源码分析_第9张图片

点击查看大图

workspace_cling和folder_cling是刚刷完机后,进入桌面时,显示的使用向导界面,介绍怎么使用workspace和folder,跳过以后就再也不会出现了,这里就不截图了。


Android 4.0 Launcher源码分析系列(二)


【51CTO.com 2月2日独家特稿】上一节我们研究了Launcher的整体结构,这一节我们看看整个Laucher的入口点,同时Laucher在加载了它的布局文件Laucher.xml时都干了些什么。

我们在源代码中可以找到LauncherApplication, 它继承了Application类,当整个Launcher启动时,它就是整个程序的入口。我们先来看它们在AndroidManifest.xml中是怎么配置的。

        
  1. <application
  2. android:name="com.android.launcher2.LauncherApplication"
  3. android:label="@string/application_name"
  4. android:icon="@drawable/ic_launcher_home"
  5. android:hardwareAccelerated="@bool/config_hardwareAccelerated"
  6. android:largeHeap="@bool/config_largeHeap">

首先通过android:name指定了整个Launcher的Application也就是入口是在 com.android.launcher2.LauncherApplication这个路径下,android:lable指定了桌面的名字是叫 Launcher,如果要改名字就改values文件夹的string.xml中的相应属性就可以了。android:icon指定了Laucher的图标,这个图标可以在应用程序管理器中看见,如下图所示,是个可爱机器人住在一个小房子里面,如果需要更改Laucher的图片,重新设置这个属性就可以了。

android launcher源码分析_第10张图片

android:hardwareAccelerated="@bool/config_hardwareAccelerated" 指定了整个应用程序是启用硬件加速的,这样整个应用程序的运行速度会更快。

android:largeHeap="@bool/config_largeHeap" 指定了应用程序使用了大的堆内存,能在一定程度上避免,对内存out of memory错误的出现。我们可以在values文件夹的config.xml中看到对是否启用硬件加速和大内存的配置。如下所示:

        
  1. <boolname="config_hardwareAccelerated">true</bool>
  2. <boolname="config_largeHeap">false</bool>

在Application中onCreate()方法通过:sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE || screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE; 和sScreenDensity = getResources().getDisplayMetrics().density;来判断是否是大屏幕,同时得到它的屏幕密度。同时通过mIconCache = new IconCache(this); 来设置了应用程序的图标的cache,然后申明了LauncherModel,mModel = new LauncherModel(this, mIconCache); LauncherModel主要用于加载桌面的图标、插件和文件夹,同时LaucherModel是一个广播接收器,在程序包发生改变、区域、或者配置文件发生改变时,都会发送广播给LaucherModel,LaucherModel会根据不同的广播来做相应加载操作,此部分会在后面做详细介绍。

在LauncherApplication完成初始化工作之后,我们就来到了Launcher.java的onCreate()方法,同样是启动桌面时的一系列初始化工作。

首先需要注意的是在加载launcher布局文件时的一个TraceView的调试方法,它能够对在他们之间的方法进行图形化的性能分析,并且能够具体到method 代码如下:

        
  1. if(PROFILE_STARTUP){
  2. android.os.Debug.startMethodTracing(
  3. Environment.getDataDirectory()+"/data/com.android.launcher/launcher");
  4. }
  5. if(PROFILE_STARTUP){
  6. android.os.Debug.stopMethodTracing();
  7. }

我指定的生成性能分析的路径是:/data/data/com.android.launcher/launcher,启动launcher后我们会发现在指定的目录下生成了launcher.trace文件,如下图所示:

android launcher源码分析_第11张图片

把launcher.trace文件通过DDMS pull到电脑上,在SDK的tools目录里,执行traceview工具来打开launcher.trace .如下图所示:

android launcher源码分析_第12张图片

点击查看大图

可以看到setContentView使用了448.623ms,占整个跟踪代码时间的62%,所以说在加载布局文件时,肯定经过了一系列的加载运算,我们接着分析。

当加载launcher布局文件的过程时,最为关键的时对整个workspace的加载,workspace是一个自定义组件,它的继承关系如下所示,可以看到Workspace实际上也是一个ViewGroup,可以加入其他控件。

android launcher源码分析_第13张图片

当ViewGroup组件进行加载的时候首先会读取本控件对应的XML文件,然后Framework层会执行它的onMeasure()方法,根据它所包含的子控件大小来计算出整个控件要在屏幕上占的大小。Workspace重写了ViewGroup的onMeasure方法(在PagedView中),在workspace中是对5个子CellLayout进行测量,的方法如下, 具体含义请看注释:

        
  1. @Override
  2. protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
  3. if(!mIsDataReady){
  4. super.onMeasure(widthMeasureSpec,heightMeasureSpec);
  5. return;
  6. }
  7. //得到宽度的模式(在配置文件中对应的是match_parent或者wrap_content)和其大小
  8. finalintwidthMode=MeasureSpec.getMode(widthMeasureSpec);
  9. finalintwidthSize=MeasureSpec.getSize(widthMeasureSpec);
  10. //宽度必须是match_parent,否则会抛出异常。
  11. if(widthMode!=MeasureSpec.EXACTLY){
  12. thrownewIllegalStateException("WorkspacecanonlybeusedinEXACTLYmode.");
  13. }
  14. /*AllowtheheighttobesetasWRAP_CONTENT.Thisallowstheparticularcase
  15. *oftheAllappsviewonXLargedisplaystonottakeupmorespacethenitneeds.Width
  16. *isstillnotallowedtobesetasWRAP_CONTENTsincemanypartsofthecodeexpect
  17. *eachpagetohavethesamewidth.
  18. */
  19. //高度允许是wrap_content,因为在大屏幕的情况下,会占了多余的位置
  20. finalintheightMode=MeasureSpec.getMode(heightMeasureSpec);
  21. intheightSize=MeasureSpec.getSize(heightMeasureSpec);
  22. intmaxChildHeight=0;
  23. //得到在竖值方向上和水平方向上的Padding
  24. finalintverticalPadding=mPaddingTop+mPaddingBottom;
  25. finalinthorizontalPadding=mPaddingLeft+mPaddingRight;
  26. //Thechildrenaregiventhesamewidthandheightastheworkspace
  27. //unlesstheyweresettoWRAP_CONTENT
  28. if(DEBUG)Log.d(TAG,"PagedView.onMeasure():"+widthSize+","+heightSize+"mPaddingTop="+mPaddingTop+"mPaddingBottom="+mPaddingBottom);
  29. finalintchildCount=getChildCount();
  30. //对workspace的子View进行遍历,从而对它的几个子view进行测量。
  31. for(inti=0;i<childCount;i++){
  32. //disallowingpaddinginpagedview(justpass0)
  33. finalViewchild=getPageAt(i);
  34. finalLayoutParamslp=(LayoutParams)child.getLayoutParams();
  35. intchildWidthMode;
  36. if(lp.width==LayoutParams.WRAP_CONTENT){
  37. childWidthMode=MeasureSpec.AT_MOST;
  38. }else{
  39. childWidthMode=MeasureSpec.EXACTLY;
  40. }
  41. intchildHeightMode;
  42. if(lp.height==LayoutParams.WRAP_CONTENT){
  43. childHeightMode=MeasureSpec.AT_MOST;
  44. }else{
  45. childHeightMode=MeasureSpec.EXACTLY;
  46. }
  47. finalintchildWidthMeasureSpec=
  48. MeasureSpec.makeMeasureSpec(widthSize-horizontalPadding,childWidthMode);
  49. finalintchildHeightMeasureSpec=
  50. MeasureSpec.makeMeasureSpec(heightSize-verticalPadding,childHeightMode);
  51. //对子View的大小进行设置,传入width和height参数
  52. child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
  53. maxChildHeight=Math.max(maxChildHeight,child.getMeasuredHeight());
  54. if(DEBUG)Log.d(TAG,"\tmeasure-child"+i+":"+child.getMeasuredWidth()+","
  55. +child.getMeasuredHeight());
  56. }
  57. if(heightMode==MeasureSpec.AT_MOST){
  58. heightSize=maxChildHeight+verticalPadding;
  59. }
  60. //存储测量后的宽度和高度
  61. setMeasuredDimension(widthSize,heightSize);
  62. //Wecan'tcallgetChildOffset/getRelativeChildOffsetuntilwesetthemeasureddimensions.
  63. //Wealsowaituntilwesetthemeasureddimensionsbeforeflushingthecacheaswell,to
  64. //ensurethatthecacheisfilledwithgoodvalues.
  65. invalidateCachedOffsets();
  66. updateScrollingIndicatorPosition();
  67. if(childCount>0){
  68. mMaxScrollX=getChildOffset(childCount-1)-getRelativeChildOffset(childCount-1);
  69. }else{
  70. mMaxScrollX=0;
  71. }
  72. }

测量完毕之后就可以对子控件进行布局了,这时候Framework层会调用PagedView中重写的onLayout方法。

        
  1. @Override
  2. protectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){
  3. if(!mIsDataReady){
  4. return;
  5. }
  6. if(DEBUG)Log.d(TAG,"PagedView.onLayout()");
  7. //竖值方向的Padding
  8. finalintverticalPadding=mPaddingTop+mPaddingBottom;
  9. finalintchildCount=getChildCount();
  10. intchildLeft=0;
  11. if(childCount>0){
  12. if(DEBUG)Log.d(TAG,"getRelativeChildOffset():"+getMeasuredWidth()+","
  13. +getChildWidth(0));
  14. childLeft=getRelativeChildOffset(0);
  15. //偏移量为0
  16. if(DEBUG)Log.d(TAG,"childLeft:"+childLeft);
  17. //Calculatethevariablepagespacingifnecessary
  18. //如果mPageSpacing小于0的话,就重新计算mPageSpacing,并且给它赋值。
  19. if(mPageSpacing<0){
  20. setPageSpacing(((right-left)-getChildAt(0).getMeasuredWidth())/2);
  21. }
  22. }
  23. for(inti=0;i<childCount;i++){
  24. finalViewchild=getPageAt(i);
  25. if(child.getVisibility()!=View.GONE){
  26. finalintchildWidth=getScaledMeasuredWidth(child);
  27. finalintchildchildHeight=child.getMeasuredHeight();
  28. intchildTop=mPaddingTop;
  29. if(mCenterPagesVertically){
  30. childTop+=((getMeasuredHeight()-verticalPadding)-childHeight)/2;
  31. }
  32. if(DEBUG)Log.d(TAG,"\tlayout-child"+i+":"+childLeft+","+childTop);
  33. //把5个CellLayout布局到相应的位置,layout的4个参数分别是左、上、右、下。
  34. child.layout(childLeft,childTop,
  35. childLeft+child.getMeasuredWidth(),childTop+childHeight);
  36. childLeft+=childWidth+mPageSpacing;
  37. }
  38. }
  39. //第一次布局完毕之后,就根据当前页偏移量(当前页距离Workspace最左边的距离)滚动到默认的页面去,第一次布局时
  40. //默认的当前页是3,则它的便宜量就是两个CellLayout的宽度。
  41. if(mFirstLayout&&mCurrentPage>=0&&mCurrentPage<getChildCount()){
  42. setHorizontalScrollBarEnabled(false);
  43. intnewX=getChildOffset(mCurrentPage)-getRelativeChildOffset(mCurrentPage);
  44. //滚动到指定的位置
  45. scrollTo(newX,0);
  46. mScroller.setFinalX(newX);
  47. if(DEBUG)Log.d(TAG,"newXis"+newX);
  48. setHorizontalScrollBarEnabled(true);
  49. mFirstLayout=false;
  50. }
  51. if(mFirstLayout&&mCurrentPage>=0&&mCurrentPage<getChildCount()){
  52. mFirstLayout=false;
  53. }
  54. }


Android 4.0 Launcher源码分析系列(三)

首先傻蛋先画了个图来再来阐述一下WorkSpace的结构。如下图:

android launcher源码分析_第14张图片

点击查看大图

桌面的左右滑动功能主要是在PagedView类中实现的,而WorkSpace是PagedView类的子类,所以会继承PagedView中的方法。当我们的手指点击WorkSpace时,首先就会触发PageView中的onInterceptTouchEvent()方法,会根据相应的条件来判断是否对Touch事件进行拦截,如果onInterceptTouchEvent()方法返回为true,则会对Touch事件进行拦截,PageView类的onTouch方法会进行响应从而得到调用。如果返回false,就分两钟情况:(1)我们是点击在它的子控键上进行滑动时,比如我们是点击在桌面的图标上进行左右滑动的,workspace则会把Touch事件分发给它的子控件。(2)而如果仅仅是点击到桌面的空白出Touch事件就不会发生响应。

在我们手指第一次触摸到屏幕时,首先会对onInterceptTouchEvent中的事件进行判断,如果是按下事件(MotionEvent.ACTION_DOWN), 则会记录按下时的X坐标、Y坐标等等数据,同时改变现在Workspace的状态为滚动状态(OUCH_STATE_SCROLLING),这时会返回ture,把事件交给onTouchEvent函数来处理,onTouchEvent中同样会对事件类型进行判断,当事件方法为(otionEvent.ACTION_DOWN)的时候,就可以开始显示滚动的指示条了(就是Hotseat上显示第几屏的屏点)。当我们按着屏幕不放进行滑动的时候,又会在onInterceptTouchEvent进行事件拦截,但是现在的事件类型变为了 MotionEvent.ACTION_MOVE,因为是移动的操作,所以会在拦截的时候取消桌面长按的事件的响应,同时转到onTouchEvent中对ACTION_MOVE事件的响应中,判断我们移动了多少距离,使用scrollBy方法来对桌面进行移动,并刷新屏幕。最后我们放开手后会触发onTouchEvent中的MotionEvent.ACTION_UP事件,这时会根据滑动的情况来判断是朝左滑动还是朝右滑动,如果手指只滑动了屏幕宽度的少一半距离,则会弹回原来的页面,滑动多于屏幕宽度的一半则会进行翻页。同时要注意无论在什么情况下触发了WorkSpace滑动的事件,则系统会不断调用computeScroll()方法,我们重写这个方法同时在这个方法中调用刷新界面等操作。

滑动过程中所要注意的主要方法如下,具体见代码注释。

        
  1. //对Touch事件进行拦截主要用于在拦截各种Touch事件时,设置mTouchState的各种状态
  2. @Override
  3. publicbooleanonInterceptTouchEvent(MotionEventev){
  4. /*
  5. *ThismethodJUSTdetermineswhetherwewanttointerceptthemotion.
  6. *Ifwereturntrue,onTouchEventwillbecalledandwedotheactual
  7. *scrollingthere.
  8. *这个方法仅仅决定了我们是否愿意去对滑动事件进行拦截,如果返回为true,则会调用onTouchEvent我们将会在那里进行事件处理
  9. */
  10. //对滑动的速率进行跟踪。
  11. acquireVelocityTrackerAndAddMovement(ev);
  12. //Skiptouchhandlingiftherearenopagestoswipe
  13. //如果没有页面,则跳过操作。
  14. if(getChildCount()<=0)returnsuper.onInterceptTouchEvent(ev);
  15. /*
  16. *Shortcutthemostrecurringcase:theuserisinthedragging
  17. *stateandheismovinghisfinger.Wewanttointerceptthis
  18. *motion.
  19. *shortcut最常见的情况是:用户处于拖动的状态下,同时在移动它的手指,这时候我们需要拦截这个动作。
  20. *
  21. */
  22. finalintaction=ev.getAction();
  23. //如果是在MOVE的情况下,则进行Touch事件拦截
  24. if((action==MotionEvent.ACTION_MOVE)&&
  25. (mTouchState==TOUCH_STATE_SCROLLING)){
  26. returntrue;
  27. }
  28. switch(action&MotionEvent.ACTION_MASK){
  29. caseMotionEvent.ACTION_MOVE:{
  30. /*
  31. *mIsBeingDragged==false,otherwisetheshortcutwouldhavecaughtit.Check
  32. *whethertheuserhasmovedfarenoughfromhisoriginaldowntouch.
  33. *如果mIsBeingDragged==false,否则快捷方式应该捕获到该事件,检查一下用户从它点击的地方位移是否足够
  34. */
  35. if(mActivePointerId!=INVALID_POINTER){
  36. //根据移动的距离判断是翻页还是移动一段位移,同时设置lastMotionX或者mTouchState这些值。同时取消桌面长按事件。
  37. determineScrollingStart(ev);
  38. break;
  39. }
  40. //ifmActivePointerIdisINVALID_POINTER,thenwemusthavemissedanACTION_DOWN
  41. //event.inthatcase,treatthefirstoccurenceofamoveeventasaACTION_DOWN
  42. //i.e.fallthroughtothenextcase(don'tbreak)
  43. //(WesometimesmissACTION_DOWNeventsinWorkspacebecauseitignoresallevents
  44. //whileit'ssmall-thiswascausingacrashbeforewecheckedforINVALID_POINTER)
  45. //如果mActivePointerId是INVALID_POINTER,这时候我们应该已经错过了ACTION_DOWN事件。在这种情况下,把
  46. //第一次发生移动的事件当作ACTION——DOWN事件,直接进入下一个情况下。
  47. //我们有时候会错过workspace中的ACTION_DOWN事件,因为在workspace变小的时候会忽略掉所有的事件。
  48. }
  49. caseMotionEvent.ACTION_DOWN:{
  50. finalfloatx=ev.getX();
  51. finalfloaty=ev.getY();
  52. //Rememberlocationofdowntouch
  53. //记录按下的位置
  54. mDownMotionX=x;
  55. mLastMotionX=x;
  56. mLastMotionY=y;
  57. mLastMotionXRemainder=0;
  58. mTotalMotionX=0;
  59. //Returnthepointeridentifierassociatedwithaparticularpointerdataindexisthisevent.
  60. //Theidentifiertellsyoutheactualpointernumberassociatedwiththedata,
  61. //accountingforindividualpointersgoingupanddownsincethestartofthecurrentgesture.
  62. //返回和这个事件关联的触点数据id,计算单独点的id会上下浮动,因为手势的起始位置挥发声改变。
  63. mActivePointerId=ev.getPointerId(0);
  64. mAllowLongPress=true;
  65. /*
  66. *Ifbeingflingedandusertouchesthescreen,initiatedrag;
  67. *otherwisedon't.mScroller.isFinishedshouldbefalsewhen
  68. *beingflinged.
  69. *如果被拖动同时用户触摸到了屏幕,就开始初始化拖动,否则便不会。
  70. *当拖动完成后mScroller.isFinished就应该设置为false.
  71. *
  72. */
  73. finalintxDist=Math.abs(mScroller.getFinalX()-mScroller.getCurrX());
  74. finalbooleanfinishedScrolling=(mScroller.isFinished()||xDist<mTouchSlop);
  75. if(finishedScrolling){
  76. //标记为TOUCH_STATE_REST状态
  77. mTouchState=TOUCH_STATE_REST;
  78. //取消滚动动画
  79. mScroller.abortAnimation();
  80. }else{
  81. //状态为TOUCH_STATE_SCROLLING
  82. mTouchState=TOUCH_STATE_SCROLLING;
  83. }
  84. //checkifthiscanbethebeginningofataponthesideofthepages
  85. //toscrollthecurrentpage
  86. //检测此事件是不是开始于点击页面的边缘来对当前页面进行滚动。
  87. if(mTouchState!=TOUCH_STATE_PREV_PAGE&&mTouchState!=TOUCH_STATE_NEXT_PAGE){
  88. if(getChildCount()>0){
  89. //根据触点的点位来判断是否点击到上一页,从而更新相应的状态
  90. if(hitsPreviousPage(x,y)){
  91. mTouchState=TOUCH_STATE_PREV_PAGE;
  92. }elseif(hitsNextPage(x,y)){
  93. mTouchState=TOUCH_STATE_NEXT_PAGE;
  94. }
  95. }
  96. }
  97. break;
  98. }
  99. caseMotionEvent.ACTION_UP:
  100. caseMotionEvent.ACTION_CANCEL:
  101. //触点不被相应时,所做的动作
  102. mTouchState=TOUCH_STATE_REST;
  103. mAllowLongPress=false;
  104. mActivePointerId=INVALID_POINTER;
  105. //释放速率跟踪
  106. releaseVelocityTracker();
  107. break;
  108. caseMotionEvent.ACTION_POINTER_UP:
  109. onSecondaryPointerUp(ev);
  110. releaseVelocityTracker();
  111. break;
  112. }
  113. /*
  114. *Theonlytimewewanttointerceptmotioneventsisifweareinthe
  115. *dragmode.
  116. *我们唯一会去对移动事件进行拦截的情况时我们在拖动模式下
  117. */
  118. if(DEBUG)Log.d(TAG,"onInterceptTouchEvent"+(mTouchState!=TOUCH_STATE_REST));
  119. //只要是mTouchState的状态不为TOUCH_STATE_REST,那么就进行事件拦截
  120. returnmTouchState!=TOUCH_STATE_REST;
  121. }

onTouchEvent方法,详细见代码注释:

        
  1. @Override
  2. publicbooleanonTouchEvent(MotionEventev){
  3. //Skiptouchhandlingiftherearenopagestoswipe
  4. //如果没有子页面,就直接跳过
  5. if(getChildCount()<=0)returnsuper.onTouchEvent(ev);
  6. acquireVelocityTrackerAndAddMovement(ev);
  7. finalintaction=ev.getAction();
  8. switch(action&MotionEvent.ACTION_MASK){
  9. caseMotionEvent.ACTION_DOWN:
  10. /*
  11. *Ifbeingflingedandusertouches,stopthefling.isFinished
  12. *willbefalseifbeingflinged.
  13. *如果在滑动的过程中下用户又点击桌面,则取消滑动,从而响应当前的点击。
  14. *在滑动的isFinished将返回false.
  15. */
  16. if(!mScroller.isFinished()){
  17. mScroller.abortAnimation();
  18. }
  19. //Rememberwherethemotioneventstarted
  20. mDownMotionX=mLastMotionX=ev.getX();
  21. mLastMotionXRemainder=0;
  22. mTotalMotionX=0;
  23. mActivePointerId=ev.getPointerId(0);
  24. //主要用来显示滚动条,表明要开始滚动了,这里可以进行调整,滚动条时逐渐显示还是立刻显示。
  25. if(mTouchState==TOUCH_STATE_SCROLLING){
  26. pageBeginMoving();
  27. }
  28. break;
  29. caseMotionEvent.ACTION_MOVE:
  30. if(mTouchState==TOUCH_STATE_SCROLLING){
  31. //Scrolltofollowthemotionevent
  32. finalintpointerIndex=ev.findPointerIndex(mActivePointerId);
  33. finalfloatx=ev.getX(pointerIndex);
  34. finalfloatdeltaX=mLastMotionX+mLastMotionXRemainder-x;
  35. //总共移动的距离
  36. mTotalMotionX+=Math.abs(deltaX);
  37. //OnlyscrollandupdatemLastMotionXifwehavemovedsomediscreteamount.We
  38. //keeptheremainderbecauseweareactuallytestingifwe'vemovedfromthelast
  39. //scrolledposition(whichisdiscrete).
  40. //如果我们移动了一小段距离,我们则移动和更新mLastMotionX。我们保存Remainder变量是因为会检测我们
  41. //是否是从最后的滚动点位移动的。
  42. if(Math.abs(deltaX)>=1.0f){
  43. mTouchX+=deltaX;
  44. mSmoothingTime=System.nanoTime()/NANOTIME_DIV;
  45. if(!mDeferScrollUpdate){
  46. scrollBy((int)deltaX,0);
  47. if(DEBUG)Log.d(TAG,"onTouchEvent().Scrolling:"+deltaX);
  48. }else{
  49. invalidate();
  50. }
  51. mLastMotionX=x;
  52. mLastMotionXRemainder=deltaX-(int)deltaX;
  53. }else{
  54. //Triggerthescrollbarstodraw.Wheninvokedthismethodstartsananimationtofadethe
  55. //scrollbarsoutafteradefaultdelay.Ifasubclassprovidesanimatedscrolling,
  56. //thestartdelayshouldequalthedurationofthescrollinganimation.
  57. //触发scrollbar进行绘制。使用这个方法来启动一个动画来使scrollbars经过一段时间淡出。如果子类提供了滚动的动画,则
  58. //延迟的时间等于动画滚动的时间。
  59. awakenScrollBars();
  60. }
  61. }else{
  62. determineScrollingStart(ev);
  63. }
  64. break;
  65. caseMotionEvent.ACTION_UP:
  66. if(mTouchState==TOUCH_STATE_SCROLLING){
  67. finalintactivePointerId=mActivePointerId;
  68. finalintpointerIndex=ev.findPointerIndex(activePointerId);
  69. finalfloatx=ev.getX(pointerIndex);
  70. finalVelocityTrackervelocityTracker=mVelocityTracker;
  71. velocityTracker.computeCurrentVelocity(1000,mMaximumVelocity);
  72. intvelocityX=(int)velocityTracker.getXVelocity(activePointerId);
  73. finalintdeltaX=(int)(x-mDownMotionX);
  74. finalintpageWidth=getScaledMeasuredWidth(getPageAt(mCurrentPage));
  75. //屏幕的宽度*0.4f
  76. booleanisSignificantMove=Math.abs(deltaX)>pageWidth*
  77. SIGNIFICANT_MOVE_THRESHOLD;
  78. finalintsnapVelocity=mSnapVelocity;
  79. mTotalMotionX+=Math.abs(mLastMotionX+mLastMotionXRemainder-x);
  80. booleanisFling=mTotalMotionX>MIN_LENGTH_FOR_FLING&&
  81. Math.abs(velocityX)>snapVelocity;
  82. //Inthecasethatthepageismovedfartoonedirectionandthenisflung
  83. //intheoppositedirection,weuseathresholdtodeterminewhetherweshould
  84. //justreturntothestartingpage,orifweshouldskiponefurther.
  85. //这钟情况是页面朝一个方向移动了一段距离,然后又弹回去了。我们使用一个阀值来判断是进行翻页还是返回到初始页面
  86. booleanreturnToOriginalPage=false;
  87. if(Math.abs(deltaX)>pageWidth*RETURN_TO_ORIGINAL_PAGE_THRESHOLD&&
  88. Math.signum(velocityX)!=Math.signum(deltaX)&&isFling){
  89. returnToOriginalPage=true;
  90. }
  91. intfinalPage;
  92. //Wegiveflingsprecedenceoverlargemoves,whichiswhyweshort-circuitour
  93. //testforalargemoveifaflinghasbeenregistered.Thatis,alarge
  94. //movetotheleftandflingtotherightwillregisterasaflingtotheright.
  95. //朝右移动
  96. if(((isSignificantMove&&deltaX>0&&!isFling)||
  97. (isFling&&velocityX>0))&&mCurrentPage>0){
  98. finalPage=returnToOriginalPage?mCurrentPage:mCurrentPage-1;
  99. snapToPageWithVelocity(finalPage,velocityX);
  100. //朝左移动
  101. }elseif(((isSignificantMove&&deltaX<0&&!isFling)||
  102. (isFling&&velocityX<0))&&
  103. mCurrentPage<getChildCount()-1){
  104. finalPage=returnToOriginalPage?mCurrentPage:mCurrentPage+1;
  105. snapToPageWithVelocity(finalPage,velocityX);
  106. //寻找离屏幕中心最近的页面移动
  107. }else{
  108. snapToDestination();
  109. }
  110. }
  111. //直接移动到前一页
  112. elseif(mTouchState==TOUCH_STATE_PREV_PAGE){
  113. //atthispointwehavenotmovedbeyondthetouchslop
  114. //(otherwisemTouchStatewouldbeTOUCH_STATE_SCROLLING),so
  115. //wecanjustpage
  116. intnextPage=Math.max(0,mCurrentPage-1);
  117. if(nextPage!=mCurrentPage){
  118. snapToPage(nextPage);
  119. }else{
  120. snapToDestination();
  121. }
  122. }
  123. //直接移动到下一页
  124. elseif(mTouchState==TOUCH_STATE_NEXT_PAGE){
  125. //atthispointwehavenotmovedbeyondthetouchslop
  126. //(otherwisemTouchStatewouldbeTOUCH_STATE_SCROLLING),so
  127. //wecanjustpage
  128. intnextPage=Math.min(getChildCount()-1,mCurrentPage+1);
  129. if(nextPage!=mCurrentPage){
  130. snapToPage(nextPage);
  131. }else{
  132. snapToDestination();
  133. }
  134. }else{
  135. onUnhandledTap(ev);
  136. }
  137. mTouchState=TOUCH_STATE_REST;
  138. mActivePointerId=INVALID_POINTER;
  139. releaseVelocityTracker();
  140. break;
  141. //对事件不响应
  142. caseMotionEvent.ACTION_CANCEL:
  143. if(mTouchState==TOUCH_STATE_SCROLLING){
  144. snapToDestination();
  145. }
  146. mTouchState=TOUCH_STATE_REST;
  147. mActivePointerId=INVALID_POINTER;
  148. releaseVelocityTracker();
  149. break;
  150. caseMotionEvent.ACTION_POINTER_UP:
  151. onSecondaryPointerUp(ev);
  152. break;
  153. }
  154. returntrue;
  155. }

最后有个小知识点要搞清楚,不少网友都问到过我。就是scrollTo和scrollBy的区别。我们查看View类的源代码如下所示,mScrollX记录的是当前View针对屏幕坐标在水平方向上的偏移量,而mScrollY则是记录的时当前View针对屏幕在竖值方向上的偏移量。

从以下代码我们可以得知,scrollTo就是把View移动到屏幕的X和Y位置,也就是绝对位置。而scrollBy其实就是调用的 scrollTo,但是参数是当前mScrollX和mScrollY加上X和Y的位置,所以ScrollBy调用的是相对于mScrollX和mScrollY的位置。我们在上面的代码中可以看到当我们手指不放移动屏幕时,就会调用scrollBy来移动一段相对的距离。而当我们手指松开后,会调用 mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); 来产生一段动画来移动到相应的页面,在这个过程中系统回不断调用computeScroll(),我们再使用scrollTo来把View移动到当前Scroller所在的绝对位置。

        
  1. /**
  2. *Setthescrolledpositionofyourview.Thiswillcauseacallto
  3. *{@link#onScrollChanged(int,int,int,int)}andtheviewwillbe
  4. *invalidated.
  5. *@paramxthexpositiontoscrollto
  6. *@paramytheypositiontoscrollto
  7. */
  8. publicvoidscrollTo(intx,inty){
  9. if(mScrollX!=x||mScrollY!=y){
  10. intoldX=mScrollX;
  11. intoldY=mScrollY;
  12. mScrollX=x;
  13. mScrollY=y;
  14. invalidateParentCaches();
  15. onScrollChanged(mScrollX,mScrollY,oldX,oldY);
  16. if(!awakenScrollBars()){
  17. invalidate(true);
  18. }
  19. }
  20. }
  21. /**
  22. *Movethescrolledpositionofyourview.Thiswillcauseacallto
  23. *{@link#onScrollChanged(int,int,int,int)}andtheviewwillbe
  24. *invalidated.
  25. *@paramxtheamountofpixelstoscrollbyhorizontally
  26. *@paramytheamountofpixelstoscrollbyvertically
  27. */
  28. publicvoidscrollBy(intx,inty){
  29. scrollTo(mScrollX+x,mScrollY+y);
  30. }

更多相关文章

  1. Android异步加载源码示例
  2. Android中实现浮动选项框源码
  3. 【记录】Android监听蓝牙耳机的按键事件
  4. Android Studio 查看Android内部隐藏源码
  5. eclipse 开发 android源码
  6. Android onTouch事件解析
  7. [置顶] Android AsyncTask的源码分析
  8. Android 动画之Tween动画详细讲解及java源码实现
  9. 多种方式实现动态替换Android默认桌面Launcher

随机推荐

  1. (android控件)ImageSwitcher介绍和使用说
  2. 魔方动态壁纸android源码下载
  3. android8.0应用崩溃 Only fullscreen opa
  4. Android(安卓)星体动效实现
  5. Android模块化编程——WebView使用之清理
  6. Android(安卓)控件BottomNavigationView
  7. android手机测试中如何查看内存泄露
  8. android 适配华为透明状态栏 ,保留虚拟导
  9. 浅析android下propt怎么通过init进程传递
  10. android开发教程之入门