安卓开发- 安卓13 Launcher3 主页结构修改

打印 上一主题 下一主题

主题 1012|帖子 1012|积分 3036

修改主页结构

概述

​ 在 Android 操纵系统中,Launcher (主页应用)是用户与设备交互的核心界面之一,它负责显示应用程序列表、提供快捷方式、管理小部件等功能。其中,Launcher3 是 Android 系统默认的启动器应用程序,我们可以通过修改Launcher3的源码,来改变主页应用的样式和结构。下面将联合源码,分析如何修改Launcher3主页的相关样式和结构。
结构构成

​ Launcher3最核心的类是一个Launcher.java(可以看作是Launcher中的MainActivity),基本上所有操纵(包括UI的定制)都会合在这个Activity上。在Launcher.java 中,通过setContentView()设置的结构参数是R.layout.launcher,对应的是launcher.xml文件,它定义了启动器界面的整体结构和组件的位置,我们看下这个结构文件里的内容:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <com.android.launcher3.LauncherRootView
  3.     xmlns:android="http://schemas.android.com/apk/res/android"
  4.     xmlns:launcher="http://schemas.android.com/apk/res-auto"
  5.     android:id="@+id/launcher"
  6.     android:layout_width="match_parent"
  7.     android:layout_height="match_parent"
  8.     android:fitsSystemWindows="true">
  9.     <com.android.launcher3.dragndrop.DragLayer
  10.         android:id="@+id/drag_layer"
  11.         android:layout_width="match_parent"
  12.         android:layout_height="match_parent"
  13.         android:clipChildren="false"
  14.         android:clipToPadding="false"
  15.         android:importantForAccessibility="no">
  16.         <com.android.launcher3.views.AccessibilityActionsView
  17.             android:layout_width="match_parent"
  18.             android:layout_height="match_parent"
  19.             android:contentDescription="@string/home_screen"
  20.             />
  21.         <!-- The workspace contains 5 screens of cells -->
  22.         <!-- DO NOT CHANGE THE ID -->
  23.         <com.android.launcher3.Workspace
  24.             android:id="@+id/workspace"
  25.             android:layout_width="match_parent"
  26.             android:layout_height="match_parent"
  27.             android:layout_gravity="center"
  28.             android:theme="@style/HomeScreenElementTheme"
  29.             launcher:pageIndicator="@+id/page_indicator" />
  30.         <!-- DO NOT CHANGE THE ID -->
  31.         <include
  32.             android:id="@+id/hotseat"
  33.             layout="@layout/hotseat" />
  34.         <!-- Keep these behind the workspace so that they are not visible when
  35.          we go into AllApps -->
  36.         <com.android.launcher3.pageindicators.WorkspacePageIndicator
  37.             android:id="@+id/page_indicator"
  38.             android:layout_width="match_parent"
  39.             android:layout_height="@dimen/workspace_page_indicator_height"
  40.             android:layout_gravity="bottom|center_horizontal"
  41.             android:theme="@style/HomeScreenElementTheme" />
  42.         <include
  43.             android:id="@+id/drop_target_bar"
  44.             layout="@layout/drop_target_bar" />
  45.         <com.android.launcher3.views.ScrimView
  46.             android:layout_width="match_parent"
  47.             android:layout_height="match_parent"
  48.             android:id="@+id/scrim_view"
  49.             android:background="@android:color/transparent" />
  50.         <include
  51.             android:id="@+id/apps_view"
  52.             layout="@layout/all_apps"
  53.             android:layout_width="match_parent"
  54.             android:layout_height="match_parent" />
  55.         <include
  56.             android:id="@+id/overview_panel"
  57.             layout="@layout/overview_panel" />
  58.     </com.android.launcher3.dragndrop.DragLayer>
  59. </com.android.launcher3.LauncherRootView>
复制代码
​ 由上面内容可以看到,Launcher3,父结构是一个自定义的LauncherRootView,在这个LauncherRootView中只有一个子结构DragLayer,在DragLayer中放置了Workspace、Hotseat、WorkspacePageIndicator等内容。这里我们先看下Launcher3主页的构造图,以便我们相识各个子结构的相关内容:



  • Launcher3结构的最外貌是一个自定义的View:LauncherRootView,它是继承自FrameLayout;LauncherRootView内里只有一个叫DragLayer的ViewGroup,它同样继承自FrameLayout,主要功能就是处置惩罚拖拽事件,当你在拖拽一个图标的时候,就相当于是一个view放到了DragLayer内里,这个view会跟随你的手在屏幕上移动。
  • 屏幕上可以左右滑动的整个页面叫做Workspace,Workspace的父类是PagedView,PagedViewk用来处置惩罚左右滑动。
  • Workspace内里可能含有多个页面(屏),多个页面存在时,可以左右滑动来切换页面;可以滑动的单独一屏就是一个CellLayout,CellLayout负责本身页面图标和小部件的显示和整齐摆放。
  • 在左右滑动屏幕切换页面时 屏幕最下方会出现的指示器PageIndicator(一样平常是几个小圆点,这里图示中被隐藏了),告诉你桌面有几屏,当前在哪一屏上(圆点会高亮)。
  • 在Workspace中,向上滑动屏幕可以唤出所有应用列表(抽屉样式),向下滑动可以唤出状态栏和关照栏。
  • 在CellLayout中可以放置组件应用包罗应用的文件夹等。如上图中顶部的搜索框就是谷歌提供的原生搜索框(实在它并不算一个组件,而是一个特别元素),屏幕靠下方的左右两边分别是应用文件夹和单个应用图标。当长按CellLayout上的组件、应用、文件夹或者空缺地方的时候,会出现一个MENU菜单,可以对组件或应用进行配置,或者添加组件等。当长按组件、应用或文件夹,并拖动到屏幕上方时,屏幕上方会显示一个DropTargetBar区域,内里有“移除”按钮,可以对组件进行移除,对app进行卸载等操纵。
  • 底部有五个固定不动的图标所在的区域叫做Hotseat,用来放置比较常用的应用,好比拨号,短信,相机等。
  • 底部右侧有三个白色按钮的区域是导航栏。导航栏可以在所有页面中常显,用于全局控制(返回、回到主页、最近应用)。
这里简单展示下几个控件的结构代码:
hotseat.xml
  1. <com.android.launcher3.Hotseat
  2.     xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:launcher="http://schemas.android.com/apk/res-auto"
  4.     android:id="@+id/hotseat"
  5.     android:layout_width="match_parent"
  6.     android:layout_height="match_parent"
  7.     android:theme="@style/HomeScreenElementTheme"
  8.     android:importantForAccessibility="no"
  9.     android:preferKeepClear="true"
  10.     launcher:containerType="hotseat" />
复制代码
all_apps.xml
  1. <com.android.launcher3.allapps.LauncherAllAppsContainerView xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:id="@+id/apps_view"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent"
  5.     android:clipChildren="true"
  6.     android:clipToPadding="false"
  7.     android:focusable="false"
  8.     android:saveEnabled="false" />
复制代码
drop_target_bar.xml
  1. <com.android.launcher3.DropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
  2.     android:layout_width="match_parent"
  3.     android:layout_height="@dimen/dynamic_grid_drop_target_size"
  4.     android:layout_gravity="center_horizontal|top"
  5.     android:focusable="false"
  6.     android:alpha="0"
  7.     android:theme="@style/HomeScreenElementTheme"
  8.     android:visibility="invisible">
  9.     <!-- Delete target -->
  10.     <com.android.launcher3.DeleteDropTarget
  11.         android:id="@+id/delete_target_text"
  12.         style="@style/DropTargetButton"
  13.         android:layout_width="wrap_content"
  14.         android:layout_height="wrap_content"
  15.         android:layout_gravity="center"
  16.         android:gravity="center"
  17.         android:text="@string/remove_drop_target_label" />
  18.     <!-- Uninstall target -->
  19.     <com.android.launcher3.SecondaryDropTarget
  20.         android:id="@+id/uninstall_target_text"
  21.         style="@style/DropTargetButton"
  22.         android:layout_width="wrap_content"
  23.         android:layout_height="wrap_content"
  24.         android:layout_gravity="center"
  25.         android:gravity="center"
  26.         android:text="@string/uninstall_drop_target_label" />
  27. </com.android.launcher3.DropTargetBar>
复制代码
上面这三个控件都是直接include在Launcher.xml的结构中的,其他子结构也是以类似于自定义View的情势被添加到Launcher.xml中的,好比workspace:
  1. <com.android.launcher3.Workspace
  2.     android:id="@+id/workspace"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent"
  5.     android:layout_gravity="center"
  6.     android:theme="@style/HomeScreenElementTheme"
  7.     launcher:pageIndicator="@+id/page_indicator" />
复制代码
Workspace.java是在Launcher的onCreate()阶段被创建并添加到主页内里的:
  1. // Launcher3/src/com/android/launcher3/Launcher.java
  2. @Thunk
  3. Workspace<?> mWorkspace;
  4. @Thunk
  5. DragLayer mDragLayer;
  6. @Override
  7. @TargetApi(Build.VERSION_CODES.S)
  8. protected void onCreate(Bundle savedInstanceState) {
  9.     // ...
  10.     setupViews();
  11.     // ...
  12. }
  13. protected void setupViews() {
  14.     inflateRootView(R.layout.launcher);
  15.     mDragLayer = findViewById(R.id.drag_layer);
  16.     mFocusHandler = mDragLayer.getFocusIndicatorHelper();
  17.     // 绑定workspace
  18.     mWorkspace = mDragLayer.findViewById(R.id.workspace);
  19.    
  20.     // workspace做一些初始化的工作
  21.     mWorkspace.initParentViews(mDragLayer);
  22.     mOverviewPanel = findViewById(R.id.overview_panel);
  23.     mHotseat = findViewById(R.id.hotseat);
  24.     mHotseat.setWorkspace(mWorkspace);
  25.     mDragLayer.setup(mDragController, mWorkspace);
  26.     mWorkspace.setup(mDragController);
  27.     mWorkspace.lockWallpaperToDefaultPage();
  28.     mWorkspace.bindAndInitFirstWorkspaceScreen();
  29.     mDragController.addDragListener(mWorkspace);
  30.     // ...
  31. }
复制代码
​ 各子结构的添加和加载就不展开介绍了,这里先介绍一个Launcher3很重要的内容:DeviceProfile.java和device_profiles.xml
DeviceProfile

​ DeviceProfile.java包罗了多个用于描述设备配置的属性,比方mNumColumns(列数)、mNumRows(行数)、mHotseatHeight(Dock的高度)、mIconSizePx(图标大小)等,这些属性用于确定Launcher的结构和各个组件的位置。DeviceProfile在Launcher启动时被创建,并根据设备的现实配置(device_profiles.xml)进行初始化。


  • 初始化过程中会根据设备的屏幕尺寸、方向等因素盘算出各种关键属性。
  • 当设备的配置发生变化时(比方旋转屏幕),DeviceProfile会被更新以反映新的配置。
  • 更新过程中会重新盘算关键属性,并关照Launcher的各个组件进行相应的调解。
​ device_profiles.xml是一个XML配置文件,位于Launcher3项目的res/xml目次下。它定义了一系列设备配置,每种配置对应着不同的屏幕尺寸、方向和密度等信息,这一系列配置用于在不同设备上适配Launcher。这样可以确保Launcher在不同设备上都可以大概良好地显示和运行。
​ 当Launcher启动时,它会被DeviceProfile.java动态加载,并根据当前设备的现实配置找到最合适的预设配置,这些预设配置【mNumColumns(列数)、mNumRows(行数)、mIconSizePx(图标大小)等】用于初始化DeviceProfile对象。【各属性的介绍放到下一末节中了】
下面简单介绍下DeviceProfile.java和device_profiles.xml的加载流程:
  1. // Launcher3/src/com/android/launcher3/Launcher.java
  2. @Override
  3. @TargetApi(Build.VERSION_CODES.S)
  4. protected void onCreate(Bundle savedInstanceState) {
  5.     // ...
  6.         LauncherAppState app = LauncherAppState.getInstance(this);
  7.     // 通过LauncherAppState的getInvariantDeviceProfile方法获取InvariantDeviceProfile对象
  8.     InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
  9.     // 在initDeviceProfile方法里获取DeviceProfile对象
  10.     initDeviceProfile(idp);
  11.     // ...
  12. }
  13. /**
  14. * Returns {@code true} if a new DeviceProfile is initialized, and {@code false} otherwise.
  15. */
  16. protected boolean initDeviceProfile(InvariantDeviceProfile idp) {
  17.     // 获取DeviceProfile,这里DeviceProfile对象来源是InvariantDeviceProfile.getDeviceProfile()
  18.     DeviceProfile deviceProfile = idp.getDeviceProfile(this);
  19.     if (mDeviceProfile == deviceProfile) {
  20.         return false;
  21.     }
  22.     mDeviceProfile = deviceProfile;
  23.     // ...
  24. }
复制代码
​ 在Launcher.java的onCreate()方法中,先调用LauncherAppState.getInvariantDeviceProfile()方法获取一个InvariantDeviceProfile对象,然后在initDeviceProfile()方法中,通过InvariantDeviceProfile对象的getDeviceProfile()方法获取到DeviceProfile对象。跟进到LauncherAppState中看下:
  1. // Launcher3/src/com/android/launcher3/LauncherAppState.java
  2. private final InvariantDeviceProfile mInvariantDeviceProfile;
  3. public LauncherAppState(Context context) {
  4.     // 调用两个参数的构造方法
  5.     this(context, LauncherFiles.APP_ICONS_DB);
  6.     Log.v(Launcher.TAG, "LauncherAppState initiated");
  7.     // ...
  8. }
  9. public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
  10.     mContext = context;
  11.     // 获取mInvariantDeviceProfile对象
  12.     mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
  13.     // ...
  14. }
  15. public InvariantDeviceProfile getInvariantDeviceProfile() {
  16.      // 返回InvariantDeviceProfile对象
  17.     return mInvariantDeviceProfile;
  18. }
复制代码
​ 由上面代码可以看出,InvariantDeviceProfile对象是在LauncherAppState的构造方法中创建的,并通过getInvariantDeviceProfile()方法返回一个InvariantDeviceProfile对象。继续跟进到InvariantDeviceProfile.java中:
  1. // Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
  2. // 这里的 INSTANCE可以理解为获取自身的单例对象
  3. // MainThreadInitializedObject是一个用于定义在主线程上启动的单例的实用工具类
  4. public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
  5.         new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
  6. @TargetApi(23)
  7. private InvariantDeviceProfile(Context context) {
  8.     String gridName = getCurrentGridName(context);
  9.      // 调用initGrid()方法
  10.     String newGridName = initGrid(context, gridName);
  11.     // ...
  12. }
  13. private String initGrid(Context context, String gridName) {
  14.     Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
  15.     @DeviceType int deviceType = getDeviceType(displayInfo);
  16.     // 调用getPredefinedDeviceProfiles方法加载device_profiles.xml文件
  17.     ArrayList<DisplayOption> allOptions =
  18.             getPredefinedDeviceProfiles(context, gridName, deviceType,
  19.                     RestoreDbTask.isPending(context));
  20.     // 把device_profiles.xml文件中的配置信息记录到displayOption中
  21.     DisplayOption displayOption =
  22.             invDistWeightedInterpolate(displayInfo, allOptions, deviceType);
  23.     // 调用四个参数的initGrid()方法
  24.     initGrid(context, displayInfo, displayOption, deviceType);
  25.     return displayOption.grid.name;
  26. }
  27. private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context,
  28.         String gridName, @DeviceType int deviceType, boolean allowDisabledGrid) {
  29.     ArrayList<DisplayOption> profiles = new ArrayList<>();
  30.     // 加载device_profiles.xml文件
  31.     try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
  32.         final int depth = parser.getDepth();
  33.         // ...
  34.     } catch (IOException | XmlPullParserException e) {
  35.         throw new RuntimeException(e);
  36.     }
  37.     // ...
  38. }
  39. private void initGrid(Context context, Info displayInfo, DisplayOption displayOption, @DeviceType int deviceType) {
  40.     DisplayMetrics metrics = context.getResources().getDisplayMetrics();
  41.     // 获取displayOption里面配置信息(实际上就是device_profiles.xml中读取的信息)
  42.     GridOption closestProfile = displayOption.grid;
  43.     numRows = closestProfile.numRows;
  44.     numColumns = closestProfile.numColumns;
  45.     numSearchContainerColumns = closestProfile.numSearchContainerColumns;
  46.     // ...
  47.     final List<DeviceProfile> localSupportedProfiles = new ArrayList<>();
  48.     defaultWallpaperSize = new Point(displayInfo.currentSize);
  49.     SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
  50.     for (WindowBounds bounds : displayInfo.supportedBounds) {
  51.         // 通过DeviceProfile.Build()方法创建DeviceProfile对象
  52.         localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
  53.                 .setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY)
  54.                 .setWindowBounds(bounds)
  55.                 .setDotRendererCache(dotRendererCache)
  56.                 .build());
  57.         // ...
  58.     }
  59.    
  60.     // 把DeviceProfile列表存到supportedProfiles中,supportedProfiles是一个类型为List<DeviceProfile>的列表
  61.     // 这里创建了DeviceProfile列表,是因为DeviceProfile中有多个不同的配置,一套配置可以视为一个DeviceProfile对象
  62.     supportedProfiles = Collections.unmodifiableList(localSupportedProfiles);
  63.    
  64.     // ...
  65. }
复制代码
​ 在InvariantDeviceProfile.java的构造方法中,调用了initGrid(context, gridName)方法,然后在initGrid()方法中调用getPredefinedDeviceProfiles()方法去加载device_profiles.xml配置文件,随后又调用了四个参数的initGrid()方法创建DeviceProfile对象列表,并把它赋值给supportedProfiles,supportedProfiles是一个类型为List<DeviceProfile>的列表。
​ 在Launcher.java中是是通过idp.getDeviceProfile(this)来获取DeviceProfile对象的,我们看下InvariantDeviceProfile的getDeviceProfile()方法:
  1. // Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
  2. public DeviceProfile getDeviceProfile(Context context) {
  3.     Resources res = context.getResources();
  4.     Configuration config = context.getResources().getConfiguration();
  5.     float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
  6.     float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
  7.     int rotation = WindowManagerProxy.INSTANCE.get(context).getRotation(context);
  8.     // ...
  9.     // 根据屏幕的宽高等信息,获取最适合的DeviceProfile配置文件
  10.     return getBestMatch(screenWidth, screenHeight, rotation);
  11. }
  12. /**
  13. * 返回与所提供的屏幕配置相匹配的设备配置文件
  14. */
  15. public DeviceProfile getBestMatch(float screenWidth, float screenHeight, int rotation) {
  16.     DeviceProfile bestMatch = supportedProfiles.get(0);
  17.     float minDiff = Float.MAX_VALUE;
  18.     // 遍历supportedProfiles列表,找出最匹配的配置文件。这里的supportedProfiles就是前面保存的DeviceProfile列表
  19.     for (DeviceProfile profile : supportedProfiles) {
  20.         float diff = Math.abs(profile.widthPx - screenWidth) + Math.abs(profile.heightPx - screenHeight);
  21.         if (diff < minDiff) {
  22.             minDiff = diff;
  23.             bestMatch = profile;
  24.         } else if (diff == minDiff && profile.rotationHint == rotation) {
  25.             bestMatch = profile;
  26.         }
  27.     }
  28.     return bestMatch;
  29. }
复制代码
​ 所以执行到这里,Launcher.java中就可以拿到与当前屏幕最匹配的DeviceProfile配置了,通过这个DeviceProfile配置,可以去调解桌面的结构和组件,如下:
  1. // Launcher3/src/com/android/launcher3/Launcher.java
  2. public void finishBindingItems(IntSet pagesBoundFirst) {
  3.     Object traceToken = TraceHelper.INSTANCE.beginSection("finishBindingItems");
  4.     mWorkspace.restoreInstanceStateForRemainingPages();
  5.         // ...
  6.     // 获取mDeviceProfile配置信息里面的numFolderColumns和numFolderRows去做计算
  7.     getViewCache().setCacheSize(R.layout.folder_application,
  8.             mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows);
  9.     getViewCache().setCacheSize(R.layout.folder_page, 2);
  10.     TraceHelper.INSTANCE.endSection(traceToken);
  11.     mWorkspace.removeExtraEmptyScreen(true);
  12. }
复制代码
实在不但是Launcher.java,在其他的类内里也会调用去做一些UI上的处置惩罚,好比Workspace.java中更新格子的Padding:
  1. // Launcher3/src/com/android/launcher3/Workspace.java
  2. private void updateCellLayoutPadding() {
  3.     // 获取配置信息里面的cellLayoutPaddingPx
  4.     Rect padding = mLauncher.getDeviceProfile().cellLayoutPaddingPx;
  5.     mWorkspaceScreens.forEach(s -> s.setPadding(padding.left, padding.top, padding.right, padding.bottom));
  6. }
复制代码
​ 到这里DeviceProfile的加载分析流程就结束了,如上面示例所说,在Launcher3中,DeviceProfile.java和device_profiles.xml是两个非常重要的文件,它们对Launcher3主页结构是至关重要的,想要修改Launcher3主页的结构,可以从这个方面入手。
主页结构修改

​ 上面的内容简单介绍了DeviceProfile的功能和加载流程,下面来分析,如何修改主页结构,还是沿着上面的思绪,从DeviceProfile配置文件入手。
​ Launcher3启动的时候,会加载 Launcher3\res\xml\device_profiles.xml 文件中预设的结构,内容如下:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
  3.     <grid-option
  4.         launcher:name="3_by_3"
  5.         launcher:numRows="3"
  6.         launcher:numColumns="3"
  7.         launcher:numFolderRows="2"
  8.         launcher:numFolderColumns="3"
  9.         launcher:numHotseatIcons="3"
  10.         launcher:dbFile="launcher_3_by_3.db"
  11.         launcher:defaultLayoutId="@xml/default_workspace_3x3"
  12.         launcher:deviceCategory="phone" >
  13.         
  14.         <!--省略多个display-option子标签内容-->
  15.         
  16.     </grid-option>
  17.     <grid-option
  18.         launcher:name="4_by_4"
  19.         launcher:numRows="4"
  20.         launcher:numColumns="4"
  21.         launcher:numFolderRows="3"
  22.         launcher:numFolderColumns="4"
  23.         launcher:numHotseatIcons="4"
  24.         launcher:numExtendedHotseatIcons="6"
  25.         launcher:dbFile="launcher_4_by_4.db"
  26.         launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split"
  27.         launcher:defaultLayoutId="@xml/default_workspace_4x4"
  28.         launcher:deviceCategory="phone|multi_display" >
  29.         
  30.         <!--省略多个display-option子标签内容-->
  31.             
  32.     </grid-option>
  33.     <grid-option
  34.         launcher:name="5_by_5"
  35.         launcher:numRows="5"
  36.         launcher:numColumns="5"
  37.         launcher:numFolderRows="4"
  38.         launcher:numFolderColumns="4"
  39.         launcher:numHotseatIcons="5"
  40.         launcher:numExtendedHotseatIcons="6"
  41.         launcher:dbFile="launcher.db"
  42.         launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split"
  43.         launcher:defaultLayoutId="@xml/default_workspace_5x5"
  44.         launcher:deviceCategory="phone|multi_display" >
  45.         
  46.         <!--省略多个display-option子标签内容-->
  47.         
  48.     </grid-option>
  49.     <grid-option
  50.         launcher:name="6_by_5"
  51.         launcher:numRows="6"
  52.         launcher:numColumns="7"
  53.         launcher:numSearchContainerColumns="5"
  54.         launcher:numFolderRows="3"
  55.         launcher:numFolderColumns="4"
  56.         launcher:numHotseatIcons="0"
  57.         launcher:hotseatColumnSpanLandscape="2"
  58.         launcher:numAllAppsColumns="6"
  59.         launcher:isScalable="true"
  60.         launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_6_5"
  61.         launcher:devicePaddingId="@xml/paddings_6x5"
  62.         launcher:dbFile="launcher_6_by_5.db"
  63.         launcher:defaultLayoutId="@xml/default_workspace_6x5"
  64.         launcher:deviceCategory="tablet" >
  65.         <display-option
  66.             launcher:name="Tablet"
  67.             launcher:minWidthDps="900"
  68.             launcher:minHeightDps="820"
  69.             launcher:minCellHeight="120"
  70.             launcher:minCellWidth="102"
  71.             launcher:minCellHeightLandscape="104"
  72.             launcher:minCellWidthLandscape="120"
  73.             launcher:iconImageSize="60"
  74.             launcher:iconTextSize="14"
  75.             launcher:borderSpaceHorizontal="16"
  76.             launcher:borderSpaceVertical="64"
  77.             launcher:borderSpaceLandscapeHorizontal="64"
  78.             launcher:borderSpaceLandscapeVertical="16"
  79.             launcher:horizontalMargin="54"
  80.             launcher:horizontalMarginLandscape="120"
  81.             launcher:allAppsCellWidth="96"
  82.             launcher:allAppsCellHeight="142"
  83.             launcher:allAppsCellWidthLandscape="126"
  84.             launcher:allAppsCellHeightLandscape="126"
  85.             launcher:allAppsIconSize="60"
  86.             launcher:allAppsIconTextSize="14"
  87.             launcher:allAppsBorderSpaceHorizontal="8"
  88.             launcher:allAppsBorderSpaceVertical="16"
  89.             launcher:allAppsBorderSpaceLandscape="16"
  90.             launcher:hotseatBarBottomSpace="30"
  91.             launcher:hotseatBarBottomSpaceLandscape="40"
  92.             launcher:canBeDefault="true" />
  93.     </grid-option>
  94. </profiles>
复制代码
​ 这个文件配置代码是用于定义Google Launcher在不同设备和显示模式下的结构参数,特别是针对具有特定屏幕尺寸和平板电脑类设备的结构。该文件里定义了4个不同的结构类型,分别是3x3、4x4、5x5、6x5,上面有讲到,在调用InvariantDeviceProfile的getDeviceProfile()方法时,设备会根据当前屏幕的尺寸来加载相应的结构配置。
​ 那我们在开发时如何确定设备加载哪个结构呢?可以在桌面长按应用图标,然后拖动一下,观察在x和y方向上可以移动多少个格子,这样就可以确定本身的设备加载的是哪一个结构类型了。一样平常平板(或者移动大屏等)设备会加载6*5的结构,如今较新的手机也可以支持5x5乃至6x5的结构了。
​ device_profiles.xml文件中的<grid-option>标签包罗了各种属性,用于定制Launcher界面的外貌和活动。结构内里具体的属性说明如下:


  • launcher:name="6_by_5": 定义了结构的名称,这里是“6_by_5”,代表的是6行5列的基本结构结构,但现实上后续可以通过launcher:numRows、launcher:numColumns属性设置为其他的结构。因为项目需要,我这里对6 * 5的结构做了改动,改成了 7 * 6的结构。
  • launcher:numRows="6" 和 launcher:numColumns="7": 指定了主屏幕上行数和列数。
  • launcher:numSearchContainerColumns="5": 搜索栏容器的列数。
  • launcher:numFolderRows="3" 和 launcher:numFolderColumns="4": 文件夹中的行数和列数。
  • launcher:numHotseatIcons="0": Hotseat(快速启动栏)中的图标数目,在这里设置为0,意味着不显示Hotseat。
  • launcher:hotseatColumnSpanLandscape="2": 在横屏模式下,Hotseat占据的列数。
  • launcher:numAllAppsColumns="6": 所有应用列表中的列数。
  • launcher:isScalable="true": 表示是否可以缩放结构。
  • launcher:inlineNavButtonsEndSpacing 和 launcher:devicePaddingId: 分别定义了内联导航按钮的结束间距和设备的填充空间ID。
  • launcher:dbFile 和 launcher:defaultLayoutId: 数据库文件名和默认结构ID,用于存储和恢复结构状态。
  • launcher:deviceCategory="tablet": 设备类别,这里是平板电脑。
接下来的 <display-option> 标签提供了更多细节:


  • launcher:minWidthDps="900" 和 launcher:minHeightDps="820": 最小宽度和高度(以密度无关像素dp为单元),用于确定此结构适用于哪种屏幕尺寸。
  • launcher:minCellHeight 和 launcher:minCellWidth: 单个单元格的最小高度和宽度。
  • 背面的属性,如 launcher:iconImageSize、launcher:iconTextSize 等,分别定义了图标、文本、边框空间等元素的尺寸和间距。
  • launcher:horizontalMargin 和 launcher:horizontalMarginLandscape: 程度方向上的外边距,在竖屏和横屏模式下可能不同。
  • launcher:hotseatBarBottomSpace 和 launcher:hotseatBarBottomSpaceLandscape: Hotseat在竖屏和横屏模式下的底部间距。
  • launcher:canBeDefault="true": 表示此结构选项可以作为默认结构。
​ 这里我们重点关注下defaultLayoutId这个属性:在上面的配置文件中,6x5结构的defaultLayoutId=“@xml/default_workspace_6x5”,即指定了default_workspace_6x5.xml文件作为6x5样式的结构资源文件。
​ default_workspace_MxN.xml这是用于定义用户桌面快捷方式(Favorites)配置的一个资源文件,代表主页结构是M行N列,我们可以在内里定义显示在利用这个结构情况下,worksapce中的各个控件,如下面给出了显示一个Hotseat应用的示例:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
  3.     <!-- 配置一个Hotseat应用-->
  4.     <resolve
  5.         launcher:container="-101"
  6.         launcher:screen="0"
  7.         launcher:x="0"
  8.         launcher:y="0" >
  9.         <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
  10.         <favorite launcher:uri="mailto:" />
  11.     </resolve>
  12.     <!-- 其他配置 -->
  13. </favorites>
复制代码
​ 文件由<favorites>标签括起来,内里可以包罗一个或多个<resolve>标签,每个<resolve>标签都定义了一组快捷方式项,这些快捷方式可以直接链接到特定的应用或者执行特定的操纵(如打开邮件客户端、日历、图库等)。<resolve>的子标签表示快捷方式项的类型,快捷方式项必须放在<resolve>标签内才生效。<resolve>支持的子标签如下:
  1. favorite //应用程序快捷方式
  2. widget   //桌面控件(小组件)
  3. shortcut //链接,如网址、本地磁盘路径等
  4. search   //搜索框(谷歌搜索框)
  5. clock    //桌面上的钟表Widget
  6. folder   //桌面文件夹(如谷歌应用的文件夹)
复制代码
​ 同时<resolve>标签或其子标签通过launcher:XXX来设定快捷方式项的位置等信息,这些属性可以写在<resolve>标签或其子标签中,支持的属性如下:
  1. // resolve标签支持的属性
  2. launcher:title  // 图标下面的文字,目前只支持引用,不能直接书写字符串;
  3. launcher:icon   // 图标引用(适用于应用快捷方式);
  4. launcher:uri    // 链接地址,链接网址用的,使用shortcut标签就可以定义一个超链接,打开某个网址,文件等。
  5. launcher:packageName   // 应用程序的包名;
  6. launcher:className     // 应用程序的启动类名(要写全路径);
  7. launcher:screen        // 图标所在的屏幕编号,0表示第一页
  8. launcher:x   // 应用图标所处x位置(从左到右,从0开始),(-1是默认值:第一行或者第一列)
  9. launcher:y   // 应用图标所处y位置(从上往下,从0开始)
  10. launcher:container   // 定义一个快捷方式(Favorite)或桌面项目应该放置在哪个容器中;-101表示HotSeat、-100表示DeskTop、0表示默认的桌面容器、其他正整数表示App shortcut
  11. launcher:spanX  //在x方向上所占格数
  12. launcher:spanY  //在y方向上所占格数
复制代码
这里特别说明下launcher:uri属性:uri 定义了控件点击时的链接操纵,通常以#Intent;...;end的格式编写,指定了一系列操纵(如打开某个应用的主界面)或数据类型(如打开图库应用或欣赏特定网站)。
  1. // 下面列举几个常用launcher:uri的写法:
  2. 跳转到网页: "http://www.google.com"
  3. 跳转到设置的辅助功能:"#Intent;action=android.settings.ACCESSIBILITY_SETTINGS;end"
  4. 打开音乐文件:"file:///mnt/sdcard/song.mp3#Intent;action=android.intent.action.VIEW;type=audio/mp3;end"
  5. 指定应用程序打开音乐文件:"file:///mnt/sdcard/song.mp3#Intent;action=android.intent.action.VIEW;type=audio/mp3;component=com.android.music/.MusicBrowserActivity;end"
  6. // 指定应用程序打开音乐文件,在Java中对应的操作如下
  7. Intent it = new Intent(Intent.ACTION_VIEW);
  8. Uri uri = Uri.fromFile(new File("/mnt/sdcard/song.mp3" ));
  9. it.setDataAndType(uri, “audio/mp3”);
  10. it.setClassName(“com.android.music”, “com.android.music.MusicBrowserActivity”);
  11. String lancher_uri = it.toUri(0);
复制代码
常用几种桌面控件的示例:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
  3.    
  4.     <!--定义一个HotSeat-->
  5.     <resolve
  6.         launcher:container="-101"
  7.         launcher:screen="0"
  8.         launcher:x="0"
  9.         launcher:y="5" >
  10.         <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
  11.         <favorite launcher:uri="mailto:" />
  12.     </resolve>
  13.    
  14.     <!--定义一个时钟小组件-->
  15.     <resolve>
  16.         <appwidget
  17.             launcher:screen="0"
  18.             launcher:x="2"
  19.             launcher:y="0"
  20.             launcher:spanX="3"
  21.             launcher:spanY="1"
  22.             launcher:packageName="com.google.android.deskclock"
  23.             launcher:className="com.android.alarmclock.DigitalAppWidgetProvider"/>
  24.     </resolve>
  25.    
  26.     <!--定义一个谷歌搜索框-->
  27.     <resolve>
  28.         <search
  29.             launcher:screen="0"
  30.             launcher:x="1"
  31.             launcher:y="2"
  32.             launcher:spanX="5"
  33.             launcher:spanY="1"
  34.             launcher:packageName="com.google.android.googlequicksearchbox"
  35.             launcher:className="com.google.android.googlequicksearchbox.SearchWidgetProvider"/>
  36.     </resolve>
  37.    
  38.     <!--定义一个文件夹,里面包含三个应用-->
  39.         <resolve>
  40.                 <folder
  41.             launcher:title="@string/google_folder_title"
  42.             launcher:screen="0"
  43.             launcher:x="1"
  44.             launcher:y="3">
  45.             <favorite
  46.                 launcher:packageName="com.google.android.googlequicksearchbox"
  47.                 launcher:className="com.google.android.googlequicksearchbox.SearchActivity"/>
  48.             <favorite
  49.                 launcher:packageName="com.android.chrome"
  50.                 launcher:className="com.google.android.apps.chrome.Main"/>
  51.             <favorite
  52.                 launcher:packageName="com.google.android.gm"
  53.                 launcher:className="com.google.android.gm.ConversationListActivityGmail"/>
  54.                   </folder>
  55.     </resolve>
  56.    
  57.     <!--定义一个普通应用程序快捷方式-->
  58.     <resolve>
  59.         <favorite
  60.             launcher:screen="0"
  61.             launcher:x="2"
  62.             launcher:y="3"
  63.             launcher:packageName="com.android.vending"
  64.             launcher:className="com.android.vending.AssetBrowserActivity"/>
  65.     </resolve>
  66.    
  67.     <!--定义一个shortcut链接-->
  68.     <resolve>
  69.         <shortcut
  70.             launcher:title="@string/google"
  71.             launcher:icon="@drawable/google"
  72.             launcher:uri="http://www.baidu.com"
  73.             launcher:screen="0"
  74.             launcher:x="3"
  75.             launcher:y="3" />
  76.     </resolve>
  77. </favorites>
复制代码
注意:这些属性并不都是resolve标签支持的,大多数的属性可以写在resolve标签中,也可以写在子标签中,但是对于uri、packageName、className等几个特别的属性,只能写在对应的子标签内。
除了上面的方式,还可以利用GMS中的配置文件来处置惩罚(条件是项目是有嵌入GMS框架)。在google_gms包下的配置文件release\vendor\partner_gms\apps\GmsSampleIntegration\res_dhs_full\xml\partner_default_layout.xml(下面是我项目中改的示例):
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!-- Copyright (C) 2017 Google Inc. All Rights Reserved. -->
  3. <favorites>
  4.     <!--定义应用文件夹-->
  5.     <folder title="@string/google_folder_title" screen="0" x="1" y="3">
  6.         <favorite packageName="com.google.android.googlequicksearchbox" className="com.google.android.googlequicksearchbox.SearchActivity"/>
  7.         <favorite packageName="com.android.chrome" className="com.google.android.apps.chrome.Main"/>
  8.         <favorite packageName="com.google.android.gm" className="com.google.android.gm.ConversationListActivityGmail"/>
  9.         <favorite packageName="com.google.android.apps.maps" className="com.google.android.maps.MapsActivity"/>
  10.         <favorite packageName="com.google.android.youtube" className="com.google.android.youtube.app.honeycomb.Shell$HomeActivity"/>
  11.         <favorite packageName="com.google.android.apps.docs" className="com.google.android.apps.docs.app.NewMainProxyActivity"/>
  12.         <favorite packageName="com.google.android.apps.youtube.music" className="com.google.android.apps.youtube.music.activities.MusicActivity"/>
  13.         <favorite packageName="com.google.android.videos" className="com.google.android.videos.GoogleTvEntryPoint"/>
  14.         <favorite packageName="com.google.android.apps.tachyon" className="com.google.android.apps.tachyon.MainActivity"/>
  15.         <favorite packageName="com.google.android.apps.photos" className="com.google.android.apps.photos.home.HomeActivity"/>
  16.     </folder>
  17.   
  18.     <!--添加应用-->
  19.     <favorite screen="0" x="2" y="3" packageName="com.android.vending" className="com.android.vending.AssetBrowserActivity"/>
  20.         <!--添加组件-->
  21.     <appwidget screen="0" x="2" y="0" packageName="com.google.android.deskclock" className="com.android.alarmclock.DigitalAppWidgetProvider" spanX="3" spanY="2" />
  22.    
  23.         <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
  24.     <!-- 定义桌面的hotSeat显示的应用,本项目中已经设定不显示HotSeat,所以下面的代码设置也不起作用 -->
  25.     <favorite container="-101" screen="0" x="0" y="0" packageName="com.google.android.dialer" className="com.google.android.dialer.extensions.GoogleDialtactsActivity"/>
  26.     <favorite container="-101" screen="1" x="1" y="0" packageName="com.google.android.apps.messaging" className="com.google.android.apps.messaging.ui.ConversationListActivity"/>
  27.     <favorite container="-101" screen="0" x="0" y="0" packageName="com.android.settings" className="com.android.settings.Settings"/>
  28.     <favorite container="-101" screen="1" x="1" y="0" packageName="com.android.deskclock" className="com.android.deskclock.DeskClock"/>
  29.     <favorite container="-101" screen="2" x="2" y="0" packageName="com.google.android.calendar" className="com.android.calendar.event.LaunchInfoActivity"/>
  30.     <favorite container="-101" screen="3" x="3" y="0" packageName="com.google.android.contacts" className="com.android.contacts.activities.PeopleActivity"/>
  31.     <favorite container="-101" screen="4" x="4" y="0" packageName="com.android.camera2" className="com.android.camera.CameraLauncher"/>
  32. </favorites>
复制代码
上面文件有几个属性参数解析一下:


  • folder标签表示文件夹;
  • favorite标签表示应用;
  • appwidget表示组件;
  • container表示放置的位置或区域,-101表示hotseat;-100代表是desktop;container=正数,则代表App shortcut;
  • screen表示位于第几页屏幕,0表示第一页;
  • x表示x轴方向的位置(从左到右,从0开始);
  • y表示y轴方向的位置(从上往下,从0开始);
  • packageName表示应用或组件的包名;
  • className表示应用或组件对应的Activity或AppWidgetProvider;
  • spanX表示x轴方向占用的格子数;
  • spanY表示y轴方向占用的格子数
在partner_default_layout.xml中改动效果如下:

注意:如果你修改了结构文件,但是重新编译后运行模仿器,发现桌面应用并没有改变,有可能是launcher3的database没有更新,因为这些结构文件的内容,在launcher第一次开机启动时会创建database并生存数据到其中,后续有利用直接从db读取,而不消重复读结构文件
  1. # 恢复默认布局
  2. adb root
  3. adb shell rm /data/data/com.android.launcher3/databases/launcher.db
  4. # 实测我这里使用的是6x5布局,所以db文件是:launcher_6_by_5.db
  5. # 重启模拟器即可生效
复制代码
修改谷歌搜索框

​ 如果要修改搜索框占用格子的宽度,可以在Launcher3\res\xml\device_profiles.xml文件内里,对应的尺寸结构中修改numSearchContainerColumns的值:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
  3.     <grid-option
  4.         launcher:name="6_by_5"
  5.         launcher:numRows="6"
  6.         launcher:numColumns="7"
  7.         launcher:numSearchContainerColumns="5"
  8.         .../>
  9. />
复制代码
我在6x5的结构(现实上是后7x6)中将谷歌搜索框的宽度改成了5,即在x轴方向上,占用五个格子。
接下来是修改谷歌搜索框的位置:在workspace.javabindAndInitFirstWorkspaceScreen()方法中:
  1. // Launcher3/src/com/android/launcher3/Workspace.java
  2. /**
  3. * Initializes and binds the first page
  4. */
  5. public void bindAndInitFirstWorkspaceScreen() {
  6.     if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
  7.         return;
  8.     }
  9.     // Add the first page
  10.     CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
  11.     // Always add a first page pinned widget on the first screen.
  12.     if (mFirstPagePinnedItem == null) {
  13.         mFirstPagePinnedItem = LayoutInflater.from(getContext())
  14.                 .inflate(R.layout.search_container_workspace, firstPage, false);
  15.     }
  16.     // 先获取device_profiles.xml中定义的numSearchContainerColumns值
  17.     int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
  18.     // 在这里修改搜索框的位置,五个参数分别代表:x坐标、y坐标,占用宽度、占用高度、屏幕id
  19.     // 所以这里效果是:在第一个CellLayout的第二列第三行(xy都是从0开始算的)添加谷歌搜索框,宽度为5个格子,高度为1个格子
  20.     CellLayoutLayoutParams lp = new CellLayoutLayoutParams(1, 2, cellHSpan, 1, FIRST_SCREEN_ID);
  21.    
  22.     lp.canReorder = false;
  23.     // 将谷歌搜索框添加到布局中,最后一个参数是代表是否可移动的意思
  24.     if (!firstPage.addViewToCellLayout(
  25.             mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
  26.         Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
  27.         mFirstPagePinnedItem = null;
  28.     }
  29. }
复制代码
另外:还要在LoaderCursor.java的checkItemPlacement()方法中修改搜索框对位置的占用效果:
  1. // Launcher3/src/com/android/launcher3/model/LoaderCursor.java
  2. /**
  3. * check & update map of what's occupied; used to discard overlapping/invalid items
  4. */
  5. protected boolean checkItemPlacement(ItemInfo item) {
  6.     int containerIndex = item.screenId;
  7.         // ...
  8.     if (!occupied.containsKey(item.screenId)) {
  9.         GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
  10.         if (item.screenId == Workspace.FIRST_SCREEN_ID && FeatureFlags.QSB_ON_FIRST_SCREEN) {
  11.             int spanX = mIDP.numSearchContainerColumns;
  12.             int spanY = 1;
  13.             // 这里需要跟WorkSpace.java中设置的搜索框位置保持一致
  14.             screen.markCells(1, 2, spanX, spanY, true);
  15.         }
  16.         occupied.put(item.screenId, screen);
  17.     }
  18.     final GridOccupancy occupancy = occupied.get(item.screenId);
  19.     // Check if any workspace icons overlap with each other
  20.     if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
  21.         occupancy.markCells(item, true);
  22.         return true;
  23.     } else {
  24.         Log.e(TAG, "Error loading shortcut " + item
  25.                 + " into cell (" + containerIndex + "-" + item.screenId + ":"
  26.                 + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
  27.                 + ") already occupied");
  28.         return false;
  29.     }
  30. }
复制代码
​ 如果只修改WorkSpace、而没有修改LoaderCursor中的代码,那么搜索框原本的结构(默认位置是x=0,y=0)占用效果会一直存在,导致背面一些应用或者组件无法放置在首页的首行位置。
注意:Workspace.java的bindAndInitFirstWorkspaceScreen方法不但是设定谷歌搜索框位置的关键代码,也是控制了谷歌搜索框是否显示,如果不需要显示谷歌搜索框,可以将上面代码注释:
  1. // Launcher3/src/com/android/launcher3/Workspace.java
  2. /**
  3. * Initializes and binds the first page
  4. */
  5. public void bindAndInitFirstWorkspaceScreen() {
  6.     if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
  7.         return;
  8.     }
  9.     // 注意要保留这一行,否则会报错
  10.     CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
  11.    
  12. //        if (mFirstPagePinnedItem == null) {
  13. //            mFirstPagePinnedItem = LayoutInflater.from(getContext())
  14. //                    .inflate(R.layout.search_container_workspace, firstPage, false);
  15. //        }
  16. //
  17. //        int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
  18. //        CellLayoutLayoutParams lp = new CellLayoutLayoutParams(1, 2, cellHSpan, 1, FIRST_SCREEN_ID);
  19. //        lp.canReorder = false;
  20. //        if (!firstPage.addViewToCellLayout(
  21. //                mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
  22. //            Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
  23. //            mFirstPagePinnedItem = null;
  24. //        }
  25. }
复制代码
除了通过Java文件配置谷歌搜索框之外,也可以通过XML文件来配置谷歌搜索框,主要是在default_workspace.xmlpartner_default_layout.xml中进行配置。如果项目有配置GMS框架,那么会读取partner_default_layout.xml文件中的配置,否则会读取default_workspace.xml的配置。
先在device_profiles.xml文件内里定义桌面的行列数:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
  3.     <grid-option
  4.         launcher:name="6_by_5"
  5.         launcher:numRows="6"
  6.         launcher:numColumns="7"
  7.         launcher:numSearchContainerColumns="5"
  8.         .../>
  9. />
复制代码
​ 这里定义的app分布结构是六行七列。然后根据系统是否会读取partner_default_layout.xml结构文件来决定修改的位置:
注意:利用xml配置时,需要先将Java代码中配置谷歌搜索框的代码(Workspace.java和LoaderCursor.java)注释掉,避免相互影响。
  1. // Launcher3/src/com/android/launcher3/Workspace.java
  2. public void bindAndInitFirstWorkspaceScreen() {
  3.     if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
  4.         return;
  5.     }
  6.     // 注意要保留这一行,否则会报错
  7.     CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
  8.    
  9. //        if (mFirstPagePinnedItem == null) {
  10. //            mFirstPagePinnedItem = LayoutInflater.from(getContext())
  11. //                    .inflate(R.layout.search_container_workspace, firstPage, false);
  12. //        }
  13. //
  14. //        int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
  15. //        CellLayoutLayoutParams lp = new CellLayoutLayoutParams(1, 2, cellHSpan, 1, FIRST_SCREEN_ID);
  16. //        lp.canReorder = false;
  17. //        if (!firstPage.addViewToCellLayout(
  18. //                mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
  19. //            Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
  20. //            mFirstPagePinnedItem = null;
  21. //        }
  22. }
复制代码
  1. // Launcher3/src/com/android/launcher3/model/LoaderCursor.java
  2. protected boolean checkItemPlacement(ItemInfo item) {
  3.     int containerIndex = item.screenId;
  4.         // ...
  5.     if (!occupied.containsKey(item.screenId)) {
  6.         GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
  7.         // 注释下面if中的内容
  8. //            if (item.screenId == Workspace.FIRST_SCREEN_ID && FeatureFlags.QSB_ON_FIRST_SCREEN) {
  9. //                // Mark the first X columns (X is width of the search container) in the first row as
  10. //                // occupied (if the feature is enabled) in order to account for the search
  11. //                // container.
  12. //                int spanX = mIDP.numSearchContainerColumns;
  13. //                int spanY = 1;
  14. //                screen.markCells(1, 2, spanX, spanY, true);
  15. //            }
  16.         occupied.put(item.screenId, screen);
  17.     }
  18.     final GridOccupancy occupancy = occupied.get(item.screenId);
  19.     // Check if any workspace icons overlap with each other
  20.     if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
  21.         occupancy.markCells(item, true);
  22.         return true;
  23.     } else {
  24.         Log.e(TAG, "Error loading shortcut " + item
  25.                 + " into cell (" + containerIndex + "-" + item.screenId + ":"
  26.                 + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
  27.                 + ") already occupied");
  28.         return false;
  29.     }
  30. }
复制代码
(1)如果适配了GMS框架并读取partner_default_layout.xml结构文件,则在google_gms包下的release\vendor\partner_gms\apps\GmsSampleIntegration\res_dhs_full\xml\partner_default_layout.xml内里定义应用的包名和类型以及位置信息,比方
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!-- Copyright (C) 2017 Google Inc. All Rights Reserved. -->
  3. <favorites>
  4.     <!--其他配置-->
  5.     <appwidget screen="0" x="1" y="2" packageName="com.google.android.googlequicksearchbox" className="com.android.alarmclock.DigitalAppWidgetProvider" spanX="5" spanY="1" />
  6. </favorites>
复制代码
(2)如果不读取partner_default_layout.xml结构文件,则在对应的default_workspace_MxN.xml内里修改应用的位置(MN分别代表行列数):
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
  3.     <!--其他配置-->
  4.     <resolve>
  5.         <search
  6.             launcher:screen="0"
  7.             launcher:x="1"
  8.             launcher:y="2"
  9.             launcher:spanX="5"
  10.             launcher:spanY="1"
  11.             launcher:packageName="com.google.android.googlequicksearchbox"
  12.             launcher:className="com.google.android.googlequicksearchbox.SearchWidgetProvider"/>
  13.     </resolve>
  14. </favorites>
复制代码
通过XML配置出来的效果和在Java代码中配置的一样,这里就不贴图了。
修改主页应用、组件

​ 和谷歌搜索框的改动一样,也是先在device_profiles.xml文件内里定义桌面的行列数:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
  3.     <grid-option
  4.         launcher:name="6_by_5"
  5.         launcher:numRows="6"
  6.         launcher:numColumns="7"
  7.         launcher:numSearchContainerColumns="5"
  8.         .../>
  9. />
复制代码
​ 这里定义的app分布结构是六行七列。然后根据系统是否会读取partner_default_layout.xml结构文件来决定修改的位置:
(1)如果读取partner_default_layout.xml结构文件,则在partner_default_layout.xml内里定义应用的包名和类型以及位置信息,比方
  1. <favorite screen="0" x="2" y="3" packageName="com.android.vending" className="com.android.vending.AssetBrowserActivity"/>
  2. <!--表示在screen=“0”(第一屏)的第3列格子,第4行格子添加谷歌商店app。-->
复制代码
(2)如果不读取partner_default_layout.xml结构文件,则在对应的default_workspace_MxN.xml内里修改应用的位置(MN分别代表行列数):
  1. <!--定义一个普通应用程序快捷方式-->
  2. <resolve>
  3.     <favorite
  4.         launcher:screen="0"
  5.         launcher:x="2"
  6.         launcher:y="3"
  7.         launcher:packageName="com.android.vending"
  8.         launcher:className="com.android.vending.AssetBrowserActivity"/>
  9. </resolve>
复制代码
由于个人能力有限,上面的分析和讲解不免有错漏之处,接待各位批评指正;接待大家相互互换学习,一起进步,共勉!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

飞不高

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表