Launcher3 开发定制 (Android10)

本文详细介绍了如何对Android10的Launcher3进行开发定制,包括加载全部应用、响应应用变动、隐藏抽屉功能、添加定制主页、实现图标交互以及UI适配。内容涵盖工作区加载、应用安装更新删除的处理、隐藏抽屉、添加自定义主页、图标点击长按拖拽功能的实现,以及桌面图标UI的调整和自定义桌面微件的开发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Launcher3 开发定制 (Android10)

1 Workspace 加载全部应用

1.1 Workspace 加载全部应用

Launcher 启动,会在oncreate中调用LauncherModel.startLoader()执行加载桌面图标到workspace的方法,这里是修改入口。

// src/com/android/launcher3/Launcher.java
private LauncherModel mModel;
protected void onCreate(Bundle savedInstanceState) {
   
    //...
    if (!mModel.startLoader(currentScreen)) {
   
            ...
    }
}

后面会调用startLoaderForResults(loaderResults)方法执行一次图标加载的任务

// src/com/android/launcher3/LauncherModel.java
public void startLoaderForResults(LoaderResults results) {
   
    synchronized (mLock) {
   
        stopLoader();
        mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
        MODEL_EXECUTOR.post(mLoaderTask);
    }
}

在LoaderTask这个任务的run()方法中,我们可以定义加载应用图标到workspace的方法:

  1. loadAllAppsToWorkspace方法负责加载所有可显示的应用信息列表,然后调用 LauncherModel.addAndBindAddedWorkspaceItems()方法添加到workspace上,具体的添加逻辑可前往 AddWorkspaceItemsTask 这个任务类中查看。
  2. 修改内部类InstallShortcutReceiver.PendingInstallShortcutInfo的访问修饰符为public, 使外部可访问。其中 getItemInfo的相关参数可自行查看。
// src/com/android/launcher3/model/LoaderTask.java
public void run() {
   
    //...
    // second step
    TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
    List<LauncherActivityInfo> allActivityList = loadAllApps();
	// 添加此逻辑,LauncherAppState.isDisableAllApps()方法默认返回true
    if (LauncherAppState.isDisableAllApps()) {
   
        loadAllAppsToWorkspace();
    }
}

private void loadAllAppsToWorkspace() {
   
    List<LauncherActivityInfo> allActivityList = getAllApps();
    if (!allActivityList.isEmpty()) {
   
        final Context context = mApp.getContext();
        ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
        synchronized (this) {
   
            for (LauncherActivityInfo launcherActivityInfo : allActivityList) {
   
                MyLogUtils.i(TAG, "pkg: " + launcherActivityInfo.getComponentName().getPackageName());
                // 构造快捷方式的信息, 这里需要修改内部类InstallShortcutReceiver.PendingInstallShortcutInfo的访问修饰符为public, 使外部可访问。
                InstallShortcutReceiver.PendingInstallShortcutInfo shortcutInfo =
                    new InstallShortcutReceiver.PendingInstallShortcutInfo(launcherActivityInfo, context);
                installQueue.add(shortcutInfo.getItemInfo());
            }
        }

        if (!installQueue.isEmpty()) {
   
            MyLogUtils.i(TAG, "loadAllAppsToWorkspace installQueue size: " + installQueue.size());
            mApp.getModel().addAndBindAddedWorkspaceItems(installQueue);
        }
    }
}

// src/com/android/launcher3/LauncherModel.java
public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
   
    Callbacks callbacks = getCallback();
    if (callbacks != null) {
   
        callbacks.preAddApps();
    }
    enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
}

我们可以来看一下AddWorkspaceItemsTask任务类的构造方法和execute()方法,在这里,系统会默认不处理加载两种情况的应用图标:1.已存在workspace中的,2.系统应用, 我们需要修改第二种情况,系统应用和非系统应用都需要添加到workspace。

// src/com/android/launcher3/model/AddWorkspaceItemsTask.java
public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) {
   
    mItemList = itemList;
}

@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
   
    for (Pair<ItemInfo, Object> entry : mItemList) {
   
        // item类型, 具体的item信息可参考 ItemInfo、PendingInstallShortcutInfo.getItemInfo方法
        if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
            item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
   
            // Short-circuit this logic if the icon exists somewhere on the workspace
            if (shortcutExists(dataModel, item.getIntent(), item.user)) {
   
                continue;
            }

            // b/139663018 Short-circuit this logic if the icon is a system app
            if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) {
   
               // 注释此行代码,不排除系统应用的情况
                //continue;
            }
        }
        // 以下是处理逻辑,不需要修改,暂不分析
       //...
    }

1.2 Workspace 列表响应应用安装/更新/删除的情况

到这里,workspace会加载全部应用的图标,我们还需要考虑应用安装/更新/删除的情况,此时也需要更新workspace的图标状态。

在LauncherModel类中,会有应用安装卸载后等一系列的回调方法,此时会调用 PackageUpdatedTask任务类对桌面图标执行一些操作,我们也需要对workspace进行一些更新处理。此处以应用安装为例:

// src/com/android/launcher3/LauncherModel.java
@Override
public void onPackageAdded(String packageName, UserHandle user) {
   
    int op = PackageUpdatedTask.OP_ADD;
    enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}

这里需要添加updateAllAppToWorkspace()方法来更新workspace图标,逻辑和加载全部应用图标到workspace大致相似,可以自行比对。

// src/com/android/launcher3/model/PackageUpdatedTask.java
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
   
    //...
    // 在方法末尾添加更新workspace图标方法
    if (LauncherAppState.isDisableAllApps()) {
   
        updateAllAppToWorkspace(app, appsList);
    }
}

private void updateAllAppToWorkspace(LauncherAppState app, AllAppsList appsList) {
   
    if (appsList.data.isEmpty()) {
   
        return;
    }

    Context context = app.getContext();
    final List<UserHandle> userProfiles = UserManagerCompat.getInstance(context).getUserProfiles();

    ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
    for (UserHandle user : userProfiles) {
   
        final List<LauncherActivityInfo> apps =
            LauncherAppsCompat.getInstance(context).getActivityList(null, user);
        synchronized (this) {
   
            for (LauncherActivityInfo launcherActivityInfo : apps) {
   
                for (AppInfo appInfo : appsList.data) {
   
                    if (launcherActivityInfo.getComponentName().equals(appInfo.componentName)) {
   
                        InstallShortcutReceiver.PendingInstallShortcutInfo shortcutInfo =
                            new InstallShortcutReceiver.PendingInstallShortcutInfo(launcherActivityInfo, context);
                        installQueue.add(shortcutInfo.getItemInfo());
                    }
                }
            }
        }
    }

    if (!installQueue.isEmpty()) {
   
        MyLogUtils.i(TAG, "updateAllAppToWorkspace, installQueue size: " + installQueue.size());
        app.getModel().addAndBindAddedWorkspaceItems(installQueue);
    }

    appsList.data.clear();
}

另外,PackageUpdatedTask类继承于BaseModelUpdateTask,在BaseModelUpdateTask的execute()方法中,会有一个判断的短路逻辑,阻止更新任务的执行,我们需要手动注释,否则,更新方法将会不执行。

// src/com/android/launcher3/model/BaseModelUpdateTask.java
@Override
public final void run() {
   
    if (!mModel.isModelLoaded()) {
   
        if (DEBUG_TASKS) {
   
            Log.d(TAG, "Ignoring model task since loader is pending=" + this);
        }
        // 注释return, 继续向下执行
        // return;
    }
    execute(mApp, mDataModel, mAllAppsList);
}

2 Workspace 隐藏抽屉功能

到这里,workspace已经实现了应用列表展示,抽屉入口的功能可以移除。可以看到的是,我们在wworkspace界面通过手势上滑操作来打开抽屉。

// src/com/android/launcher3/dragndrop/DragLayer.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
   
    ev.offsetLocation(getTranslationX(), 0);
    try {
   
        return super.dispatchTouchEvent(ev);
    } finally {
   
        ev.offsetLocation(-getTranslationX(), 0);
    }
}

我们可以取消滑动时的监听,达到不能通过抽屉进入all app界面的效果。

// src/com/android/launcher3/touch/BaseSwipeDetector.java
public boolean onTouchEvent(MotionEvent ev) {
   
    case MotionEvent.ACTION_MOVE:
    //...
    // handle state and listener calls.
    // 添加以下代码取消上滑监听,隐藏抽屉入口
    // BaseFlags.DRAG_TO_ALL_APPS_DISABLED 默认设置为true
    if (!BaseFlags.DRAG_TO_ALL_APPS_DISABLED) {
   
        // handle state and listener calls.
        if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
   
            setState(ScrollState.DRAGGING);
        }
        if (mState == ScrollState.DRAGGING) {
   
            reportDragging(ev);
        }
    }
    mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
    break;
}

将在workspace界面的提示向上的小箭头去掉。注释以下代码,不绘制箭头:

// src/com/android/launcher3/views/ScrimView.java
private void updateDragHandleVisibility(Drawable recycle) {
   
    boolean visible = mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
    boolean wasVisible = mDragHandle != null;
    if (visible != wasVisible) {
   
        // 添加以下代码取消隐藏箭头
        if (BaseFlags.DRAG_TO_ALL_APPS_DISABLED) {
   
            // set mDragHandle as null to hide arrow
            mDragHandle = null;
        } else {
   
            if (visible) {
   
                mDragHandle = recycle != null ? recycle :
                mLauncher.getDrawable(R.drawable.drag_handle_indicator);
                mDragHandle.setBounds(mDragHandleBounds);

                updateDragHandleAlpha();
            } else {
   
                mDragHandle = null;
            }
        }
        invalidate();
    }
}

@Override
    public boolean onTouchEvent(MotionEvent event) {
   
        boolean value = super.onTouchEvent(event);
        if (BaseFlags.DRAG_TO_ALL_APPS_DISABLED) {
   
            return value;
        }
        if (!value && mDragHandle != null && event.getAction() == ACTION_DOWN
                && mDragHandle.getAlpha() == 255
                && mHitRect.contains(event.getX(), event.getY())) {
   
            //...
            return true;
        }
        return value;
    }

侧滑指示器的修改一般有两种:1.隐藏指示器;2.修改指示器样式

  1. 隐藏workspace的侧滑指示器。当页面滑动时会通过调用到WorkspacePageIndicator的invalidate()->draw()进行指示器的绘制。
// src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
@Override
protected void onDraw(Canvas canvas) {
   
    if (mTotalScroll == 0 || mNumPagesFloat == 0) {
   
        return;
    }
    // 隐藏 WorkspacePageIndicator
    // Compute and draw line rect.
    /*float progress = Utilities.boundToRange(((float) mCurrentScroll) / mTotalScroll, 0f, 1f);
        int availableWidth = getWidth();
        int lineWidth = (int) (availableWidth / mNumPagesFloat);
        int lineLeft = (int) (progress * (availableWidth - lineWidth));
        int lineRight = lineLeft + lineWidth;

        canvas.drawRoundRect(lineLeft, getHeight() / 2 - mLineHeight / 2, lineRight,
                getHeight() / 2 + mLineHeight / 2, mLineHeight, mLineHeight, mLinePaint);*/
}

  1. 修改指示器样式。ondraw 方法进行以下修改。
private boolean mShouldAutoHide = false;
private final int mLineRadius;
private int mLineWidth = 22;
private final float mCircleRadius = 6;
private final float mCircleSpacing = mCircleRadius * 2 + 17;  

@Override
protected void onDraw(Canvas canvas) {
   
    //Modify workspacePageIndicator style
    mLinePaint.setAlpha(45);
    float circleFirstX = getWidth() / 2.0f - ((mNumPagesFloat / 2.0f) * mCircleSpacing);
    for (int i = 0; i < mNumPagesFloat; i++) {
   
        canvas.drawCircle(circleFirstX + i * mCircleSpacing, getHeight() / 2.0f, mCircleRadius, mLinePaint);
    }
    mLinePaint.setAlpha(255);

    if (mTotalScroll == 0 || mNumPagesFloat == 0) {
   
        float left = circleFirstX - mLineWidth / 2.0f;
        float right = left + mLineWidth;
        canvas.drawRoundRect(left, getHeight() / 2.0f - mLineHeight / 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值