美文网首页
Launcher3 抽屉型桌面改造成横屏桌面

Launcher3 抽屉型桌面改造成横屏桌面

作者: 愤怒皮卡 | 来源:发表于2017-12-27 11:21 被阅读0次

    Google Launcher3默认是抽屉型的桌面,到Android 8.0依然是没有这样的功能。这样的功能是手机厂商提供给我们的,不得不说,横向排列的桌面
    更适合国人的使用习惯,可能是使用iphone的习惯吧。

    好,那我们如何实现这样功能呢?其实并不会太难的。

    在Launcher加载流程里,我们知道桌面的数据是在LauncherModel的 LoaderTask完成加载的

    我们在loadAndBindAllApps()方法调用之后添加一个verifyApplications()方法调用,为什么在这里调用呢?
    因为只用当应用数据加载完全后,我们才能讲所有的应用进行横向绑定到Workspace的操作

    
            @Override
            public void run() {
                AppTypeHelper.configSystemAppIcon(mContext);
    
                synchronized (mLock) {
                    if (mStopped) {
                        return;
                    }
                    mIsLoaderTaskRunning = true;
                }
                // Optimize for end-user experience: if the Launcher is up and // running with the
                // All Apps interface in the foreground, load All Apps first. Otherwise, load the
                // workspace first (default).
                keep_running:
                {
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "step 1: loading workspace");
                    }
                    loadAndBindWorkspace();
    
                    if (mStopped) {
                        break keep_running;
                    }
    
                    waitForIdle();
    
                    // second step
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "step 2: loading all apps");
                    }
                    loadAndBindAllApps();
                }
    
                if (LauncherAppState.getInstance().getInvariantDeviceProfile()
                        .isDisableAllApps) {
                    verifyApplications();
                }
                // Clear out this reference, otherwise we end up holding it until all of the
                // callback runnables are done.
                mContext = null;
    
                synchronized (mLock) {
                    // If we are still the last one to be scheduled, remove ourselves.
                    if (mLoaderTask == this) {
                        mLoaderTask = null;
                    }
                    mIsLoaderTaskRunning = false;
                    mHasLoaderCompletedOnce = true;
                }
            }
    
    

    这里呢,我简单的添加了一个布尔值 LauncherAppState.getInstance().getInvariantDeviceProfile().isDisableAllApps 表示是否启用横屏桌面,小伙伴开发的时候建议做成开关的方式,以满足不同的产品需求。

    verifyApplications方法里怎么实现呢?来看

     private void verifyApplications() {
            final Context context = mApp.getContext();
    
            // Cross reference all the applications in our apps list with items in the workspace
            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()) {
                        // ignore the apps
                        if (mIgnoreAppsList.contain(app.componentName.getPackageName())) {
                            continue;
                        }
                        // We are missing an application icon, so add this to the workspace
                        added.add(app);
                        // This is a rare event, so lets log it
                        // Log.e(TAG, "Missing Application on load: " + app);
                    }
                }
            }
            if (!added.isEmpty()) {
                addAndBindAddedWorkspaceItems(context, added);
            }
        }
    
    

    如果小伙伴有用心看加载流程的细节的话,在loadAndBindAllApps()方法里,会把获取到的所有应用信息保存到 AllAppsList这个类里,也就是 mBgAllAppsList.data 里面,故
    我们遍历data数据,将需要绑定的数据绑定到Workspace上就可以了。这里还有一个方法 getItemInfoForComponentName ,作用是 mBgAllAppsList.data的数据跟sBgItemsIdMap里
    的数据做匹配,避免因为线程的关系将不必要的数据添加到桌面

    拿到数据的备份added集合后,我们使用LauncherModel里的 addAndBindAddedWorkspaceItems 方法添加item

       /**
         * Adds the provided items to the workspace.
         */
        public void addAndBindAddedWorkspaceItems(final Context context,
                                                  final ArrayList<? extends ItemInfo> workspaceApps) {
            final Callbacks callbacks = getCallback();
            if (workspaceApps.isEmpty()) {
                return;
            }
            // Process the newly added applications and add them to the database first
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
                    final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
    
                    // Get the list of workspace screens.  We need to append to this list and
                    // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
                    // called.
                    ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
                    synchronized (sBgLock) {
                        for (ItemInfo item : workspaceApps) {
    
                            if (item instanceof ShortcutInfo) {
                                // Short-circuit this logic if the icon exists somewhere on the workspace
                                if (shortcutExists(context, item.getIntent(), item.user)) {
                                    continue;
                                }
                            }
                            // Find appropriate space for the item.
                            Pair<Long, int[]> coords = findSpaceForItem(context,
                                    workspaceScreens, addedWorkspaceScreensFinal,
                                    1, 1);
                            long screenId = coords.first;
                            int[] cordinates = coords.second;
    
                            ItemInfo itemInfo;
                            if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
                                itemInfo = item;
                            } else if (item instanceof AppInfo) {
                                itemInfo = ((AppInfo) item).makeShortcut();
                            } else {
                                throw new RuntimeException("Unexpected info type");
                            }
    
                            // Add the shortcut to the db
                            addItemToDatabase(context, itemInfo,
                                    LauncherSettings.Favorites.CONTAINER_DESKTOP,
                                    screenId, cordinates[0], cordinates[1]);
                            // Save the ShortcutInfo for binding in the workspace
                            addedShortcutsFinal.add(itemInfo);
                        }
                    }
    
                    // Update the workspace screens
                    updateWorkspaceScreenOrder(context, workspaceScreens);
    
                    if (!addedShortcutsFinal.isEmpty()) {
                        runOnMainThread(new Runnable() {
                            @Override
                            public void run() {
                                Callbacks cb = getCallback();
                                if (callbacks == cb && cb != null) {
                                    final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
                                    final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
                                    if (!addedShortcutsFinal.isEmpty()) {
                                        ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
                                        long lastScreenId = info.screenId;
                                        for (ItemInfo i : addedShortcutsFinal) {
                                            if (i.screenId == lastScreenId) {
                                                addAnimated.add(i);
                                            } else {
                                                addNotAnimated.add(i);
                                            }
                                        }
                                    }
                                    callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
                                            addNotAnimated, addAnimated, null);
                                }
                            }
                        });
                    }
                }
            };
            runOnWorkerThread(r);
        }
    
    

    这里就跟加载流程里的绑定worksspace的Screen类似了。简单的介绍一下细节,

    1. 从数据库拿到ScreenId信息 workspaceScreens,遍历需要添加的item信息
    2. 通过findSpaceForItem 方法在workspace上找到空余的位置,如果没有位置会新创建一个Screen出来。
    3. 根据ItemInfo的类型创建ShortcutInfo,将ShortcutInfo,screen order信息更新到数据库
    4. 拿到Launcher 这个callbacks调用 bindAppsAdded,开始绑定到workspace
    
        @Override
        public void bindAppsAdded(final ArrayList<Long> newScreens,
                                  final ArrayList<ItemInfo> addNotAnimated,
                                  final ArrayList<ItemInfo> addAnimated,
                                  final ArrayList<AppInfo> addedApps) {
            Log.e(TAG, "bindAppsAdded");
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
                }
            };
            if (waitUntilResume(r)) {
                return;
            }
    
            // Add the new screens
            if (newScreens != null) {
                bindAddScreens(newScreens);
            }
    
            // We add the items without animation on non-visible pages, and with
            // animations on the new page (which we will try and snap to).
            if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
                bindItems(addNotAnimated, 0,
                        addNotAnimated.size(), false);
            }
            if (addAnimated != null && !addAnimated.isEmpty()) {
                bindItems(addAnimated, 0,
                        addAnimated.size(), true);
            }
    
            // Remove the extra empty screen
            mWorkspace.removeExtraEmptyScreen(false, false);
    
            if (addedApps != null && mAppsView != null) {
                mAppsView.addApps(addedApps);
            }
        }
    
    

    可以发现,会先使用新生成的ScreenId创建screen,之后才开始bindItems, 如果继续往bindItems里看你就会发现,会在WorkSpace里调用addInScreenFromBind,完成图标的创建。

    这里有个地方值得我们提一下,就是 waitUntilResume 方法的使用,在很多地方都会使用这个方法。 作用是在Launcher onResume的时候再执行我们的Runnable。通常,类似的操作
    我们会直接在onResume调用或实现,如果操作一多,onResume里就会很臃肿,不好维护

        @Thunk
        boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
            if (mPaused) {
                if (LOGD) {
                    Log.d(TAG, "Deferring update until onResume");
                }
                if (deletePreviousRunnables) {
                    while (mBindOnResumeCallbacks.remove(run)) {
                    }
                }
                mBindOnResumeCallbacks.add(run);
                return true;
            } else {
                return false;
            }
        }
    
    

    这里使用的是一个状态的机制,在mPaused的状态,把需要执行的runnable添加到mBindOnResumeCallbacks,在onResume的时候在遍历出来执行即可

    这样就能将抽屉型的Launcher改造成横向的Launcher了,当然改完之后可能会有一些bug,比如桌面里的应用都是 ShortcutInfo类型的,在拖拽时没有查看信息的功能等等
    就需要小伙伴自己修改啦

    感谢阅读~

    相关文章

      网友评论

          本文标题:Launcher3 抽屉型桌面改造成横屏桌面

          本文链接:https://www.haomeiwen.com/subject/dhzygxtx.html