1.简介

默许的launcher便是launcher3这个app了,手机发动今后主动发动的app,便是咱们常说的桌面。点击home键会回来桌面app,假如手机上装有多个桌面app,那么点击home键会提示让你挑选一个。

2.launcher.xml

布局简单剖析下

<com.android.launcher3.LauncherRootView
    android:id="@+id/launcher"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <com.android.launcher3.dragndrop.DragLayer
        android:id="@+id/drag_layer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:importantForAccessibility="no">
        <com.android.launcher3.views.AccessibilityActionsView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:contentDescription="@string/home_screen"
            />
        <!-- The workspace contains 5 screens of cells -->
        <!-- 左右滑动的控件 DO NOT CHANGE THE ID -->
        <com.android.launcher3.Workspace
            android:id="@+id/workspace"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:theme="@style/HomeScreenElementTheme"
            launcher:pageIndicator="@+id/page_indicator" />
        <!-- home页底部那几个方便方式,DO NOT CHANGE THE ID -->
        <include
            android:id="@+id/hotseat"
            layout="@layout/hotseat" />
        <!-- 对应workspace的指示器,只要一页的话不显现。Keep these behind the workspace so that they are not visible when
         we go into AllApps -->
        <com.android.launcher3.pageindicators.WorkspacePageIndicator
            android:id="@+id/page_indicator"
            android:layout_width="match_parent"
            android:layout_height="@dimen/workspace_page_indicator_height"
            android:layout_gravity="bottom|center_horizontal"
            android:theme="@style/HomeScreenElementTheme" />
        <!-- 这个是长按app方便方式的时分,屏幕顶部呈现的操作按钮,撤销,卸载等按钮-->
        <include
            android:id="@+id/drop_target_bar"
            layout="@layout/drop_target_bar" />
        <com.android.launcher3.views.ScrimView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/scrim_view"
            android:background="@android:color/transparent" />
        <!--这个是手势上划今后看到的页面,便是一个搜索框,下边是一切已装置的app-->
        <include
            android:id="@+id/apps_view"
            layout="@layout/all_apps"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <!--用来显现recents内容的,详细布局在quickStep目录下重写了-->
        <include
            android:id="@+id/overview_panel"
            layout="@layout/overview_panel" />
    </com.android.launcher3.dragndrop.DragLayer>
</com.android.launcher3.LauncherRootView>

下图1 是id/workspace ,图2 是id/page_indicator ,图3是id/hotseat

android framework13-launcher3【01launcher】

下图是布局里的id://apps_views

android framework13-launcher3【01launcher】

2.0.include layout

>hotseat.xml

<com.android.launcher3.Hotseat
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto"
    android:id="@+id/hotseat"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:theme="@style/HomeScreenElementTheme"
    android:importantForAccessibility="no"
    android:preferKeepClear="true"
    launcher:containerType="hotseat" />
public class Hotseat extends CellLayout implements Insettable {

hotseat的onLayout里打印了下

System.out.println("log========="+l+"/"+t+","+r+"/"+b);
//log=========0/2568,1440/2960

>all_apps.xml

<com.android.launcher3.allapps.LauncherAllAppsContainerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/apps_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="true"
    android:clipToPadding="false"
    android:focusable="false"
    android:saveEnabled="false" />

>drop_target_bar.xml

<com.android.launcher3.DropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dynamic_grid_drop_target_size"
    android:layout_gravity="center_horizontal|top"
    android:focusable="false"
    android:alpha="0"
    android:theme="@style/HomeScreenElementTheme"
    android:visibility="invisible">
    <!-- Delete target -->
    <com.android.launcher3.DeleteDropTarget
        android:id="@+id/delete_target_text"
        style="@style/DropTargetButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="@string/remove_drop_target_label" />
    <!-- Uninstall target -->
    <com.android.launcher3.SecondaryDropTarget
        android:id="@+id/uninstall_target_text"
        style="@style/DropTargetButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="@string/uninstall_drop_target_label" />
</com.android.launcher3.DropTargetBar>

2.1 DeviceProfile.java

从布局看,Workspace控件是铺满全屏的,可实际作用,很明显,距离底部有一段距离。WorkspacePageIndicator也是,布局上看,它便是底部居中的,可实际上的方位,明显和底部有一段距离。

查看了下这两控件的onlayout办法,并未进行处理,所以应该是其他地方处理的。

DeviceProfile是经过Builder来创立的,如下

    public static class Builder {
        public DeviceProfile build() {
//...
            return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
                    mIsMultiWindowMode, mTransposeLayoutWithOrientation, mIsMultiDisplay,
                    mIsGestureMode, mViewScaleProvider);
        }

DeviceProfile里有如下的变量,看姓名就很像咱们要的

    public final Rect workspacePadding = new Rect();

数据的改动是经过下边这个办法,这个办法在结构办法里会调用2次,先在updateAvailableDimensions(res)办法里,之后等cellLayoutPaddingPx这个核算出来今后,又从头调用了一次。

        private void updateWorkspacePadding() {

>workspace里运用

能够看到Workspace控件设置了padding,indicator控件设置了margin

    public void setInsets(Rect insets) {
        DeviceProfile grid = mLauncher.getDeviceProfile();
        mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
        //拿到padding并给控件设置padding
        Rect padding = grid.workspacePadding;
        setPadding(padding.left, padding.top, padding.right, padding.bottom);
        mInsets.set(insets);
        if (mWorkspaceFadeInAdjacentScreens) {
            // In landscape mode the page spacing is set to the default.
            setPageSpacing(grid.edgeMarginPx);
        } else {
            // In portrait, we want the pages spaced such that there is no
            // overhang of the previous / next page into the current page viewport.
            // We assume symmetrical padding in portrait mode.
            int maxInsets = Math.max(insets.left, insets.right);
            int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
            setPageSpacing(Math.max(maxInsets, maxPadding));
        }
        updateCellLayoutPadding();
        updateWorkspaceWidgetsSizes();
        setPageIndicatorInset();
    }
    //这个是给indicator设置margin
        private void setPageIndicatorInset() {
            DeviceProfile grid = mLauncher.getDeviceProfile();
            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPageIndicator.getLayoutParams();
            // Set insets for page indicator
            Rect padding = grid.workspacePadding;
            if (grid.isVerticalBarLayout()) {
                lp.leftMargin = padding.left + grid.workspaceCellPaddingXPx;
                lp.rightMargin = padding.right + grid.workspaceCellPaddingXPx;
                lp.bottomMargin = padding.bottom;
            } else {
                lp.leftMargin = lp.rightMargin = 0;
                lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
                lp.bottomMargin = grid.hotseatBarSizePx;
            }
            //Workspace的布局里有指明indicator的id,所以这儿拿到了对应的view
            mPageIndicator.setLayoutParams(lp);
        }

2.2.InvariantDeviceProfile.java

    private void initGrid(Context context, Info displayInfo, DisplayOption displayOption,
            @DeviceType int deviceType) {
    //...
    for (WindowBounds bounds : displayInfo.supportedBounds) {
        localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
                .setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY)
                .setWindowBounds(bounds)
                .setDotRendererCache(dotRendererCache)
                .build());

这儿的bounds有3种,依据结果猜测应该是竖屏加两种横屏,模拟器日志如下

//bounds and inset
Rect(0, 0 - 1440, 2960)==Rect(0, 84 - 0, 168)
Rect(0, 0 - 2960, 1440)==Rect(0, 84 - 168, 0)
Rect(0, 0 - 2960, 1440)==Rect(168, 84 - 0, 0)

3.View

3.1.CellLayout

public class CellLayout extends ViewGroup {
//...
public @interface ContainerType{}
public static final int WORKSPACE = 0;
public static final int HOTSEAT = 1;
public static final int FOLDER = 2;
    public CellLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
        //容器的类型,有3种,默许是workspace
        mContainerType = a.getInteger(R.styleable.CellLayout_containerType, WORKSPACE);
        a.recycle();
        // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
        // the user where a dragged item will land when dropped.
        setWillNotDraw(false);
        setClipToPadding(false);
        mActivity = ActivityContext.lookupContext(context);
        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
        resetCellSizeInternal(deviceProfile);
    //每页切割的行数和列数
        mCountX = deviceProfile.inv.numColumns;
        mCountY = deviceProfile.inv.numRows;
        mOccupied =  new GridOccupancy(mCountX, mCountY);
        mTmpOccupied = new GridOccupancy(mCountX, mCountY);
        mFolderLeaveBehind.mDelegateCellX = -1;
        mFolderLeaveBehind.mDelegateCellY = -1;
        setAlwaysDrawnWithCacheEnabled(false);
        Resources res = getResources();
        mBackground = getContext().getDrawable(R.drawable.bg_celllayout);
        mBackground.setCallback(this);
        mBackground.setAlpha(0);
    //便是长按图标的时分,图标底部会呈现一个椭圆的边框,便是那个颜色
        mGridColor = Themes.getAttrColor(getContext(), R.attr.workspaceAccentColor);
        mGridVisualizationRoundingRadius =
                res.getDimensionPixelSize(R.dimen.grid_visualization_rounding_radius);
        mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
        // Initialize the data structures used for the drag visualization.
        mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
        mDragCell[0] = mDragCell[1] = -1;
        mDragCellSpan[0] = mDragCellSpan[1] = -1;
        for (int i = 0; i < mDragOutlines.length; i++) {
            mDragOutlines[i] = new CellLayoutLayoutParams(0, 0, 0, 0, -1);
        }
        mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));
    //...
        mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
                mBorderSpace);
                //默许增加了一个容器
        addView(mShortcutsAndWidgets);
    }

3.2.Hotseat.java

public class Hotseat extends CellLayout implements Insettable {
//...
    public Hotseat(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
//这个应该是曾经用的,曾经的qsb是在hotseat里,现在的是在顶部显现的,所以这个能够不重视,宽高都是0
        mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
        addView(mQsb);
    }
    public void resetLayout(boolean hasVerticalHotseat) {
        removeAllViewsInLayout();
        mHasVerticalHotseat = hasVerticalHotseat;
        DeviceProfile dp = mActivity.getDeviceProfile();
        resetCellSize(dp);
        //设置几行几列
        if (hasVerticalHotseat) {
            setGridSize(1, dp.numShownHotseatIcons);//横屏是左右一列
        } else {
            setGridSize(dp.numShownHotseatIcons, 1);//竖屏是底部一行
        }
    }
    public void setInsets(Rect insets) {
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
        DeviceProfile grid = mActivity.getDeviceProfile();
        if (grid.isVerticalBarLayout()) {
        //横屏,高度铺满,宽度限制,依据seascape决定显现在左面仍是右边
            mQsb.setVisibility(View.GONE);
            lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
            if (grid.isSeascape()) {
                lp.gravity = Gravity.LEFT;
                lp.width = grid.hotseatBarSizePx + insets.left;
            } else {
                lp.gravity = Gravity.RIGHT;
                lp.width = grid.hotseatBarSizePx + insets.right;
            }
        } else {
        //竖屏,底部显现,宽度铺满,高度限制
            mQsb.setVisibility(View.VISIBLE);
            lp.gravity = Gravity.BOTTOM;
            lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
            lp.height = grid.hotseatBarSizePx;
        }
        Rect padding = grid.getHotseatLayoutPadding(getContext());
        setPadding(padding.left, padding.top, padding.right, padding.bottom);
        setLayoutParams(lp);
        InsettableFrameLayout.dispatchInsets(this, insets);
    }
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    //接触事情交给workspace处理了
        int yThreshold = getMeasuredHeight() - getPaddingBottom();
        if (mWorkspace != null && ev.getY() <= yThreshold) {
            mSendTouchToWorkspace = mWorkspace.onInterceptTouchEvent(ev);
            return mSendTouchToWorkspace;
        }
        return false;
    }

3.3.WorkspacePageIndicator.java

与workspace翻页对应的一个自定义指示器,不论横屏仍是竖屏,都是在底部的,当然了,竖屏的时分是在hotseat上边的。

这儿首要阐明的是,workspace里有个办法【setPageIndicatorInset()】,设置了这个indicator的margin。

public class WorkspacePageIndicator extends View implements Insettable, PageIndicator {
//
    public WorkspacePageIndicator(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    Resources res = context.getResources();
    mLinePaint = new Paint();
    mLinePaint.setAlpha(0);
    mLauncher = Launcher.getLauncher(context);
    mLineHeight = res.getDimensionPixelSize(R.dimen.workspace_page_indicator_line_height);
    boolean darkText = Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText);
    mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
    mLinePaint.setColor(darkText ? Color.BLACK : Color.WHITE);
}
protected void onDraw(Canvas canvas) {
    if (mTotalScroll == 0 || mNumPagesFloat == 0) {
        return;
    }
//依据当前地点的页面,画条钱
    canvas.drawRoundRect(lineLeft, getHeight() / 2 - mLineHeight / 2, lineRight,
            getHeight() / 2 + mLineHeight / 2, mLineHeight, mLineHeight, mLinePaint);
}
//主动躲藏
    public void setShouldAutoHide(boolean shouldAutoHide) {
    mShouldAutoHide = shouldAutoHide;
    if (shouldAutoHide && mLinePaint.getAlpha() > 0) {
        hideAfterDelay();
    } else if (!shouldAutoHide) {
        mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
    }
}

3.4.Workspace.java

里面的child是CellLayout

/**
 * The workspace is a wide area with a wallpaper and a finite number of pages.
 * Each page contains a number of icons, folders or widgets the user can
 * interact with. A workspace is meant to be used with a fixed width only.
 * @param <T> Class that extends View and PageIndicator
 */
public class Workspace<T extends View & PageIndicator> extends PagedView<T>
        implements DropTarget, DragSource, View.OnTouchListener,
        DragController.DragListener, Insettable, StateHandler<LauncherState>,
        WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {
   //...
    public Workspace(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mLauncher = Launcher.getLauncher(context);
        mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
        mWallpaperManager = WallpaperManager.getInstance(context);
        mAllAppsIconSize = mLauncher.getDeviceProfile().allAppsIconSizePx;
        mWallpaperOffset = new WallpaperOffsetInterpolator(this);
        setHapticFeedbackEnabled(false);
        initWorkspace();
        // Disable multitouch across the workspace/all apps/customize tray
        setMotionEventSplittingEnabled(true);
        //接触事情的处理
        setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
        mStatsLogManager = StatsLogManager.newInstance(context);
    }

>setInsets

public void setInsets(Rect insets) {
    DeviceProfile grid = mLauncher.getDeviceProfile();
    mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
    Rect padding = grid.workspacePadding;
    //设置padding
    setPadding(padding.left, padding.top, padding.right, padding.bottom);
    mInsets.set(insets);
    if (mWorkspaceFadeInAdjacentScreens) {
        // In landscape mode the page spacing is set to the default.
        setPageSpacing(grid.edgeMarginPx);
    } else {
        // In portrait, we want the pages spaced such that there is no
        // overhang of the previous / next page into the current page viewport.
        // We assume symmetrical padding in portrait mode.
        int maxInsets = Math.max(insets.left, insets.right);
        int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
        setPageSpacing(Math.max(maxInsets, maxPadding));
    }
    //celllayout便是workspace里的child了,设置下padding
    updateCellLayoutPadding();
    //设置里面的widget的巨细,又是套在ShortcutAndWidgetContainer容器里的child
    updateWorkspaceWidgetsSizes();
    //给indicator设置margin,确保竖屏的话indicator在hotseat上边,横屏的话在右边或者左面
    setPageIndicatorInset();
}

>drag start/end

    public void onDragStart(DragObject dragObject, DragOptions options) {
        if (mDragInfo != null && mDragInfo.cell != null) {
            CellLayout layout = (CellLayout) (mDragInfo.cell instanceof LauncherAppWidgetHostView
                    ? dragObject.dragView.getContentViewParent().getParent()
                    : mDragInfo.cell.getParent().getParent());
            layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
        }
        updateChildrenLayersEnabled();
//判别下是否需求增加新的页面 
        boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this);
        if (addNewPage) {
            mDeferRemoveExtraEmptyScreen = false;
            //增加一个空白页面
            addExtraEmptyScreenOnDrag(dragObject);
            if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
                    && dragObject.dragSource != this) {
                int currentPage = getDestinationPage();
                for (int pageIndex = currentPage; pageIndex < getPageCount(); pageIndex++) {
                    CellLayout page = (CellLayout) getPageAt(pageIndex);
                    if (page.hasReorderSolution(dragObject.dragInfo)) {
                        setCurrentPage(pageIndex);
                        break;
                    }
                }
            }
        }
        // Always enter the spring loaded mode
        mLauncher.getStateManager().goToState(SPRING_LOADED);
    }
public void onDragEnd() {
    updateChildrenLayersEnabled();
    StateManager<LauncherState> stateManager = mLauncher.getStateManager();
    stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
        @Override
        public void onStateTransitionComplete(LauncherState finalState) {
            if (finalState == NORMAL) {
                if (!mDeferRemoveExtraEmptyScreen) {
                    removeExtraEmptyScreen(true /* stripEmptyScreens */);
                }
                stateManager.removeStateListener(this);
            }
        }
    });
    mDragInfo = null;
    mDragSourceInternal = null;
}

>onViewAdded

public void onViewAdded(View child) {
//能够看到,只能增加CellLayout
    if (!(child instanceof CellLayout)) {
        throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
    }
    CellLayout cl = ((CellLayout) child);
    cl.setOnInterceptTouchListener(this);//child是否阻拦接触事情,由父类来处理
    cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
    super.onViewAdded(child);
}

>bindAndInitFirstWorkspaceScreen

一个是launcher类里调用,一个是下边removeAll的时分调用

public void bindAndInitFirstWorkspaceScreen() {
    if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
        return;
    }
    // 增加第一个页面
    CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
    // Always add a first page pinned widget on the first screen.
    if (mFirstPagePinnedItem == null) {
    //这个便是那个顶部的search bar
        mFirstPagePinnedItem = LayoutInflater.from(getContext())
                .inflate(R.layout.search_container_workspace, firstPage, false);
    }
    int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
    CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, cellHSpan, 1, FIRST_SCREEN_ID);
    lp.canReorder = false;
    //把search bar 增加到第一个cellLayout页面里去
    if (!firstPage.addViewToCellLayout(
            mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
        Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
        mFirstPagePinnedItem = null;
    }
}

+search_container_workspace.xml

<com.android.launcher3.qsb.QsbContainerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:id="@id/search_container_workspace"
        android:padding="0dp" >
    <fragment
        android:name="com.android.launcher3.qsb.QsbContainerView$QsbFragment"
        android:layout_width="match_parent"
        android:tag="qsb_view"
        android:layout_height="match_parent"/>
</com.android.launcher3.qsb.QsbContainerView>

>insertNewWorkspaceScreen

    public CellLayout insertNewWorkspaceScreen(int screenId, int insertIndex) {
        CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                        R.layout.workspace_screen, this, false /* attachToRoot */);
        mWorkspaceScreens.put(screenId, newScreen);
        mScreenOrder.add(insertIndex, screenId);
        addView(newScreen, insertIndex);//增加到workspace里
        mStateTransitionAnimation.applyChildState(
                mLauncher.getStateManager().getState(), newScreen, insertIndex);
        updatePageScrollValues();
        updateCellLayoutPadding();
        return newScreen;
    }

外部调用的是下边的办法

    public void insertNewWorkspaceScreenBeforeEmptyScreen(int screenId) {
        // 有空白页的话插在空白页之前,没有的话插在容器结尾
        int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
        if (insertIndex < 0) {
            insertIndex = mScreenOrder.size();
        }
        insertNewWorkspaceScreen(screenId, insertIndex);
    }
    public void insertNewWorkspaceScreen(int screenId) {
    //默许插入在结尾
        insertNewWorkspaceScreen(screenId, getChildCount());
    }

>removeAllWorkspaceScreens

在launcher里会调用这个办法,首要是清空workspace内容,并初始化一个默许的页面

    public void removeAllWorkspaceScreens() {
        disableLayoutTransitions();
        // 移除主页那个search bar
        if (mFirstPagePinnedItem != null) {
            ((ViewGroup) mFirstPagePinnedItem.getParent()).removeView(mFirstPagePinnedItem);
        }
        // 清空view以及集合里保存的引证
        removeFolderListeners();
        removeAllViews();
        mScreenOrder.clear();
        mWorkspaceScreens.clear();
        // Remove any deferred refresh callbacks
        mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
        // 从头初始化第一个默许的页面
        bindAndInitFirstWorkspaceScreen();
        // Re-enable the layout transitions
        enableLayoutTransitions();
    }

+workspace_screen.xml

<com.android.launcher3.CellLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hapticFeedbackEnabled="false"
    launcher:containerType="workspace" />

>workspace元素介绍

如下图,有3种,

  • 第一种是LauncherAppWidgetHostView,也便是常说的(某个app的)小部件
  • 第二种是文件夹FolderIcon,其实拖拽的时分也是当个图标处理的
  • 第三种便是app的方便图标了DoubleShadowBubbleTextView
    android framework13-launcher3【01launcher】

>addExtraEmptyScreenOnDrag

处理下拖拽的时分,需不需求增加新的空白页。

变量阐明:

  • dragSourceChildCount—-开始拖动今后,页面上元素的个数,假如是小部件的话,开始拖动的时分这个小部件就主动从页面移除了,所以拖动小部件的时分这个count会少一,举个列子,假如页面上就只要一个小部件,你拖动今后回来的count便是0了。

      private void addExtraEmptyScreenOnDrag(DragObject dragObject) {
          boolean lastChildOnScreen = false;
          boolean childOnFinalScreen = false;
          if (mDragSourceInternal != null) {
          //看上边阐明
              int dragSourceChildCount = mDragSourceInternal.getChildCount();
              //这玩意判别的是折叠屏吗? If the icon was dragged from Hotseat, there is no page pair
              if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) {
                  int pagePairScreenId = getScreenPair(dragObject.dragInfo.screenId);
                  CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId);
                  dragSourceChildCount += pagePair.getShortcutsAndWidgets().getChildCount();
              }
    //拖动的是小部件的话,这个count数少一个,这儿需求加回来
              if (dragObject.dragView.getContentView() instanceof LauncherAppWidgetHostView) {
                  dragSourceChildCount++;
              }
      //阐明咱们拖动的是页面上的唯一一个元素
              if (dragSourceChildCount == 1) {
                  lastChildOnScreen = true;
              }
              CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
              //这个拖动的图标地点的页面是workspace的最后一个页面
              if (getLeftmostVisiblePageForIndex(indexOfChild(cl))
                      == getLeftmostVisiblePageForIndex(getPageCount() - 1)) {
                  childOnFinalScreen = true;
              }
          }
          // 拖动的是最后一个页面的唯一一个元素,那么不必创立新的空白页面
          if (lastChildOnScreen && childOnFinalScreen) {
              return;
          }
          forEachExtraEmptyPageId(extraEmptyPageId -> {
          //没有空白页的话,创立一个
              if (!mWorkspaceScreens.containsKey(extraEmptyPageId)) {
                  insertNewWorkspaceScreen(extraEmptyPageId);
              }
          });
      }
    
    private void forEachExtraEmptyPageId(Consumer<Integer> callback) {
        callback.accept(EXTRA_EMPTY_SCREEN_ID);//空屏的id,固定的
        if (isTwoPanelEnabled()) {//双屏的话,再加一个空的
            callback.accept(EXTRA_EMPTY_SCREEN_SECOND_ID);
        }
    }

>removeExtraEmptyScreen

    public void removeExtraEmptyScreen(boolean stripEmptyScreens) {
        removeExtraEmptyScreenDelayed(0, stripEmptyScreens, null);
    }
    public void removeExtraEmptyScreenDelayed(
        int delay, boolean stripEmptyScreens, Runnable onComplete) {
    if (mLauncher.isWorkspaceLoading()) {
        // Don't strip empty screens if the workspace is still loading
        return;
    }
    if (delay > 0) {
        postDelayed(
                () -> removeExtraEmptyScreenDelayed(0, stripEmptyScreens, onComplete), delay);
        return;
    }
    // First we convert the last page to an extra page if the last page is empty
    // and we don't already have an extra page.
    convertFinalScreenToEmptyScreenIfNecessary();
    // Then we remove the extra page(s) if they are not the only pages left in Workspace.
    if (hasExtraEmptyScreens()) {
        forEachExtraEmptyPageId(extraEmptyPageId -> {
            removeView(mWorkspaceScreens.get(extraEmptyPageId));
            mWorkspaceScreens.remove(extraEmptyPageId);
            mScreenOrder.removeValue(extraEmptyPageId);
        });
        setCurrentPage(getNextPage());
        // Update the page indicator to reflect the removed page.
        showPageIndicatorAtCurrentScroll();
    }
    if (stripEmptyScreens) {
        // This will remove all empty pages from the Workspace. If there are no more pages left,
        // it will add extra page(s) so that users can put items on at least one page.
        stripEmptyScreens();
    }
    if (onComplete != null) {
        onComplete.run();
    }
}    

3.5.WorkspaceTouchListener

用来处理workspace的空白区域的点击事情的,首要便是弹一个如下弹框选项

android framework13-launcher3【01launcher】


    //处理workspace空白区域的touch事情,并显现一个选项弹框
    public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListener
    implements OnTouchListener {
    //...
    private int mLongPressState = STATE\_CANCELLED;
    private final GestureDetector mGestureDetector;//手势监听,这儿就处理下长按事情
    public WorkspaceTouchListener(Launcher launcher, Workspace\<?> workspace) {
    //..
    mGestureDetector = new GestureDetector(workspace.getContext(), this);
    }
    @Override
    public boolean onTouch(View view, MotionEvent ev) {
    mGestureDetector.onTouchEvent(ev);//这儿就简单的监听了长按事情
         int action = ev.getActionMasked();
         if (action == ACTION_DOWN) {
             // 是否能够处理长按操作
             boolean handleLongPress = canHandleLongPress();
             if (handleLongPress) {
                 // Check if the event is not near the edges
                 DeviceProfile dp = mLauncher.getDeviceProfile();
                 DragLayer dl = mLauncher.getDragLayer();
                 Rect insets = dp.getInsets();
                 mTempRect.set(insets.left, insets.top, dl.getWidth() - insets.right,
                         dl.getHeight() - insets.bottom);
                 mTempRect.inset(dp.edgeMarginPx, dp.edgeMarginPx);
                 //判别下点击是否在可操作规模
                 handleLongPress = mTempRect.contains((int) ev.getX(), (int) ev.getY());
             }
             if (handleLongPress) {
                 mLongPressState = STATE_REQUESTED;//修正状态,后边用到
                 mTouchDownPoint.set(ev.getX(), ev.getY());
                 // Mouse right button's ACTION_DOWN should immediately show menu
                 if (TouchUtil.isMouseRightClickDownOrMove(ev)) {
                     maybeShowMenu();//鼠标右键的话这儿就直接弹框了
                     return true;
                 }
             }
             mWorkspace.onTouchEvent(ev);
             // Return true to keep receiving touch events
             return true;
         }
         if (mLongPressState == STATE_PENDING_PARENT_INFORM) {
             // Inform the workspace to cancel touch handling
             ev.setAction(ACTION_CANCEL);
             mWorkspace.onTouchEvent(ev);
             ev.setAction(action);
             mLongPressState = STATE_COMPLETED;
         }
         boolean isInAllAppsBottomSheet = mLauncher.isInState(ALL_APPS)
                 && mLauncher.getDeviceProfile().isTablet;
         final boolean result;
         if (mLongPressState == STATE_COMPLETED) {
             // We have handled the touch, so workspace does not need to know anything anymore.
             result = true;
         } else if (mLongPressState == STATE_REQUESTED) {
             mWorkspace.onTouchEvent(ev);
             //正在拖拽或者移动的话,撤销长按事情
             if (mWorkspace.isHandlingTouch()) {
                 cancelLongPress();
             } else if (action == ACTION_MOVE && PointF.length(
                     mTouchDownPoint.x - ev.getX(), mTouchDownPoint.y - ev.getY()) > mTouchSlop) {
                 cancelLongPress();
             }
             result = true;
         } else {
             // We don't want to handle touch unless we're in AllApps bottom sheet, let workspace
             // handle it as usual.
             result = isInAllAppsBottomSheet;
         }
         if (action == ACTION_UP || action == ACTION_POINTER_UP) {
             if (!mWorkspace.isHandlingTouch()) {
                 final CellLayout currentPage =
                         (CellLayout) mWorkspace.getChildAt(mWorkspace.getCurrentPage());
                 if (currentPage != null) {
                 //手指抬起的话,把方位发送给壁纸处理
                     mWorkspace.onWallpaperTap(ev);
                 }
             }
         }
         if (action == ACTION_UP || action == ACTION_CANCEL) {
             cancelLongPress();
         }
         if (action == ACTION_UP && isInAllAppsBottomSheet) {
             mLauncher.getStateManager().goToState(NORMAL);
         }
         return result;
    }
    //是否支撑长按事情
    private boolean canHandleLongPress() {
    return AbstractFloatingView\.getTopOpenView(mLauncher) == null
    && mLauncher.isInState(NORMAL);
    }
    private void cancelLongPress() {
    mLongPressState = STATE\_CANCELLED;
    }
    @Override
    public void onLongPress(MotionEvent event) {
    maybeShowMenu(); //手势的长按事情回调
    }
    private void maybeShowMenu() {
    if (mLongPressState == STATE\_REQUESTED) {//这个state前边有剖析,在可点击规模即可
    if (canHandleLongPress()) {//再次判别是否能够长按
    mLongPressState = STATE\_PENDING\_PARENT\_INFORM;
    //这儿便是显现弹框的操作了
    mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y);
    } else {
    cancelLongPress();
    }
    }
    }
    }

3.6.OptionsPopupView

    public void showDefaultOptions(float x, float y) {
        OptionsPopupView.show(this, getPopupTarget(x, y), OptionsPopupView.getOptions(this),
                false);
    }
    public static OptionsPopupView show(ActivityContext launcher, RectF targetRect,
            List<OptionItem> items, boolean shouldAddArrow, int width) {
            //这玩意便是个线性布局
        OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
                .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
        popup.mTargetRect = targetRect;
        popup.setShouldAddArrow(shouldAddArrow);
        for (OptionItem item : items) {
            DeepShortcutView view =
                    (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
            if (width > 0) {
                view.getLayoutParams().width = width;
            }
            view.getIconView().setBackgroundDrawable(item.icon);
            view.getBubbleText().setText(item.label);
            view.setOnClickListener(popup);
            view.setOnLongClickListener(popup);
            popup.mItemMap.put(view, item);
        }
        popup.show();
        return popup;
    }

3.7.ShortcutAndWidgetContainer

   public ShortcutAndWidgetContainer(Context context, @ContainerType int containerType) {
        super(context);
        mActivity = ActivityContext.lookupContext(context);
        mWallpaperManager = WallpaperManager.getInstance(context);
        mContainerType = containerType;
    }
    public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY,
            Point borderSpace) {
        mCellWidth = cellWidth;
        mCellHeight = cellHeight;
        mCountX = countX;
        mCountY = countY;
        mBorderSpace = borderSpace;
    }
//依据传进来的索引,对比布局参数,判别点击的是哪个
    public View getChildAt(int cellX, int cellY) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
            if ((lp.cellX <= cellX) && (cellX < lp.cellX + lp.cellHSpan)
                    && (lp.cellY <= cellY) && (cellY < lp.cellY + lp.cellVSpan)) {
                return child;
            }
        }
        return null;
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(widthSpecSize, heightSpecSize);
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChild(child);
            }
        }
    }

>measureChild

首要便是经过lp的setup办法,核算child的x,y方位以及宽高

    public void measureChild(View child) {
        CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
        final DeviceProfile dp = mActivity.getDeviceProfile();
        if (child instanceof NavigableAppWidgetHostView) {
            ((NavigableAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
            final PointF appWidgetScale = dp.getAppWidgetScale((ItemInfo) child.getTag());
            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
                    appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
        } else {
            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
                    mBorderSpace, null);
    //...
            child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
        }
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
        int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
        child.measure(childWidthMeasureSpec, childheightMeasureSpec);
    }

>onLayout

    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) {
                layoutChild(child);
            }
        }
    }
public void layoutChild(View child) {
    CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
    if (child instanceof NavigableAppWidgetHostView) {
        NavigableAppWidgetHostView nahv = (NavigableAppWidgetHostView) child;
    //...
    }
    int childLeft = lp.x;
    int childTop = lp.y;
    //measure办法的时分已经经过lp的setup办法核算了方位以及宽高,这儿直接运用即可
    child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
    //...
}

3.8.CellLayoutLayoutParams

celllayout相似网格那种,所以这儿的数据都是网格的索引

public CellLayoutLayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan,
            int screenId) {
        super(CellLayoutLayoutParams.MATCH_PARENT, CellLayoutLayoutParams.MATCH_PARENT);
        this.cellX = cellX;//水平方向索引
        this.cellY = cellY;//笔直方向索引
        this.cellHSpan = cellHSpan;//水平方向跨度,便是占几个格子
        this.cellVSpan = cellVSpan;//笔直方向跨度
        this.screenId = screenId;
    }

中心办法setup,依据网格的索引,每个网格的宽高,以及自己横向和纵向占几个网格,就能够核算出方位宽高了。

    public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
            int rowCount, float cellScaleX, float cellScaleY, Point borderSpace,
            @Nullable Rect inset) {
        if (isLockedToGrid) {
            final int myCellHSpan = cellHSpan;
            final int myCellVSpan = cellVSpan;
            int myCellX = useTmpCoords ? tmpCellX : cellX;
            int myCellY = useTmpCoords ? tmpCellY : cellY;
            if (invertHorizontally) {
                myCellX = colCount - myCellX - cellHSpan;
            }
            int hBorderSpacing = (myCellHSpan - 1) * borderSpace.x;
            int vBorderSpacing = (myCellVSpan - 1) * borderSpace.y;
            float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX;
            float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY;
            width = Math.round(myCellWidth) - leftMargin - rightMargin;
            height = Math.round(myCellHeight) - topMargin - bottomMargin;
            x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpace.x);
            y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpace.y);
            if (inset != null) {
                x -= inset.left;
                y -= inset.top;
                width += inset.left + inset.right;
                height += inset.top + inset.bottom;
            }
        }
    }

3.9.FolderIcon

这个是显现在workspace上的,Foler是点击这个打开的布局

>folder_icon.xml

<com.android.launcher3.folder.FolderIcon
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:focusable="true" >
    <com.android.launcher3.views.DoubleShadowBubbleTextView
        style="@style/BaseIcon.Workspace"
        android:id="@+id/folder_icon_name"
        android:focusable="false"
        android:layout_gravity="top"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</com.android.launcher3.folder.FolderIcon>

>init

结构办法里会初始化一些工具类,

    public FolderIcon(Context context) {
        super(context);
        init();
    }
    private void init() {
        mLongPressHelper = new CheckLongPressHelper(this);//长按事情帮助类
        mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
        mPreviewItemManager = new PreviewItemManager(this);//预览作用这个来操控
        mDotParams = new DotRenderer.DrawParams();
    }
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN
            && shouldIgnoreTouchDown(event.getX(), event.getY())) {
        return false;
    }
    // Call the superclass onTouchEvent first, because sometimes it changes the state to
    // isPressed() on an ACTION_UP
    super.onTouchEvent(event);
    //接触事情交给helper类来处理长按事情
    mLongPressHelper.onTouchEvent(event);
    // Keep receiving the rest of the events
    return true;
}

看一下helper类里长按事情的处理逻辑

    private void triggerLongPress() {
        if ((mView.getParent() != null)
                && mView.hasWindowFocus()
                && (!mView.isPressed() || mListener != null)
                && !mHasPerformedLongPress) {
            boolean handled;
            if (mListener != null) {
                handled = mListener.onLongClick(mView);
            } else {
            //咱们没有设置listener,所以最后交给FolderIcon自己处理了
                handled = mView.performLongClick();
            }
            if (handled) {
                mView.setPressed(false);
                mHasPerformedLongPress = true;
            }
            clearCallbacks();
        }
    }

这儿讲一下,长按事情是在workspace增加元素的时分一致增加的,用的下边这个

    default View.OnLongClickListener getWorkspaceChildOnLongClickListener() {
        return ItemLongClickListener.INSTANCE_WORKSPACE;
    }

>获取对象

    public static <T extends Context & ActivityContext> FolderIcon inflateFolderAndIcon(int resId,
            T activityContext, ViewGroup group, FolderInfo folderInfo) {
            //创立folder,也便是foldericon打开的控件
        Folder folder = Folder.fromXml(activityContext);
        //创立foldericon并设置数据
        FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
        folder.setFolderIcon(icon);
        folder.bind(folderInfo);
        icon.setFolder(folder);
        return icon;
    }
    public static FolderIcon inflateIcon(int resId, ActivityContext activity, ViewGroup group,
            FolderInfo folderInfo) {
        DeviceProfile grid = activity.getDeviceProfile();
        FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
                .inflate(resId, group, false);
        icon.mFolderName = icon.findViewById(R.id.folder_icon_name);//文件夹姓名
        //...
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
        //设置topMargin,这样就显现在图标下边了。
        lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
        icon.setTag(folderInfo);
        //点击事情
        icon.setOnClickListener(ItemClickHandler.INSTANCE);
        //...
        icon.setDotInfo(folderDotInfo);
    //预览图数据设置
        icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile().inv);
        icon.mPreviewVerifier.setFolderInfo(folderInfo);
        icon.updatePreviewItems(false);
        folderInfo.addListener(icon);
        return icon;
    }

>点击事情

ItemClickHandler.INSTANCE

    private static void onClick(View v) {
    //依据tag拿到点击的view所属的类型,对应处理
        Object tag = v.getTag();
        if (tag instanceof WorkspaceItemInfo) {
            onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                onClickFolderIcon(v);
            }
        } else if (tag instanceof AppInfo) {
            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
        } else if (tag instanceof LauncherAppWidgetInfo) {
            if (v instanceof PendingAppWidgetHostView) {
                onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
            }
        } else if (tag instanceof ItemClickProxy) {
            ((ItemClickProxy) tag).onItemClicked(v);
        }
    }

最终是拿到folder今后调用animate办法显现

folder.animateOpen();

>dispatchDraw

    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (!mBackgroundIsVisible) return;
        mPreviewItemManager.recomputePreviewDrawingParams();
        if (!mBackground.drawingDelegated()) {
            mBackground.drawBackground(canvas);
        }
        if (mCurrentPreviewItems.isEmpty() && !mAnimating) return;
    //经过管理类画背景,便是folder里缩小的app图标
        mPreviewItemManager.draw(canvas);
        if (!mBackground.drawingDelegated()) {
            mBackground.drawBackgroundStroke(canvas);
        }
        drawDot(canvas);
    }

3.10.Folder

这玩意是个线性布局,点击FolerIcon今后显现的浮窗

public abstract class AbstractFloatingView extends LinearLayout implements TouchController {
public class Folder extends AbstractFloatingView implements ClipPathView, DragSource,
        View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
        View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
        //...
@IntDef({STATE_CLOSED, STATE_ANIMATING, STATE_OPEN})
public @interface FolderState {}

>user_folder_icon_normalized.xml

FolderPagedView也是个自定义view,相似workspace,里面也是增加celllayout作为一页容器

<com.android.launcher3.folder.Folder
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <com.android.launcher3.folder.FolderPagedView
        android:id="@+id/folder_content"
        android:clipToPadding="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        launcher:pageIndicator="@+id/folder_page_indicator" />
    <!--水平方向,文件夹姓名以及indicator-->
    <LinearLayout
        android:id="@+id/folder_footer"
        android:layout_width="match_parent"
        android:layout_height="@dimen/folder_footer_height_default"
        android:clipChildren="false"
        android:orientation="horizontal">
        <com.android.launcher3.folder.FolderNameEditText
            android:id="@+id/folder_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            style="@style/TextHeadline"
            android:layout_weight="1"/>
        <com.android.launcher3.pageindicators.PageIndicatorDots
            android:id="@+id/folder_page_indicator"
            android:layout_gravity="center_vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:elevation="1dp"
            />
    </LinearLayout>
</com.android.launcher3.folder.Folder>

>onFinishInflate

获取背景图片,以及给child设置对应的属性

    protected void onFinishInflate() {
        super.onFinishInflate();
        mBackground = (GradientDrawable) ResourcesCompat.getDrawable(getResources(),
                R.drawable.round_rect_folder, getContext().getTheme());
        mContent = findViewById(R.id.folder_content);
        mContent.setFolder(this);
        mPageIndicator = findViewById(R.id.folder_page_indicator);
        mFolderName = findViewById(R.id.folder_name);
        //...
        mFooter = findViewById(R.id.folder_footer);
        mFooterHeight = dp.folderFooterHeightPx;
    }

>animateOpen

点击FolderIcon今后会调用下边的办法显现Folder

    public void animateOpen() {
        animateOpen(mInfo.contents, 0);
    }
private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
    Folder openFolder = getOpen(mActivityContext);
    if (openFolder != null && openFolder != this) {
        //先查下,假如有在显现的folder,封闭它
        openFolder.close(true);
    }
    mContent.bindItems(items);
    centerAboutIcon();//核算下方位,巨细,设置下背景图的巨细
    mItemsInvalidated = true;
    updateTextViewFocus();
    mIsOpen = true;
    BaseDragLayer dragLayer = mActivityContext.getDragLayer();
    if (getParent() == null) {
        dragLayer.addView(this);//增加到容器里
        mDragController.addDropTarget(this);
    } else {
    }
    mContent.completePendingPageChanges();
    mContent.setCurrentPage(pageNo);
    mDeleteFolderOnDropCompleted = false;
    //...anim
    // Footer animation
    if (mContent.getPageCount() > 1 && !mInfo.hasOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION)) {
    //...
    } else {
        mFolderName.setTranslationX(0);
    }
    //...
}

>close

首要这个东西是增加到dragLayer层的,所以最开始的touch事情处理便是从layer层开始判别的。

BaseDragLayer.java

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if (action == ACTION_UP || action == ACTION_CANCEL) {
            if (mTouchCompleteListener != null) {
                mTouchCompleteListener.onTouchComplete();
            }
            mTouchCompleteListener = null;
        } else if (action == MotionEvent.ACTION_DOWN) {
            mActivity.finishAutoCancelActionMode();
        }
        return findActiveController(ev);//这一行..
    }

//持续

    protected boolean findActiveController(MotionEvent ev) {
        mActiveController = null;
        if (canFindActiveController()) {
            mActiveController = findControllerToHandleTouch(ev);//这行..
        }
        return mActiveController != null;
    }

//Folder便是承继的AbstractFloatingView,所以显现的时分,最上层查到的topView便是咱们的Folder了

    private TouchController findControllerToHandleTouch(MotionEvent ev) {
        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
        if (topView != null
                && (isEventInLauncher(ev) || topView.canInterceptEventsInSystemGestureRegion())
                && topView.onControllerInterceptTouchEvent(ev)) {//这行..
            return topView;
        }
        for (TouchController controller : mControllers) {
            if (controller.onControllerInterceptTouchEvent(ev)) {
                return controller;
            }
        }
        return null;
    }

//回到Folder.java

    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            BaseDragLayer dl = (BaseDragLayer) getParent();
            if (isEditingName()) {
                if (!dl.isEventOverView(mFolderName, ev)) {
                    mFolderName.dispatchBackKey();
                    return true;
                }
                return false;
            } else if (!dl.isEventOverView(this, ev)
                    && mLauncherDelegate.interceptOutsideTouch(ev, dl, this)) {//这行..
                return true;
            }
        }
        return false;
    }

//go on,咱们是点击空白区域躲藏Folder,所以很明显,走的else

    boolean interceptOutsideTouch(MotionEvent ev, BaseDragLayer dl, Folder folder) {
        if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
            // Do not close the container if in drag and drop.
            if (!dl.isEventOverView(mLauncher.getDropTargetBar(), ev)) {
                return true;
            }
        } else {
            // 这行。
            folder.close(true);
            return true;
        }
        return false;
    }

// Folder.java

    protected void handleClose(boolean animate) {
        mIsOpen = false;
//...
        if (animate) {
            animateClosed();//带动画的,走这儿
        } else {
            closeComplete(false);
            post(this::announceAccessibilityChanges);
        }
    }

//最终会走到这儿,从头显现FolderIcon, 并从dragLayer里移除Folder

    private void closeComplete(boolean wasAnimated) {
        BaseDragLayer parent = (BaseDragLayer) getParent();
        if (parent != null) {
            parent.removeView(this);// 移除
        }
        mDragController.removeDropTarget(this);
        clearFocus();
        if (mFolderIcon != null) {
            mFolderIcon.setVisibility(View.VISIBLE);
            mFolderIcon.setIconVisible(true);//从头显现
            mFolderIcon.mFolderName.setTextVisibility(true);
        }

FolderPagedView

public class FolderPagedView extends PagedView<PageIndicatorDots> implements ClipPathView {

4.学习记载

4.1.ItemLongClickListener.java

hotseat,workspace,allapps页面的icon的长按事情,都会调用canStartDrag这个办法判别的,所以在这个办法里处理就行。

public static final OnLongClickListener INSTANCE_WORKSPACE =
        ItemLongClickListener::onWorkspaceItemLongClick;
public static final OnLongClickListener INSTANCE_ALL_APPS =
        ItemLongClickListener::onAllAppsItemLongClick;

>onWorkspaceItemLongClick

private static boolean onWorkspaceItemLongClick(View v) {
    Launcher launcher = Launcher.getLauncher(v.getContext());
    //先判别是否支撑拖拽
    if (!canStartDrag(launcher)) return false;
    if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
    if (!(v.getTag() instanceof ItemInfo)) return false;
    launcher.setWaitingForResult(null);
    beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
    return true;
}

>onAllAppsItemLongClick

private static boolean onAllAppsItemLongClick(View view) {
    //..
    Launcher launcher = Launcher.getLauncher(v.getContext());
    //先判别是否支撑拖拽
    if (!canStartDrag(launcher)) return false;
    // When we have exited all apps or are in transition, disregard long clicks
    if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
    if (launcher.getWorkspace().isSwitchingState()) return false;
    // Start the drag
    final DragController dragController = launcher.getDragController();
    dragController.addDragListener(new DragController.DragListener() {
        @Override
        public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
            v.setVisibility(INVISIBLE);
        }
        @Override
        public void onDragEnd() {
            v.setVisibility(VISIBLE);
            dragController.removeDragListener(this);
        }
    });
    launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), new DragOptions());
    return false;
}

>canStartDrag

下边的办法回来false,一切的icon都长按都没有反应了,天然也不会拖动了。

//禁止元素拖拽的话,这儿回来false即可
    public static boolean canStartDrag(Launcher launcher) {
        if (launcher == null) {
            return false;
        }
        if (launcher.isWorkspaceLocked()) return false;
        if (launcher.getDragController().isDragging()) return false;
        return true;
    }

4.2.WorkspaceLayoutManager.java

一个抽象类,完成了把view增加到container的逻辑,workspace,hotseat里的元素增加用的这个

    default void addInScreen(View child, int container, int screenId, int x, int y,
            int spanX, int spanY) {
        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
        }
        final CellLayout layout;
        // 容器是hotseat类型
        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
                || container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
            layout = getHotseat();
            //hotseat里的只要图标,文本躲藏
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(false);
            }
        } else {
            // 其他的也便是workspace里的了,这个图标文字都显现的
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(true);
            }
            layout = getScreenWithId(screenId);
        }
        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
        CellLayoutLayoutParams lp;
        if (genericLp == null || !(genericLp instanceof CellLayoutLayoutParams)) {
            lp = new CellLayoutLayoutParams(x, y, spanX, spanY, screenId);
        } else {
            lp = (CellLayoutLayoutParams) genericLp;
            lp.cellX = x;
            lp.cellY = y;
            lp.cellHSpan = spanX;
            lp.cellVSpan = spanY;
        }
        if (spanX < 0 && spanY < 0) {
            lp.isLockedToGrid = false;
        }
        ItemInfo info = (ItemInfo) child.getTag();
        int childId = info.getViewId();
        boolean markCellsAsOccupied = !(child instanceof Folder);
        //把child增加到容器里
        if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
        }
        child.setHapticFeedbackEnabled(false);
        //看下child的长按事情,都是这个
        child.setOnLongClickListener(getWorkspaceChildOnLongClickListener());
        if (child instanceof DropTarget) {
        //这个是拖拽到顶部丢掉的child
            onAddDropTarget((DropTarget) child);
        }
    }
        // child的长按事情用的这个
    default View.OnLongClickListener getWorkspaceChildOnLongClickListener() {
        return ItemLongClickListener.INSTANCE_WORKSPACE;
    }

5.总结

  • 桌面的布局结构
  • DevicePorfile,launcher3里各种配置相关的数据基本都在这儿,比方常用的hotseat高度,workspace每页能够分成几行几列
  • cellLayout,一个能够按照网格切割显现child的容器,hotseat承继的它,workspace里增加的是它
  • workspace里每页的元素,首要有3种类型,app的图标,app的小部件,文件夹
  • 3.5小节里是空白区域的长按事情,4.1小节的是元素的长按事情