Launcher2分析-Launcher,DragController,DragLayer,Workspace,HotSeat,CellLayout,之间的关系

相关的博客:Launcher2分析——桌面(WorkSpace)图标拖拽

DragTarget为Workspace(或者是Folder),DragObject为正在拖拽的那个Item,该对象持有一个DragView 对象,这个DragView就是跟随手指动的那个View。

在桌面的视图结构中,DragLayer是Workspace和HotSeat的父视图,而CellLayout则是Workspace和HotSeat的子视图,一个CellLayout代表一页。Launcher持有DragController,DragLayer,Workspace,HotSeat,而这四个类中都持有Launcher,这个四个类就是通过Launcher互相引用的。DragController在进入拖拽模式后,发挥着主力功能,是处理拖拽相关的主要实现者

DragLayer:

继承自FrameLayout。主要负责的功能是拦截和处理与拖拽有关事件。在进入拖拽模式后,就是在长按图标后,调用Workspace的startDrag()设置mDragging标志开始一直到action_up为止。在拖拽时,首先是把CellLayout的那个快捷方式图标给隐藏了,然后把一个相同图标addView到DragLayer,也就是说跟随手指的那个图标石add到DragLayer的。DragLayer还负责在action_up后,对这个DragView(跟随手指动的)进行动画,就是从手指为止到要放到的那个位置的动画。

对于measure,layout,draw的过程没有做什么自定义,只复写了一个onLayout,也是调用父类的onLayout然后自己加了几句:

protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);//调用父类的onLayout对child进行布局
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
            if (flp instanceof LayoutParams) {
                final LayoutParams lp = (LayoutParams) flp;
                if (lp.customPosition) {//如果这个属性为true,则对该child重新layout,指定为给child要求的位置参数
                child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
                }
            }
        }
    }

分析DragLayer对进入Dragging模式后对action_move拦截:

DragLayer#onInterceptTouchEvent()->DragController#onInterceptTouchEvent()

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            if (handleTouchDown(ev, true)) {
                return true;
            }
        }
        clearAllResizeFrames();
        return mDragController.onInterceptTouchEvent(ev);
    }
public boolean onInterceptTouchEvent(MotionEvent ev) {
        @SuppressWarnings("all") // suppress dead code warning
        final boolean debug = false;
        if (debug) {
            Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
                    + mDragging);
        }

        // Update the velocity tracker
        acquireVelocityTrackerAndAddMovement(ev);

        final int action = ev.getAction();
        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());//获取此时如果要放下,要放到的格子左上角坐标
        final int dragLayerX = dragLayerPos[0];
        final int dragLayerY = dragLayerPos[1];

        switch (action) {
            case MotionEvent.ACTION_MOVE:
                break;//如果是move事件,不做处理
            case MotionEvent.ACTION_DOWN:
                // Remember location of down touch
                mMotionDownX = dragLayerX;
                mMotionDownY = dragLayerY;
                mLastDropTarget = null;
                break;
            case MotionEvent.ACTION_UP:
                mLastTouchUpTime = System.currentTimeMillis();
                if (mDragging) {
                    PointF vec = isFlingingToDelete(mDragObject.dragSource);//判断是否拖拽到要删除的地方
                    if (vec != null) {
                        dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
                    } else {
                        drop(dragLayerX, dragLayerY);//如果不是删除,则是要把图标放到某个格子,格子的左上角坐标为对应的参数
                    }
                }
                endDrag();
                break;
            case MotionEvent.ACTION_CANCEL:
                cancelDrag();
                break;
        }

        return mDragging;//如果进入了拖拽模式,则返回true。就是说,只要是进入拖拽模式后的事件,都会被拦截
    }

看看DragController#drop()

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);//这是一个关键的方法,由Workspace实现(也可能是Folder实现)
                accepted = true;
            }
        }
        mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
    }

Workspace#onDrop()

源码很多,我就不贴了。里面主要是做的是准备添加view的各种参数,然后对放下这个过程做一个Animate,Animate由DragLayer实现。


Workspace:

这个类间接继承自PagedView,对于滑动,管理page相关的,Workspace都没有去修改,还是按PagedView的代码走。

Workspace实现的拖拽模式相关的部分代码,还有另外一个重要的工作就是管理WallPaper。这个WallPaper是作为Workspace的background。下面是Workspace为Wallpaper而复写的onLayout()和onDraw():

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
            mUpdateWallpaperOffsetImmediately = true;//和壁纸有关
        }
        super.onLayout(changed, left, top, right, bottom);//接着是调用父类的方法
    }

    @Override
    protected void onDraw(Canvas canvas) {
        updateWallpaperOffsets();

        // Draw the background gradient if necessary
        if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {//mBackground是Drawable
            int alpha = (int) (mBackgroundAlpha * 255);就是向左8位
            mBackground.setAlpha(alpha);
            mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(),
                    getMeasuredHeight());根据滚动而让壁纸跟着滚动
            mBackground.draw(canvas);//绘制壁纸到Canvas上}

        super.onDraw(canvas);//绘制壁纸到显示屏

        // Call back to LauncherModel to finish binding after the first draw
        post(mBindPages);//将之前的一些因某种原因延迟未执行的Runnable提交到消息队列中。
    }

下面分析一个Workspace中比较重要且常用的方法,该方法用于把快捷方式图标放到桌面上,addInScreen():

/**
     * Adds the specified child in the specified screen. The position and dimension of
     * the child are defined by x, y, spanX and spanY.
     *
     * @param child The child to add in one of the workspace's screens.
     * @param screen The screen in which to add the child.
     * @param x The X position of the child in the screen's grid.
     * @param y The Y position of the child in the screen's grid.
     * @param spanX The number of cells spanned horizontally by the child.
     * @param spanY The number of cells spanned vertically by the child.
     * @param insert When true, the child is inserted at the beginning of the children list.
     */
    void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
            boolean insert) {
        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
            if (screen < 0 || screen >= getChildCount()) {
                Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
                    + " (was " + screen + "); skipping child");
                return;
            }
        }

        final CellLayout layout;
        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {//如果该图标石要放到HotSeat中的,进入。这就是workspace帮Hotseat实现的,虽然Hotseat和workspace是平行的两个View
            layout = mLauncher.getHotseat().getLayout();
            child.setOnKeyListener(null);

            // Hide folder title in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(false);//隐藏标题
            }

            if (screen < 0) {
                screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
            } else {
                // Note: We do this to ensure that the hotseat is always laid out in the orientation
                // of the hotseat in order regardless of which orientation they were added
                x = mLauncher.getHotseat().getCellXFromOrder(screen);
                y = mLauncher.getHotseat().getCellYFromOrder(screen);
            }
        } else {//如果不是放到HotSeat,那么就是放到Workspace中了,进入这里
            // Show folder title if not in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(true);
            }

            layout = (CellLayout) getChildAt(screen);//选择要放到哪个CellLayout,也就是要放到哪一页
            child.setOnKeyListener(new IconKeyEventListener());
        }

        LayoutParams genericLp = child.getLayoutParams();
        CellLayout.LayoutParams lp;//下面是为这个快捷方式图标设置layoutparam,是Workspace新增的param,这些属性在ShortcutsAndWidgetContainer被解析
        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
        } else {
            lp = (CellLayout.LayoutParams) genericLp;
            lp.cellX = x;
            lp.cellY = y;
            lp.cellHSpan = spanX;
            lp.cellVSpan = spanY;
        }

        if (spanX < 0 && spanY < 0) {
            lp.isLockedToGrid = false;
        }

        // Get the canonical child id to uniquely represent this view in this screen
        int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
        boolean markCellsAsOccupied = !(child instanceof Folder);
//这里调用CellLayout的addViewToCellLayout将快捷方式加入到CellLayout的ShortcutAndWidgetContainer成员中。
        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
            // TODO: This branch occurs when the workspace is adding views
            // outside of the defined grid
            // maybe we should be deleting these items from the LauncherModel?
            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
        }

        if (!(child instanceof Folder)) {
            child.setHapticFeedbackEnabled(false);
            child.setOnLongClickListener(mLongClickListener);
        }
        if (child instanceof DropTarget) {
            mDragController.addDropTarget((DropTarget) child);
        }
    }

接着看CellLayout#addViewToCellLayout()

public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
            boolean markCells) {
        final LayoutParams lp = params;

        // Hotseat icons - remove text
        if (child instanceof BubbleTextView) {//检查是否快捷方式,是则进入这里
            BubbleTextView bubbleChild = (BubbleTextView) child;

            Resources res = getResources();
            if (mIsHotseat) {//根据是否HotSeat上的图标而处理标题的颜色
                bubbleChild.setTextColor(res.getColor(android.R.color.transparent));
            } else {
                bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color));
            }
        }

        child.setScaleX(getChildrenScale());//默认比例为1
        child.setScaleY(getChildrenScale());

        // Generate an id for each view, this assumes we have at most 256x256 cells
        // per workspace screen
        if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
            // If the horizontal or vertical span is set to -1, it is taken to
            // mean that it spans the extent of the CellLayout
            if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;//对一些参数进行检查调整
            if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
//这个ChildId是在Workspace中调用LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY)生成的。结合参数生成的唯一id
            child.setId(childId);

            mShortcutsAndWidgets.addView(child, index, lp);

            if (markCells) markCellsAsOccupiedForView(child);//检查该cell是否是否被占用,如果是快捷方式则为true,如果是folder则为false。

            return true;
        }
        return false;
    }



CellLayout:

持有一个mShortcutsAndWidgets:ShortcutAndWidgetContainer,这是CellLayout的唯一直接child,在CellLayout的构造方法中通过addView(mShortcutsAndWidgets),把mShortcutsAndWidgets加入到Celllayout中。其他图标的view都是add到ShortcutAndWidgetContainer中去的。ShortcutAndWidgetContainer继承自ViewGroup。

分析ShortcutAndWidgetContainer#onLayout()

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();

                int childLeft = lp.x;
                int childTop = lp.y;
                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);

                if (lp.dropped) {
                    lp.dropped = false;

                    final int[] cellXY = mTmpCellXY;
                    getLocationOnScreen(cellXY);
                    mWallpaperManager.sendWallpaperCommand(getWindowToken(),
                            WallpaperManager.COMMAND_DROP,
                            cellXY[0] + childLeft + lp.width / 2,
                            cellXY[1] + childTop + lp.height / 2, 0, null);
                }
            }
        }
    }

接着看看CellLayout的onMeasure()和onLayout(),明明CellLayout应该只有一个child,那就是ShortcutAndWidgetContainer,为什么要把onMeasure()弄得那么复杂

int DEFAULT_CELL_COUNT_X = 4; int DEFAULT_CELL_COUNT_Y = 4;



@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);

        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
            throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
        }
//mCountX和mCountY是由Workspace中的这两个字段决定:int DEFAULT_CELL_COUNT_X = 4; int DEFAULT_CELL_COUNT_Y = 4;如果要改成4x5就可以改那两字段
        int numWidthGaps = mCountX - 1;
        int numHeightGaps = mCountY - 1;

        if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
            int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();//计算ShortcutAndWidgetContainer总共可用的空间
            int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
            int hFreeSpace = hSpace - (mCountX * mCellWidth);//计算用于可分配给间隙的空间
            int vFreeSpace = vSpace - (mCountY * mCellHeight);
            mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);//计算空隙
            mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
            mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
                    mCountX);//前面计算都是为ShortcutAndWidgetContainer做嫁衣裳。这个作为父亲的礼物吧
        } else {
            mWidthGap = mOriginalWidthGap;
            mHeightGap = mOriginalHeightGap;
        }

        // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
        int newWidth = widthSpecSize;
        int newHeight = heightSpecSize;
        if (widthSpecMode == MeasureSpec.AT_MOST) {
            newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
                ((mCountX - 1) * mWidthGap);
            newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
                ((mCountY - 1) * mHeightGap);
            setMeasuredDimension(newWidth, newHeight);
        }

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
                    getPaddingRight(), MeasureSpec.EXACTLY);//给child的尺寸参数就是从本view尺寸中减去padding
            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
                    getPaddingBottom(), MeasureSpec.EXACTLY);
            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
        }
        setMeasuredDimension(newWidth, newHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            child.layout(getPaddingLeft(), getPaddingTop(),
                    r - l - getPaddingRight(), b - t - getPaddingBottom());//布局很简单,就只是减去padding,其余空间都是和本view共享
        }
    }


ShortcutAndWidgetContainer:

继承自ViewGroup

分析onMeasure()

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            measureChild(child);//下面接着分析这个方法
        }
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(widthSpecSize, heightSpecSize);
    }
public void measureChild(View child) {
        final int cellWidth = mCellWidth;
        final int cellHeight = mCellHeight;
        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();

        lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(), mCountX);
//width和high在CellLayout的onMeasure中已经计算好并传给了ShortcutAndWidgetContainer,这里只是生成MeasureSpec和调用child.measure就可以了
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
        int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
                MeasureSpec.EXACTLY);
        child.measure(childWidthMeasureSpec, childheightMeasureSpec);
    }

再看看onLayout()

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();

                int childLeft = lp.x;
                int childTop = lp.y;//直接使用传进来的param就好了,都是已经在前面已经计算好了
                child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);

                if (lp.dropped) {//在拖拽完成后,会设置成dropped为true。但是不知道为什么会要执行这段代码
                    lp.dropped = false;

                    final int[] cellXY = mTmpCellXY;
                    getLocationOnScreen(cellXY);
                    mWallpaperManager.sendWallpaperCommand(getWindowToken(),
                            WallpaperManager.COMMAND_DROP,
                            cellXY[0] + childLeft + lp.width / 2,
                            cellXY[1] + childTop + lp.height / 2, 0, null);
                }
            }
        }
    }


DropTarget:就是能容纳别的快捷方式的。DropTarget的子类有:Workspace,Folder,DeleteDropTarget,



猜你喜欢

转载自blog.csdn.net/b1480521874/article/details/80214426