Android Launcher3简介

一.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>

  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Android Launcher3是一个开源的桌面应用程序开发框架,用于定制和开发Android设备的启动器。它是Android操作系统中默认的启动器应用程序,通常用于管理和显示设备上的应用图标、小部件和壁纸等。 要进行Android Launcher3开发,首先需要熟悉Java编程语言和Android应用程序开发的基础知识。然后,我们可以从Launcher3的源代码库中获取代码,并导入到Android开发环境中。 在开发过程中,我们可以根据自己的需求和设计理念,定制和修改Launcher3的外观和功能。例如,我们可以更改应用程序图标的布局、调整文件夹管理方式、添加自定义小部件等。 同时,Launcher3还支持插件扩展和第三方应用程序的集成。我们可以通过插件机制,为Launcher3添加新的功能和特性,提供用户更好的体验。此外,Launcher3还可以与其他应用程序和服务进行集成,例如与天气应用程序关联,实时显示天气信息。 在进行Launcher3开发时,我们还可以考虑性能优化和用户体验的改进。通过减少资源占用、缓存数据和优化绘制过程等,提高启动速度和界面流畅度。 最后,在开发完毕后,我们可以通过发布应用程序来将我们的自定义Launcher3带给用户。可以通过发布到Google Play商店或其他应用商店,让更多的用户体验我们开发的桌面应用程序。 总而言之,Android Launcher3开发是一个开源且灵活的框架,可以根据自己的需求和创意进行定制和开发。通过对其源代码的理解和修改,可以实现一个独特且符合自己品味的Android启动器。 ### 回答2: Android Launcher3是一个开源的Launcher应用程序,Android原生系统中的默认桌面应用。开发人员可以基于Launcher3进行定制和开发,以创建自己的个性化桌面应用。 使用Launcher3进行开发,首先要了解Launcher3的架构和基本的工作原理。Launcher3的核心组件包括桌面、工作区、小部件、文件夹、图标等,并且支持主题、壁纸、小部件、手势等特性。 在开发过程中,首先需要配置开发环境,包括安装Android Studio和配置相应的依赖项。然后,可以通过下载Launcher3的源代码进行项目的导入。在导入项目后,可以对其进行相关的配置,例如修改应用的包名、应用图标等。接着,可以根据需求对Launcher3进行定制,例如修改桌面布局、添加新的小部件、定义不同的主题等。 在定制Launcher3时,可以利用Android Studio提供的各种开发工具和库。例如,可以使用布局编辑器进行界面的设计和排版,可以使用调试工具进行代码调试和测试。 在开发过程中,还需要关注用户体验和性能优化。可以通过在代码中实现异步加载、缓存数据、延迟加载等技术手段来提高应用的响应速度和稳定性。 最后,完成开发后,可以将应用测试和发布到各个渠道。可以利用Google Play Store等应用商店进行发布,也可以通过APK文件进行安装和分享。 总的来说,Android Launcher3开发需要掌握相关的开发技术和工具,充分理解Launcher3的架构和工作原理,并根据需求进行相应的定制和优化。开发过程中需要注重用户体验和性能优化,最终完成的应用可以提供一个个性化、灵活和高效的桌面体验。 ### 回答3: Android Launcher3是一个开源的Android桌面应用程序。它作为Android操作系统的一部分,负责提供用户与手机系统之间的交互接口。 Android Launcher3开发主要包括以下几个方面: 1. 桌面布局:开发者可以自定义桌面的布局和样式,包括图标大小、行列数、壁纸、Dock栏等。可以根据用户需求设计不同的桌面布局,提供更好的用户体验。 2. 图标管理:开发者可以实现图标的添加、删除、排序、重命名等功能。用户可以根据自己的喜好对图标进行自定义管理,方便快捷地访问常用应用程序。 3. 动态壁纸:开发者可以实现动态壁纸功能,如天气、时钟、日历等。用户可以根据自己喜好选择不同的动态壁纸,使桌面更加生动有趣。 4. 文件夹管理:开发者可以实现文件夹的创建、删除、重命名等功能。用户可以将相似的应用程序放在同一个文件夹中,方便整理和查找。 5. 快捷方式:开发者可以实现添加快捷方式的功能,用户可以将常用的应用程序、联系人、网页等创建快捷方式到桌面,方便快速访问。 6. 搜索功能:开发者可以实现桌面搜索功能,用户可以通过输入关键词搜索应用程序、联系人、网页等。提供快速搜索和访问功能。 7. 主题设置:开发者可以实现主题设置功能,用户可以选择不同的主题来改变桌面的外观和样式,个性化桌面。 通过以上的开发,可以创建一个功能丰富、界面美观的Android Launcher应用程序,满足用户多样化的需求,提供更好的用户体验。开发者可以根据自己的需求和创意来进行功能扩展和定制化,并通过开源社区的力量一起优化和改善这个开源项目。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值