修改主页结构
概述
在 Android 操纵系统中,Launcher (主页应用)是用户与设备交互的核心界面之一,它负责显示应用程序列表、提供快捷方式、管理小部件等功能。其中,Launcher3 是 Android 系统默认的启动器应用程序,我们可以通过修改Launcher3的源码,来改变主页应用的样式和结构。下面将联合源码,分析如何修改Launcher3主页的相关样式和结构。
结构构成
Launcher3最核心的类是一个Launcher.java(可以看作是Launcher中的MainActivity),基本上所有操纵(包括UI的定制)都会合在这个Activity上。在Launcher.java 中,通过setContentView()设置的结构参数是R.layout.launcher,对应的是launcher.xml文件,它定义了启动器界面的整体结构和组件的位置,我们看下这个结构文件里的内容:
- <?xml version="1.0" encoding="utf-8"?>
- <com.android.launcher3.LauncherRootView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:id="@+id/launcher"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fitsSystemWindows="true">
- <com.android.launcher3.dragndrop.DragLayer
- android:id="@+id/drag_layer"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:importantForAccessibility="no">
- <com.android.launcher3.views.AccessibilityActionsView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:contentDescription="@string/home_screen"
- />
- <!-- The workspace contains 5 screens of cells -->
- <!-- DO NOT CHANGE THE ID -->
- <com.android.launcher3.Workspace
- android:id="@+id/workspace"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:theme="@style/HomeScreenElementTheme"
- launcher:pageIndicator="@+id/page_indicator" />
- <!-- DO NOT CHANGE THE ID -->
- <include
- android:id="@+id/hotseat"
- layout="@layout/hotseat" />
- <!-- Keep these behind the workspace so that they are not visible when
- we go into AllApps -->
- <com.android.launcher3.pageindicators.WorkspacePageIndicator
- android:id="@+id/page_indicator"
- android:layout_width="match_parent"
- android:layout_height="@dimen/workspace_page_indicator_height"
- android:layout_gravity="bottom|center_horizontal"
- android:theme="@style/HomeScreenElementTheme" />
- <include
- android:id="@+id/drop_target_bar"
- layout="@layout/drop_target_bar" />
- <com.android.launcher3.views.ScrimView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/scrim_view"
- android:background="@android:color/transparent" />
- <include
- android:id="@+id/apps_view"
- layout="@layout/all_apps"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- <include
- android:id="@+id/overview_panel"
- layout="@layout/overview_panel" />
- </com.android.launcher3.dragndrop.DragLayer>
- </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
- <com.android.launcher3.Hotseat
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:launcher="http://schemas.android.com/apk/res-auto"
- android:id="@+id/hotseat"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:theme="@style/HomeScreenElementTheme"
- android:importantForAccessibility="no"
- android:preferKeepClear="true"
- launcher:containerType="hotseat" />
复制代码 all_apps.xml
- <com.android.launcher3.allapps.LauncherAllAppsContainerView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/apps_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="true"
- android:clipToPadding="false"
- android:focusable="false"
- android:saveEnabled="false" />
复制代码 drop_target_bar.xml
- <com.android.launcher3.DropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/dynamic_grid_drop_target_size"
- android:layout_gravity="center_horizontal|top"
- android:focusable="false"
- android:alpha="0"
- android:theme="@style/HomeScreenElementTheme"
- android:visibility="invisible">
- <!-- Delete target -->
- <com.android.launcher3.DeleteDropTarget
- android:id="@+id/delete_target_text"
- style="@style/DropTargetButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:text="@string/remove_drop_target_label" />
- <!-- Uninstall target -->
- <com.android.launcher3.SecondaryDropTarget
- android:id="@+id/uninstall_target_text"
- style="@style/DropTargetButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:text="@string/uninstall_drop_target_label" />
- </com.android.launcher3.DropTargetBar>
复制代码 上面这三个控件都是直接include在Launcher.xml的结构中的,其他子结构也是以类似于自定义View的情势被添加到Launcher.xml中的,好比workspace:
- <com.android.launcher3.Workspace
- android:id="@+id/workspace"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:theme="@style/HomeScreenElementTheme"
- launcher:pageIndicator="@+id/page_indicator" />
复制代码 Workspace.java是在Launcher的onCreate()阶段被创建并添加到主页内里的:
- // Launcher3/src/com/android/launcher3/Launcher.java
- @Thunk
- Workspace<?> mWorkspace;
- @Thunk
- DragLayer mDragLayer;
- @Override
- @TargetApi(Build.VERSION_CODES.S)
- protected void onCreate(Bundle savedInstanceState) {
- // ...
- setupViews();
- // ...
- }
- protected void setupViews() {
- inflateRootView(R.layout.launcher);
- mDragLayer = findViewById(R.id.drag_layer);
- mFocusHandler = mDragLayer.getFocusIndicatorHelper();
- // 绑定workspace
- mWorkspace = mDragLayer.findViewById(R.id.workspace);
-
- // workspace做一些初始化的工作
- mWorkspace.initParentViews(mDragLayer);
- mOverviewPanel = findViewById(R.id.overview_panel);
- mHotseat = findViewById(R.id.hotseat);
- mHotseat.setWorkspace(mWorkspace);
- mDragLayer.setup(mDragController, mWorkspace);
- mWorkspace.setup(mDragController);
- mWorkspace.lockWallpaperToDefaultPage();
- mWorkspace.bindAndInitFirstWorkspaceScreen();
- mDragController.addDragListener(mWorkspace);
- // ...
- }
复制代码 各子结构的添加和加载就不展开介绍了,这里先介绍一个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的加载流程:
- // Launcher3/src/com/android/launcher3/Launcher.java
- @Override
- @TargetApi(Build.VERSION_CODES.S)
- protected void onCreate(Bundle savedInstanceState) {
- // ...
- LauncherAppState app = LauncherAppState.getInstance(this);
- // 通过LauncherAppState的getInvariantDeviceProfile方法获取InvariantDeviceProfile对象
- InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
- // 在initDeviceProfile方法里获取DeviceProfile对象
- initDeviceProfile(idp);
- // ...
- }
- /**
- * Returns {@code true} if a new DeviceProfile is initialized, and {@code false} otherwise.
- */
- protected boolean initDeviceProfile(InvariantDeviceProfile idp) {
- // 获取DeviceProfile,这里DeviceProfile对象来源是InvariantDeviceProfile.getDeviceProfile()
- DeviceProfile deviceProfile = idp.getDeviceProfile(this);
- if (mDeviceProfile == deviceProfile) {
- return false;
- }
- mDeviceProfile = deviceProfile;
- // ...
- }
复制代码 在Launcher.java的onCreate()方法中,先调用LauncherAppState.getInvariantDeviceProfile()方法获取一个InvariantDeviceProfile对象,然后在initDeviceProfile()方法中,通过InvariantDeviceProfile对象的getDeviceProfile()方法获取到DeviceProfile对象。跟进到LauncherAppState中看下:
- // Launcher3/src/com/android/launcher3/LauncherAppState.java
- private final InvariantDeviceProfile mInvariantDeviceProfile;
- public LauncherAppState(Context context) {
- // 调用两个参数的构造方法
- this(context, LauncherFiles.APP_ICONS_DB);
- Log.v(Launcher.TAG, "LauncherAppState initiated");
- // ...
- }
- public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
- mContext = context;
- // 获取mInvariantDeviceProfile对象
- mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
- // ...
- }
- public InvariantDeviceProfile getInvariantDeviceProfile() {
- // 返回InvariantDeviceProfile对象
- return mInvariantDeviceProfile;
- }
复制代码 由上面代码可以看出,InvariantDeviceProfile对象是在LauncherAppState的构造方法中创建的,并通过getInvariantDeviceProfile()方法返回一个InvariantDeviceProfile对象。继续跟进到InvariantDeviceProfile.java中:
- // Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
- // 这里的 INSTANCE可以理解为获取自身的单例对象
- // MainThreadInitializedObject是一个用于定义在主线程上启动的单例的实用工具类
- public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
- new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
- @TargetApi(23)
- private InvariantDeviceProfile(Context context) {
- String gridName = getCurrentGridName(context);
- // 调用initGrid()方法
- String newGridName = initGrid(context, gridName);
- // ...
- }
- private String initGrid(Context context, String gridName) {
- Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
- @DeviceType int deviceType = getDeviceType(displayInfo);
- // 调用getPredefinedDeviceProfiles方法加载device_profiles.xml文件
- ArrayList<DisplayOption> allOptions =
- getPredefinedDeviceProfiles(context, gridName, deviceType,
- RestoreDbTask.isPending(context));
- // 把device_profiles.xml文件中的配置信息记录到displayOption中
- DisplayOption displayOption =
- invDistWeightedInterpolate(displayInfo, allOptions, deviceType);
- // 调用四个参数的initGrid()方法
- initGrid(context, displayInfo, displayOption, deviceType);
- return displayOption.grid.name;
- }
- private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context,
- String gridName, @DeviceType int deviceType, boolean allowDisabledGrid) {
- ArrayList<DisplayOption> profiles = new ArrayList<>();
- // 加载device_profiles.xml文件
- try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
- final int depth = parser.getDepth();
- // ...
- } catch (IOException | XmlPullParserException e) {
- throw new RuntimeException(e);
- }
- // ...
- }
- private void initGrid(Context context, Info displayInfo, DisplayOption displayOption, @DeviceType int deviceType) {
- DisplayMetrics metrics = context.getResources().getDisplayMetrics();
- // 获取displayOption里面配置信息(实际上就是device_profiles.xml中读取的信息)
- GridOption closestProfile = displayOption.grid;
- numRows = closestProfile.numRows;
- numColumns = closestProfile.numColumns;
- numSearchContainerColumns = closestProfile.numSearchContainerColumns;
- // ...
- final List<DeviceProfile> localSupportedProfiles = new ArrayList<>();
- defaultWallpaperSize = new Point(displayInfo.currentSize);
- SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
- for (WindowBounds bounds : displayInfo.supportedBounds) {
- // 通过DeviceProfile.Build()方法创建DeviceProfile对象
- localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
- .setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY)
- .setWindowBounds(bounds)
- .setDotRendererCache(dotRendererCache)
- .build());
- // ...
- }
-
- // 把DeviceProfile列表存到supportedProfiles中,supportedProfiles是一个类型为List<DeviceProfile>的列表
- // 这里创建了DeviceProfile列表,是因为DeviceProfile中有多个不同的配置,一套配置可以视为一个DeviceProfile对象
- supportedProfiles = Collections.unmodifiableList(localSupportedProfiles);
-
- // ...
- }
复制代码 在InvariantDeviceProfile.java的构造方法中,调用了initGrid(context, gridName)方法,然后在initGrid()方法中调用getPredefinedDeviceProfiles()方法去加载device_profiles.xml配置文件,随后又调用了四个参数的initGrid()方法创建DeviceProfile对象列表,并把它赋值给supportedProfiles,supportedProfiles是一个类型为List<DeviceProfile>的列表。
在Launcher.java中是是通过idp.getDeviceProfile(this)来获取DeviceProfile对象的,我们看下InvariantDeviceProfile的getDeviceProfile()方法:
- // Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
- public DeviceProfile getDeviceProfile(Context context) {
- Resources res = context.getResources();
- Configuration config = context.getResources().getConfiguration();
- float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
- float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
- int rotation = WindowManagerProxy.INSTANCE.get(context).getRotation(context);
- // ...
- // 根据屏幕的宽高等信息,获取最适合的DeviceProfile配置文件
- return getBestMatch(screenWidth, screenHeight, rotation);
- }
- /**
- * 返回与所提供的屏幕配置相匹配的设备配置文件
- */
- public DeviceProfile getBestMatch(float screenWidth, float screenHeight, int rotation) {
- DeviceProfile bestMatch = supportedProfiles.get(0);
- float minDiff = Float.MAX_VALUE;
- // 遍历supportedProfiles列表,找出最匹配的配置文件。这里的supportedProfiles就是前面保存的DeviceProfile列表
- for (DeviceProfile profile : supportedProfiles) {
- float diff = Math.abs(profile.widthPx - screenWidth) + Math.abs(profile.heightPx - screenHeight);
- if (diff < minDiff) {
- minDiff = diff;
- bestMatch = profile;
- } else if (diff == minDiff && profile.rotationHint == rotation) {
- bestMatch = profile;
- }
- }
- return bestMatch;
- }
复制代码 所以执行到这里,Launcher.java中就可以拿到与当前屏幕最匹配的DeviceProfile配置了,通过这个DeviceProfile配置,可以去调解桌面的结构和组件,如下:
- // Launcher3/src/com/android/launcher3/Launcher.java
- public void finishBindingItems(IntSet pagesBoundFirst) {
- Object traceToken = TraceHelper.INSTANCE.beginSection("finishBindingItems");
- mWorkspace.restoreInstanceStateForRemainingPages();
- // ...
- // 获取mDeviceProfile配置信息里面的numFolderColumns和numFolderRows去做计算
- getViewCache().setCacheSize(R.layout.folder_application,
- mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows);
- getViewCache().setCacheSize(R.layout.folder_page, 2);
- TraceHelper.INSTANCE.endSection(traceToken);
- mWorkspace.removeExtraEmptyScreen(true);
- }
复制代码 实在不但是Launcher.java,在其他的类内里也会调用去做一些UI上的处置惩罚,好比Workspace.java中更新格子的Padding:
- // Launcher3/src/com/android/launcher3/Workspace.java
- private void updateCellLayoutPadding() {
- // 获取配置信息里面的cellLayoutPaddingPx
- Rect padding = mLauncher.getDeviceProfile().cellLayoutPaddingPx;
- mWorkspaceScreens.forEach(s -> s.setPadding(padding.left, padding.top, padding.right, padding.bottom));
- }
复制代码 到这里DeviceProfile的加载分析流程就结束了,如上面示例所说,在Launcher3中,DeviceProfile.java和device_profiles.xml是两个非常重要的文件,它们对Launcher3主页结构是至关重要的,想要修改Launcher3主页的结构,可以从这个方面入手。
主页结构修改
上面的内容简单介绍了DeviceProfile的功能和加载流程,下面来分析,如何修改主页结构,还是沿着上面的思绪,从DeviceProfile配置文件入手。
Launcher3启动的时候,会加载 Launcher3\res\xml\device_profiles.xml 文件中预设的结构,内容如下:
- <?xml version="1.0" encoding="utf-8"?>
- <profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
- <grid-option
- launcher:name="3_by_3"
- launcher:numRows="3"
- launcher:numColumns="3"
- launcher:numFolderRows="2"
- launcher:numFolderColumns="3"
- launcher:numHotseatIcons="3"
- launcher:dbFile="launcher_3_by_3.db"
- launcher:defaultLayoutId="@xml/default_workspace_3x3"
- launcher:deviceCategory="phone" >
-
- <!--省略多个display-option子标签内容-->
-
- </grid-option>
- <grid-option
- launcher:name="4_by_4"
- launcher:numRows="4"
- launcher:numColumns="4"
- launcher:numFolderRows="3"
- launcher:numFolderColumns="4"
- launcher:numHotseatIcons="4"
- launcher:numExtendedHotseatIcons="6"
- launcher:dbFile="launcher_4_by_4.db"
- launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split"
- launcher:defaultLayoutId="@xml/default_workspace_4x4"
- launcher:deviceCategory="phone|multi_display" >
-
- <!--省略多个display-option子标签内容-->
-
- </grid-option>
- <grid-option
- launcher:name="5_by_5"
- launcher:numRows="5"
- launcher:numColumns="5"
- launcher:numFolderRows="4"
- launcher:numFolderColumns="4"
- launcher:numHotseatIcons="5"
- launcher:numExtendedHotseatIcons="6"
- launcher:dbFile="launcher.db"
- launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split"
- launcher:defaultLayoutId="@xml/default_workspace_5x5"
- launcher:deviceCategory="phone|multi_display" >
-
- <!--省略多个display-option子标签内容-->
-
- </grid-option>
- <grid-option
- launcher:name="6_by_5"
- launcher:numRows="6"
- launcher:numColumns="7"
- launcher:numSearchContainerColumns="5"
- launcher:numFolderRows="3"
- launcher:numFolderColumns="4"
- launcher:numHotseatIcons="0"
- launcher:hotseatColumnSpanLandscape="2"
- launcher:numAllAppsColumns="6"
- launcher:isScalable="true"
- launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_6_5"
- launcher:devicePaddingId="@xml/paddings_6x5"
- launcher:dbFile="launcher_6_by_5.db"
- launcher:defaultLayoutId="@xml/default_workspace_6x5"
- launcher:deviceCategory="tablet" >
- <display-option
- launcher:name="Tablet"
- launcher:minWidthDps="900"
- launcher:minHeightDps="820"
- launcher:minCellHeight="120"
- launcher:minCellWidth="102"
- launcher:minCellHeightLandscape="104"
- launcher:minCellWidthLandscape="120"
- launcher:iconImageSize="60"
- launcher:iconTextSize="14"
- launcher:borderSpaceHorizontal="16"
- launcher:borderSpaceVertical="64"
- launcher:borderSpaceLandscapeHorizontal="64"
- launcher:borderSpaceLandscapeVertical="16"
- launcher:horizontalMargin="54"
- launcher:horizontalMarginLandscape="120"
- launcher:allAppsCellWidth="96"
- launcher:allAppsCellHeight="142"
- launcher:allAppsCellWidthLandscape="126"
- launcher:allAppsCellHeightLandscape="126"
- launcher:allAppsIconSize="60"
- launcher:allAppsIconTextSize="14"
- launcher:allAppsBorderSpaceHorizontal="8"
- launcher:allAppsBorderSpaceVertical="16"
- launcher:allAppsBorderSpaceLandscape="16"
- launcher:hotseatBarBottomSpace="30"
- launcher:hotseatBarBottomSpaceLandscape="40"
- launcher:canBeDefault="true" />
- </grid-option>
- </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应用的示例:
- <?xml version="1.0" encoding="utf-8"?>
- <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
- <!-- 配置一个Hotseat应用-->
- <resolve
- launcher:container="-101"
- launcher:screen="0"
- launcher:x="0"
- launcher:y="0" >
- <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
- <favorite launcher:uri="mailto:" />
- </resolve>
- <!-- 其他配置 -->
- </favorites>
复制代码 文件由<favorites>标签括起来,内里可以包罗一个或多个<resolve>标签,每个<resolve>标签都定义了一组快捷方式项,这些快捷方式可以直接链接到特定的应用或者执行特定的操纵(如打开邮件客户端、日历、图库等)。<resolve>的子标签表示快捷方式项的类型,快捷方式项必须放在<resolve>标签内才生效。<resolve>支持的子标签如下:
- favorite //应用程序快捷方式
- widget //桌面控件(小组件)
- shortcut //链接,如网址、本地磁盘路径等
- search //搜索框(谷歌搜索框)
- clock //桌面上的钟表Widget
- folder //桌面文件夹(如谷歌应用的文件夹)
复制代码 同时<resolve>标签或其子标签通过launcher:XXX来设定快捷方式项的位置等信息,这些属性可以写在<resolve>标签或其子标签中,支持的属性如下:
- // resolve标签支持的属性
- launcher:title // 图标下面的文字,目前只支持引用,不能直接书写字符串;
- launcher:icon // 图标引用(适用于应用快捷方式);
- launcher:uri // 链接地址,链接网址用的,使用shortcut标签就可以定义一个超链接,打开某个网址,文件等。
- launcher:packageName // 应用程序的包名;
- launcher:className // 应用程序的启动类名(要写全路径);
- launcher:screen // 图标所在的屏幕编号,0表示第一页
- launcher:x // 应用图标所处x位置(从左到右,从0开始),(-1是默认值:第一行或者第一列)
- launcher:y // 应用图标所处y位置(从上往下,从0开始)
- launcher:container // 定义一个快捷方式(Favorite)或桌面项目应该放置在哪个容器中;-101表示HotSeat、-100表示DeskTop、0表示默认的桌面容器、其他正整数表示App shortcut
- launcher:spanX //在x方向上所占格数
- launcher:spanY //在y方向上所占格数
复制代码 这里特别说明下launcher:uri属性:uri 定义了控件点击时的链接操纵,通常以#Intent;...;end的格式编写,指定了一系列操纵(如打开某个应用的主界面)或数据类型(如打开图库应用或欣赏特定网站)。
- // 下面列举几个常用launcher:uri的写法:
-
- 跳转到网页: "http://www.google.com"
- 跳转到设置的辅助功能:"#Intent;action=android.settings.ACCESSIBILITY_SETTINGS;end"
- 打开音乐文件:"file:///mnt/sdcard/song.mp3#Intent;action=android.intent.action.VIEW;type=audio/mp3;end"
- 指定应用程序打开音乐文件:"file:///mnt/sdcard/song.mp3#Intent;action=android.intent.action.VIEW;type=audio/mp3;component=com.android.music/.MusicBrowserActivity;end"
- // 指定应用程序打开音乐文件,在Java中对应的操作如下
- Intent it = new Intent(Intent.ACTION_VIEW);
- Uri uri = Uri.fromFile(new File("/mnt/sdcard/song.mp3" ));
- it.setDataAndType(uri, “audio/mp3”);
- it.setClassName(“com.android.music”, “com.android.music.MusicBrowserActivity”);
- String lancher_uri = it.toUri(0);
复制代码 常用几种桌面控件的示例:
- <?xml version="1.0" encoding="utf-8"?>
- <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-
- <!--定义一个HotSeat-->
- <resolve
- launcher:container="-101"
- launcher:screen="0"
- launcher:x="0"
- launcher:y="5" >
- <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
- <favorite launcher:uri="mailto:" />
- </resolve>
-
- <!--定义一个时钟小组件-->
- <resolve>
- <appwidget
- launcher:screen="0"
- launcher:x="2"
- launcher:y="0"
- launcher:spanX="3"
- launcher:spanY="1"
- launcher:packageName="com.google.android.deskclock"
- launcher:className="com.android.alarmclock.DigitalAppWidgetProvider"/>
- </resolve>
-
- <!--定义一个谷歌搜索框-->
- <resolve>
- <search
- launcher:screen="0"
- launcher:x="1"
- launcher:y="2"
- launcher:spanX="5"
- launcher:spanY="1"
- launcher:packageName="com.google.android.googlequicksearchbox"
- launcher:className="com.google.android.googlequicksearchbox.SearchWidgetProvider"/>
- </resolve>
-
- <!--定义一个文件夹,里面包含三个应用-->
- <resolve>
- <folder
- launcher:title="@string/google_folder_title"
- launcher:screen="0"
- launcher:x="1"
- launcher:y="3">
- <favorite
- launcher:packageName="com.google.android.googlequicksearchbox"
- launcher:className="com.google.android.googlequicksearchbox.SearchActivity"/>
- <favorite
- launcher:packageName="com.android.chrome"
- launcher:className="com.google.android.apps.chrome.Main"/>
- <favorite
- launcher:packageName="com.google.android.gm"
- launcher:className="com.google.android.gm.ConversationListActivityGmail"/>
- </folder>
- </resolve>
-
- <!--定义一个普通应用程序快捷方式-->
- <resolve>
- <favorite
- launcher:screen="0"
- launcher:x="2"
- launcher:y="3"
- launcher:packageName="com.android.vending"
- launcher:className="com.android.vending.AssetBrowserActivity"/>
- </resolve>
-
- <!--定义一个shortcut链接-->
- <resolve>
- <shortcut
- launcher:title="@string/google"
- launcher:icon="@drawable/google"
- launcher:uri="http://www.baidu.com"
- launcher:screen="0"
- launcher:x="3"
- launcher:y="3" />
- </resolve>
- </favorites>
复制代码 注意:这些属性并不都是resolve标签支持的,大多数的属性可以写在resolve标签中,也可以写在子标签中,但是对于uri、packageName、className等几个特别的属性,只能写在对应的子标签内。
除了上面的方式,还可以利用GMS中的配置文件来处置惩罚(条件是项目是有嵌入GMS框架)。在google_gms包下的配置文件release\vendor\partner_gms\apps\GmsSampleIntegration\res_dhs_full\xml\partner_default_layout.xml(下面是我项目中改的示例):
- <?xml version="1.0" encoding="utf-8"?>
- <!-- Copyright (C) 2017 Google Inc. All Rights Reserved. -->
- <favorites>
- <!--定义应用文件夹-->
- <folder title="@string/google_folder_title" screen="0" x="1" y="3">
- <favorite packageName="com.google.android.googlequicksearchbox" className="com.google.android.googlequicksearchbox.SearchActivity"/>
- <favorite packageName="com.android.chrome" className="com.google.android.apps.chrome.Main"/>
- <favorite packageName="com.google.android.gm" className="com.google.android.gm.ConversationListActivityGmail"/>
- <favorite packageName="com.google.android.apps.maps" className="com.google.android.maps.MapsActivity"/>
- <favorite packageName="com.google.android.youtube" className="com.google.android.youtube.app.honeycomb.Shell$HomeActivity"/>
- <favorite packageName="com.google.android.apps.docs" className="com.google.android.apps.docs.app.NewMainProxyActivity"/>
- <favorite packageName="com.google.android.apps.youtube.music" className="com.google.android.apps.youtube.music.activities.MusicActivity"/>
- <favorite packageName="com.google.android.videos" className="com.google.android.videos.GoogleTvEntryPoint"/>
- <favorite packageName="com.google.android.apps.tachyon" className="com.google.android.apps.tachyon.MainActivity"/>
- <favorite packageName="com.google.android.apps.photos" className="com.google.android.apps.photos.home.HomeActivity"/>
- </folder>
-
- <!--添加应用-->
- <favorite screen="0" x="2" y="3" packageName="com.android.vending" className="com.android.vending.AssetBrowserActivity"/>
- <!--添加组件-->
- <appwidget screen="0" x="2" y="0" packageName="com.google.android.deskclock" className="com.android.alarmclock.DigitalAppWidgetProvider" spanX="3" spanY="2" />
-
- <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
- <!-- 定义桌面的hotSeat显示的应用,本项目中已经设定不显示HotSeat,所以下面的代码设置也不起作用 -->
- <favorite container="-101" screen="0" x="0" y="0" packageName="com.google.android.dialer" className="com.google.android.dialer.extensions.GoogleDialtactsActivity"/>
- <favorite container="-101" screen="1" x="1" y="0" packageName="com.google.android.apps.messaging" className="com.google.android.apps.messaging.ui.ConversationListActivity"/>
- <favorite container="-101" screen="0" x="0" y="0" packageName="com.android.settings" className="com.android.settings.Settings"/>
- <favorite container="-101" screen="1" x="1" y="0" packageName="com.android.deskclock" className="com.android.deskclock.DeskClock"/>
- <favorite container="-101" screen="2" x="2" y="0" packageName="com.google.android.calendar" className="com.android.calendar.event.LaunchInfoActivity"/>
- <favorite container="-101" screen="3" x="3" y="0" packageName="com.google.android.contacts" className="com.android.contacts.activities.PeopleActivity"/>
- <favorite container="-101" screen="4" x="4" y="0" packageName="com.android.camera2" className="com.android.camera.CameraLauncher"/>
- </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读取,而不消重复读结构文件
- # 恢复默认布局
- adb root
- adb shell rm /data/data/com.android.launcher3/databases/launcher.db
- # 实测我这里使用的是6x5布局,所以db文件是:launcher_6_by_5.db
- # 重启模拟器即可生效
复制代码 修改谷歌搜索框
如果要修改搜索框占用格子的宽度,可以在Launcher3\res\xml\device_profiles.xml文件内里,对应的尺寸结构中修改numSearchContainerColumns的值:
- <?xml version="1.0" encoding="utf-8"?>
- <profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
- <grid-option
- launcher:name="6_by_5"
- launcher:numRows="6"
- launcher:numColumns="7"
- launcher:numSearchContainerColumns="5"
- .../>
- />
复制代码 我在6x5的结构(现实上是后7x6)中将谷歌搜索框的宽度改成了5,即在x轴方向上,占用五个格子。
接下来是修改谷歌搜索框的位置:在workspace.java的bindAndInitFirstWorkspaceScreen()方法中:
- // Launcher3/src/com/android/launcher3/Workspace.java
- /**
- * Initializes and binds the first page
- */
- public void bindAndInitFirstWorkspaceScreen() {
- if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
- return;
- }
- // Add the first page
- CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
- // Always add a first page pinned widget on the first screen.
- if (mFirstPagePinnedItem == null) {
- mFirstPagePinnedItem = LayoutInflater.from(getContext())
- .inflate(R.layout.search_container_workspace, firstPage, false);
- }
- // 先获取device_profiles.xml中定义的numSearchContainerColumns值
- int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
- // 在这里修改搜索框的位置,五个参数分别代表:x坐标、y坐标,占用宽度、占用高度、屏幕id
- // 所以这里效果是:在第一个CellLayout的第二列第三行(xy都是从0开始算的)添加谷歌搜索框,宽度为5个格子,高度为1个格子
- CellLayoutLayoutParams lp = new CellLayoutLayoutParams(1, 2, cellHSpan, 1, FIRST_SCREEN_ID);
-
- lp.canReorder = false;
- // 将谷歌搜索框添加到布局中,最后一个参数是代表是否可移动的意思
- if (!firstPage.addViewToCellLayout(
- mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
- Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
- mFirstPagePinnedItem = null;
- }
- }
复制代码 另外:还要在LoaderCursor.java的checkItemPlacement()方法中修改搜索框对位置的占用效果:
- // Launcher3/src/com/android/launcher3/model/LoaderCursor.java
- /**
- * check & update map of what's occupied; used to discard overlapping/invalid items
- */
- protected boolean checkItemPlacement(ItemInfo item) {
- int containerIndex = item.screenId;
- // ...
- if (!occupied.containsKey(item.screenId)) {
- GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
- if (item.screenId == Workspace.FIRST_SCREEN_ID && FeatureFlags.QSB_ON_FIRST_SCREEN) {
- int spanX = mIDP.numSearchContainerColumns;
- int spanY = 1;
- // 这里需要跟WorkSpace.java中设置的搜索框位置保持一致
- screen.markCells(1, 2, spanX, spanY, true);
- }
- occupied.put(item.screenId, screen);
- }
- final GridOccupancy occupancy = occupied.get(item.screenId);
- // Check if any workspace icons overlap with each other
- if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
- occupancy.markCells(item, true);
- return true;
- } else {
- Log.e(TAG, "Error loading shortcut " + item
- + " into cell (" + containerIndex + "-" + item.screenId + ":"
- + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
- + ") already occupied");
- return false;
- }
- }
复制代码 如果只修改WorkSpace、而没有修改LoaderCursor中的代码,那么搜索框原本的结构(默认位置是x=0,y=0)占用效果会一直存在,导致背面一些应用或者组件无法放置在首页的首行位置。
注意:Workspace.java的bindAndInitFirstWorkspaceScreen方法不但是设定谷歌搜索框位置的关键代码,也是控制了谷歌搜索框是否显示,如果不需要显示谷歌搜索框,可以将上面代码注释:
- // Launcher3/src/com/android/launcher3/Workspace.java
- /**
- * Initializes and binds the first page
- */
- public void bindAndInitFirstWorkspaceScreen() {
- if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
- return;
- }
- // 注意要保留这一行,否则会报错
- CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
-
- // if (mFirstPagePinnedItem == null) {
- // mFirstPagePinnedItem = LayoutInflater.from(getContext())
- // .inflate(R.layout.search_container_workspace, firstPage, false);
- // }
- //
- // int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
- // CellLayoutLayoutParams lp = new CellLayoutLayoutParams(1, 2, cellHSpan, 1, FIRST_SCREEN_ID);
- // lp.canReorder = false;
- // if (!firstPage.addViewToCellLayout(
- // mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
- // Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
- // mFirstPagePinnedItem = null;
- // }
- }
复制代码 除了通过Java文件配置谷歌搜索框之外,也可以通过XML文件来配置谷歌搜索框,主要是在default_workspace.xml或partner_default_layout.xml中进行配置。如果项目有配置GMS框架,那么会读取partner_default_layout.xml文件中的配置,否则会读取default_workspace.xml的配置。
先在device_profiles.xml文件内里定义桌面的行列数:
- <?xml version="1.0" encoding="utf-8"?>
- <profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
- <grid-option
- launcher:name="6_by_5"
- launcher:numRows="6"
- launcher:numColumns="7"
- launcher:numSearchContainerColumns="5"
- .../>
- />
复制代码 这里定义的app分布结构是六行七列。然后根据系统是否会读取partner_default_layout.xml结构文件来决定修改的位置:
注意:利用xml配置时,需要先将Java代码中配置谷歌搜索框的代码(Workspace.java和LoaderCursor.java)注释掉,避免相互影响。
- // Launcher3/src/com/android/launcher3/Workspace.java
- public void bindAndInitFirstWorkspaceScreen() {
- if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
- return;
- }
- // 注意要保留这一行,否则会报错
- CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
-
- // if (mFirstPagePinnedItem == null) {
- // mFirstPagePinnedItem = LayoutInflater.from(getContext())
- // .inflate(R.layout.search_container_workspace, firstPage, false);
- // }
- //
- // int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
- // CellLayoutLayoutParams lp = new CellLayoutLayoutParams(1, 2, cellHSpan, 1, FIRST_SCREEN_ID);
- // lp.canReorder = false;
- // if (!firstPage.addViewToCellLayout(
- // mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
- // Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
- // mFirstPagePinnedItem = null;
- // }
- }
复制代码- // Launcher3/src/com/android/launcher3/model/LoaderCursor.java
- protected boolean checkItemPlacement(ItemInfo item) {
- int containerIndex = item.screenId;
- // ...
- if (!occupied.containsKey(item.screenId)) {
- GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
- // 注释下面if中的内容
- // if (item.screenId == Workspace.FIRST_SCREEN_ID && FeatureFlags.QSB_ON_FIRST_SCREEN) {
- // // Mark the first X columns (X is width of the search container) in the first row as
- // // occupied (if the feature is enabled) in order to account for the search
- // // container.
- // int spanX = mIDP.numSearchContainerColumns;
- // int spanY = 1;
- // screen.markCells(1, 2, spanX, spanY, true);
- // }
- occupied.put(item.screenId, screen);
- }
- final GridOccupancy occupancy = occupied.get(item.screenId);
- // Check if any workspace icons overlap with each other
- if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) {
- occupancy.markCells(item, true);
- return true;
- } else {
- Log.e(TAG, "Error loading shortcut " + item
- + " into cell (" + containerIndex + "-" + item.screenId + ":"
- + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY
- + ") already occupied");
- return false;
- }
- }
复制代码 (1)如果适配了GMS框架并读取partner_default_layout.xml结构文件,则在google_gms包下的release\vendor\partner_gms\apps\GmsSampleIntegration\res_dhs_full\xml\partner_default_layout.xml内里定义应用的包名和类型以及位置信息,比方
- <?xml version="1.0" encoding="utf-8"?>
- <!-- Copyright (C) 2017 Google Inc. All Rights Reserved. -->
- <favorites>
- <!--其他配置-->
- <appwidget screen="0" x="1" y="2" packageName="com.google.android.googlequicksearchbox" className="com.android.alarmclock.DigitalAppWidgetProvider" spanX="5" spanY="1" />
- </favorites>
复制代码 (2)如果不读取partner_default_layout.xml结构文件,则在对应的default_workspace_MxN.xml内里修改应用的位置(MN分别代表行列数):
- <?xml version="1.0" encoding="utf-8"?>
- <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
- <!--其他配置-->
- <resolve>
- <search
- launcher:screen="0"
- launcher:x="1"
- launcher:y="2"
- launcher:spanX="5"
- launcher:spanY="1"
- launcher:packageName="com.google.android.googlequicksearchbox"
- launcher:className="com.google.android.googlequicksearchbox.SearchWidgetProvider"/>
- </resolve>
- </favorites>
复制代码 通过XML配置出来的效果和在Java代码中配置的一样,这里就不贴图了。
修改主页应用、组件
和谷歌搜索框的改动一样,也是先在device_profiles.xml文件内里定义桌面的行列数:
- <?xml version="1.0" encoding="utf-8"?>
- <profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
- <grid-option
- launcher:name="6_by_5"
- launcher:numRows="6"
- launcher:numColumns="7"
- launcher:numSearchContainerColumns="5"
- .../>
- />
复制代码 这里定义的app分布结构是六行七列。然后根据系统是否会读取partner_default_layout.xml结构文件来决定修改的位置:
(1)如果读取partner_default_layout.xml结构文件,则在partner_default_layout.xml内里定义应用的包名和类型以及位置信息,比方
- <favorite screen="0" x="2" y="3" packageName="com.android.vending" className="com.android.vending.AssetBrowserActivity"/>
- <!--表示在screen=“0”(第一屏)的第3列格子,第4行格子添加谷歌商店app。-->
复制代码 (2)如果不读取partner_default_layout.xml结构文件,则在对应的default_workspace_MxN.xml内里修改应用的位置(MN分别代表行列数):
- <!--定义一个普通应用程序快捷方式-->
- <resolve>
- <favorite
- launcher:screen="0"
- launcher:x="2"
- launcher:y="3"
- launcher:packageName="com.android.vending"
- launcher:className="com.android.vending.AssetBrowserActivity"/>
- </resolve>
复制代码 由于个人能力有限,上面的分析和讲解不免有错漏之处,接待各位批评指正;接待大家相互互换学习,一起进步,共勉!!!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |