• 沒有找到結果。

【专家专栏】【专家专栏】【专家专栏】【专家专栏】

N/A
N/A
Protected

Academic year: 2022

Share "【专家专栏】【专家专栏】【专家专栏】【专家专栏】"

Copied!
30
0
0

加載中.... (立即查看全文)

全文

(1)

【专家专栏】Android 4.0 Launcher 源码分析系列(一)...1

【专家专栏】Android 4.0 Launcher 源码分析系列(二)... 13

【专家专栏】Android 4.0 Launcher 源码分析系列(三)... 20

【专家专栏】

【专家专栏】 【专家专栏】 【专家专栏】Android Android Android Android 4.0 4.0 4.0 4.0 Launcher Launcher Launcher Launcher 源码分析系列 源码分析系列 源码分析系列 源码分析系列((((一 一))))

2012-01-11 16:02 傻蛋 51CTO.com 我要评论(2) 字号:TTTT |

T T T T

著名手机厂商 Android 开发工程师、最牛网站长傻蛋曾为51CTO 撰稿《Android 4.0的图形硬件 加速及绘制技巧》受到读者的广泛欢迎。傻蛋同学将在新的一年里在 51CTO 开设专家专栏,本 文为傻蛋正在研究的一个方向,与网友共同探讨 Android 4.0原生的桌面程序——Launcher。

AD:

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

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

(2)

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

(3)
(4)

点击查看大图

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

1 <com.android.launcher2.DragLayer<com.android.launcher2.DragLayer<com.android.launcher2.DragLayer<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

5 android:id="@+id/drag_layer"

6 android:layout_width="match_parent"

7 android:layout_height="match_parent">>>>

8

9 <!-- Keep these behind the workspace so that they are not visible when 10 we go into AllApps -->>>>

11 <include<include<include<include

12 android:id="@+id/dock_divider"

13 layout="@layout/workspace_divider"

14 android:layout_width="match_parent"

15 android:layout_height="wrap_content"

16 android:layout_marginBottom="@dimen/button_bar_height"

17 android:layout_gravity="bottom" />/>/>/>

18 <include<include<include<include

19 android:id="@+id/paged_view_indicator"

20 layout="@layout/scroll_indicator"

21 android:layout_width="wrap_content"

22 android:layout_height="wrap_content"

23 android:layout_gravity="bottom"

24 android:layout_marginBottom="@dimen/button_bar_height" />/>/>/>

25

26 <!-- The workspace contains 5 screens of cells -->

27 <com.android.launcher2.Workspace<com.android.launcher2.Workspace<com.android.launcher2.Workspace<com.android.launcher2.Workspace 28 android:id="@+id/workspace"

29 android:layout_width="match_parent"

30 android:layout_height="match_parent"

31 android:paddingTop="@dimen/qsb_bar_height_inset"

32 android:paddingBottom="@dimen/button_bar_height"

33 launcher:defaultScreen="2"

34 launcher:cellCountX="4"

35 launcher:cellCountY="4"

36 launcher:pageSpacing="@dimen/workspace_page_spacing"

37

launcher:scrollIndicatorPaddingLeft="@dimen/workspace_divider_padding_left"

38

launcher:scrollIndicatorPaddingRight="@dimen/workspace_divider_padding_right

">>>>

39

(5)

40 <include<include<include<include android:id="@+id/cell1" layout="@layout/workspace_screen"

/>

/>

/>

/>

41 <include<include<include<include android:id="@+id/cell2" layout="@layout/workspace_screen"

/>

/>

/>

/>

42 <include<include<include<include android:id="@+id/cell3" layout="@layout/workspace_screen"

/>

/>

/>

/>

43 <include<include<include<include android:id="@+id/cell4" layout="@layout/workspace_screen"

/>

/>

/>

/>

44 <include<include<include<include android:id="@+id/cell5" layout="@layout/workspace_screen"

/>

/>

/>

/>

45 </com.android.launcher2.Workspace></com.android.launcher2.Workspace></com.android.launcher2.Workspace></com.android.launcher2.Workspace>

46

47 <include<include<include<include layout="@layout/hotseat"

48 android:id="@+id/hotseat"

49 android:layout_width="match_parent"

50 android:layout_height="@dimen/button_bar_height_plus_padding"

51 android:layout_gravity="bottom" />/>/>/>

52

53 <include<include<include<include

54 android:id="@+id/qsb_bar"

55 layout="@layout/qsb_bar" />/>/>/>

56

57 <include<include<include<include layout="@layout/apps_customize_pane"

58 android:id="@+id/apps_customize_pane"

59 android:layout_width="match_parent"

60 android:layout_height="match_parent"

61 android:visibility="invisible" />/>/>/>

62

63 <include<include<include<include layout="@layout/workspace_cling"

64 android:id="@+id/workspace_cling"

65 android:layout_width="match_parent"

66 android:layout_height="match_parent"

67 android:visibility="gone" />/>/>/>

68

69 <include<include<include<include layout="@layout/folder_cling"

70 android:id="@+id/folder_cling"

71 android:layout_width="match_parent"

72 android:layout_height="match_parent"

73 android:visibility="gone" />/>/>/>

74 </com.android.launcher2.DragLayer></com.android.launcher2.DragLayer></com.android.launcher2.DragLayer></com.android.launcher2.DragLayer>

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

其实 dock_divider 就是 dock 区域上面的那条直线。

(6)

再下面是 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 和文件夹。

(7)
(8)

点击查看大图

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

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

点击查看大图

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

(9)

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

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

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

(10)
(11)

点击查看大图

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

如下图所示:

点击查看大图

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

如下图所示:

(12)

点击查看大图

(13)

workspace_cling 和 folder_cling 是刚刷完机后,进入桌面时,显示的使用向导界面,

介绍怎么使用 workspace 和 folder,跳过以后就再也不会出现了,这里就不截图了。

【51CTO.com 独家特稿,非经授权谢绝转载,合作媒体转载请注明原文作者及出处!】

【编辑推荐】

75

【专家专栏】Android 4.0 Launcher 源码分析系列(二)

76

【专家专栏】Android 4.0 Launcher 源码分析系列(三)

77

Android 4.0的图形硬件加速及绘制技巧

【专家专栏】

【专家专栏】 【专家专栏】 【专家专栏】Android Android Android Android 4.0 4.0 4.0 4.0 Launcher Launcher Launcher Launcher 源码分析系列 源码分析系列 源码分析系列 源码分析系列((((二 二))))

2012-02-02 15:56 傻蛋 51CTO.com 我要评论(0) 字号:TTTT |

T T T T

本文为笔名“傻蛋”的51CTO 特约专家的《Android 4.0 Launcher 源码分析系列》专栏的第二 篇,这一节傻蛋将从整个 Laucher 的入口点入手,同时 Laucher 在加载了它的布局文 件 Laucher.xml 时都干了些什么。

AD:

【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">

(14)

首 先 通 过 android:name 指 定 了 整 个 Launcher 的 Application 也 就 是 入 口 是 在 com.android.launcher2.LauncherApplication 这个路径下,android:lable 指定了桌面的名字 是叫 Launcher,如果要改名字就改 values 文件夹的 string.xml 中的相应属性就可以了。

android:icon 指定了 Laucher 的图标,这个图标可以在应用程序管理器中看见,如下图所示,

是个可爱机器人住在一个小房子里面,如果需要更改 Laucher 的图片,重新设置这个属性就可 以了。

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

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

7 <bool<bool<bool<bool name="config_hardwareAccelerated">>>>true</bool></bool></bool></bool>

8 <bool<bool<bool<bool name="config_largeHeap">>>>false</bool></bool></bool></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() 方法,同样是启动桌面时的一系列初始化工作。

(15)

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

9 ifififif (PROFILE_STARTUP) {

10 android.os.Debug.startMethodTracing(

11 Environment.getDataDirectory() +

"/data/com.android.launcher/launcher");

12 }

13 ifififif (PROFILE_STARTUP) {

14 android.os.Debug.stopMethodTracing();

15 }

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

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

点击查看大图

(16)

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

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

可以加入其他控件。

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

16 @Override

17 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 18 if (!mIsDataReady) {

19 super.onMeasure(widthMeasureSpec, heightMeasureSpec);

20 return;

21 }

22 //得到宽度的模式(在配置文件中对应的是 match_parent 或者 wrap_content)和其 大小

23 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

24 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);

25 //宽度必须是 match_parent,否则会抛出异常。

(17)

26 if (widthMode != MeasureSpec.EXACTLY) {

27 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");

28 }

29

30 /* Allow the height to be set as WRAP_CONTENT. This allows the particular case

31 * of the All apps view on XLarge displays to not take up more space then it needs. Width

32 * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect

33 * each page to have the same width.

34 */

35 //高度允许是 wrap_content,因为在大屏幕的情况下,会占了多余的位置

36 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

37 int heightSize = MeasureSpec.getSize(heightMeasureSpec);

38 int maxChildHeight = 0;

39 //得到在竖值方向上和水平方向上的 Padding

40 final int verticalPadding = mPaddingTop + mPaddingBottom;

41 final int horizontalPadding = mPaddingLeft + mPaddingRight;

42 43

44 // The children are given the same width and height as the workspace 45 // unless they were set to WRAP_CONTENT

46 if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize + "mPaddingTop="+mPaddingTop + " mPaddingBottom="+mPaddingBottom);

47 final int childCount = getChildCount();

48 //对 workspace 的子 View 进行遍历,从而对它的几个子 view 进行测量。

49 for (int i = 0; i <<<< childCountchildCountchildCountchildCount; i++) {

50 // disallowing padding in paged view (just pass 0) 51 final View child = getPageAt(i);

52 final LayoutParams lp = (LayoutParams) child.getLayoutParams();

53

54 int childWidthMode;

55 if (lp.width == LayoutParams.WRAP_CONTENT) { 56 childWidthMode = MeasureSpec.AT_MOST;

57 } else {

58 childWidthMode = MeasureSpec.EXACTLY;

59 }

60

61 int childHeightMode;

62 if (lp.height == LayoutParams.WRAP_CONTENT) { 63 childHeightMode = MeasureSpec.AT_MOST;

64 } else {

65 childHeightMode = MeasureSpec.EXACTLY;

66 }

67

68 final int childWidthMeasureSpec =

(18)

69 MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);

70 final int childHeightMeasureSpec =

71 MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);

72 //对子 View 的大小进行设置,传入 width 和 height 参数

73 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

74 maxChildHeight = Math.max(maxChildHeight,

child.getMeasuredHeight());

75 if (DEBUG) Log.d(TAG, "\tmeasure-child" + i + ": " + child.getMeasuredWidth() + ", "

76 + child.getMeasuredHeight());

77 }

78

79 if (heightMode == MeasureSpec.AT_MOST) {

80 heightSize = maxChildHeight + verticalPadding;

81 }

82 //存储测量后的宽度和高度

83 setMeasuredDimension(widthSize, heightSize);

84

85 // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.

86 // We also wait until we set the measured dimensions before flushing the cache as well, to

87 // ensure that the cache is filled with good values.

88 invalidateCachedOffsets();

89 updateScrollingIndicatorPosition();

90

91 if (childCount >>>> 0) {

92 mMaxScrollX = getChildOffset(childCount - 1) -

getRelativeChildOffset(childCount - 1);

93 } else {

94 mMaxScrollX = 0;

95 }

96 }

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

97 @Override

98 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

99 if (!mIsDataReady) {

100 return;

101 } 102

103 if (DEBUG) Log.d(TAG, "PagedView.onLayout()");

(19)

104 //竖值方向的 Padding

105 final int verticalPadding = mPaddingTop + mPaddingBottom;

106 final int childCount = getChildCount();

107 int childLeft = 0;

108 if (childCount >>>> 0) {

109 if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", "

110 + getChildWidth(0));

111 childLeft = getRelativeChildOffset(0);

112 //偏移量为0

113 if (DEBUG) Log.d(TAG, "childLeft:"+childLeft);

114

115 // Calculate the variable page spacing if necessary

116 // 如果 mPageSpacing 小于0的话,就重新计算 mPageSpacing,并且给它赋值。

117 if (mPageSpacing <<<< 0000) {

118 setPageSpacing(((right - left) -

getChildAt(0).getMeasuredWidth()) / 2);

119 }

120 } 121

122 for (int i = 0; i <<<< childCountchildCountchildCountchildCount; i++) { 123 final View child = getPageAt(i);

124 if (child.getVisibility() != View.GONE) {

125 final int childWidth = getScaledMeasuredWidth(child);

126 final int childchildHeight = child.getMeasuredHeight();

127 int childTop = mPaddingTop;

128 if (mCenterPagesVertically) {

129 childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2;

130 }

131

132 if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);

133 //把5个 CellLayout 布局到相应的位置,layout 的4个参数分别是 左、上、右、

下。

134 child.layout(childLeft, childTop,

135 childLeft + child.getMeasuredWidth(), childTop + childHeight);

136 childLeft += childWidth + mPageSpacing;

137 }

138 }

139 //第一次布局完毕之后,就根据当前页偏移量(当前页距离 Workspace 最左边的距离)滚

动到默认的页面去,第一次布局时

140 //默认的当前页是3,则它的便宜量就是两个 CellLayout 的宽度。

141 if (mFirstLayout && mCurrentPage>>>>= 0 && mCurrentPage<<<< getChildCountgetChildCountgetChildCountgetChildCount()) {

142 setHorizontalScrollBarEnabled(false);

(20)

143 int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);

144 //滚动到指定的位置

145 scrollTo(newX, 0);

146 mScroller.setFinalX(newX);

147 if (DEBUG) Log.d(TAG, "newX is "+newX);

148 setHorizontalScrollBarEnabled(true);

149 mFirstLayout = false;

150 } 151

152 if (mFirstLayout && mCurrentPage>>>>= 0 && mCurrentPage<<<< getChildCountgetChildCountgetChildCountgetChildCount()) {

153 mFirstLayout = false;

154 } 155 }

【51CTO.com 独家特稿,非经授权谢绝转载,合作媒体转载请注明原文作者及出处!】

【编辑推荐】

156

【专家专栏】Android 4.0 Launcher 源码分析系列(一)

157

【专家专栏】Android 4.0 Launcher 源码分析系列(三)

158

Android 4.0的图形硬件加速及绘制技巧

159

与 iOS 的较量 Android 4.0功能评测(图)

160

最新翻译:Android 4.0设计规范 十大界面改变

专家专栏】

专家专栏】 专家专栏】 专家专栏】Android Android Android Android 4.0 4.0 4.0 4.0 Launcher Launcher Launcher Launcher 源码分析系列 源码分析系列 源码分析系列 源码分析系列((((三 三))))

2012-02-13 12:47 傻蛋 51CTO.com 我要评论(0) 字号:TTTT |

T T T T

本文为笔名“傻蛋”的51CTO 特约专家的《Android 4.0 Launcher 源码分析系列》专栏的第三 篇,这一篇傻蛋将带我们研究一下整个桌面的左右滑动是如何实现的。

AD:

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

(21)

点击查看大图

桌面的左右滑动功能主要是在 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 事件的响应中,

(22)

判断我们移动了多少距离,使用 scrollBy 方法来对桌面进行移动,并刷新屏幕。最后我们放开 手后会触发 onTouchEvent 中的 MotionEvent.ACTION_UP 事件,这时会根据滑动的情况来判断是 朝左滑动还是朝右滑动,如果手指只滑动了屏幕宽度的少一半距离,则会弹回原来的页面,滑 动多于屏幕宽度的一半则会进行翻页。同时要注意无论在什么情况下触发了 WorkSpace 滑动的 事件,则系统会不断调用 computeScroll()方法,我们重写这个方法同时在这个方法中调用刷 新界面等操作。

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

1 //对 Touch 事件进行拦截 主要用于在拦截各种 Touch 事件时,设置 mTouchState 的各种状态 2 @Override

3 publicpublicpublicpublic booleanbooleanbooleanboolean onInterceptTouchEvent(MotionEvent ev) { 4 /*

5 * This method JUST determines whether we want to intercept the motion.

6 * If we return true, onTouchEvent will be called and we do the actual 7 * scrolling there.

8 * 这 个方 法仅 仅决 定了 我们 是否 愿意 去对 滑动 事件 进行 拦截 ,如 果返 回为 true,则 会调 用 onTouchEvent 我们将会在那里进行事件处理

9 */

10 //对滑动的速率进行跟踪。

11

12 acquireVelocityTrackerAndAddMovement(ev);

13

14 // Skip touch handling if there are no pages to swipe 15 // 如果没有页面,则跳过操作。

16 ifififif (getChildCount() <= 0) returnreturnreturnreturn supersupersupersuper.onInterceptTouchEvent(ev);

17

18 /*

19 * Shortcut the most recurring case: the user is in the dragging 20 * state and he is moving his finger. We want to intercept this 21 * motion.

22 * shortcut 最常见的情况是:用户处于拖动的状态下,同时在移动它的手指,这时候我们需 要拦截这个动作。

23 * 24 */

25 finalfinalfinalfinal intintintint action = ev.getAction();

26 //如果是在 MOVE 的情况下,则进行 Touch 事件拦截 27 ifififif ((action == MotionEvent.ACTION_MOVE) &&

28 (mTouchState == TOUCH_STATE_SCROLLING)) { 29 returnreturnreturnreturn truetruetruetrue;

30 } 31

32 switchswitchswitchswitch (action & MotionEvent.ACTION_MASK) { 33 casecasecasecase MotionEvent.ACTION_MOVE: {

(23)

34 /*

35 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check

36 * whether the user has moved far enough from his original down touch.

37 * 如果 mIsBeingDragged==false ,否则快捷方式应该捕获到该事件,检查一下用 户从它点击的地方位移是否足够

38 */

39 ifififif (mActivePointerId != INVALID_POINTER) {

40 //根据移动的距离判断是翻页还是移动一段位移,同时设置 lastMotionX 或者

mTouchState 这些值。同时取消桌面长按事件。

41 determineScrollingStart(ev);

42 breakbreakbreakbreak;

43 }

44 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN

45 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN

46 // i.e. fall through to the next case (don't break)

47 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events

48 // while it's small- this was causing a crash before we checked for INVALID_POINTER)

49 // 如果 mActivePointerId 是 INVALID_POINTER,这时候我们应该已经错过 了 ACTION_DOWN 事件。在这种情况下,把

50 // 第一次发生移动的事件当作 ACTION——DOWN 事件,直接进入下一个情况下。

51 // 我们有时候会错过 workspace 中的 ACTION_DOWN 事件,因为在 workspace 变 小的时候会忽略掉所有的事件。

52 }

53

54 casecasecasecase MotionEvent.ACTION_DOWN: { 55 finalfinalfinalfinal floatfloatfloatfloat x = ev.getX();

56 finalfinalfinalfinal floatfloatfloatfloat y = ev.getY();

57 // Remember location of down touch

58 // 记录按下的位置

59 mDownMotionX = x;

60 mLastMotionX = x;

61 mLastMotionY = y;

62 mLastMotionXRemainder = 0;

63 mTotalMotionX = 0;

64 //Return the pointer identifier associated with a particular pointer data index is this event.

65 //The identifier tells you the actual pointer number associated with the data,

66 //accounting for individual pointers going up and down since the start of the current gesture.

67 //返回和这个事件关联的触点数据 id,计算单独点的 id 会上下浮动,因为手势的起始 位置挥发声改变。

68 mActivePointerId = ev.getPointerId(0);

(24)

69 mAllowLongPress = truetruetruetrue;

70

71 /*

72 * If being flinged and user touches the screen, initiate drag;

73 * otherwise don't. mScroller.isFinished should be false when 74 * being flinged.

75 * 如果被拖动同时用户触摸到了屏幕,就开始初始化拖动,否则便不会。

76 * 当拖动完成后 mScroller.isFinished 就应该设置为 false.

77 *

78 */

79 finalfinalfinalfinal intintintint xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());

80

81 finalfinalfinalfinal booleanbooleanbooleanbooleanfinishedScrolling = (mScroller.isFinished() || xDist

< mTouchSlop);

82 ifififif (finishedScrolling) {

83 //标记为 TOUCH_STATE_REST 状态 84 mTouchState = TOUCH_STATE_REST;

85 //取消滚动动画

86 mScroller.abortAnimation();

87 } elseelseelseelse {

88 //状态为 TOUCH_STATE_SCROLLING

89 mTouchState = TOUCH_STATE_SCROLLING;

90 }

91

92 // check if this can be the beginning of a tap on the side of the pages

93 // to scroll the current page

94 // 检测此事件是不是开始于点击页面的边缘来对当前页面进行滚动。

95 ifififif (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState !=

TOUCH_STATE_NEXT_PAGE) {

96 ifififif (getChildCount() > 0) {

97 //根据触点的点位来判断是否点击到上一页,从而更新相应的状态

98 ifififif (hitsPreviousPage(x, y)) {

99 mTouchState = TOUCH_STATE_PREV_PAGE;

100 } elseelseelseelse ifififif (hitsNextPage(x, y)) {

101 mTouchState = TOUCH_STATE_NEXT_PAGE;

102 }

103 }

104 }

105 breakbreakbreakbreak;

106 }

107

108 casecasecasecase MotionEvent.ACTION_UP:

109 casecasecasecase MotionEvent.ACTION_CANCEL:

110 //触点不被相应时,所做的动作

111 mTouchState = TOUCH_STATE_REST;

112 mAllowLongPress = falsefalsefalsefalse;

(25)

113 mActivePointerId = INVALID_POINTER;

114 //释放速率跟踪

115 releaseVelocityTracker();

116 breakbreakbreakbreak;

117

118 casecasecasecase MotionEvent.ACTION_POINTER_UP:

119 onSecondaryPointerUp(ev);

120 releaseVelocityTracker();

121 breakbreakbreakbreak;

122 } 123 124 /*

125 * The only time we want to intercept motion events is if we are in the 126 * drag mode.

127 * 我们唯一会去对移动事件进行拦截的情况时我们在拖动模式下 128 */

129 ifififif(DEBUG) Log.d(TAG, "onInterceptTouchEvent "+(mTouchState !=

TOUCH_STATE_REST));

130 //只要是 mTouchState 的状态不为 TOUCH_STATE_REST,那么就进行事件拦截 131 returnreturnreturnreturn mTouchState != TOUCH_STATE_REST;

132}

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

133@Override

134publicpublicpublicpublic booleanbooleanbooleanboolean onTouchEvent(MotionEvent ev) {

135 // Skip touch handling if there are no pages to swipe 136 // 如果没有子页面,就直接跳过

137 ifififif (getChildCount() <= 0) returnreturnreturnreturn supersupersupersuper.onTouchEvent(ev);

138

139 acquireVelocityTrackerAndAddMovement(ev);

140

141 finalfinalfinalfinal intintintint action = ev.getAction();

142

143 switchswitchswitchswitch (action & MotionEvent.ACTION_MASK) { 144 casecasecasecase MotionEvent.ACTION_DOWN:

145 /*

146 * If being flinged and user touches, stop the fling. isFinished 147 * will be false if being flinged.

148 * 如果在滑动的过程中下用户又点击桌面,则取消滑动,从而响应当前的点击。

149 * 在滑动的 isFinished 将返回 false.

150 */

151 ifififif (!mScroller.isFinished()) { 152 mScroller.abortAnimation();

153 }

154

155 // Remember where the motion event started 156 mDownMotionX = mLastMotionX = ev.getX();

(26)

157 mLastMotionXRemainder = 0;

158 mTotalMotionX = 0;

159 mActivePointerId = ev.getPointerId(0);

160 //主要用来显示滚动条,表明要开始滚动了,这里可以进行调整,滚动条时逐渐显示还是立

刻显示。

161 ifififif (mTouchState == TOUCH_STATE_SCROLLING) { 162 pageBeginMoving();

163 }

164 breakbreakbreakbreak;

165

166 casecasecasecase MotionEvent.ACTION_MOVE:

167 ifififif (mTouchState == TOUCH_STATE_SCROLLING) { 168 // Scroll to follow the motion event

169 finalfinalfinalfinal intintintint pointerIndex = ev.findPointerIndex(mActivePointerId);

170 finalfinalfinalfinal floatfloatfloatfloat x = ev.getX(pointerIndex);

171 finalfinalfinalfinal floatfloatfloatfloat deltaX = mLastMotionX + mLastMotionXRemainder - x;

172 //总共移动的距离

173 mTotalMotionX += Math.abs(deltaX);

174

175 // Only scroll and update mLastMotionX if we have moved some discrete amount. We

176 // keep the remainder because we are actually testing if we've moved from the last

177 // scrolled position (which is discrete).

178 // 如 果 我 们 移 动 了 一 小 段 距 离 , 我 们 则 移 动 和 更 新 mLastMotionX 。 我 们 保 存 Remainder 变量是因为会检测我们

179

180 //是否是从最后的滚动点位移动的。

181 ifififif (Math.abs(deltaX) >= 1.0f) {

182 mTouchX += deltaX;

183 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;

184 ifififif (!mDeferScrollUpdate) { 185 scrollBy((intintintint) deltaX, 0);

186 ifififif (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);

187 } elseelseelseelse {

188 invalidate();

189 }

190 mLastMotionX = x;

191 mLastMotionXRemainder = deltaX - (intintintint) deltaX;

192 } elseelseelseelse {

193 //Trigger the scrollbars to draw. When invoked this method starts an animation to fade the

194 //scrollbars out after a default delay. If a subclass provides animated scrolling,

195 //the start delay should equal the duration of the scrolling animation.

(27)

196 //触发 scrollbar 进行绘制。 使用这个方法来启动一个动画来使 scrollbars 经过 一段时间淡出。如果子类提供了滚动的动画,则

197 //延迟的时间等于动画滚动的时间。

198 awakenScrollBars();

199 }

200 } elseelseelseelse {

201 determineScrollingStart(ev);

202 }

203 breakbreakbreakbreak;

204

205 casecasecasecase MotionEvent.ACTION_UP:

206 ifififif (mTouchState == TOUCH_STATE_SCROLLING) { 207 finalfinalfinalfinal intintintint activePointerId = mActivePointerId;

208 finalfinalfinalfinal intintintint pointerIndex = ev.findPointerIndex(activePointerId);

209 finalfinalfinalfinal floatfloatfloatfloat x = ev.getX(pointerIndex);

210 finalfinalfinalfinal VelocityTracker velocityTracker = mVelocityTracker;

211 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

212 intintintint velocityX = (intintintint)

velocityTracker.getXVelocity(activePointerId);

213 finalfinalfinalfinal intintintint deltaX = (intintintint) (x - mDownMotionX);

214 finalfinalfinalfinal intintintint pageWidth =

getScaledMeasuredWidth(getPageAt(mCurrentPage));

215 // 屏幕的宽度*0.4f

216 booleanbooleanbooleanboolean isSignificantMove = Math.abs(deltaX) > pageWidth *

217 SIGNIFICANT_MOVE_THRESHOLD;

218 finalfinalfinalfinal intintintint snapVelocity = mSnapVelocity;

219

220 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);

221

222 booleanbooleanbooleanboolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&

223 Math.abs(velocityX) > snapVelocity;

224

225 // In the case that the page is moved far to one direction and then is flung

226 // in the opposite direction, we use a threshold to determine whether we should

227 // just return to the starting page, or if we should skip one further.

228 // 这钟情况是页面朝一个方向移动了一段距离,然后又弹回去了。我们使用一个阀值

来判断是进行翻页还是返回到初始页面

229 booleanbooleanbooleanboolean returnToOriginalPage = falsefalsefalsefalse;

230 ifififif (Math.abs(deltaX) > pageWidth *

RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&

231 Math.signum(velocityX) != Math.signum(deltaX) && isFling) {

232 returnToOriginalPage = truetruetruetrue;

233 }

234

(28)

235 intintintint finalPage;

236 // We give flings precedence over large moves, which is why we short-circuit our

237 // test for a large move if a fling has been registered. That is, a large

238 // move to the left and fling to the right will register as a fling to the right.

239 //朝右移动

240 ifififif (((isSignificantMove && deltaX > 0 && !isFling) ||

241 (isFling && velocityX > 0)) && mCurrentPage > 0) {

242 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;

243 snapToPageWithVelocity(finalPage, velocityX);

244 //朝左移动

245 } elseelseelseelse ifififif (((isSignificantMove && deltaX < 0 && !isFling) ||

246 (isFling && velocityX < 0)) &&

247 mCurrentPage < getChildCount() - 1) {

248 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;

249 snapToPageWithVelocity(finalPage, velocityX);

250 //寻找离屏幕中心最近的页面移动

251 } elseelseelseelse {

252 snapToDestination();

253 }

254 }

255 //直接移动到前一页

256 elseelseelseelse ifififif (mTouchState == TOUCH_STATE_PREV_PAGE) {

257 // at this point we have not moved beyond the touch slop 258 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 259 // we can just page

260 intintintint nextPage = Math.max(0, mCurrentPage - 1);

261 ifififif (nextPage != mCurrentPage) { 262 snapToPage(nextPage);

263 } elseelseelseelse {

264 snapToDestination();

265 }

266 }

267 //直接移动到下一页

268 elseelseelseelse ifififif (mTouchState == TOUCH_STATE_NEXT_PAGE) {

269 // at this point we have not moved beyond the touch slop 270 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 271 // we can just page

272 intintintint nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);

273 ifififif (nextPage != mCurrentPage) { 274 snapToPage(nextPage);

275 } elseelseelseelse {

276 snapToDestination();

277 }

(29)

278 } elseelseelseelse {

279 onUnhandledTap(ev);

280 }

281 mTouchState = TOUCH_STATE_REST;

282 mActivePointerId = INVALID_POINTER;

283 releaseVelocityTracker();

284 breakbreakbreakbreak;

285 //对事件不响应

286 casecasecasecase MotionEvent.ACTION_CANCEL:

287 ifififif (mTouchState == TOUCH_STATE_SCROLLING) { 288 snapToDestination();

289 }

290 mTouchState = TOUCH_STATE_REST;

291 mActivePointerId = INVALID_POINTER;

292 releaseVelocityTracker();

293 breakbreakbreakbreak;

294

295 casecasecasecase MotionEvent.ACTION_POINTER_UP:

296 onSecondaryPointerUp(ev);

297 breakbreakbreakbreak;

298 } 299

300 returnreturnreturnreturn truetruetruetrue;

301}

最后有个小知识点要搞清楚,不少网友都问到过我。就是 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 所在的绝对位置。

302/**

303 * Set the scrolled position of your view. This will cause a call to 304 * {@link #onScrollChanged(int, int, int, int)} and the view will be 305 * invalidated.

306 * @param x the x position to scroll to 307 * @param y the y position to scroll to

(30)

308 */

309 publicpublicpublicpublic voidvoidvoidvoid scrollTo(intintintint x, intintintint y) { 310 ifififif (mScrollX != x || mScrollY != y) { 311 intintintint oldX = mScrollX;

312 intintintint oldY = mScrollY;

313 mScrollX = x;

314 mScrollY = y;

315 invalidateParentCaches();

316 onScrollChanged(mScrollX, mScrollY, oldX, oldY);

317 ifififif (!awakenScrollBars()) { 318 invalidate(truetruetruetrue);

319 }

320 } 321 } 322 /**

323 * Move the scrolled position of your view. This will cause a call to 324 * {@link #onScrollChanged(int, int, int, int)} and the view will be 325 * invalidated.

326 * @param x the amount of pixels to scroll by horizontally 327 * @param y the amount of pixels to scroll by vertically 328 */

329 publicpublicpublicpublic voidvoidvoidvoid scrollBy(intintintint x, intintintint y) { 330 scrollTo(mScrollX + x, mScrollY + y);

331 }

【编辑推荐】

332

Android 开发 Launcher 源码初体验

333

【专家专栏】Android 4.0 Launcher 源码分析系列(一)

334

【专家专栏】Android 4.0 Launcher 源码分析系列(二)

參考文獻

相關文件

In this paper, we have studied a neural network approach for solving general nonlinear convex programs with second-order cone constraints.. The proposed neural network is based on

Numerical results are reported for some convex second-order cone programs (SOCPs) by solving the unconstrained minimization reformulation of the KKT optimality conditions,

For different types of optimization problems, there arise various complementarity problems, for example, linear complementarity problem, nonlinear complementarity problem,

Abstract In this paper, we consider the smoothing Newton method for solving a type of absolute value equations associated with second order cone (SOCAVE for short), which.. 1

 就算身處軍營中,很多男兒都還是相當關心 NBA 自己

Final report to the national forum on information

In case of any disputes regarding any competition(s), and/or other related matters, the decision of the Organiser shall be final, binding and

Provisional Final Draft of the BAFS New Senior Secondary.. Curriculum and