Android 的原生代码中的Launch2的一大功能就是支持左右滑动,但是好像它不能支持循环滑动,初想一下好像比较简单,那就是在获取第几屏的时候取余,差不多就可以实现,但是事实上源码里面会有很多误导,那么我就分享一下我分析Android2.3.3的Launch2源码并实现可以循环滑动桌面:
首先我们去找到代码的位置:。。。。/package/app/Launch2/src/com/android/Lanucher2下是整个桌面的源代码,那么我们要找到桌面滑动的代码,由于实现桌面左右滑动是由workspace来实现的,那么我们可以找到Workspace.java文件,Workspace中有五个CellLayout,分别代表五个分屏。
当左右拖动CellLayout时,就能实现滑动的效果。那么我们就开始看一下Workspace.java:
首先你可以看到:
有这两个变量的定义可能我们会想到一个是当前的屏值一个是下一屏的值,因为我就是这么想的(见笑了!!!),然而实际上mNextScreen却并不是下一屏的值,当然后面会有讲到,这也是看源码难的地方,没有注释,一下子无从下手。
回到我们的源码来,首先我们习惯性的想到先看OnCreate,但是Workspace.java继承的是ViewGroup,不是Activity,但是我们可以找到开始的地方,从其构造函数看,在构造函数里面我们可以看到 initWorkspace(),这个很明显了就是初始化Workspace,在接下来的initWorkspace()函数中我们可以看到:
01
02
03
|
mCurrentScreen = mDefaultScreen;
Launcher.setScreen(mCurrentScreen);
|
也就是初始化为第2屏(0~4),也就是中间那一屏:
起初在文件中看到很多地方有mCurrentScreen和mNextScreen,觉的只要找到怎么获得是第几屏幕就可以,根据是否满足滑动条件就到下一屏就可以了,但是经过
一番分析与修改没能成功.如:else if (mNextScreen != INVALID_SCREEN)结果发现mNextScreen的值根本就是当前的屏值,这玩笑开得,走了个大弯,而且还是错的。
因此调整了一下心态,要一步一步来:
首先了解到:
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向上传
给上一层。
滑动功能主要分两步:
1、在onInterceptTouchEvent中进行拦截。
2、在onTouchEvent中进行滑动。
因此就找到onInterceptTouchEvent函数,这个函数主要就是决定什么时候截获MotionEvent来实现滑动,避免了子View的其他事件的影响(如点击事件)。
//用户已经进入滑动状态,并且正在滑动手指。对这种情况直接进行拦截,执行onTouchEvent()继续执行滑动操作
//获取速度跟踪器,记录各个时刻的速度。并且添加当前的MotionEvent以记录更行速度值
acquireVelocityTrackerAndAddMovement(ev);
......................
在接受到ACTION_MOVE时,
这里面的那些代码还没完全理解,不过在其它地方有看到:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/**
* 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()尝试能够进入滑动状态。
*/
..............
case
MotionEvent.ACTION_DOWN: {
final
float
x = ev.getX();
final
float
y = ev.getY();
// Remember location of down touch
mLastMotionX = x;
//记下上一次touch的坐标
mLastMotionY = y;
mAllowLongPress =
true
;
//长按可以实现监听
.......................
}
|
好了onInterceptTouchEvent就讲到这里,感兴趣的可以深入分析一下,接下来看执行各种关于滑动的工作的计算,界面的刷新等工作的函数:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public
boolean
onTouchEvent(MotionEvent ev) {
。。。。。
主要看:
case
MotionEvent.ACTION_UP:
if
(mTouchState == TOUCH_STATE_SCROLLING) {
final
VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(
1000
, mMaximumVelocity);
final
int
velocityX = (
int
) velocityTracker.getXVelocity(mActivePointerId);
final
int
screenWidth = getWidth();
final
int
whichScreen = (mScrollX + (screenWidth /
2
)) / screenWidth;
final
float
scrolledPos = (
float
) mScrollX / screenWidth;
Log.i(
"velocityX"
,
"velocityX="
+velocityX+
"whichScreen="
+whichScreen);
if
(velocityX > SNAP_VELOCITY && mCurrentScreen >
0
) {
//当velocityX的值大于SNAP_VELOCITY且当前屏值大于0时就向左滑动到下一屏。
// Fling hard enough to move left.
// Don't fling across more than one screen at a time.
final
int
bound = scrolledPos < whichScreen ?
mCurrentScreen -
1
: mCurrentScreen;
snapToScreen(Math.min(whichScreen, bound), velocityX,
true
);
//向左的下一屏
}
else
if
(velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() -
1
) {当velocityX的值大于SNAP_VELOCITY且当前屏值大于
0
时就向右滑动到下一屏。
// Fling hard enough to move right
// Don't fling across more than one screen at a time.
final
int
bound = scrolledPos > whichScreen ?
mCurrentScreen +
1
: mCurrentScreen;
snapToScreen(Math.max(whichScreen, bound), velocityX,
true
);
//向右的下一屏
}
else
{
snapToScreen(whichScreen,
0
,
true
);
}
}
mTouchState = TOUCH_STATE_REST;
mActivePointerId = INVALID_POINTER;
releaseVelocityTracker();
break
;
|
那么我们要修改的就是判断条件了:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//modifided by xxnan 2012-12-10
if
(velocityX > SNAP_VELOCITY) {
//取消掉判断是否屏值小于0
// Fling hard enough to move left.
// Don't fling across more than one screen at a time.
Log.i(
"numscreen"
,
"numscreen="
+mCurrentScreen);
/* final int bound = scrolledPos < whichScreen ?
( (mCurrentScreen+ getChildCount()) - 1 )% getChildCount(): mCurrentScreen;*/
//取消判断是否小于whichScreen 也就是当前屏值(如果whichScreen ==0就不做),因此我们要注释掉
final int bound =( (mCurrentScreen+ getChildCount()) - 1 )% getChildCount() ;
Log.i("numscreen","bound="+bound);
snapToScreen( bound, velocityX, true);
} else if (velocityX < -SNAP_VELOCITY ) {//取消掉判断是否屏值大于于getChildCount()-1
// Fling hard enough to move right
// Don't fling across more than one screen at a time.
/*final int bound = scrolledPos > whichScreen ?
( mCurrentScreen + 1 )% getChildCount(): mCurrentScreen;*/
final
int
bound = ( mCurrentScreen +
1
)% getChildCount() ;
snapToScreen(bound, velocityX,
true
);
}
else
{
snapToScreen(whichScreen,
0
,
true
);
}
|
大概修改就是如此了。虽然之前修改了一些代码,大多是当屏值小于0就加getChildCount()取余,以及大于etChildCount()-1时加1取余:如:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public
void
scrollLeft() {
clearVacantCache();
/*if (mScroller.isFinished()) {
if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1);
} else {
if (mNextScreen > 0) snapToScreen(mNextScreen - 1);
}*/
if (mScroller.isFinished()) {
if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1);
else
snapToScreen(getChildCount() -1);
} else {
if (mNextScreen > 0) snapToScreen(mNextScreen - 1);
}
}
public void scrollRight() {
//modified by xxnan
clearVacantCache();
/* if (mScroller.isFinished()) {
if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1);
} else {
if (mNextScreen < getChildCount() -1) snapToScreen(mNextScreen + 1);
}*/
if
(mScroller.isFinished()) {
if
(mCurrentScreen < getChildCount() -
1
) snapToScreen(mCurrentScreen +
1
);
else
snapToScreen(
0
);
}
else
{
if
(mNextScreen < getChildCount() -
1
) snapToScreen(mNextScreen +
1
);
}
}
|
但都无关紧要,主要还是在计算滑动和界面的刷新的函数onTouchEvent中的修改