一.Launcher3概述

Launcher顾名思义,就是桌面的意思,也是android系统启动后第一个启动的应用程序,这里以android11为例,和其他应用并无区别,只是增加了对其他app和widget的管理窗口,且可以为用户定制化一些酷炫和常用的显示功能,代码上比其他app在manifest.xml中多添加一个HOME属性,eg:

<category android:name="android.intent.category.HOME" />

二.Launcher3界面显示

Launcher3的主要界面主要结构有如下几个

1.workspace工作区,主要包括SearchBar、CellLayout、PageIndicator、hotseat

 

2.所有应用列表

3.Widget

4.Wallpapers

三.Launcher3主要源码分析

1.代码结构

 2.java源码分析

1). Launcher.java   //launcher主要的activity,是launcher第一次启动的activity,显示和启动一些初始化的view

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
                TraceHelper.FLAG_UI_EVENT);
        if (DEBUG_STRICT_MODE) {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()   // or .detectAll() for all detectable problems
                    .penaltyLog()
                    .build());
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectLeakedSqlLiteObjects()
                    .detectLeakedClosableObjects()
                    .penaltyLog()
                    .penaltyDeath()
                    .build());
        }

        super.onCreate(savedInstanceState);

        LauncherAppState app = LauncherAppState.getInstance(this);
        mOldConfig = new Configuration(getResources().getConfiguration());
        mModel = app.getModel();

        mRotationHelper = new RotationHelper(this);
        InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
        initDeviceProfile(idp);
        idp.addOnChangeListener(this);
        mSharedPrefs = Utilities.getPrefs(this);
        mIconCache = app.getIconCache();
        mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);

        mDragController = new DragController(this);
        mAllAppsController = new AllAppsTransitionController(this);
        mStateManager = new StateManager<>(this, NORMAL);

        mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);

        mAppWidgetManager = new WidgetManagerHelper(this);
        mAppWidgetHost = new LauncherAppWidgetHost(this,
                appWidgetId -> getWorkspace().removeWidget(appWidgetId));
        mAppWidgetHost.startListening();

        inflateRootView(R.layout.launcher);
        setupViews();
        mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);

 

 /**
     * Finds all the views we need and configure them properly.
     */
    protected void setupViews() {
        mDragLayer = findViewById(R.id.drag_layer);
        mFocusHandler = mDragLayer.getFocusIndicatorHelper();
        mWorkspace = mDragLayer.findViewById(R.id.workspace);
        mWorkspace.initParentViews(mDragLayer);
        mOverviewPanel = findViewById(R.id.overview_panel);
        mHotseat = findViewById(R.id.hotseat);
        mHotseat.setWorkspace(mWorkspace);

        // Setup the drag layer
        mDragLayer.setup(mDragController, mWorkspace);

        mWorkspace.setup(mDragController);
        // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
        // default state, otherwise we will update to the wrong offsets in RTL
        mWorkspace.lockWallpaperToDefaultPage();
        mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
        mDragController.addDragListener(mWorkspace);

        // Get the search/delete/uninstall bar
        mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar);

        // Setup Apps
        mAppsView = findViewById(R.id.apps_view);

        // Setup Scrim
        mScrimView = findViewById(R.id.scrim_view);

        // Setup the drag controller (drop targets have to be added in reverse order in priority)
        mDropTargetBar.setup(mDragController);

        mAllAppsController.setupViews(mAppsView, mScrimView);
    }

2). Workspace.java //继承自PagedView,由N个cellLayout组成,从cellLayout更高一级的层面上对事件的处理

/**
     * Used to inflate the Workspace from XML.
     *
     * @param context The application's context.
     * @param attrs The attributes set containing the Workspace's customization values.
     * @param defStyle Unused.
     */
    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);

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

3).DeviceProfile.java //icon大小、各个icon间距,布局等计算实体类,可配置各个参数的全局变量

DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info,
            Point minSize, Point maxSize, int width, int height, boolean isLandscape,
            boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
            Point windowPosition) {

        this.inv = inv;
        this.isLandscape = isLandscape;
        this.isMultiWindowMode = isMultiWindowMode;
        windowX = windowPosition.x;
        windowY = windowPosition.y;

        // Determine sizes.
        widthPx = width;
        heightPx = height;
        if (isLandscape) {
            availableWidthPx = maxSize.x;
            availableHeightPx = minSize.y;
        } else {
            availableWidthPx = minSize.x;
            availableHeightPx = maxSize.y;
        }

        mInfo = info;

        // Constants from resources
        float swDPs = Utilities.dpiFromPx(
                Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics);
        isTablet = swDPs >= TABLET_MIN_DPS;
        isLargeTablet = swDPs >= LARGE_TABLET_MIN_DPS;
        isPhone = !isTablet && !isLargeTablet;
        aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
        boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;

        // Some more constants
        this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;

        context = getContext(context, info, isVerticalBarLayout()
                ? Configuration.ORIENTATION_LANDSCAPE
                : Configuration.ORIENTATION_PORTRAIT);
        final Resources res = context.getResources();

        edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
        desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;

        int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
                ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
        int cellLayoutPadding = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
        if (isLandscape) {
            cellLayoutPaddingLeftRightPx = 0;
            cellLayoutBottomPaddingPx = cellLayoutPadding;
        } else {
            cellLayoutPaddingLeftRightPx = cellLayoutPaddingLeftRightMultiplier * cellLayoutPadding;
            cellLayoutBottomPaddingPx = 0;
        }

        workspacePageIndicatorHeight = res.getDimensionPixelSize(
                R.dimen.workspace_page_indicator_height);
        mWorkspacePageIndicatorOverlapWorkspace =
                res.getDimensionPixelSize(R.dimen.workspace_page_indicator_overlap_workspace);

        iconDrawablePaddingOriginalPx =
                res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
        dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
        workspaceSpringLoadedBottomSpace =
                res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);

        workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);

        hotseatBarTopPaddingPx =
                res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
        hotseatBarBottomPaddingPx = (isTallDevice ? 0
                : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
                + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
        hotseatBarSidePaddingEndPx =
                res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
        // Add a bit of space between nav bar and hotseat in vertical bar layout.
        hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
        hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics)
                + (isVerticalBarLayout()
                ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
                : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
                        + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));

        // Calculate all of the remaining variables.
        updateAvailableDimensions(res);

        // Now that we have all of the variables calculated, we can tune certain sizes.
        if (!isVerticalBarLayout() && isPhone && isTallDevice) {
            // We increase the hotseat size when there is extra space.
            // ie. For a display with a large aspect ratio, we can keep the icons on the workspace
            // in portrait mode closer together by adding more height to the hotseat.
            // Note: This calculation was created after noticing a pattern in the design spec.
            int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
                    - workspacePageIndicatorHeight;
            hotseatBarSizePx += extraSpace;
            hotseatBarBottomPaddingPx += extraSpace;

            // Recalculate the available dimensions using the new hotseat size.
            updateAvailableDimensions(res);
        }
        updateWorkspacePadding();

        // This is done last, after iconSizePx is calculated above.
        mDotRendererWorkSpace = new DotRenderer(iconSizePx, IconShape.getShapePath(),
                IconShape.DEFAULT_PATH_SIZE);
        mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
                new DotRenderer(allAppsIconSizePx, IconShape.getShapePath(),
                        IconShape.DEFAULT_PATH_SIZE);
    }

4).BubbleTextView.java //继承自TextView,Launcher所有各个icon间距文字显示的父类,包括文字的大小,文字的刷新

public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mActivity = ActivityContext.lookupContext(context);

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.BubbleTextView, defStyle, 0);
        mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
        DeviceProfile grid = mActivity.getDeviceProfile();

        mDisplay = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
        final int defaultIconSize;
        if (mDisplay == DISPLAY_WORKSPACE) {//判断显示是不是在工作区
            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
            setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
            defaultIconSize = grid.iconSizePx;
        } else if (mDisplay == DISPLAY_ALL_APPS) {//判断显示是不是在所有应用
            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
            setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
            defaultIconSize = grid.allAppsIconSizePx;
        } else if (mDisplay == DISPLAY_FOLDER) {//判断显示是不是在文件夹
            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
            setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
            defaultIconSize = grid.folderChildIconSizePx;
        } else {
            // widget_selection or shortcut_popup
            defaultIconSize = grid.iconSizePx;
        }

        mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);

        mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
                defaultIconSize);
        a.recycle();

        mLongPressHelper = new CheckLongPressHelper(this);

        mDotParams = new DotRenderer.DrawParams();

        setEllipsize(TruncateAt.END);
        setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
        setTextAlpha(1f);
    }

5).CellLayout.java  //继承自viewgroup,Launcher布局的计算类,图标的显示边距等,组成workspace的view,既是一个dragSource又是一个dropTarget,可以将它里面的item拖出去,也可以容纳拖动过来的item。在workspace_screen里面定了一些它的view参数

 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
        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 grid = mActivity.getDeviceProfile();

        mCellWidth = mCellHeight = -1;
        mFixedCellWidth = mFixedCellHeight = -1;

        mCountX = grid.inv.numColumns;
        mCountY = grid.inv.numRows;
        mOccupied =  new GridOccupancy(mCountX, mCountY);
        mTmpOccupied = new GridOccupancy(mCountX, mCountY);

        mPreviousReorderDirection[0] = INVALID_DIRECTION;
        mPreviousReorderDirection[1] = INVALID_DIRECTION;

        mFolderLeaveBehind.mDelegateCellX = -1;
        mFolderLeaveBehind.mDelegateCellY = -1;

        setAlwaysDrawnWithCacheEnabled(false);
        final Resources res = getResources();

        mBackground = res.getDrawable(R.drawable.bg_celllayout);
        mBackground.setCallback(this);
        mBackground.setAlpha(0);

        mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx);

        // Initialize the data structures used for the drag visualization.
        mEaseOutInterpolator = Interpolators.DEACCEL_2_5; // Quint ease out
        mDragCell[0] = mDragCell[1] = -1;
        for (int i = 0; i < mDragOutlines.length; i++) {
            mDragOutlines[i] = new Rect(-1, -1, -1, -1);
        }
        mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor));

        // When dragging things around the home screens, we show a green outline of
        // where the item will land. The outlines gradually fade out, leaving a trail
        // behind the drag path.
        // Set up all the animations that are used to implement this fading.
        final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
        final float fromAlphaValue = 0;
        final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);

        Arrays.fill(mDragOutlineAlphas, fromAlphaValue);

        for (int i = 0; i < mDragOutlineAnims.length; i++) {
            final InterruptibleInOutAnimator anim =
                new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
            anim.getAnimator().setInterpolator(mEaseOutInterpolator);
            final int thisIndex = i;
            anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
                public void onAnimationUpdate(ValueAnimator animation) {
                    final Bitmap outline = (Bitmap)anim.getTag();

                    // If an animation is started and then stopped very quickly, we can still
                    // get spurious updates we've cleared the tag. Guard against this.
                    if (outline == null) {
                        if (LOGD) {
                            Object val = animation.getAnimatedValue();
                            Log.d(TAG, "anim " + thisIndex + " update: " + val +
                                     ", isStopped " + anim.isStopped());
                        }
                        // Try to prevent it from continuing to run
                        animation.cancel();
                    } else {
                        mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
                        CellLayout.this.invalidate(mDragOutlines[thisIndex]);
                    }
                }
            });
            // The animation holds a reference to the drag outline bitmap as long is it's
            // running. This way the bitmap can be GCed when the animations are complete.
            anim.getAnimator().addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
                        anim.setTag(null);
                    }
                }
            });
            mDragOutlineAnims[i] = anim;
        }

        mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
        addView(mShortcutsAndWidgets);
    }

6).FolderGridOrganizer.java //展开文件夹显示的计算逻辑类,文件夹图标呈现是网格状,此类主要给文件夹各应用图标制定显示规则,eg: 3*3 、4*4

 /**
     * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
     */
    public FolderGridOrganizer(InvariantDeviceProfile profile) {
        mMaxCountX = profile.numFolderColumns;
        mMaxCountY = profile.numFolderRows;
        mMaxItemsPerPage = mMaxCountX * mMaxCountY;
    }

    /**
     * Updates the organizer with the provided folder info
     */
    public FolderGridOrganizer setFolderInfo(FolderInfo info) {
        return setContentSize(info.contents.size());
    }

7). LoaderTask.java //继承Runnable,加载各个模块Task的显示类,如workspace工作区icon、所有应用icon的初始化工作

public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
            LoaderResults results) {
        mApp = app;
        mBgAllAppsList = bgAllAppsList;
        mBgDataModel = dataModel;
        mResults = results;

        mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
        mUserManager = mApp.getContext().getSystemService(UserManager.class);
        mUserCache = UserCache.INSTANCE.get(mApp.getContext());
        mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext());
        mIconCache = mApp.getIconCache();
    }

8).PackageUpdatedTask.java //继承BaseModelUpdateTask,实际也是Runnable,PMS安装应用后更新Launcher图标及逻辑的实现类

 * or when a user availability changes.
 */
public class PackageUpdatedTask extends BaseModelUpdateTask {

    private static final boolean DEBUG = false;
    private static final String TAG = "PackageUpdatedTask";

    public static final int OP_NONE = 0;
    public static final int OP_ADD = 1;
    public static final int OP_UPDATE = 2;
    public static final int OP_REMOVE = 3; // uninstalled
    public static final int OP_UNAVAILABLE = 4; // external media unmounted
    public static final int OP_SUSPEND = 5; // package suspended
    public static final int OP_UNSUSPEND = 6; // package unsuspended
    public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable

9). BaseIconFactory.java //Launcher icon的工厂类,控制icon UI展示(eg:图标白边控制)

 protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
            boolean shapeDetection) {
        mContext = context.getApplicationContext();
        mShapeDetection = shapeDetection;
        mFillResIconDpi = fillResIconDpi;
        mIconBitmapSize = iconBitmapSize;

        mPm = mContext.getPackageManager();
        mColorExtractor = new ColorExtractor();

        mCanvas = new Canvas();
        mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
        clear();
    }

10). SecondaryDropTarget.java //继承自ButtonDropTarget,实际也是TextView,长按APP icon的操作类,对icon进行移动、删除、移除、取消、卸载等操作

  public SecondaryDropTarget(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mCacheExpireAlarm = new Alarm();
        mStatsLogManager = StatsLogManager.newInstance(context);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mHadPendingAlarm) {
            mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
            mCacheExpireAlarm.setOnAlarmListener(this);
            mHadPendingAlarm = false;
        }
    }

11).PortraitStatesTouchController.java //继承自AbstractStateChangeTouchController,纵向控制抽屉式All应用界面的触摸类,用于处理纵向UI中各种状态转换

public PortraitStatesTouchController(Launcher l, boolean allowDragToOverview) {
        super(l, SingleAxisSwipeDetector.VERTICAL);
        mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l);
        mAllowDragToOverview = allowDragToOverview;
    }

    @Override
    protected boolean canInterceptTouch(MotionEvent ev) {
        if (mCurrentAnimation != null) {
            if (mFinishFastOnSecondTouch) {
                mCurrentAnimation.getAnimationPlayer().end();
            }

            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
            if (ev.getY() >= allAppsController.getShiftRange() * allAppsController.getProgress()) {
                // If we are already animating from a previous state, we can intercept as long as
                // the touch is below the current all apps progress (to allow for double swipe).
                return true;
            }
            // Otherwise, make sure everything is settled and don't intercept so they can scroll
            // recents, dismiss a task, etc.
            if (mAtomicAnim != null) {
                mAtomicAnim.end();
            }
            return false;
        }
        if (mLauncher.isInState(ALL_APPS)) {
            // In all-apps only listen if the container cannot scroll itself
            if (!mLauncher.getAppsView().shouldContainerScroll(ev)) {
                return false;
            }
        } else if (mLauncher.isInState(OVERVIEW)) {
            if (!mOverviewPortraitStateTouchHelper.canInterceptTouch(ev)) {
                return false;
            }
        } else {
            // If we are swiping to all apps instead of overview, allow it from anywhere.
            boolean interceptAnywhere = mLauncher.isInState(NORMAL) && !mAllowDragToOverview;
            // For all other states, only listen if the event originated below the hotseat height
            if (!interceptAnywhere && !isTouchOverHotseat(mLauncher, ev)) {
                return false;
            }
        }
        if (getTopOpenViewWithType(mLauncher, TYPE_ACCESSIBLE | TYPE_ALL_APPS_EDU) != null) {
            return false;
        }
        return true;
    }

12).OverviewToAllAppsTouchController.java //继承自PortraitStatesTouchController,横向控制抽屉式All应用界面的触摸控制器

 public OverviewToAllAppsTouchController(Launcher l) {
        super(l, true /* allowDragToOverview */);
    }

    @Override
    protected boolean canInterceptTouch(MotionEvent ev) {
        if (mCurrentAnimation != null) {
            // If we are already animating from a previous state, we can intercept.
            return true;
        }
        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
            return false;
        }
        if (mLauncher.isInState(ALL_APPS)) {
            // In all-apps only listen if the container cannot scroll itself
            return mLauncher.getAppsView().shouldContainerScroll(ev);
        } else if (mLauncher.isInState(NORMAL)) {
            return (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0;
        } else if (mLauncher.isInState(OVERVIEW)) {
            RecentsView rv = mLauncher.getOverviewPanel();
            return ev.getY() > (rv.getBottom() - rv.getPaddingBottom());
        } else {
            return false;
        }
    }

    @Override
    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
        if (fromState == ALL_APPS && !isDragTowardPositive) {
            // Should swipe down go to OVERVIEW instead?
            return TouchInteractionService.isConnected() ?
                    mLauncher.getStateManager().getLastState() : NORMAL;
        } else if (isDragTowardPositive) {
            return ALL_APPS;
        }
        return fromState;
    }

3.部分xml文件解析

1).device_profiles.xml //默认Launcher的网格配置,主要包括一下几点
1'. numRows/numColumns //workspace的行和列
2'.numFolderRows/numFolderColumns //文件夹中配置的行和列
3'.iconImageSize //图标大小
4'.iconTextSize// 图标名称文字大小
5'.numHotseatIcons //hotseat图标的数目
6'.defaultLayoutId //默认选择加载哪个网格xml的配置文件

<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2016 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >

    <grid-option
        launcher:name="3_by_3"
        launcher:numRows="3"
        launcher:numColumns="3"
        launcher:numFolderRows="2"
        launcher:numFolderColumns="3"
        launcher:numHotseatIcons="3"
        launcher:dbFile="launcher_3_by_3.db"
        launcher:defaultLayoutId="@xml/default_workspace_3x3" >

        <display-option
            launcher:name="Super Short Stubby"
            launcher:minWidthDps="255"
            launcher:minHeightDps="300"
            launcher:iconImageSize="48"
            launcher:iconTextSize="13.0"
            launcher:canBeDefault="true" />

        <display-option
            launcher:name="Shorter Stubby"
            launcher:minWidthDps="255"
            launcher:minHeightDps="400"
            launcher:iconImageSize="48"
            launcher:iconTextSize="13.0"
            launcher:canBeDefault="true" />

    </grid-option>

    <grid-option
        launcher:name="4_by_4"
        launcher:numRows="4"
        launcher:numColumns="4"
        launcher:numFolderRows="3"
        launcher:numFolderColumns="4"
        launcher:numHotseatIcons="4"
        launcher:dbFile="launcher_4_by_4.db"
        launcher:defaultLayoutId="@xml/default_workspace_4x4" >

        <display-option
            launcher:name="Short Stubby"
            launcher:minWidthDps="275"
            launcher:minHeightDps="420"
            launcher:iconImageSize="48"
            launcher:iconTextSize="13.0"
            launcher:canBeDefault="true" />

        <display-option
            launcher:name="Stubby"
            launcher:minWidthDps="255"
            launcher:minHeightDps="450"
            launcher:iconImageSize="48"
            launcher:iconTextSize="13.0"
            launcher:canBeDefault="true" />

        <display-option
            launcher:name="Nexus S"
            launcher:minWidthDps="296"
            launcher:minHeightDps="491.33"
            launcher:iconImageSize="48"
            launcher:iconTextSize="13.0"
            launcher:canBeDefault="true" />

        <display-option
            launcher:name="Nexus 4"
            launcher:minWidthDps="359"
            launcher:minHeightDps="567"
            launcher:iconImageSize="54"
            launcher:iconTextSize="13.0"
            launcher:canBeDefault="true" />

        <display-option
            launcher:name="Nexus 5"
            launcher:minWidthDps="335"
            launcher:minHeightDps="567"
            launcher:iconImageSize="54"
            launcher:iconTextSize="13.0"
            launcher:canBeDefault="true" />

    </grid-option>

    <grid-option
        launcher:name="5_by_5"
        launcher:numRows="5"
        launcher:numColumns="5"
        launcher:numFolderRows="4"
        launcher:numFolderColumns="4"
        launcher:numHotseatIcons="5"
        launcher:dbFile="launcher.db"
        launcher:defaultLayoutId="@xml/default_workspace_5x5" >

        <display-option
            launcher:name="Large Phone"
            launcher:minWidthDps="406"
            launcher:minHeightDps="694"
            launcher:iconImageSize="56"
            launcher:iconTextSize="14.4"
            launcher:canBeDefault="true" />

        <display-option
            launcher:name="Shorter Stubby"
            launcher:minWidthDps="255"
            launcher:minHeightDps="400"
            launcher:iconImageSize="48"
            launcher:iconTextSize="13.0"
            launcher:canBeDefault="true" />

    </grid-option>

</profiles>

2).default_workspace_xxx.xml //默认排序各个icon位置的配置文件,包括文件夹默认创建显示及位置

<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">

    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
    <!-- Messaging, [All Apps], Dialer -->

    <resolve
        launcher:container="-101"
        launcher:screen="0"
        launcher:x="0"
        launcher:y="0" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
        <favorite launcher:uri="sms:" />
        <favorite launcher:uri="smsto:" />
        <favorite launcher:uri="mms:" />
        <favorite launcher:uri="mmsto:" />
    </resolve>

    <!-- All Apps -->

    <resolve
        launcher:container="-101"
        launcher:screen="2"
        launcher:x="2"
        launcher:y="0" >
        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
        <favorite launcher:uri="tel:123" />
        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
    </resolve>

    <!-- Bottom row -->
    <resolve
        launcher:screen="0"
        launcher:x="0"
        launcher:y="-1" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
        <favorite launcher:uri="mailto:" />
    </resolve>

    <resolve
        launcher:screen="0"
        launcher:x="1"
        launcher:y="-1" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
        <favorite launcher:uri="#Intent;type=images/*;end" />
    </resolve>

    <resolve
        launcher:screen="0"
        launcher:x="2"
        launcher:y="-1" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
        <favorite launcher:uri="market://details?id=com.android.launcher" />
    </resolve>

</favorites>

 screen  //表示第几屏,eg: 0表示第一屏

 x  //表示横向的位置,eg: 0表示第一列

 y  //表示纵向的位置,eg: 0表示第一行

3).folder_shapes.xml //Workspace工作区icon的圆角大小控制配置文件

<shapes xmlns:launcher="http://schemas.android.com/apk/res-auto" >

    <Circle launcher:folderIconRadius="1" />

    <!-- Default icon for AOSP -->
    <RoundedSquare launcher:folderIconRadius="0.16" />

    <!-- Rounded icon from RRO -->
    <RoundedSquare launcher:folderIconRadius="0.6" />

    <!-- Square icon -->
    <RoundedSquare launcher:folderIconRadius="0" />

    <TearDrop launcher:folderIconRadius="0.3" />
    <Squircle launcher:folderIconRadius="0.2" />

</shapes>

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐