Android Launcher3定制开发之改造横向化

Posted by Toeii on May 7, 2020

前言

为了加深对Launcher3的整体印象,记录在工作上所遇到和解决的关于Launcher3定制开发的一些知识点,并且归纳总结成本系列文章。同时,也能起到帮助他人解决一些关于Launcher3相关定制的需求开发。

简介

因为国内用户大多数习惯桌面横向滑动,所以很多时候需要把原生luancher从抽屉形态改造成横向形态。我发现这方面的资料不多,所以自己研究了一下,并写下这篇博客记录。

思路

  • 修改Hotseat中的allAppsButton相关业务
  • 将AllAppsContainerView中的所有应用放在LoadTask加载
  • 检测替换APP时并更新Workspace
  • 改造Workspace的长按呼出菜单

一句话概括就是“屏蔽原有抽屉化业务代码,将数据全放入第一层桌面,然后新增更新第一层桌面的相关业务代码”

具体实现

修改Hotseat中的allAppsButton相关业务

这里主要是为了去除Hotseat中的allAppsButton加载,并去除上滑打开抽屉桌面相关代码。

因此,我直接屏蔽了Hotseat中的addViewToCellLayout关键代码。


   void resetLayout() {
        mContent.removeAllViewsInLayout();
       /* 
            ...

            mContent.addViewToCellLayout(allAppsButtonallAppsButton, -1, allAppsButton.getId(), lp, true);

            ...
        */
    }

接着,处理在LauncherModel屏蔽数据集加载


       private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item,
                   ArrayList<Long> workspaceScreens) {
            LauncherAppState app = LauncherAppState.getInstance();
            InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
            final int countX = profile.numColumns;
            final int countY = profile.numRows;

            long containerIndex = item.screenId;
            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                //TODO 过滤Main HotSeat处理
                //    if (mCallbacks == null ||
                //            mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
                //        Log.e(TAG, "Error loading shortcut into hotseat " + item
                //                + " into position (" + item.screenId + ":" + item.cellX + ","
                //                + item.cellY + ") occupied by all apps");
                //        return false;
                //    }
            }
            ...
        }

然后在PackageUpdatedTask类中屏蔽掉多余的数据初始化,避免不必要的操作。


final Callbacks callbacks = getCallback();

if (callbacks == null) {
    return;
}

final HashMap<ComponentName, AppInfo> addedOrUpdatedApps =
        new HashMap<ComponentName, AppInfo>();

if (added != null) {

//  addAppsToAllApps(context, added);

    final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
    addAndBindAddedWorkspaceItems(context, addedInfos);

    for (AppInfo ai : added) {
        addedOrUpdatedApps.put(ai.componentName, ai);
    }

}


将AllAppsContainerView中的所有应用放在LoadTask加载

找到LoaderTask.Run方法,然后将桌面数据重新添加到第一层桌面上显示。


keep_running: {

    loadAndBindWorkspace();

    if (mStopped) {
        break keep_running;
    }

    waitForIdle();

    loadAndBindAllApps();

    //加载第一层桌面数据
    loadVerifyApplications();

}

private void loadVerifyApplications() {
    final Context context = mApp.getContext();

    ArrayList<ItemInfo> tmpInfos;
    ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
    synchronized (sBgLock) {
        for (AppInfo app : mBgAllAppsList.data) {
            tmpInfos = getItemInfoForComponentName(app.componentName, app.user);
            if (tmpInfos.isEmpty()) {
                added.add(app);
            }
        }
    }
    if (!added.isEmpty()) {
        addAndBindAddedWorkspaceItems(context, added);
    }
}

检测替换APP时并更新Workspace

修改PackageUpdatedTask类中execute方法,添加第一层横向桌面的数据更新逻辑。


public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {

    ....

     final ArrayList<AppInfo> addedOrModified = new ArrayList<>();
    addedOrModified.addAll(appsList.added);
        
    //更新workspace
    if (LauncherAppState.isDisableAllApps()) {
        updateToWorkSpace(context, app, appsList);
    }
    
    ...
}

public void updateToWorkSpace(Context context, LauncherAppState app , AllAppsList appsList){
         ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();

    final List<UserHandle> profiles = UserManagerCompat.getInstance(context).getUserProfiles();
    
    ArrayList<InstallShortcutReceiver.PendingInstallShortcutInfo> added 
    = new ArrayList<InstallShortcutReceiver.PendingInstallShortcutInfo>();
    
    for (UserHandle user : profiles) {
        final List<LauncherActivityInfo> apps = LauncherAppsCompat.getInstance(context).getActivityList(null, user);
        synchronized (this) {
            for (LauncherActivityInfo info : apps) {
                for (AppInfo appInfo : appsList.added) {
                    if(info.getComponentName().equals(appInfo.componentName)){
                        InstallShortcutReceiver.PendingInstallShortcutInfo mPendingInstallShortcutInfo 
                        =  new InstallShortcutReceiver.PendingInstallShortcutInfo(info,context);
                        added.add(mPendingInstallShortcutInfo);
                        installQueue.add(mPendingInstallShortcutInfo.getItemInfo());
                    }
                }
            }
        }
    }

    if (!added.isEmpty()) {
        app.getModel().addAndBindAddedWorkspaceItems(installQueue);
    }

}

改造Workspace的长按呼出菜单

屏蔽Workspace长按删除操作,该删除会导致第一层桌面数据丢失。

在DeleteDropTarget类supportsDrop方法中,屏蔽菜单键的显示。


public static boolean supportsDrop(Object info) {

    //    TODO 屏蔽长按删除
    //    return (info instanceof ShortcutInfo)
    //            || (info instanceof LauncherAppWidgetInfo)
    //            || (info instanceof FolderInfo);

    if (info instanceof ShortcutInfo) {
        ShortcutInfo item = (ShortcutInfo) info;
        return item.itemType != LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
    }
    return info instanceof LauncherAppWidgetInfo;

}

结语

Launcher横向化到此也改造完毕。 本系列文章相关代码,可以通过点击这里找到。希望对大家学习和了解Launcher开发有所帮助。