編輯:關於Android編程
一.WorkSpace是什麼
前面已經介紹了一個WorkSpace包含了多個CellLayout,再回憶下之前畫過的圖
WorkSpace是一個ViewGroup,它的布局如下
defaultScreen是默認的屏幕序號
pageIndicator是滑動指示器
pageSpacing是頁面之間的距離
二.WorkSpace代碼分析
WorkSpace的繼承關系如下
實現了DropTarget、DragSource等多個接口
public class Workspace extends SmoothPagedView implements DropTarget, DragSource, DragScroller, View.OnTouchListener, DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener, Insettable {看下它的構造函數
<pre name="code" class="java"> public Workspace(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContentIsRefreshable = false; //獲取繪制輪廓的輔助類對象 mOutlineHelper = HolographicOutlineHelper.obtain(context); //獲取拖動的監聽對象 mDragEnforcer = new DropTarget.DragEnforcer(context); // With workspace, data is available straight from the get-go setDataIsReady(); mLauncher = (Launcher) context; final Resources res = getResources(); mWorkspaceFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens); mFadeInAdjacentScreens = false; //獲取壁紙管理者 mWallpaperManager = WallpaperManager.getInstance(context); //獲取自定義屬性 TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.Workspace, defStyle, 0); //在all app列表裡拖動app時workspace的縮放比例 mSpringLoadedShrinkFactor =res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; //可以滑動的區域 mOverviewModeShrinkFactor =res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100.0f; mOverviewModePageOffset = res.getDimensionPixelSize(R.dimen.overview_mode_page_offset); //滑動屏幕到邊緣不能再滑動時拖動的Z軸距離 mCameraDistance = res.getInteger(R.integer.config_cameraDistance); //開機時的屏幕 mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1); a.recycle(); //監聽view層次的變化 setOnHierarchyChangeListener(this); //打開觸摸反饋 setHapticFeedbackEnabled(false); //初始化WorkSpace initWorkspace(); // Disable multitouch across the workspace/all apps/customize tray setMotionEventSplittingEnabled(true); setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); }
SpringLoadedShrinkFactor是在所有應用列表裡長按item時workspace的縮略圖比例,默認的是0.8,我把它改為0.01,看下效果,workspace縮小到只有一點點了
mOverviewModeShrinkFactor是可以滑動的區域縮放比例, 如果你把item拖出這個區域,那麼刪除框就會出現, 我把它改為4,默認的是0.58,看下效果
mCameraDistance是滑動屏幕到邊緣不能再滑動時拖動的Z軸距離,就是那種3D效果,默認的是8000,我把它改為1000,3D效果更明顯了
mOriginalDefaultPage是開機時默認的屏幕序號.
往下看initWorkspace()方法
protected void initWorkspace() { Context context = getContext(); mCurrentPage = mDefaultPage; //當前頁設置為默認頁 Launcher.setScreen(mCurrentPage); LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); //保存應用圖片的緩存 mIconCache = app.getIconCache(); setWillNotDraw(false); setClipChildren(false); setClipToPadding(false); //設置子view繪圖緩存開啟 setChildrenDrawnWithCacheEnabled(true); // This is a bit of a hack to account for the fact that we translate the workspace // up a bit, and still need to draw the background covering the whole screen. setMinScale(mOverviewModeShrinkFactor - 0.2f); setupLayoutTransition(); final Resources res = getResources(); //設置桌面縮略圖背景 try { mBackground = res.getDrawable(R.drawable.apps_customize_bg); } catch (Resources.NotFoundException e) { // In this case, we will skip drawing background protection } //wallPaper 偏移 mWallpaperOffset = new WallpaperOffsetInterpolator(); //獲取屏幕大小,此方法在android 4.0之前不支持 Display display = mLauncher.getWindowManager().getDefaultDisplay(); display.getSize(mDisplaySize); mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx); mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); }在這個方法裡設置當前頁為默認頁,並設置workspace縮略圖背景,我把它換成手指的圖片,看下
WorkSpace實現了DragSource和DropTarget,說明它既是一個拖動的容器也是一個拖動的源,那就看下它的startDrag方法
void startDrag(CellLayout.CellInfo cellInfo) { View child = cellInfo.cell; // Make sure the drag was started by a long press as opposed to a long click. if (!child.isInTouchMode()) { return; } mDragInfo = cellInfo; //原位置的item設置為不可見 child.setVisibility(INVISIBLE); CellLayout layout = (CellLayout) child.getParent().getParent(); layout.prepareChildForDrag(child); child.clearFocus(); child.setPressed(false); final Canvas canvas = new Canvas(); // 當item拖動時跟隨著的的背景圖 mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING); beginDragShared(child, this); }在開始拖動時,就隱藏了原來位置的item,我把它改為不隱藏,mDragOutline是item拖動時跟著移動的背景圖,我把它替換為手指的圖片,看下效果
接下來分析它的觸摸事件onInterceptTouchEvent和onTouch
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: mXDown = ev.getX(); mYDown = ev.getY(); //紀錄按下的時間 mTouchDownTime = System.currentTimeMillis(); break; case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: if (mTouchState == TOUCH_STATE_REST) { final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); if (!currentPage.lastDownOnOccupiedCell()) { onWallpaperTap(ev); } } } //調用父類的onInterceptTouchEvent,這裡是調用了PagedView return super.onInterceptTouchEvent(ev); }把攔截事件交給父類PageView處理了.
OnTouch事件當workspace進入縮略圖的場景或者沒有完成狀態切換時返回true
@Override public boolean onTouch(View v, MotionEvent event) { return (isSmall() || !isFinishedSwitchingState()) || (!isSmall() && indexOfChild(v) != mCurrentPage); }
WorkSpace作為一個ViewGroup的子類,看下它重寫的view方法.它只重寫onLayout和ondraw方法.
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { mWallpaperOffset.syncWithScroll(); mWallpaperOffset.jumpToFinal(); } super.onLayout(changed, left, top, right, bottom); }
class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
這個類是處理UI繪制的.syncWithScroll方法是處理壁紙偏移的
public void syncWithScroll() { //獲取壁紙偏移量 float offset = wallpaperOffsetForCurrentScroll(); //設置壁紙偏移量 mWallpaperOffset.setFinalX(offset); //更新壁紙偏移量 updateOffset(true); }
public void jumpToFinal() { mCurrentOffset = mFinalOffset; }
三、屏幕滑動分析
桌面滑動是在WorkSpace的父類PagedView裡處理的.前面已經分析了,WorkSpace的onInterceptTouchEvent方法調用了父類的onInterceptTouchEvent.這裡就是分析入口.看下
PagedView的onInterceptTouchEvent方法
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (DISABLE_TOUCH_INTERACTION) { return false; } // 獲取速度跟蹤器,記錄各個時刻的速度。並且添加當前的MotionEvent以記錄更行速度值。 acquireVelocityTrackerAndAddMovement(ev); // 沒有頁面,直接跳過給父類處理。 if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); //最常見的需要攔截的情況:用戶已經進入滑動狀態,而且正在移動手指滑動,對這種情況直接進行攔截,調用PagedView的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: { // 如果已經發生觸摸 if (mActivePointerId != INVALID_POINTER) { // 檢查用戶滑動距離是否足夠遠 determineScrollingStart(ev); } break; } case MotionEvent.ACTION_DOWN: { final float x = ev.getX(); final float y = ev.getY(); // 記下觸摸位置 mDownMotionX = x; mDownMotionY = y; mDownScrollX = getScrollX(); mLastMotionX = x; mLastMotionY = y; // 做一個該坐標在view上對parent的映射, float[] p = mapPointFromViewToParent(this, x, y); mParentDownMotionX = p[0]; mParentDownMotionY = p[1]; mLastMotionXRemainder = 0; mTotalMotionX = 0; // 第一個觸摸點,返回0 mActivePointerId = ev.getPointerId(0); 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 { if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) { // 設置當前桌面狀態為滑動中 mTouchState = TOUCH_STATE_SCROLLING; } else { // 設置當前桌面狀態為靜止 mTouchState = TOUCH_STATE_REST; } } // 如果頁面可以觸摸 if (!DISABLE_TOUCH_SIDE_PAGES) { // 識別觸摸狀態是否是直接翻頁狀態,如果是直接翻頁,在onTouchEvent裡面會直接調用 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: // 重置桌面狀態 resetTouchState(); break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); releaseVelocityTracker(); break; } // 只要是mTouchState的狀態不為TOUCH_STATE_REST,那麼就進行事件攔截,調用onTouchEvent return mTouchState != TOUCH_STATE_REST; }重點看最後一行代碼的返回,mTouchState是紀錄桌面狀態的一個int值,默認是TOUCH_STATE_REST,總共有5種狀態
/** * 滑動結束狀態 */ protected final static int TOUCH_STATE_REST = 0; /** * 正在滑動 */ protected final static int TOUCH_STATE_SCROLLING = 1; /** * 滑動到上一頁 */ protected final static int TOUCH_STATE_PREV_PAGE = 2; /** * 滑動到下一頁 */ protected final static int TOUCH_STATE_NEXT_PAGE = 3; /** * 滑動狀態重新排序 */ protected final static int TOUCH_STATE_REORDERING = 4;如果mTouchState的值不為TOUCH_STATE_REST,即桌面靜止,那麼就攔截事件,交給onTouchEvent處理.在onInterceptTouchEvent得down move up事件裡進行mTouchState的改變.滑動肯定是在move事件裡,它裡面調用了determineScrollingStart方法,這個方法是判斷滑動距離是否足夠大到滑動頁面
protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { // 禁止滾動,如果我們沒有一個有效的指針指數 final int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) return; // 如果我們從滾動視圖外開始的手勢那麼禁止 final float x = ev.getX(pointerIndex); final float y = ev.getY(pointerIndex); if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return; final int xDiff = (int) Math.abs(x - mLastMotionX); final int yDiff = (int) Math.abs(y - mLastMotionY); final int touchSlop = Math.round(touchSlopScale * mTouchSlop); boolean xPaged = xDiff > mPagingTouchSlop; boolean xMoved = xDiff > touchSlop; boolean yMoved = yDiff > touchSlop; if (xMoved || xPaged || yMoved) { if (mUsePagingTouchSlop ? xPaged : xMoved) { // 如果用戶滑動距離足夠,那麼開始滑動 mTouchState = TOUCH_STATE_SCROLLING; mTotalMotionX += Math.abs(mLastMotionX - x); mLastMotionX = x; mLastMotionXRemainder = 0; mTouchX = getViewportOffsetX() + getScrollX(); mSmoothingTime = System.nanoTime() / NANOTIME_DIV; pageBeginMoving(); } } }這個方法裡判斷如果滑動距離足夠,就把mTouchState的值設為TOUCH_STATE_SCROLLING,即滑動中.然後調用pageBeginMoving
protected void pageBeginMoving() { // 如果沒正在移動,那麼移動 if (!mIsPageMoving) { mIsPageMoving = true; onPageBeginMoving(); } }而onPageBeginMoving是個空方法,是讓子類去重寫的.
在move時間裡返回了true,那麼攔截事件,由onTouchEvent來處理,看下onTouchEvent的move事件
代碼很多
case MotionEvent.ACTION_MOVE: // 如果桌面正在滑動 if (mTouchState == TOUCH_STATE_SCROLLING) { // Scroll to follow the motion event final int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) return true; final float x = ev.getX(pointerIndex); final float deltaX = mLastMotionX + mLastMotionXRemainder - x; mTotalMotionX += Math.abs(deltaX); // Only scroll and update mLastMotionX if we have moved some // discrete amount. We // keep the remainder because we are actually testing if we've // moved from the last // scrolled position (which is discrete). if (Math.abs(deltaX) >= 1.0f) { mTouchX += deltaX; mSmoothingTime = System.nanoTime() / NANOTIME_DIV; // 如果滑動狀態未更新 if (!mDeferScrollUpdate) { // 滑動 scrollBy((int) deltaX, 0); if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX); } else { invalidate(); } mLastMotionX = x; mLastMotionXRemainder = deltaX - (int) deltaX; } else { awakenScrollBars(); } } else if (mTouchState == TOUCH_STATE_REORDERING) { // 更新最後一次的觸摸坐標 mLastMotionX = ev.getX(); mLastMotionY = ev.getY(); // Update the parent down so that our zoom animations take this // new movement into // account float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); mParentDownMotionX = pt[0]; mParentDownMotionY = pt[1]; updateDragViewTranslationDuringDrag(); // 尋找離觸摸點最近的頁面 final int dragViewIndex = indexOfChild(mDragView); // Change the drag view if we are hovering over the drop target boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget((int) mParentDownMotionX, (int) mParentDownMotionY); setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete); if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX); if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY); if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX); if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY); final int pageUnderPointIndex = getNearestHoverOverPageIndex(); if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView) && !isHoveringOverDelete) { mTempVisiblePagesRange[0] = 0; mTempVisiblePagesRange[1] = getPageCount() - 1; getOverviewModePages(mTempVisiblePagesRange); if (mTempVisiblePagesRange[0] <= pageUnderPointIndex && pageUnderPointIndex <= mTempVisiblePagesRange[1] && pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) { mSidePageHoverIndex = pageUnderPointIndex; mSidePageHoverRunnable = new Runnable() { @Override public void run() { // Setup the scroll to the correct page before // we swap the views snapToPage(pageUnderPointIndex); // For each of the pages between the paged view // and the drag view, // animate them from the previous position to // the new position in // the layout (as a result of the drag view // moving in the layout) int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1; int lowerIndex = (dragViewIndex < pageUnderPointIndex) ? dragViewIndex + 1 : pageUnderPointIndex; int upperIndex = (dragViewIndex > pageUnderPointIndex) ? dragViewIndex - 1 : pageUnderPointIndex; for (int i = lowerIndex; i <= upperIndex; ++i) { View v = getChildAt(i); // dragViewIndex < pageUnderPointIndex, so // after we remove the // drag view all subsequent views to // pageUnderPointIndex will // shift down. int oldX = getViewportOffsetX() + getChildOffset(i); int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta); // Animate the view translation from its old // position to its new // position AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY); if (anim != null) { anim.cancel(); } v.setTranslationX(oldX - newX); anim = new AnimatorSet(); anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION); anim.playTogether(ObjectAnimator.ofFloat(v, "translationX", 0f)); anim.start(); v.setTag(anim); } removeView(mDragView); onRemoveView(mDragView, false); addView(mDragView, pageUnderPointIndex); onAddView(mDragView, pageUnderPointIndex); mSidePageHoverIndex = -1; mPageIndicator.setActiveMarker(getNextPage()); } }; postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT); } } else { removeCallbacks(mSidePageHoverRunnable); mSidePageHoverIndex = -1; } } else { determineScrollingStart(ev); } break;如果滑動距離大於1.0f,那麼調用scrollBy滑動.在滑動的時候會調用snapToPage方法,這個方法有很多重載,但最終會進入到
protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) { mNextPage = whichPage; View focusedChild = getFocusedChild(); if (focusedChild != null && whichPage != mCurrentPage && focusedChild == getPageAt(mCurrentPage)) { focusedChild.clearFocus(); } sendScrollAccessibilityEvent(); pageBeginMoving(); awakenScrollBars(duration); if (immediate) { duration = 0; } else if (duration == 0) { duration = Math.abs(delta); } if (!mScroller.isFinished()) { mScroller.abortAnimation(); } // 滑動的持續時間 mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); notifyPageSwitchListener(); // Trigger a compute() to finish switching pages if necessary if (immediate) { computeScroll(); } // Defer loading associated pages until the scroll settles mDeferLoadAssociatedPagesUntilScrollCompletes = true; mForceScreenScrolled = true; invalidate(); }這個方法裡定義了一些滑動的操作,比如距離,滑動持續時間,滑到哪一頁等.比如我把這個持續時間duration改為9000,看下效果
歡迎留言
記得第一次見到ViewPager這個控件,瞬間愛不釋手,做東西的主界面通通ViewPager,以及圖片切換也拋棄了ImageSwitch之類的,開始讓ViewPager來
DatePicker控件繼承自FrameLayout類,日期選擇控件的主要功能是向用戶提供包含年、月、日的日期數據並允許用戶對其修改。TimePicker控件繼承自Fra
1.把eclipse工程配置文件復制到Android源碼根目錄下cp development/ide/eclipse/.classpath ./2.修改eclipse程序
位置定位(Location)服務(Service)類的基本操作本文地址: http://blog.csdn.net/caroline_wendy定位服務(Location