Launcher3源码地址:Launcher3-master
[This tutorial was written by Ticoo]

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

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

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

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

@Overridepublic 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 stepif (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 workspaceArrayList<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 appsif (mIgnoreAppsList.contain(app.componentName.getPackageName())) {continue;}// We are missing an application icon, so add this to the workspaceadded.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 firstRunnable r = new Runnable() {@Overridepublic 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 workspaceif (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 dbaddItemToDatabase(context, itemInfo,LauncherSettings.Favorites.CONTAINER_DESKTOP,screenId, cordinates[0], cordinates[1]);// Save the ShortcutInfo for binding in the workspaceaddedShortcutsFinal.add(itemInfo);}}// Update the workspace screensupdateWorkspaceScreenOrder(context, workspaceScreens);if (!addedShortcutsFinal.isEmpty()) {runOnMainThread(new Runnable() {@Overridepublic 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
@Overridepublic 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() {@Overridepublic void run() {bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);}};if (waitUntilResume(r)) {return;}// Add the new screensif (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 screenmWorkspace.removeExtraEmptyScreen(false, false);if (addedApps != null && mAppsView != null) {mAppsView.addApps(addedApps);}}

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

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

    @Thunkboolean 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类型的,在拖拽时没有查看信息的功能等等
就需要小伙伴自己修改啦

感谢阅读~

+qq群:853967238。获取以上高清技术思维图,以及相关技术的免费视频学习资料。

Launcher3桌面开发-Launcher3 抽屉型桌面改造成横屏桌面相关推荐

  1. 旧计算机 云桌面,该不该利用旧PC机改造成云桌面虚拟化模式呢?

    原标题:该不该利用旧PC机改造成云桌面虚拟化模式呢? 由于传统PC电脑办公模式有数据安全隐患.维护成本高.占用空间及耗电量高,噪音大,使用寿命低等弊端正在逐步退出办公领域,越来越多的企业选择桌面虚拟化 ...

  2. python桌面开发吐血_想用java写个桌面小demo,就布局都差点写吐血了,学艺不精...

    demo简略需求 项目背景 很多文件重复存放,除了管理混乱,还会对患有强迫症用户的身心造成10000点的伤害...其实就是360云盘当时上传了有上传,造成很多重复的图片+视频,前阵子360个人云盘&q ...

  3. Cell:绝对异养型生物改造成完全自养型生物

    撰文 | 胡小话 责编 | 陈哲 自接触生物科学以来,我们就被告知自然界的生物可以分为三类:以植物为主的"生产者",以动物为主的"消费者"和以微生物为主的&qu ...

  4. Electron、QT和JAVA PC桌面开发技术比较

    近几年PC桌面开发越来越多的被Electron,QT和Java技术占领.下面简单比较一下它们的优劣. Electron,势是开发用时快,社区轮子多,整合一下就能用.缺点是打包大,js计算弱. Java ...

  5. window 桌面开发_C#桌面开发的未来WebWindow

    WebWindow WebWindow是跨平台的库.Web Window的当前实验实现可在以下平台上运行: Windows – 需要基于Chromium的Edge Linux – 使用WebKit M ...

  6. 【Visual Studio】Visual Studio 2019 创建 Windows 控制台程序 ( 安装 ‘使用 C++ 的桌面开发‘ 组件 | 创建并运行 Windows 控制台程序 )

    文章目录 一.安装 C++ 桌面开发组件 二.创建并运行 Windows 控制台程序 一.安装 C++ 桌面开发组件 打开 Visual Studio Installer , 点击 " 修改 ...

  7. 【Qt】Qt 开发桌面程序 ( Qt 版本 5.14.2 | 编辑 Qt 桌面按钮控件 | 修改按钮文本 | 为按钮添加点击事件 | 系统调用 | 去掉系统调用命令窗口 )

    文章目录 一.添加按钮控件 二.修改按钮文本 三.为按钮添加点击事件 ( 弹出对话框 ) 四.为按钮添加点击事件 ( 打开记事本 ) 五.为按钮添加点击事件 ( 打开计算器 ) 六.去掉系统调用时弹出 ...

  8. C#桌面开发的未来WebWindow

    WebWindow WebWindow是跨平台的库.Web Window的当前实验实现可在以下平台上运行: Windows – 需要基于Chromium的Edge Linux – 使用WebKit M ...

  9. Google 要用 Flutter 一统移动、桌面开发江湖?

    "Flutter 的核心是一个独立的可执行二进制文件,所以它不仅能改变移动开发的世界,也能改变桌面开发的世界.你只需编写一次代码,就可以在 Android.iOS.Windows.Mac 和 ...

最新文章

  1. TinyML-TVM是如何驯服Tiny的(上)
  2. Android4.0 Design之UI设计易犯的错误2
  3. U盘制做DOS启动盘
  4. POI 使用替换字符方式进行模板生成word
  5. 2020年10月份学习总结,项目管理案例
  6. Git复习(十二)之命令专场
  7. sqlserver 创建对某个存储过程执行情况的跟踪
  8. Shadow DOM及自定义标签
  9. Android官方开发文档Training系列课程中文版:分享简单数据之从其它APP接收简单数据
  10. 个人pkm软件 pim软件_个人申请软件著作权需要走哪些流程
  11. 2018 年 Android 应用程序的发展趋势
  12. 【Flink】FLink Assigned key must not be null
  13. 【HTML+CSS网页设计与布局 从入门到精通】第12章-CSS
  14. openstack rocky 安装_ubuntu 18.04 安装网易云音乐
  15. hash算法_阿里面试官:讲一下Hashmap中hash算法!
  16. Deqin-python计算器
  17. java json.stringify_浅谈 JSON.stringify 方法
  18. html ios视频播放器,iOS 视频播放器(整理)
  19. java使用aspose-words组件word转换图片
  20. 分析DuxCms之AdminUserModel

热门文章

  1. 前端必学的6个HTML+CSS特效
  2. 后端开发基础能力以及就Java的主流开发框架介绍
  3. Multi-Dimensional Pruning[译]
  4. Linux常用指令学习(篇二):权限管理指令
  5. 基于Gmap的箭头随路径方向旋转
  6. CoreData - 查询
  7. k8s集群部署二进制(一)
  8. 探索性数据分析(EDA)
  9. java判断文件编码是UTF-8还是UTF-8(BOM)还是其他编码
  10. 【ACWing】2572. 生成魔咒