在android的源代码中,屏幕之间的跳转是如何实现的呢?在workspace.java中开始。在这个类中,为实现屏幕切换主要重写了以下几个方法:onMeasure()、onLayout()、onInterceptTouchEvent()、onTouchEvent()方法,另外还是用了CustomScroller mScroller来平滑过渡各个页面之间的切换。
首先,我们看一下onMeasure()方法,根据字面意思,就是测量,主要负责测量各个控件的高度和宽度:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
}
// The children are given the same width and height as the workspace
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
//ADW: measure wallpaper when using old rendering
if(!lwpSupport){
if (mWallpaperLoaded) {
mWallpaperLoaded = false;
mWallpaperWidth = mWallpaperDrawable.getIntrinsicWidth();
mWallpaperHeight = mWallpaperDrawable.getIntrinsicHeight();
}
final int wallpaperWidth = mWallpaperWidth;
mWallpaperOffset = wallpaperWidth > width ? (count * width - wallpaperWidth) /
((count - 1) * (float) width) : 1.0f;
}
if (mFirstLayout) {
scrollTo(mCurrentScreen * width, 0);
mScroller.startScroll(0, 0, mCurrentScreen * width, 0, 0);
if(lwpSupport)updateWallpaperOffset(width * (getChildCount() - 1));
mFirstLayout = false;
}
/*int max = 3;
int aW = getMeasuredWidth();
float w = aW / max;
maxPreviewWidth=(int) w;
maxPreviewHeight=(int) (getMeasuredHeight()*(w/getMeasuredWidth()));*/
}
在这里,得到屏幕的宽高,
然后再枚举其中所有的子view,设置它们的布局(使他们的高和父控件一样),这样每一个子view就是充满屏幕可以滑动显示的其中一页。
下面是 onLayout()方法:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int childLeft = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
final int childWidth = child.getMeasuredWidth();
child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
childLeft += childWidth;
}
}
//ADW:updateWallpaperoffset
if(lwpSupport){
if(mWallpaperScroll)
updateWallpaperOffset();
else
centerWallpaperOffset();
}
}
onLayout方法中,横向画出每一个子view,view的高与屏幕高一致,宽度为getChildCount()-1个屏幕宽度的view。
再看一下onInterceptTouchEvent()方法:
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(mStatus==SENSE_OPEN){
if(ev.getAction()==MotionEvent.ACTION_DOWN){
findClickedPreview(ev.getX(),ev.getY());
}
return true;
}
//Wysie: If multitouch event is detected
if (multiTouchController.onTouchEvent(ev)) {
return false;
}
if (mLocked || mLauncher.isAllAppsVisible()) {
return true;
}
/*
* 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.
*/
/*
* Shortcut the most recurring case: the user is in the dragging
* state and he is moving his finger. We want to intercept this
* motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
return true;
}
final float x = ev.getX();
final float y = ev.getY();
switch (action) {
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.
*/
/*
* Locally do absolute value. mLastMotionX is set to the y value
* of the down event.
*/
final int xDiff = (int) Math.abs(x - mLastMotionX);
final int yDiff = (int) Math.abs(y - mLastMotionY);
final int touchSlop = mTouchSlop;
boolean xMoved = xDiff > touchSlop;
boolean yMoved = yDiff > touchSlop;
if (xMoved || yMoved) {
// If xDiff > yDiff means the finger path pitch is smaller than 45deg so we assume the user want to scroll X axis
if (xDiff > yDiff) {
// Scroll if the user moved far enough along the X axis
mTouchState = TOUCH_STATE_SCROLLING;
enableChildrenCache();
}
// If yDiff > xDiff means the finger path pitch is bigger than 45deg so we assume the user want to either scroll Y or Y-axis gesture
else if (getOpenFolder()==null)
{
// As x scrolling is left untouched (more or less untouched;)), every gesture should start by dragging in Y axis. In fact I only consider useful, swipe up and down.
// Guess if the first Pointer where the user click belongs to where a scrollable widget is.
mTouchedScrollableWidget = isWidgetAtLocationScrollable((int)mLastMotionX,(int)mLastMotionY);
if (!mTouchedScrollableWidget)
{
// Only y axis movement. So may be a Swipe down or up gesture
if ((y - mLastMotionY) > 0){
if(Math.abs(y-mLastMotionY)>(touchSlop*2))mTouchState = TOUCH_SWIPE_DOWN_GESTURE;
}else{
if(Math.abs(y-mLastMotionY)>(touchSlop*2))mTouchState = TOUCH_SWIPE_UP_GESTURE;
}
}
}
// Either way, cancel any pending longpress
if (mAllowLongPress) {
mAllowLongPress = false;
// Try canceling the long press. It could also have been scheduled
// by a distant descendant, so use the mAllowLongPress flag to block
// everything
final View currentScreen = getChildAt(mCurrentScreen);
currentScreen.cancelLongPress();
}
}
break;
case MotionEvent.ACTION_DOWN:
// Remember location of down touch
mLastMotionX = x;
mLastMotionY = y;
mAllowLongPress = true;
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mTouchState != TOUCH_STATE_SCROLLING && mTouchState != TOUCH_SWIPE_DOWN_GESTURE && mTouchState != TOUCH_SWIPE_UP_GESTURE) {
final CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
if (!currentScreen.lastDownOnOccupiedCell()) {
getLocationOnScreen(mTempCell);
// Send a tap to the wallpaper if the last down was on empty space
if(lwpSupport)
mWallpaperManager.sendWallpaperCommand(getWindowToken(),
"android.wallpaper.tap",
mTempCell[0] + (int) ev.getX(),
mTempCell[1] + (int) ev.getY(), 0, null);
}
}
// Release the drag
clearChildrenCache();
mTouchState = TOUCH_STATE_REST;
mAllowLongPress = false;
break;
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mTouchState != TOUCH_STATE_REST;
}
onInterceptTouchEvent()方法和下面的onTouchEvent()主要是来响应手指按下划动时所需要捕获的消息,例如划动的速度,划动的距离等。再配合使用scrollBy (int x, int y)方法得到慢速滑动小距离的时候,所需要显示的内容。最后当手指起来时,根据划动的速度与跨度来判断是向左滑动一页还是向右滑动一页,确保每次用户操作结束之后显示的都是整体的一个子view.
public boolean onTouchEvent(MotionEvent ev) {
//Wysie: If multitouch event is detected
/*if (multiTouchController.onTouchEvent(ev)) {
return false;
}*/
if (mLocked || mLauncher.isAllAppsVisible() || mSensemode) {
return true;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionX = x;
break;
case MotionEvent.ACTION_MOVE:
if (mTouchState == TOUCH_STATE_SCROLLING) {
// Scroll to follow the motion event
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
if (deltaX < 0) {
if (mScrollX > -mScrollingBounce) {
scrollBy(Math.min(deltaX,mScrollingBounce), 0);
if(lwpSupport)updateWallpaperOffset();
if(mLauncher.getDesktopIndicator()!=null)mLauncher.getDesktopIndicator().indicate((float)getScrollX()/(float)(getChildCount()*getWidth()));
}
} else if (deltaX > 0) {
final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
mScrollX - getWidth()+mScrollingBounce;
if (availableToScroll > 0) {
scrollBy(deltaX, 0);
if(lwpSupport)updateWallpaperOffset();
if(mLauncher.getDesktopIndicator()!=null)mLauncher.getDesktopIndicator().indicate((float)getScrollX()/(float)(getChildCount()*getWidth()));
}
}
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityX = (int) velocityTracker.getXVelocity();
if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
// Fling hard enough to move left
snapToScreen(mCurrentScreen - 1);
} else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
// Fling hard enough to move right
snapToScreen(mCurrentScreen + 1);
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
} else if (mTouchState == TOUCH_SWIPE_DOWN_GESTURE )
{
mLauncher.fireSwipeDownAction();
} else if (mTouchState == TOUCH_SWIPE_UP_GESTURE )
{
mLauncher.fireSwipeUpAction();
}
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
}
return true;
}
以上就是launcher中左右滑动屏幕切换源码。
有需要代码的朋友,请到http://download.csdn.net/detail/aomandeshangxiao/4063177下载即可。