白银市城乡建设局网站庞馨妩,wordpress批量添加分类,免费制作相册影集,浏览器老是出现站长工具文章目录 前言遇到的困难点针对性解决困难 需求相关资料Launcher3 源码 目录简单介绍Launcher3 简介及页面布局分析UI整体架构数据加载布局加载布局加载核心思想device_profiles.xml 加载InvariantDeviceProfileinitGrid(context, gridName)getPredefinedDeviceProfilesinvDist… 文章目录 前言遇到的困难点针对性解决困难 需求相关资料Launcher3 源码 目录简单介绍Launcher3 简介及页面布局分析UI整体架构数据加载布局加载布局加载核心思想device_profiles.xml 加载InvariantDeviceProfileinitGrid(context, gridName)getPredefinedDeviceProfilesinvDistWeightedInterpolateinitGrid(Context context, Info displayInfo, DisplayOption displayOption,boolean isSplitDisplay) default_workspace_MxN.xml查找资源文件 default_workspace_MxN.xml 位置 MTK Launcher3 源码位置需求实现 前言
第一次拿着开发板想研究一下Launcher3源码第一次接触Launcher3,一步一步看看Launcher3 定制的话有哪些内容需要掌握的。
遇到的困难点
拿到源码从何看起从何研究起。源码无论在线的谷歌源码还是各大半导体厂商提供的Launcher3源码存在一定的差别。 需要针对性看特别是针对自己的开发板上面看尤其重要不然对不上。部分代码看不明白布局和UI如何对上的。源码到底在哪里配置文件、布局文件到底是哪一个对不上琢磨实验好久。
针对性解决困难
多从广义角度、全局角度来看Launcher3不要一下子钻进了牛角尖 出不来毫无收获多看一看网上在线的第三方博客、别人的总结、比人的分析从中自己体会理解多打印日志调试在源码里面搜索关键字、关键文件路径不管你是开发板或者公司产品务必先针对一款源码熟悉、了解、分析尽量不要好多平台源码一起看源码部分架构不一样的代码也不一样的。多啃、多吃啃源码还是有必要的
需求
remind:初识Launcher首页布局是怎么加载的
首页页面是如何加载的如何配置如下图界面我想更改一下每个图标的位置我想自定义这个界面如何实现 比如如下看网上别人定制的桌面蛮好看的如下图
相关资料
Launcher3 相关资料参考 菜鸟成长之路-源码分析专栏 Android Launcher3 简介 Launcher3 高端定制 Launcher3 开发 Launcher3 Android Code Search在线源码查看 Launcher3 xref 在线源码查看 Launcher3 RK 源码查看 Launcher3 解析 Launcher3 AndroidP AS版本 谷歌Launcher3 Android13源码修改 Launcher3 和 Launcher3QuickStep 区别 Android14 不分Launcher3修改 Launcher3 LoaderTask 的数据加载 Android14 浅析Launcher Android O Launcher3-Workspace加载
Launcher3 源码 目录简单介绍
当拿到Launcher3 源码时候对源码还是一脸懵逼 用了这么久的手机源码不熟悉 也正常先有个大概了解
allapps 目录主要存放主菜单界面相关代码。 anim目录存放动画相关代码主要是动画基类代码。 compat目录主要存放解决兼容性相关的代码。 config目录主要配置Launcher相关功能的宏开关目前Launcher原生新增的功能宏开关都在这个目录。 dragndrop目录主要存放拖拽相关操作的代码 graphics目录主要存放处理图标大小、颜色、自适应等相关的代码 model目录存放Launcher加载流程相关模块化的代码 notification目录存放通知相关的代码 pageindicators目录存放桌面页面指示器相关的代码 popu目录存放长按图标显示弹出框相关的代码 provider目录存放Launcher数据库相关的代码 qsb目录存放搜索功能相关的代码 shortcuts目录存放桌面所属应用某些功能的快捷图标相关的代码。
Launcher3 简介及页面布局分析
回归到需求我们需要了解的是布局相关那还是从整体架构来看看 Launcher3
UI整体架构 UI 架构是我们比较熟悉的内容用了这么多年的手机手机桌面部分不就是这些内容的嘛从研发的角度讲我们可以和对应的名称 关联起来。
数据加载
数据加载是Launcher3中一个比较核心的内容后续需要深入了解下面给一下加载流程图后续再继续分析在看源码过程中提供一个源码查看方向针对本文就此打住需要了解 非深入研究部分
布局加载
本身我们通过Launcher3 找到主Activity主Activity里面再找对应的layout 布局launcher.xml这些和上面的UI架构对应 对应的是模块非本文核心问题。我们的核心问题是找桌面的那些Icon 文件夹 快捷 搜索栏 UI和布局及数据如何展现的这些其实是配置或者在源码里面硬编码更改。
布局加载核心思想
我自己开发过程中看 device_profiles.xml 时候一脸懵不清楚每个字段 比如 grid-option 、display-option、numRows、numColumns、numFolderRows、numFolderColumns、numHotseatIcons、minWidthDps、minHeightDps、iconImageSize、iconTextSize… 到底啥玩意 这么多配置后来想一想见名知意。 就是显示的属性和配置呀都是见名知意。
launcher:defaultLayoutIdxml/default_workspace_5x5
launcher:defaultLayoutIdxml/default_workspace_4x4
launcher:defaultLayoutIdxml/default_workspace_3x3布局里面好多 grid-option 、display-option,到底用哪一个对应的 default_workspace_MxN
核心思想
动态选择更具屏幕大小分辨率动态适配加载选择更具横竖屏动态选择
device_profiles.xml 加载
我们先给一个简单的流程图
device_profiles.xml 文件用于定义不同设备配置的布局该文件是启动器根据设备的特性如屏幕尺寸、分辨率、密度等来适配布局和图标大小等元素的重要配置文件。
该文件的主要功能有定义网格布局、设置图标大小、配置热区、定义所有应用列表的布局、屏幕和设备类型特定配置、壁纸和背景设置、提供默认布局等。 profiles xmlns:launcherhttp://schemas.android.com/apk/res-auto grid-optionlauncher:name3_by_3launcher:numRows3launcher:numColumns3launcher:numFolderRows2launcher:numFolderColumns3launcher:numHotseatIcons3launcher:dbFilelauncher_3_by_3.dblauncher:defaultLayoutIdxml/default_workspace_3x3 display-optionlauncher:nameSuper Short Stubbylauncher:minWidthDps255launcher:minHeightDps300launcher:iconImageSize48launcher:iconTextSize13.0launcher:canBeDefaulttrue /display-optionlauncher:nameShorter Stubbylauncher:minWidthDps255launcher:minHeightDps400launcher:iconImageSize48launcher:iconTextSize13.0launcher:canBeDefaulttrue //grid-optiongrid-optionlauncher:name4_by_4launcher:numRows4launcher:numColumns4launcher:numFolderRows3launcher:numFolderColumns4launcher:numHotseatIcons4launcher:dbFilelauncher_4_by_4.dblauncher:defaultLayoutIdxml/default_workspace_4x4 display-optionlauncher:nameShort Stubbylauncher:minWidthDps275launcher:minHeightDps420launcher:iconImageSize48launcher:iconTextSize13.0launcher:canBeDefaulttrue /display-optionlauncher:nameStubbylauncher:minWidthDps255launcher:minHeightDps450launcher:iconImageSize48launcher:iconTextSize13.0launcher:canBeDefaulttrue /display-optionlauncher:nameNexus Slauncher:minWidthDps296launcher:minHeightDps491.33launcher:iconImageSize48launcher:iconTextSize13.0launcher:canBeDefaulttrue /display-optionlauncher:nameNexus 4launcher:minWidthDps359launcher:minHeightDps567launcher:iconImageSize54launcher:iconTextSize13.0launcher:canBeDefaulttrue /display-optionlauncher:nameNexus 5launcher:minWidthDps335launcher:minHeightDps567launcher:iconImageSize54launcher:iconTextSize13.0launcher:canBeDefaulttrue //grid-optiongrid-optionlauncher:name5_by_5launcher:numRows5launcher:numColumns5launcher:numFolderRows4launcher:numFolderColumns4launcher:numHotseatIcons5launcher:dbFilelauncher.dblauncher:defaultLayoutIdxml/default_workspace_5x5 display-optionlauncher:nameLarge Phonelauncher:minWidthDps406launcher:minHeightDps694launcher:iconImageSize56launcher:iconTextSize14.4launcher:canBeDefaulttrue /display-optionlauncher:nameLarge Phone Split Displaylauncher:minWidthDps406launcher:minHeightDps694launcher:iconImageSize56launcher:iconTextSize14.4launcher:canBeDefaultsplit_display /display-optionlauncher:nameShorter Stubbylauncher:minWidthDps255launcher:minHeightDps400launcher:iconImageSize48launcher:iconTextSize13.0launcher:canBeDefaulttrue //grid-option
InvariantDeviceProfile
初始化地方 Launcher.javaInvariantDeviceProfile idp app.getInvariantDeviceProfile();LauncherAppState.javapublic InvariantDeviceProfile getInvariantDeviceProfile() {return mInvariantDeviceProfile;}LauncherAppState 构造方法mInvariantDeviceProfile InvariantDeviceProfile.INSTANCE.get(context);InvariantDeviceProfile.java 构造方法TargetApi(23)private InvariantDeviceProfile(Context context) {String gridName getCurrentGridName(context);String newGridName initGrid(context, gridName);
....}initGrid(context, gridName)
大家这么想为啥是Grid init Grid 是做什么的。 其实桌面中WorkSpack 中的CellLayout, 不就是由网格组成的嘛 然后给对应的坐标告诉放到哪一个位置不就可以了嘛。 所以这个名字 Grid 是很有意义的。 //初始化网格private String initGrid(Context context, String gridName) {......// getPredefinedDeviceProfiles 获取预定义的文件配置列表ArrayListDisplayOption allOptions getPredefinedDeviceProfiles(context, gridName, isSplitDisplay);DisplayOption displayOption invDistWeightedInterpolate(displayInfo, allOptions, isSplitDisplay);Log.d(TAG, initGrid gridName:displayOption.grid.name); initGrid(context, displayInfo, displayOption, isSplitDisplay);return displayOption.grid.name;}getPredefinedDeviceProfiles
getPredefinedDeviceProfiles 获取预定义的文件配置列表 private static ArrayListDisplayOption getPredefinedDeviceProfiles(Context context, String gridName, boolean isSplitDisplay) {ArrayListDisplayOption profiles new ArrayList();Log.d(TAG,getPredefinedDeviceProfiles huoqu yudingyi device list gridName:gridName);try (XmlResourceParser parser context.getResources().getXml(R.xml.device_profiles)) {final int depth parser.getDepth();int type;while (((type parser.next()) ! XmlPullParser.END_TAG ||parser.getDepth() depth) type ! XmlPullParser.END_DOCUMENT) {Log.d(TAG,getPredefinedDeviceProfiles GridOption.TAG_NAME:GridOption.TAG_NAME parser.getName:parser.getName());if ((type XmlPullParser.START_TAG) GridOption.TAG_NAME.equals(parser.getName())) {GridOption gridOption new GridOption(context, Xml.asAttributeSet(parser));final int displayDepth parser.getDepth();while (((type parser.next()) ! XmlPullParser.END_TAG ||parser.getDepth() displayDepth) type ! XmlPullParser.END_DOCUMENT) {if ((type XmlPullParser.START_TAG) display-option.equals(parser.getName())) {Log.d(TAG,getPredefinedDeviceProfiles display-option parser.getName:parser.getName());profiles.add(new DisplayOption(gridOption, context,Xml.asAttributeSet(parser),isSplitDisplay ? DEFAULT_SPLIT_DISPLAY : DEFAULT_TRUE));}}}}} catch (IOException|XmlPullParserException e) {throw new RuntimeException(e);}ArrayListDisplayOption filteredProfiles new ArrayList();if (!TextUtils.isEmpty(gridName)) {for (DisplayOption option : profiles) {if (gridName.equals(option.grid.name)) {filteredProfiles.add(option);}}}Log.d(TAG,getPredefinedDeviceProfiles 000 profiles:profiles.size() filteredProfiles:filteredProfiles.size() );if (filteredProfiles.isEmpty()) {// No grid found, use the default optionsfor (DisplayOption option : profiles) {if (option.canBeDefault) {filteredProfiles.add(option);}}}Log.d(TAG,getPredefinedDeviceProfiles 111 profiles:profiles.size() filteredProfiles:filteredProfiles.size() );if (filteredProfiles.isEmpty()) {throw new RuntimeException(No display option with canBeDefaulttrue);}return filteredProfiles;}这个方法比较核心关注三点
加载R.xml.device_profiles 文件并解析DisplayOption类和device_profiles 里面的display-option 属性不就对上了嘛得到一个displayOption 配置列表解析device_profiles将grid-option 节点想的一级属性信息封装在了DisplayOption,这样实现了 通过displayOption 能够获取 配置文件中上一层的grid 信息。 比如获取gridname, 也就是接下来要讲的MxN.xml invDistWeightedInterpolate
接着上面的 getPredefinedDeviceProfiles 方法讲这个方法返回的是List 集合
ArrayListDisplayOption getPredefinedDeviceProfiles那么为什么会返回一个集合 我们通过 上面分析 device_profiles.xml 配置文件中grid-option 节点下对应的是多个display-option的。 比如如下日志可以说明问题
返回了DisplayOption 集合后如何选择其一适合自己的呢 invDistWeightedInterpolate 就派上用场了
private static DisplayOption invDistWeightedInterpolate(Info displayInfo, ArrayListDisplayOption points, boolean isSplitDisplay) {int minWidthPx Integer.MAX_VALUE;int minHeightPx Integer.MAX_VALUE;for (WindowBounds bounds : displayInfo.supportedBounds) {boolean isTablet displayInfo.isTablet(bounds);if (isTablet isSplitDisplay) {// For split displays, take half width per pageminWidthPx Math.min(minWidthPx, bounds.availableSize.x / 2);minHeightPx Math.min(minHeightPx, bounds.availableSize.y);} else if (!isTablet bounds.isLandscape()) {// We will use transposed layout in this caseminWidthPx Math.min(minWidthPx, bounds.availableSize.y);minHeightPx Math.min(minHeightPx, bounds.availableSize.x);} else {minWidthPx Math.min(minWidthPx, bounds.availableSize.x);minHeightPx Math.min(minHeightPx, bounds.availableSize.y);}}float width dpiFromPx(minWidthPx, displayInfo.densityDpi);float height dpiFromPx(minHeightPx, displayInfo.densityDpi);// Sort the profiles based on the closeness to the device sizeCollections.sort(points, (a, b) -Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),dist(width, height, b.minWidthDps, b.minHeightDps)));GridOption closestOption points.get(0).grid;float weights 0;DisplayOption p points.get(0);if (dist(width, height, p.minWidthDps, p.minHeightDps) 0) {return p;}DisplayOption out new DisplayOption(closestOption);for (int i 0; i points.size() i KNEARESTNEIGHBOR; i) {p points.get(i);float w weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);weights w;out.add(new DisplayOption().add(p).multiply(w));}return out.multiply(1.0f / weights);}如则经过一系列的计算来得到与当前屏幕可用宽高最为合适的各参数大小。具体算法这边就不细究了。
initGrid(Context context, Info displayInfo, DisplayOption displayOption,boolean isSplitDisplay)
得到了gridNameDisplayOption 不就可以获取得到 快捷方式、文件夹、图标等一些列的参数了嘛且看 源码。 private void initGrid(Context context, Info displayInfo, DisplayOption displayOption,boolean isSplitDisplay) {Log.d(TAG,initGrid...); DisplayMetrics metrics context.getResources().getDisplayMetrics();GridOption closestProfile displayOption.grid;numRows closestProfile.numRows;numColumns closestProfile.numColumns;dbFile closestProfile.dbFile;defaultLayoutId closestProfile.defaultLayoutId;demoModeLayoutId closestProfile.demoModeLayoutId;numFolderRows closestProfile.numFolderRows;numFolderColumns closestProfile.numFolderColumns;isScalable closestProfile.isScalable;devicePaddingId closestProfile.devicePaddingId;mExtraAttrs closestProfile.extraAttrs;iconSize displayOption.iconSize;landscapeIconSize displayOption.landscapeIconSize;iconBitmapSize ResourceUtils.pxFromDp(iconSize, metrics);iconTextSize displayOption.iconTextSize;landscapeIconTextSize displayOption.landscapeIconTextSize;fillResIconDpi getLauncherIconDensity(iconBitmapSize);minCellHeight displayOption.minCellHeight;minCellWidth displayOption.minCellWidth;borderSpacing displayOption.borderSpacing;Log.d(TAG,initGrid displayOption iconSize:iconSize landscapeIconSize:landscapeIconSize iconTextSize:iconTextSize landscapeIconTextSize:landscapeIconTextSize);Log.d(TAG,initGrid displayOption minCellHeight:minCellHeight minCellWidth:minCellWidth borderSpacing:borderSpacing );numShownHotseatIcons closestProfile.numHotseatIcons;numDatabaseHotseatIcons isSplitDisplay? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;numAllAppsColumns closestProfile.numAllAppsColumns;numDatabaseAllAppsColumns isSplitDisplay? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;Log.d(TAG,initGrid closestProfile numRows:numRows numColumns:numColumns dbFile:dbFile defaultLayoutId:defaultLayoutId demoModeLayoutId:demoModeLayoutId);Log.d(TAG,initGrid closestProfile numFolderRows:numFolderRows numFolderColumns:numFolderColumns isScalable:isScalable devicePaddingId:devicePaddingId);Log.d(TAG,initGrid closestProfile numShownHotseatIcons:numShownHotseatIcons numDatabaseHotseatIcons:numDatabaseHotseatIcons);if (Utilities.isGridOptionsEnabled(context)) {allAppsIconSize displayOption.allAppsIconSize;allAppsIconTextSize displayOption.allAppsIconTextSize;} else {allAppsIconSize iconSize;allAppsIconTextSize iconTextSize;}if (devicePaddingId ! 0) {devicePaddings new DevicePaddings(context, devicePaddingId);}// If the partner customization apk contains any grid overrides, apply them// Supported overrides: numRows, numColumns, iconSizeapplyPartnerDeviceProfileOverrides(context, metrics);final ListDeviceProfile localSupportedProfiles new ArrayList();defaultWallpaperSize new Point(displayInfo.currentSize);for (WindowBounds bounds : displayInfo.supportedBounds) {localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo).setUseTwoPanels(isSplitDisplay).setWindowBounds(bounds).build());// Wallpaper size should be the maximum of the all possible sizes Launcher expectsint displayWidth bounds.bounds.width();int displayHeight bounds.bounds.height();defaultWallpaperSize.y Math.max(defaultWallpaperSize.y, displayHeight);// We need to ensure that there is enough extra space in the wallpaper// for the intended parallax effectsfloat parallaxFactor dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.densityDpi) 720? 2: wallpaperTravelToScreenWidthRatio(displayWidth, displayHeight);defaultWallpaperSize.x Math.max(defaultWallpaperSize.x, Math.round(parallaxFactor * displayWidth));}supportedProfiles Collections.unmodifiableList(localSupportedProfiles);ComponentName cn new ComponentName(context.getPackageName(), getClass().getName());defaultWidgetPadding AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);}部分日志如下
initGrid displayOption iconSize:55.69532 landscapeIconSize:55.69532 iconTextSize:14.34668 landscapeIconTextSize:14.34668
initGrid displayOption minCellHeight:0.0 minCellWidth:0.0 borderSpacing:0.0
initGrid closestProfile numRows:5 numColumns:5 dbFile:launcher.db defaultLayoutId:2131951619 demoModeLayoutId:2131951619
initGrid closestProfile numFolderRows:4 numFolderColumns:4 isScalable:false devicePaddingId:0
initGrid closestProfile numShownHotseatIcons:5 numDatabaseHotseatIcons:5
InvariantDeviceProfile gridName:5_by_5 newGridName:5_by_5default_workspace_MxN.xml
经过上面的分析其实已经找到了GridName如当前 调试是5_by_5。获取后保存一份下次获取。每次也要获取一份新的在上面分析中 筛选DisPlayOption 里面
String newGridName initGrid(context, gridName);private String initGrid(Context context, String gridName) {...DisplayOption displayOption invDistWeightedInterpolate(displayInfo, allOptions, isSplitDisplay);Log.d(TAG, initGrid gridName:displayOption.grid.name); initGrid(context, displayInfo, displayOption, isSplitDisplay);return displayOption.grid.name;}解析文件要解析道德其实是 launcher:defaultLayoutIdxml/default_workspace_5x5
defaultLayoutId 属性 在initGrid 中解析到defaultLayoutId 然后找到对应的布局
favorites xmlns:launcherhttp://schemas.android.com/apk/res-auto/com.android.launcher3!-- Hotseat (We use the screen as the position of the item in the hotseat) --!-- Dialer Messaging [All Apps] Contacts Camera --favorite container-101 screen0 x0 y0 packageNamecom.google.android.dialer classNamecom.google.android.dialer.extensions.GoogleDialtactsActivity/favorite container-101 screen1 x1 y0 packageNamecom.google.android.apps.messaging classNamecom.google.android.apps.messaging.ui.ConversationListActivity/favorite container-101 screen2 x2 y0 packageNamecom.google.android.calendar classNamecom.android.calendar.event.LaunchInfoActivity/favorite container-101 screen3 x3 y0 packageNamecom.google.android.contacts classNamecom.android.contacts.activities.PeopleActivity/favorite container-101 screen4 x4 y0 packageNamecom.mediatek.camera classNamecom.mediatek.camera.CameraLauncher/!-- In Launcher3, workspaces extend infinitely to the right, incrementing from zero --!-- Google folder --!-- Google, Chrome, Gmail, Maps, YouTube, (Drive), (Music), (Movies), Hangouts, Photos --folder titlestring/google_folder_title screen0 x0 y4favorite packageNamecom.google.android.googlequicksearchbox classNamecom.google.android.googlequicksearchbox.SearchActivity/favorite packageNamecom.android.chrome classNamecom.google.android.apps.chrome.Main/favorite packageNamecom.google.android.gm classNamecom.google.android.gm.ConversationListActivityGmail/favorite packageNamecom.google.android.apps.maps classNamecom.google.android.maps.MapsActivity/favorite packageNamecom.google.android.youtube classNamecom.google.android.youtube.app.honeycomb.Shell$HomeActivity/favorite packageNamecom.google.android.apps.docs classNamecom.google.android.apps.docs.app.NewMainProxyActivity/favorite packageNamecom.google.android.apps.youtube.music classNamecom.google.android.apps.youtube.music.activities.MusicActivity/favorite packageNamecom.google.android.videos classNamecom.google.android.videos.GoogleTvEntryPoint/favorite packageNamecom.google.android.apps.tachyon classNamecom.google.android.apps.tachyon.MainActivity/favorite packageNamecom.google.android.apps.photos classNamecom.google.android.apps.photos.home.HomeActivity/favorite packageNamecom.google.android.apps.adm classNamecom.google.android.apps.adm.activities.MainActivity//folderfavorite screen0 x2 y4 packageNamecom.google.android.apps.googleassistant classNamecom.google.android.apps.googleassistant.AssistantActivity/favorite screen0 x4 y4 packageNamecom.android.vending classNamecom.android.vending.AssetBrowserActivity/
/favorites查找资源文件 default_workspace_MxN.xml 位置
找到 default_workspace_5x5.xml 在哪里呢 我用的是mtk 平台搜索文件名如下 你会发现好多个呀下面给出具体位置
MTK 平台GMS版本
\vendor\google\overlay\gms_overlay\vendor\google\apps\SearchLauncher\res\xml\default_workspace_5x5.xml
MTK Launcher3 源码位置
我在MTK 平台上面测试验证GMS 版本下 Launcher3 源码存在两份分别位于 package/app/和vendor/mediatek/proprietary/packages/apps/下当前调试源码位置
packages\apps\Launcher3需求实现
上面我们已经找到了 default_workspace_5x5.xml 首页的配置就在上面 我们更改配置即可具体更改 每个 字段含有见名知意的。 可以自己实验。 比如我自己更改如下实际效果如下 favorite screen0 x2 y4 packageNamecom.google.android.apps.googleassistant classNamecom.google.android.apps.googleassistant.AssistantActivity/