1. 程式人生 > >Launcher3原始碼分析(Workspace)

Launcher3原始碼分析(Workspace)

Workspace主要功能:完成多個螢幕的以及桌布的顯示,多個螢幕之間的切換和桌布的新增。

    /**
     * Used to inflate the Workspace from XML.
     */
    public Workspace(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //獲取繪製輪廓的輔助類物件
        mOutlineHelper = HolographicOutlineHelper.obtain(context);

        mLauncher = (Launcher) context;
        mStateTransitionAnimation = new
WorkspaceStateTransitionAnimation(mLauncher, this); final Resources res = getResources(); DeviceProfile grid = mLauncher.getDeviceProfile(); mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens(); mFadeInAdjacentScreens = false; //獲取Wallpaper管理器
mWallpaperManager = WallpaperManager.getInstance(context); //獲取自定義屬性 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0); //在allapp 應用程式選單裡拖動app時workspace的縮放比例 mSpringLoadedShrinkFactor = res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0
f; //長按桌面進入預覽模式時的縮放比例 mOverviewModeShrinkFactor = grid.getOverviewModeScale(mIsRtl); //開機時的螢幕 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); }

初始化workspace

/**
     * Initializes various states for this workspace.
     */
    protected void initWorkspace() {
        //預設頁
        mCurrentPage = mDefaultPage;
        LauncherAppState app = LauncherAppState.getInstance();
        DeviceProfile grid = mLauncher.getDeviceProfile();
        //儲存應用圖片的快取
        mIconCache = app.getIconCache();
        setWillNotDraw(false);
        setClipChildren(false);
        setClipToPadding(false);
        //設定子view繪圖快取開始
        setChildrenDrawnWithCacheEnabled(true);

        setMinScale(mOverviewModeShrinkFactor);
        setupLayoutTransition();
        //wallpaper偏移
        mWallpaperOffset = new WallpaperOffsetInterpolator();
        //獲取螢幕大小
        Display display = mLauncher.getWindowManager().getDefaultDisplay();
        display.getSize(mDisplaySize);

        mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);

        // Set the wallpaper dimensions when Launcher starts up
        setWallpaperDimension();

        setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color));
    }

workspace實現了DragSource和Dragtarget,說明它即是一個拖動的容器也是一個拖動的源,分析開始拖動方法:

public void startDrag(CellLayout.CellInfo cellInfo){
    startDrag(cellInfo,false);
}
public void startDrag(CellLayout.CellInfo cellInfo,boolean accessible){
    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);

        beginDragShared(child, this, accessible);
}

public void beginDragShared(View child, DragSource source,boolean accessible){
    beginDragShared(child, new Point(), source, accessible);
}

public void beginDragShared(View child, Point relativeTouchPos,DragSource source, boolean accessible){
    child.clearFocus();
    child.setPressed(false);
    //當item拖動時跟隨著的背景圖
    mDragOutline = createDragOutline(child,DRAG_BITMAP_PADDING);
    //開始拖動
    mLauncher.onDragStarted(child);
    // The drag bitmap follows the touch point around on the screen
    AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);

    final Bitmap b = createDragBitmap(child,padding);

    float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
        int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
        int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2 - padding.get() / 2);

    DeviceProfile grid = mLauncher.getDeviceProfile();
    Point dragVisualizeOffset = null;
    Rect dragRect = null;
    if (child instanceof BubbleTextView) {
            BubbleTextView icon = (BubbleTextView) child;
            int iconSize = grid.iconSizePx;
            int top = child.getPaddingTop();
            int left = (bmpWidth - iconSize) / 2;
            int right = left + iconSize;
            int bottom = top + iconSize;
            if (icon.isLayoutHorizontal()) {
                // If the layout is horizontal, then if we are just picking up the icon, then just
                // use the child position since the icon is top-left aligned.  Otherwise, offset
                // the drag layer position horizontally so that the icon is under the current
                // touch position.
                if (icon.getIcon().getBounds().contains(relativeTouchPos.x, relativeTouchPos.y)) {
                    dragLayerX = Math.round(mTempXY[0]);
                } else {
                    dragLayerX = Math.round(mTempXY[0] + relativeTouchPos.x - (bmpWidth / 2));
                }
            }
            dragLayerY += top;
            // Note: The drag region is used to calculate drag layer offsets, but the
            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
            dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
            dragRect = new Rect(left, top, right, bottom);
        } else if (child instanceof FolderIcon) {
            int previewSize = grid.folderIconSizePx;
            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
        }

        // Clear the pressed state if necessary
        if (child instanceof BubbleTextView) {
            BubbleTextView icon = (BubbleTextView) child;
            icon.clearPressedBackground();
        }

        if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
            String msg = "Drag started with a view that has no tag set. This "
                    + "will cause a crash (issue 11627249) down the line. "
                    + "View: " + child + "  tag: " + child.getTag();
            throw new IllegalStateException(msg);
        }

        //呼叫DragController的startDrag方法
        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());

        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
        }

        b.recycle();

}

workspace主要負責左右滑動,滑動功能主要分兩步:1、在onInterceptTouchEvent中進行攔截。2、在onTouchEvent中進行滑動

public boolean onTouch(View v, MotionEvent event){
    return (worspaceInModalSate() || !isFinishedSwitchingState()) || (!workspaceInModalState() && indexofChild(V) != mCurrentPage);
}

public boolean onInterceptTounchEvent(MotionEvent ev){
    switch(ev.getAction() & MptionEvent.ACTION_MASK){
        case MotionEvcent.ACTION_DOWN:
            mXDown = ev.getX();
            mYDown = ev.getY();
            //記錄按下的時間
            mTouchDownTime = system.currentTimeMillis();
            break;
        case MotionEvent.ACTION_POINTER_UP:
        case MotionEvent.ACTION_up:
            if(mTouvhState == TOUCH_STATE_RESET){
                final CellLayout currentPage == (CellLayout)getChildAt(mCurrentPage);
                if(currentPage != null){
                    onWallpaperTap(ev);
                }
            }
        //呼叫父類的onInterceptTouchEvent(ev);主要功能是在onTouchEvent()方法之前處理touch事件。包括:down、up、move事件。
        return super.onInterceptTouchEvent(ev);
    }
}

重寫了父類的computeScroll();主要功能是計算拖動的位移量、更新背景、設定要顯示的螢幕。

public void computeScroll(){
    super.computeScroll();
    mWallpaperOffset.syncWithScroll();
}

public void syncWithScroll(){
    float offset = wallpaperOffsetForCurrentScroll();
    mWallpaperOffset.setFinalX(offset);
    updateOffset(true);
}
//處理桌布移動的類
 class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
        float mFinalOffset = 0.0f;
        float mCurrentOffset = 0.5f; // to force an initial update
        boolean mWaitingForUpdate;
        Choreographer mChoreographer;
        Interpolator mInterpolator;
        boolean mAnimating;
        long mAnimationStartTime;
        float mAnimationStartOffset;
        private final int ANIMATION_DURATION = 250;
        // Don't use all the wallpaper for parallax until you have at least this many pages
        private final int MIN_PARALLAX_PAGE_SPAN = 3;
        int mNumScreens;

        public WallpaperOffsetInterpolator() {
            mChoreographer = Choreographer.getInstance();
            mInterpolator = new DecelerateInterpolator(1.5f);
        }

        @Override
        public void doFrame(long frameTimeNanos) {
            //呼叫了updateOffset方法,這個方法即是處理桌布的位移的。
            updateOffset(false);
        }

        private void updateOffset(boolean force) {
            if (mWaitingForUpdate || force) {
                mWaitingForUpdate = false;
                if (computeScrollOffset() && mWindowToken != null) {
                    try {
                        mWallpaperManager.setWallpaperOffsets(mWindowToken,
                                mWallpaperOffset.getCurrX(), 0.5f);
                        setWallpaperOffsetSteps();
                    } catch (IllegalArgumentException e) {
                        Log.e(TAG, "Error updating wallpaper offset: " + e);
                    }
                }
            }
        }

        public boolean computeScrollOffset() {
            final float oldOffset = mCurrentOffset;
            if (mAnimating) {
                long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
                float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
                float t1 = mInterpolator.getInterpolation(t0);
                mCurrentOffset = mAnimationStartOffset +
                        (mFinalOffset - mAnimationStartOffset) * t1;
                mAnimating = durationSinceAnimation < ANIMATION_DURATION;
            } else {
                mCurrentOffset = mFinalOffset;
            }

            if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
                scheduleUpdate();
            }
            if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
                return true;
            }
            return false;
        }

        private float wallpaperOffsetForCurrentScroll() {
            // TODO: do different behavior if it's  a live wallpaper?
            // Don't use up all the wallpaper parallax until you have at least
            // MIN_PARALLAX_PAGE_SPAN pages
            int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
            int parallaxPageSpan;
            if (mWallpaperIsLiveWallpaper) {
                parallaxPageSpan = numScrollingPages - 1;
            } else {
                parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
            }
            mNumPagesForWallpaperParallax = parallaxPageSpan;

            if (getChildCount() <= 1) {
                if (mIsRtl) {
                    return 1 - 1.0f/mNumPagesForWallpaperParallax;
                }
                return 0;
            }

            // Exclude the leftmost page
            int emptyExtraPages = numEmptyScreensToIgnore();
            int firstIndex = numCustomPages();
            // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
            int lastIndex = getChildCount() - 1 - emptyExtraPages;
            if (mIsRtl) {
                int temp = firstIndex;
                firstIndex = lastIndex;
                lastIndex = temp;
            }

            int firstPageScrollX = getScrollForPage(firstIndex);
            int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
            if (scrollRange == 0) {
                return 0;
            } else {
                // Sometimes the left parameter of the pages is animated during a layout transition;
                // this parameter offsets it to keep the wallpaper from animating as well
                int adjustedScroll =
                        getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
                float offset = Math.min(1, adjustedScroll / (float) scrollRange);
                offset = Math.max(0, offset);

                // On RTL devices, push the wallpaper offset to the right if we don't have enough
                // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
                if (!mWallpaperIsLiveWallpaper && numScrollingPages < MIN_PARALLAX_PAGE_SPAN
                        && mIsRtl) {
                    return offset * (parallaxPageSpan - numScrollingPages + 1) / parallaxPageSpan;
                }
                return offset * (numScrollingPages - 1) / parallaxPageSpan;
            }
        }

        private int numEmptyScreensToIgnore() {
            int numScrollingPages = getChildCount() - numCustomPages();
            if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
                return 1;
            } else {
                return 0;
            }
        }

        private int getNumScreensExcludingEmptyAndCustom() {
            int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
            return numScrollingPages;
        }

        public void syncWithScroll() {
            //獲取桌布偏移量
            float offset = wallpaperOffsetForCurrentScroll();
            //設定桌布偏移量
            mWallpaperOffset.setFinalX(offset);
            //更新桌布偏移量
            updateOffset(true);
        }

        public float getCurrX() {
            return mCurrentOffset;
        }

        public float getFinalX() {
            return mFinalOffset;
        }

        private void animateToFinal() {
            mAnimating = true;
            mAnimationStartOffset = mCurrentOffset;
            mAnimationStartTime = System.currentTimeMillis();
        }

        private void setWallpaperOffsetSteps() {
            // Set wallpaper offset steps (1 / (number of screens - 1))
            float xOffset = 1.0f / mNumPagesForWallpaperParallax;
            if (xOffset != mLastSetWallpaperOffsetSteps) {
                mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f);
                mLastSetWallpaperOffsetSteps = xOffset;
            }
        }

        public void setFinalX(float x) {
            scheduleUpdate();
            mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
            if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
                if (mNumScreens > 0) {
                    // Don't animate if we're going from 0 screens
                    animateToFinal();
                }
                mNumScreens = getNumScreensExcludingEmptyAndCustom();
            }
        }

        private void scheduleUpdate() {
            if (!mWaitingForUpdate) {
                mChoreographer.postFrameCallback(this);
                mWaitingForUpdate = true;
            }
        }
        //把桌布最終偏移量設定為當前偏移量
        public void jumpToFinal() {
            mCurrentOffset = mFinalOffset;
        }
    }

onLayout()交給父類PageView處理

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);
}

查詢新螢幕的索引,如果存在空螢幕,則在那之前插入它

public void long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId){

}
public long insertNewWorkspaceScreen(long screenId, int insertIndex){
    if(mWorkspaceScreens.containsKey(screenId)){
        throw new RuntimeException("Screen id " + screenId + "already exists!");
    }
    //inflate xml
    CellLayout newScreen = (CellLayout)mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen,this,false);
    //設定螢幕的長按、點選事件監聽
    newScreen.setOnLongClickListener(mLongClickListener);
    newScreen.setOnClickListener(mLauncher);
    //螢幕聲音效果
    newScreen.setSoundEffectEnabled(false);
    mWorkspaceScreens.put(screenId,newScreen);
    mScreenOrder.add(insertIndex,screenId);
    addView(newScreen,insertIndex);
    //螢幕允許拖曳?
    LauncherAccessibilityDelegate delegate = LauncherAppSate.getInstance().getAccessibleDrag();
    if(delegate != null && dlelegate.isInAccessibleDrag()){
        newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILTY_DRAG);
        return screenId;
    }
}