展会信息港展会大全

Android 4.0 Launcher2源码分析——Workspace滑动
来源:互联网   发布日期:2016-01-06 19:26:57   浏览:1802次  

导读:Launcher桌面的一大功能就是支持左右滑动,这样的功能在现在的应用中使用非常广泛,并且有很多实现的方式,可以通过使用Fragment来实现也可以通过自定义的控件来实现。Launcher采...

Launcher桌面的一大功能就是支持左右滑动,这样的功能在现在的应用中使用非常广泛,并且有很多实现的方式,可以通过使用Fragment来实现也可以通过自定义的控件来实现。

Launcher采用了后者,这一功能的实现在Workspace来完成。首先来看一下Workspace的继承关系:

从图中可以看出Workspace是PagedView的子类,而实际上滑动功能的实现是在PagedView中实现的。在Launcher中,Workspace中有五个CellLayout,分别代表五个分屏。

当左右拖动CellLayout时,就能实现滑动的效果。但实际上CellLayout中还有其他的子View,PagedView是如何避免了来自子View的干扰的呢?这里就需要讨论另一个问题,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向上传给上一层。拦截的过程草图如下:

有了touch事件的拦截机制之后,View tree中的各个层之间的分工也就更加明确了。在Launcher的View tree中,从上到下的主要的节点有,DragLayer,Workspace,

CellLayout。DragLayer层的主要任务是负责对图标和AppWidget进行拖拽,Workspace则主要负责左右滑动,CellLayout则用于容纳各种桌面的快捷方式。大概的分工如下:

那么现在就进入PagedView中去了解实现的过程吧。

滑动功能主要分两步:1、在onInterceptTouchEvent中进行拦截。2、在onTouchEvent中进行滑动。

1,onInterceptTouchEvent(MotionEvent en)

在这个方法中,决定了什么时候截获MotionEvent来实现滑动,避免了子View的其他事件的影响(如点击事件)。

[java]

public boolean onInterceptTouchEvent(MotionEvent ev) {

/**

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

* If we return true, onTouchEvent will be called and we do the actual

* scrolling there.

**/

//获取速度跟踪器,记录各个时刻的速度。并且添加当前的MotionEvent以记录更行速度值。

acquireVelocityTrackerAndAddMovement(ev);

......

/**

* Shortcut the most recurring case: the user is in the dragging

* state and he is moving his finger.We want to intercept this

* motion.

* 最常见的需要拦截的情况:用户已经进入滑动状态,并且正在滑动手指。

* 对这种情况直接进行拦截,执行onTouchEvent()继续执行滑动操作。

**/

final int action = ev.getAction();

if ((action == MotionEvent.ACTION_MOVE) &&

(mTouchState == TOUCH_STATE_SCROLLING)) {

return true;

}

switch (action & MotionEvent.ACTION_MASK) {

case MotionEvent.ACTION_MOVE: {

/**

*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()尝试能够进入滑动状态。

*/

if (mActivePointerId != INVALID_POINTER) {

determineScrollingStart(ev);

break;

}

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

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

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

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

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

}

case MotionEvent.ACTION_DOWN: {

final float x = ev.getX();

final float y = ev.getY();

// Remember location of down touch

//记录按下的x的坐标值

mDownMotionX = x;

//记录前次发生touch时的坐标

mLastMotionX = x;

mLastMotionY = y;

//因为在ScrollBy时只能使用int,而记录的x和y都是float,会产生误差,故这里用mLastMotionXRemainder记录余数

//用于消除误差

mLastMotionXRemainder = 0;

//x方向上的总位移

mTotalMotionX = 0;

mActivePointerId = ev.getPointerId(0);

//设置mAllowLongPress=true,允许LongClick事件发生。LongClick事件定义在Launcher中

//处理的内容包括启动对shortcut的拖拽或弹出壁纸选择的对话框,若mAllowLongPress=false,

//则不会响应以上事件。

mAllowLongPress = true;

/**

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

* otherwise don't.mScroller.isFinished should be false when

* being flinged.

* 当屏幕处于flinged状态(快速滑动)时,若此时用户触摸了屏幕,需要使滑动停止。

* 并且初始化拖拽的条件

**/

final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());

final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);

if (finishedScrolling) {

mTouchState = TOUCH_STATE_REST;

mScroller.abortAnimation();

} else {

mTouchState = TOUCH_STATE_SCROLLING;

}

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

// to scroll the current page

if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {

if (getChildCount() > 0) {

if (hitsPreviousPage(x, y)) {

mTouchState = TOUCH_STATE_PREV_PAGE;

} else if (hitsNextPage(x, y)) {

mTouchState = TOUCH_STATE_NEXT_PAGE;

}

}

}

break;

}

case MotionEvent.ACTION_UP:

case MotionEvent.ACTION_CANCEL:

mTouchState = TOUCH_STATE_REST;

mAllowLongPress = false;

mActivePointerId = INVALID_POINTER;

releaseVelocityTracker();

break;

case MotionEvent.ACTION_POINTER_UP:

onSecondaryPointerUp(ev);

releaseVelocityTracker();

break;

}

/**

* The only time we want to intercept motion events is if we are in the

* drag mode.

* 只有进入了滑动状态,才进行拦截,进入onTouchEvent执行滑动操作。当mTouchState != TOUCH_STATE_REST

* 时,就说明没有进入滑动状态。

**/

return mTouchState != TOUCH_STATE_REST;

}

代码的主要工作是根据MotionEvent和当前的状态,在各个状态中切换mTouchState。其中一共有四个状态值,TOUCH_STATE_REST、TOUCH_STATE_SCROLLING、

TOUCH_STATE_PREV_PAGE、TOUCH_STATE_NEXT_PAGE。状态之间的切换可以用如下的草图表示:

当mTouchState==TOUCH_STATE_REST时,不需要任何滑动操作,将MotionEvent向子View传递。这里,分析一种最简单的情况:初始桌面静止,手指按下,然后开始左右

滑动。通过这种简单情况的分析,来帮助理解onInterceptTouceEvent的作用。开始桌面静止,则mTouchState==TOUCH_STATE_REST,触发switch分支中

MotionEvent.ACTION_DOWN的代码。记录按下点的坐标,设置mAllowLongPress=true。由于mTouchState=TOUCH_STATE_REST,所以动作被传向了子View。接下来,在长

按事件被触发之前移动手指则会在代码中调用determineScrollingStart()来决定是否进入滑动状态。进入滑动状态之后mTouchState的值就变为TOUCH_STATE_SCROLLING

,然后onTouchEvent中的操作就会被用,开始滑动。

总结一下onInterceptTouchEvent()的工作就是:切换PagedView的状态,如果处于滑动状态就拦截。

2,onTouchEvent(MotionEvent en)

在这个方法中,执行各种关于滑动的工作的计算,界面的刷新等工作。

[java]

public boolean onTouchEvent(MotionEvent ev) {

......

switch (action & MotionEvent.ACTION_MASK) {

case MotionEvent.ACTION_DOWN:

/*

* If being flinged and user touches, stop the fling. isFinished

* will be false if being flinged.

*/

/**

* 如果Workspace此时已经被“掷出去”(靠惯性滑动)。

* 此时发生ACTION_DOWN则需要停止滑动。

*/

if (!mScroller.isFinished()) {

mScroller.abortAnimation();

}

// Remember where the motion event started

mDownMotionX = mLastMotionX = ev.getX();

mLastMotionXRemainder = 0;

mTotalMotionX = 0;

mActivePointerId = ev.getPointerId(0);

if (mTouchState == TOUCH_STATE_SCROLLING) {

pageBeginMoving();

}

break;

case MotionEvent.ACTION_MOVE:

if (mTouchState == TOUCH_STATE_SCROLLING) {

......

if (Math.abs(deltaX) >= 1.0f) {

......

if (!mDeferScrollUpdate) {

//调用scrollBy滑动桌面

scrollBy((int) deltaX, 0);

......

} else {

......

}

mLastMotionX = x;

mLastMotionXRemainder = deltaX - (int) deltaX;

} else {

awakenScrollBars();

}

} else {

/**

* 如果条件满足,则进入滑动状态,开始滑动。

*/

determineScrollingStart(ev);

}

break;

case MotionEvent.ACTION_UP:

if (mTouchState == TOUCH_STATE_SCROLLING) {

......

boolean isSignificantMove = Math.abs(deltaX) > MIN_LENGTH_FOR_MOVE;

boolean returnToOriginalPage = false;

final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));

if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&

Math.signum(velocityX) != Math.signum(deltaX)) {

returnToOriginalPage = true;

}

//当速率超过snapVelocity或者总的移动距离超过MIN_LENGTH_FOR_FLING

//则判定isFling=true

boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&

Math.abs(velocityX) > snapVelocity;

int finalPage;

//判断拿起手指之后应该进入哪个分屏

if (((isSignificantMove && deltaX > 0 && !isFling) ||

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

finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;

snapToPageWithVelocity(finalPage, velocityX);

} else if (((isSignificantMove && deltaX < 0 && !isFling) ||

(isFling && velocityX < 0)) &&

mCurrentPage < getChildCount() - 1) {

finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;

snapToPageWithVelocity(finalPage, velocityX);

} else {

snapToDestination();

}

} else if (mTouchState == TOUCH_STATE_PREV_PAGE) {

// at this point we have not moved beyond the touch slop

// (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so

// we can just page

//直接进入前一屏

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

if (nextPage != mCurrentPage) {

snapToPage(nextPage);

} else {

snapToDestination();

}

} else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {

// at this point we have not moved beyond the touch slop

// (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so

// we can just page

//直接进入后一屏

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

if (nextPage != mCurrentPage) {

snapToPage(nextPage);

} else {

snapToDestination();

}

} else {

onUnhandledTap(ev);

}

mTouchState = TOUCH_STATE_REST;

mActivePointerId = INVALID_POINTER;

releaseVelocityTracker();

break;

case MotionEvent.ACTION_CANCEL:

if (mTouchState == TOUCH_STATE_SCROLLING) {

snapToDestination();

}

mTouchState = TOUCH_STATE_REST;

mActivePointerId = INVALID_POINTER;

releaseVelocityTracker();

break;

case MotionEvent.ACTION_POINTER_UP:

onSecondaryPointerUp(ev);

break;

}

return true;

}

在onTouchEvent中,对接受到的不同的事件进行了分类的处理,大致可以将功能分类为:

1、当接受到ACTION_DOWN时,若滑动正在进行,则停止。

2、当接受到ACTION_MOVE时,根据当前的状态调用scrollBy进行滑动或则调用determineScrollingStart准备开始滑动。

3、当接受到ACTION_UP时,根据当前所滑动的位移和速度,判断松手后进入到哪一个分屏。

到这里,Workspace滑动过程的基本流程就介绍完毕了。

赞助本站

人工智能实验室

相关热词: android开发 教程

AiLab云推荐
推荐内容
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港