Launcher3源码浅析(5.1)--Workspace


目录

  • 前言
  • 初始化
    • 布局
    • 页面初始化
  • 桌面图标
    • 图标生成
    • 图标拖动
    • 图标点击效果
  • 页面滑动

前言

  Workspace是桌面的主要一个部分,一般设备(如手机)启动起来所看到的桌面的主要界面就是Workspace,在Launcher里其继承关系如下:

Workspace->SmoothPagedView->PagedView->ViewGroup

  所以可以说Workspace是一个视图容器类,容器里面主要放插件和应用快捷方式的图标。它负责桌面视图的布局工作,如桌面图标是多少行多少列;用户事件的分发与处理;桌面图标的拖放;子视图的更新等操作。本文简单解析一下Workspace的源码。

初始化

1.布局

1.1.xml布局

  其布局在launcher.xml里如下:

<com.android.launcher3.Workspace
        android:id="@+id/workspace"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        launcher:defaultScreen="@integer/config_workspaceDefaultScreen" />

然后在Launcher.java的onCreate里调用setupViews初始化变量mWorkspace,以便调用Workspace里的一些方法和变量,初始化如下:

mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);

1.2.参数初始化

  Workspace的真正Layout是在DeviceProfile.java里。首先在Launcher.java的onCreate里初始化DeviceProfile,然后调用其layout方法实现布局,如下:

.....
DeviceProfile grid = app.initDynamicGrid(this);
.....
grid.layout(this);

  先看看DeviceProfile的初始化过程,调用的是LauncherAppState的initDynamicGrid方法,具体如下:

DeviceProfile initDynamicGrid(Context context) {
    mDynamicGrid = createDynamicGrid(context, mDynamicGrid);
    mDynamicGrid.getDeviceProfile().addCallback(this);
    return mDynamicGrid.getDeviceProfile();
}

  先调用createDynamicGrid得到mDynamicGrid对象,再通过mDynamicGrid来获取DeviceProfile对象。

LauncherAppState.java里的createDynamicGrid:

static DynamicGrid createDynamicGrid(Context context, DynamicGrid dynamicGrid) {
    .....
    if (dynamicGrid == null) {
        Point smallestSize = new Point();
        Point largestSize = new Point();
        display.getCurrentSizeRange(smallestSize, largestSize);

        dynamicGrid = new DynamicGrid(context,
                context.getResources(),
                Math.min(smallestSize.x, smallestSize.y),
                Math.min(largestSize.x, largestSize.y),
                realSize.x, realSize.y,
                dm.widthPixels, dm.heightPixels);
    }
    .....
    return dynamicGrid;
}

DynamicGrid.java的构造函数:

public DynamicGrid(Context context, Resources resources,
                   int minWidthPx, int minHeightPx,
                   int widthPx, int heightPx,
                   int awPx, int ahPx) {
    DisplayMetrics dm = resources.getDisplayMetrics();
    ArrayList<DeviceProfile> deviceProfiles =
            new ArrayList<DeviceProfile>();
    boolean hasAA = !LauncherAppState.isDisableAllApps();
    DEFAULT_ICON_SIZE_PX = pxFromDp(DEFAULT_ICON_SIZE_DP, dm);
    // Our phone profiles include the bar sizes in each orientation
    deviceProfiles.add(new DeviceProfile("Super Short Stubby",
            255, 300,  2, 3,  48, 13, (hasAA ? 3 : 5), 48, R.xml.default_workspace_4x4));
    deviceProfiles.add(new DeviceProfile("Shorter Stubby",
            255, 400,  3, 3,  48, 13, (hasAA ? 3 : 5), 48, R.xml.default_workspace_4x4));
    ......
    mMinWidth = dpiFromPx(minWidthPx, dm);
    mMinHeight = dpiFromPx(minHeightPx, dm);
    mProfile = new DeviceProfile(context, deviceProfiles,
            mMinWidth, mMinHeight,
            widthPx, heightPx,
            awPx, ahPx,
            resources);
}

  可以看到根据不同分辨率加载不同的默认布局文件,最后new DeviceProfile对象。

  再看看DeviceProfile的构造函数,上面调用了DeviceProfile的两个构造函数,先看第一个:

DeviceProfile(String n, float w, float h, float r, float c,
              float is, float its, float hs, float his, int dlId) {
    // Ensure that we have an odd number of hotseat items (since we need to place all apps)
    if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
        throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
    }
    name = n;
    minWidthDps = w;
    minHeightDps = h;
    numRows = r;
    numColumns = c;
    iconSize = is;
    iconTextSize = its;
    numHotseatIcons = hs;
    hotseatIconSize = his;
    defaultLayoutId = dlId;
}

  这就是初始化一个页面的一些参数,其中numRows和numColumns对应的是桌面图标的排列分别是几行几列,iconSize就是图标的大小了,看这些变量名基本都能猜到是用来干嘛的了。

  第二个构造函数则是根据当前设备找出最接近最合适的一个布局,最终确定一个页面的具体参数。

DeviceProfile(Context context,
             ArrayList<DeviceProfile> profiles,
             float minWidth, float minHeight,
             int wPx, int hPx,
             int awPx, int ahPx,
             Resources res) {
    .....
    DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
    // Snap to the closest row count
    numRows = closestProfile.numRows;
    // Snap to the closest column count
    numColumns = closestProfile.numColumns;
    .....
}

1.3.layout实现

   接着看看DeviceProfile.java里的layout实现。继续看代码:

public void layout(Launcher launcher) {
    FrameLayout.LayoutParams lp;
    ......
    // Layout the workspace
    PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
    lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
    lp.gravity = Gravity.CENTER;
    int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
    Rect padding = getWorkspacePadding(orientation);
    workspace.setLayoutParams(lp);
    workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
    workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
    .....
}

可以看到初始化了workspace,然后设置其布局和间距Padding等。

2.页面初始化

  WorkSpace里面可以包含多个页面,一个页面就是一个CellLayout。首先,Launcher在启动的时候,会在LauncherModel里加载桌面图标等资源,加载完成之后,最终会在bindWorkspaceScreens方法里通过回调调用bindScreens方法,而Launcher.java实现了LauncherModel.Callbacks的接口,所以最终调用的是Launcher.java的bindScreens方法。

bindScreens里调用bindAddScreens方法,bindAddScreens里根据实际加载的页面数,循环调用Workspace的insertNewWorkspaceScreenBeforeEmptyScreen方法来生成页面。

for (int i = 0; i < count; i++) {
    mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(
      orderedScreenIds.get(i));
}

  insertNewWorkspaceScreenBeforeEmptyScreen里又调用insertNewWorkspaceScreen,insertNewWorkspaceScreen方法如下:

public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
    // Log to disk
    Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
            " at index: " + insertIndex, true);

    if (mWorkspaceScreens.containsKey(screenId)) {
        throw new RuntimeException("Screen id " + screenId + " already exists!");
    }

    CellLayout newScreen = (CellLayout)
            mLauncher.getLayoutInflater().inflate(
            R.layout.workspace_screen, null);

    newScreen.setOnLongClickListener(mLongClickListener);
    newScreen.setOnClickListener(mLauncher);
    newScreen.setSoundEffectsEnabled(false);
    mWorkspaceScreens.put(screenId, newScreen);
    mScreenOrder.add(insertIndex, screenId);
    addView(newScreen, insertIndex);
    return screenId;
}

  可以看到通过workspace_screen.xml布局实例化了一个CellLayout对象newScreen,最后将该newScreen通过addView方法添加到了Workspace的这个容器里。

桌面图标

1.图标生成

  桌面图标其实是一个继承TextView的BubbleTextView对象,其实就是一个小icon加文字的TextView。这个桌面图标的生成可以有三种途径:

  

①.默认配置生成;

②.从抽屉(或编辑模式里的小部件)里拖动到桌面生成;

③.第三方应用主动生成。

  具体途径的各个流程就不分析了,不管是从哪个途径生成的,最终都是调用applyFromShortcutInfo方法来生成最终的图标,所以要改桌面图标风格/样式的都可以在这里修改。具体看下代码:

public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,boolean setDefaultPadding, boolean promiseStateChanged) {
    Bitmap b = info.getIcon(iconCache);
    LauncherAppState app = LauncherAppState.getInstance();
    FastBitmapDrawable iconDrawable = Utilities.createIconDrawable(b);
    iconDrawable.setGhostModeEnabled(info.isDisabled != 0);
    setCompoundDrawables(null, iconDrawable, null, null);
    if (setDefaultPadding) {
        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
        setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
    }
    if (info.contentDescription != null) {
        setContentDescription(info.contentDescription);
    }
    setText(info.title);
    setTag(info);
    if (promiseStateChanged || info.isPromise()) {
        applyState(promiseStateChanged);
    }
}

  可以看到桌面icon是用工具类Utilities生成的FastBitmapDrawable,调用setCompoundDrawables把icon放在Text的上面,setText设置图标文字内容。

2.图标拖动

  Launcher里的图标拖动都是由DragController进行控制的,先来看看DragController是怎么控制的。从Launcher.java的onCreate开始,初始化了变量mDragController,然后调用setupViews()进行一些处理:

private void setupViews() {
    final DragController dragController = mDragController;
    .....
    // Setup the drag layer
    mDragLayer.setup(this, dragController);
    .....
    // Setup the workspace
    mWorkspace.setHapticFeedbackEnabled(false);
    mWorkspace.setOnLongClickListener(this);
    mWorkspace.setup(dragController);
    dragController.addDragListener(mWorkspace);
    // Get the search/delete bar
    mSearchDropTargetBar = (SearchDropTargetBar)
            mDragLayer.findViewById(R.id.search_drop_target_bar);
    // Setup AppsCustomize
    mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
    mAppsCustomizeContent = (AppsCustomizePagedView)
            mAppsCustomizeTabHost.findViewById(
            R.id.apps_customize_pane_content);
    mAppsCustomizeContent.setup(this, dragController);
    // Setup the drag controller (drop targets have to be added in reverse order in priority)
    dragController.setDragScoller(mWorkspace);
    dragController.setScrollView(mDragLayer);
    dragController.setMoveTarget(mWorkspace);
    dragController.addDropTarget(mWorkspace);
    if (mSearchDropTargetBar != null) {
        mSearchDropTargetBar.setup(this, dragController);
        mSearchDropTargetBar.setQsbSearchBar(getQsbBar());
    }
   .....

  先是对DragLayer对象进行设置,然后把有拖动处理的对象添加到DragController的拖动列表mListeners里。DragLayer实现了对View树改变的监听接口,主要就是拦截触屏事件,然后将事件转到DragController里处理。

图标的拖动有三种情况:

①.在Workspace上进行拖动;

②.从桌面文件夹里拖动到桌面;

③.从抽屉(或编辑模式)里拖动到桌面;

2.1.开始拖动

  我们知道拖动事件都是我们长按图标开始的,所以都是从onLongClick方法开始,上面三种情况对应处理:第一种是在Launcher.java的onLongClick里,第二种是在

Folder.java的onLongClick里,第三种则是在AppsCustomizePagedView的onLongClick里。

  这三种情况最终都会转到Workspace.beginDragShared方法来处理。那就看看beginDragShared的实现:

public void beginDragShared(View child, DragSource source) {
    child.clearFocus();
    child.setPressed(false);
    // The outline is used to visualize where the item will land if dropped
    mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);
    .....
    final Bitmap b = createDragBitmap(child, padding);
    .....
    int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
    int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2 - padding.get() / 2);
    ......
    DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
    ......
}

  先是调用createDragOutline画出拖动时在桌面显示的原图标轮廓Bitmap,接着调用createDragBitmap创建拖动时的图标,然后dragLayerX和dragLayerY是图标拖动时对应的偏移量,最后调用了DragController的startDrag方法开始拖动。

  接着看看DragController里的startDrag方法:

public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
        DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,float initialDragViewScale) {
    if (PROFILE_DRAWING_DURING_DRAG) {
        android.os.Debug.startMethodTracing("Launcher");
    }
    // Hide soft keyboard, if visible
    if (mInputMethodManager == null) {
        mInputMethodManager = (InputMethodManager)
        mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
    }
    mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
    for (DragListener listener : mListeners) {
        listener.onDragStart(source, dragInfo, dragAction);
    }
    final int registrationX = mMotionDownX - dragLayerX;
    final int registrationY = mMotionDownY - dragLayerY;
    final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
    final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
    mDragging = true;
    mDragObject = new DropTarget.DragObject();
    mDragObject.dragComplete = false;
    mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
    mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
    mDragObject.dragSource = source;
    mDragObject.dragInfo = dragInfo;
    final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
            registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
    if (dragOffset != null) {
        dragView.setDragVisualizeOffset(new Point(dragOffset));
    }
    if (dragRegion != null) {
        dragView.setDragRegion(new Rect(dragRegion));
    }
    mLauncher.getDragLayer().performHapticFeedback(
      HapticFeedbackConstants.LONG_PRESS);
    dragView.show(mMotionDownX, mMotionDownY);
    handleMoveEvent(mMotionDownX, mMotionDownY);
    return dragView;
}

  可以看到循环遍历mListeners了,通知所有之前加进来的拖动监听对象DragListener拖动开始;然后创建拖动对象mDragObject,并设置响应属性;接着创建DragView,并调用其show显示拖动,开始一个属性动画;最后调用handleMoveEvent来移动DragView。

2.2.结束拖动

  当用户将被拖拽物移动到相应位置后,将手指从屏幕上移开,此时要处理的就是MotionEvent.ACTION_UP事件,最终在DragController里调用drop方法把拖动对象放到相应位置,调用endDrag()做一些改变变量/释放拖动对象等结束拖动的操作。

private void drop(float x, float y) {
    final int[] coordinates = mCoordinatesTemp;
    final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
    mDragObject.x = coordinates[0];
    mDragObject.y = coordinates[1];
    boolean accepted = false;
    if (dropTarget != null) {
        mDragObject.dragComplete = true;
        dropTarget.onDragExit(mDragObject);
        if (dropTarget.acceptDrop(mDragObject)) {
            dropTarget.onDrop(mDragObject);
            accepted = true;
        }
    }
    mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
}

  可以看到drop里先调用findDropTarget查找到当前拖动的对象,如果找到了则把该对象放到最终的位置上。

3.图标点击效果

  点击桌面图标,会在BubbleTextView的updateIconState方法里处理点击的效果。

private void updateIconState() {
    Drawable top = getCompoundDrawables()[1];
    if (top instanceof FastBitmapDrawable) {
        ((FastBitmapDrawable) top).setPressed(isPressed() || mStayPressed);
    }
}

  先获取点击的图标,这应该是一个FastBitmapDrawable对象,然后调用FastBitmapDrawable的setPressed方法处理。接着看看FastBitmapDrawable的setPressed方法:

 public void setPressed(boolean pressed) {
    if (mPressed != pressed) {
        mPressed = pressed;
        if (mPressed) {
            mPressedAnimator = ObjectAnimator
                    .ofInt(this, "brightness", PRESSED_BRIGHTNESS)
                    .setDuration(CLICK_FEEDBACK_DURATION);
            mPressedAnimator.setInterpolator(
            CLICK_FEEDBACK_INTERPOLATOR);
            mPressedAnimator.start();
        } else if (mPressedAnimator != null) {
            mPressedAnimator.cancel();
            setBrightness(0);
        }
    }
    invalidateSelf();
}

  可以看到这里使用了属性动画ObjectAnimator,设置的动画的属性是”brightness”,变化范围是0-100,ObjectAnimator在动画的过程中会自动更新属性值,即会调用setBrightness方法。

public void setBrightness(int brightness) {
    if (mBrightness != brightness) {
        mBrightness = brightness;
        updateFilter();
        invalidateSelf();
    }
}

  setBrightness里可以看到最终是调用了updateFilter()方法处理点击效果。updateFilter()里最终通过Paint.setColorFilter方法实现了点击时亮度改变的这个效果。

页面滑动

  滑动首先是从触摸事件开始,onInterceptTouchEvent拦截触摸事件,onInterceptTouchEvent返回false则事件传递给子view处理,返回true则在PagedView里的onTouchEvent处理。页面滑动就是在PagedView里实现的。所以我们看看PagedView的onTouchEvent处理,主要就是处理down/move/up这三个事件,这个代码比较多,一个个事件来看吧。down这个事件其实没太多处理,就是初始化按下的位置等变量值。move事件就是要获取滑动到的位置然后重新绘制界面了,先看看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();
        }
    }
  .....

  从代码可以看到当移动距离deltaX大于等于1时才做滑动处理,调用invalidate()来重新绘制界面。这里PagedView继承ViewGroup,而ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法,而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法。所以接下来会在dispatchDraw里重新绘制界面,如果想实现自己的滑动效果,修改dispatchDraw的实现就可以了。

  再看看up事件的处理,up事件即手指离开了屏幕,最后决定是否需要切换页面。

case MotionEvent.ACTION_UP:
    if (mTouchState == TOUCH_STATE_SCROLLING) {
        final int activePointerId = mActivePointerId;
        final int pointerIndex = ev.findPointerIndex(activePointerId);
        final float x = ev.getX(pointerIndex);
        final VelocityTracker velocityTracker = mVelocityTracker;
        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
        final int deltaX = (int) (x - mDownMotionX);
        final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
        boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
                    SIGNIFICANT_MOVE_THRESHOLD;

        mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
        boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
                    Math.abs(velocityX) > mFlingThresholdVelocity;

        ......
        if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
            (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
                finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
                snapToPageWithVelocity(finalPage, velocityX);
        } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||(isFling && isVelocityXLeft)) &&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();
            }
    }
    ......

  首先是页面在滑动(TOUCH_STATE_SCROLLING)的处理,根据isSignificantMove/isDeltaXLeft/isFling/isVelocityXLeft/mCurrentPage这几个变量,确定页面是向左移动还是向右移动,还是留在当前页面。isSignificantMove是判断移动距离是否超过页面宽40%的;isDeltaXLeft是根据移动距离来判断是滑动从左到右还是从右到左;isFling是根据滑动距离和速率,判断是否是滑动;isVelocityXLeft是横向滑动的速率是否大于0;mCurrentPage则是当前页是否超出了页面个数的限制。

页面切换都调用了snapToPage方法,snapToPageWithVelocity方法是根据传进来的whichPage来切换到该页面,snapToDestination方法则是向离屏幕中心最近的页面移动。

  接着是不在滑动状态的处理,根据状态是直接切换到上一页(TOUCH_STATE_PREV_PAGE),还是是直接切换到下一页(TOUCH_STATE_NEXT_PAGE)。

  接着看看页面切换的方法snapToPage:

protected void snapToPage(int whichPage, int delta, int duration, boolean immediate,TimeInterpolator interpolator) {
    whichPage = validateNewPage(whichPage);
    mNextPage = whichPage;
    .....
     mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);

    updatePageIndicator();
    // 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();
}

  snapToPage里调用了mScroller.startScroll开始切换操作,完成切换在computeScroll()方法里面,然后会调用scrollTo()方法进行最终的切换。这里面还会调用invalidate()方法进行界面的绘制刷新,形成动画效果和页面切换效果。

时间: 2024-11-03 22:40:46

Launcher3源码浅析(5.1)--Workspace的相关文章

android7.x Launcher3源码解析(3)---workspace和allapps加载流程

Launcher系列目录: 一.android7.x Launcher3源码解析(1)-启动流程 二.android7.x Launcher3源码解析(2)-框架结构 三.android7.x Launcher3源码解析(3)-workspace和allapps加载流程 前两篇博客分别对Lancher的启动和Launcher的框架结构进行了一些分析,这一篇,将着重开始分析界面的加载流程. 1.整体流程 先上一张整体的流程图吧.(图片看不清可以下载下来看或者右击新开个页面查看图片) 先从Launc

Volley框架源码浅析(一)

尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天开始,我打算为大家呈现关于Volley框架的源码分析的文章,Volley框架是Google在2013年发布的,主要用于实现频繁而且粒度比较细小的Http请求,在此之前Android中进行Http请求通常是使用HttpUrlConnection和HttpClient进行,但是使用起来非常麻烦,而且效率比较地下,我想谷歌正式基于此种原因发布了Volley框架,其实出了Voll

PM2源码浅析

PM2工作原理 最近在玩一个游戏,<地平线:黎明时分>,最终Boss是一名叫黑底斯的人,所谓为人,也许不对,黑底斯是一段强大的毁灭进程,破坏了盖娅主进程,从而引发的整个大陆机械兽劣化故事. 为什么要讲这么一段呢,是希望大家可以更好地理解pm2的原理,要理解pm2就要理解god和santan的关系,god和santan的关系就相当于盖娅和黑底斯在pm2中的01世界中,每一行代码每一个字节都安静的工作god就是Daemon进程 守护进程,重启进程,守护node程序世界的安宁,santan就是进程的

Android源码浅析(一)——VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置

Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置 最近地方工作,就是接触源码的东西了,所以好东西还是要分享,系列开了这么多,完结 的也没几个,主要还是自己覆盖的太广了,却又不精通,嘿嘿,工作需要,所以写下了本篇博客 一.VMware 12 我选择的虚拟机试VMware,挺好用的感觉,下载VMware就不说了,善用搜索键嘛,这里我提供一个我现在在用的 下载地址:链接:http://pan.baidu.com/s/1k

ReactiveCocoa2 源码浅析

ReactiveCocoa2 源码浅析 标签(空格分隔): ReactiveCocoa iOS Objective-C ? 开车不需要知道离合器是怎么工作的,但如果知道离合器原理,那么车子可以开得更平稳. ReactiveCocoa 是一个重型的 FRP 框架,内容十分丰富,它使用了大量内建的 block,这使得其有强大的功能的同时,内部源码也比较复杂.本文研究的版本是2.4.4,小版本间的差别不是太大,无需担心此问题. 这里只探究其核心 RACSignal 源码及其相关部分.本文不会详细解释里

【Spark Core】任务执行机制和Task源码浅析2

引言 上一小节<任务执行机制和Task源码浅析1>介绍了Executor的注册过程. 这一小节,我将从Executor端,就接收LaunchTask消息之后Executor的执行任务过程进行介绍. 1. Executor的launchTasks函数 DriverActor提交任务,发送LaunchTask指令给CoarseGrainedExecutorBackend,接收到指令之后,让它内部的executor来发起任务,即调用空闲的executor的launchTask函数. 下面是Coars

Volley框架源码浅析(二)

尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列:本地队列和网络队列 /** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<

Android手势源码浅析-----手势绘制(GestureOverlayView)

Android手势源码浅析-----手势绘制(GestureOverlayView)

【Spark】Stage生成和Stage源码浅析

引入 上一篇文章<DAGScheduler源码浅析>中,介绍了handleJobSubmitted函数,它作为生成finalStage的重要函数存在,这一篇文章中,我将就DAGScheduler生成Stage过程继续学习,同时介绍Stage的相关源码. Stage生成 Stage的调度是由DAGScheduler完成的.由RDD的有向无环图DAG切分出了Stage的有向无环图DAG.Stage的DAG通过最后执行的Stage为根进行广度优先遍历,遍历到最开始执行的Stage执行,如果提交的St