在android的源代码中,屏幕之间的跳转是如何实现的呢?在workspace.java中开始。在这个类中,为实现屏幕切换主要重写了以下几个方法:onMeasure()、onLayout()、onInterceptTouchEvent()、onTouchEvent()方法,另外还是用了CustomScroller mScroller来平滑过渡各个页面之间的切换。
首先,我们看一下onMeasure()方法,根据字面意思,就是测量,主要负责测量各个控件的高度和宽度:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {在这里,得到屏幕的宽高, 然后再枚举其中所有的子view,设置它们的布局(使他们的高和父控件一样),这样每一个子view就是充满屏幕可以滑动显示的其中一页。
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()));*/
}
下面是 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下载即可。