前言
阅读本篇文章之前,有几个需要说明一下:
- 调试设备:平板,如果你是开发者手机,一样可以加 Log 调试,源码仍然是手机和平板一起分析;
- 文章中的 Log 信息所显示的数值可能跟你的设备不一样,以你调试的数据为准。
- 装个逼:目前好像 OH 社区或者其它开发者还没有针对 OH 的系统应用,比如 Launcher 写过非常深入的源码解析类文章,所以此类文章,仅供大家参考学习,如转载或引用,请标明出处!
一、计算桌面布局参数
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
calculateDesktop(): any {
Log.showInfo(TAG, 'calculateDesktop start');
/**
* 01. 计算桌面布局参数
*/
/**
* 获取桌面布局的边距值 mMargin,用于计算实际的可用宽度
*/
let margin = this.mLauncherLayoutStyleConfig.mMargin;
/**
* 根据屏幕宽度 mScreenWidth 和边距计算实际可用的宽度 realWidth,边距在两侧都有,所以要乘以 2
*/
let realWidth = this.mScreenWidth - 2 * margin;
/**
* 计算实际可用高度 realHeight
* 工作区高度 mWorkSpaceHeight 减去指示器高度 mIndicatorHeight 和系统顶部高度 mSysUITopHeight
*/
let realHeight = this.mWorkSpaceHeight - this.mIndicatorHeight - this.mSysUITopHeight;
/**
* 检查导航栏状态 mNavigationBarStatus
* 如果导航栏存在,则从 realHeight 中减去系统底部高度 mSysBottomHeight
*/
if (this.mNavigationBarStatus) {
realHeight = realHeight - this.mLauncherLayoutStyleConfig.mSysBottomHeight;
}
...
}
1.1 边距
let margin = this.mLauncherLayoutStyleConfig.mMargin;
这里的 this.mLauncherLayoutStyleConfig 是 LauncherLayoutStyleConfig 类,它有两个子类:
- 如果你的设备是 Phone 类型,那么就会从 PhoneLauncherLayoutStyleConfig 里面去取 mMargin 值:
📄 product/phone/src/main/ets/common/PhoneLauncherLayoutStyleConfig.ts
mMargin = PhonePresetStyleConstants.DEFAULT_LAYOUT_MARGIN;
进而读取 PhonePresetStyleConstants 里面默认配置的 DEFAULT_LAYOUT_MARGIN 值:
📄 product/phone/src/main/ets/common/constants/PhonePresetStyleConstants.ts
static readonly DEFAULT_LAYOUT_MARGIN = 12;
- 如果你的设备是 Pad 类型,那么就会从 PadLauncherLayoutStyleConfig 里面去取 mMargin 值:
📄 product/pad/src/main/ets/common/PadLauncherLayoutStyleConfig.ts
mMargin = PadPresetStyleConstants.DEFAULT_LAYOUT_MARGIN;
进而读取 PadPresetStyleConstants 里面默认配置的 DEFAULT_LAYOUT_MARGIN 值:
📄 product/phone/src/main/ets/common/constants/PhonePresetStyleConstants.ts
static readonly DEFAULT_LAYOUT_MARGIN = 82;
1.2 可用宽度
let realWidth = this.mScreenWidth - 2 * margin;
我们来看看 mScreenWidth(屏幕宽度)是从哪里来的:
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
initScreen(navigationBarStatus?: string): void {
this.mScreenWidth = AppStorage.get('screenWidth');
this.mScreenHeight = AppStorage.get('screenHeight');
...
}
通过 AppStorage.get() 获取的,那肯定有地方 AppStorage.setOrCreate() 了。
- 如果设备是 Phone 类型
📄 product/phone/src/main/ets/pages/EntryView.ets
aboutToAppear(): void {
Log.showInfo(TAG, 'aboutToAppear');
...
this.getWindowSize();
...
}
private getWindowSize(): void {
try {
this.screenWidth = px2vp(windowManager.getWindowWidth());
this.screenHeight = px2vp(windowManager.getWindowHeight());
AppStorage.setOrCreate('screenWidth', this.screenWidth);
AppStorage.setOrCreate('screenHeight', this.screenHeight);
} catch (error) {
Log.showError(TAG, `getWindowWidth or getWindowHeight error: ${error}`);
}
}
- 如果设备是 Pad 类型
📄 product/pad/src/main/ets/pages/EntryView.ets
aboutToAppear(): void {
Log.showInfo(TAG, 'aboutToAppear');
...
this.getWindowSize();
...
}
private getWindowSize(): void {
try {
this.screenWidth = px2vp(windowManager.getWindowWidth());
this.screenHeight = px2vp(windowManager.getWindowHeight());
AppStorage.setOrCreate('screenWidth', this.screenWidth);
AppStorage.setOrCreate('screenHeight', this.screenHeight);
} catch (error) {
Log.showError(TAG, `getWindowWidth or getWindowHeight error: ${error}`);
}
}
可以发现,逻辑一摸一样。
我们带着看下 windowManager.getWindowWidth() 里面的逻辑:
📄 common/src/main/ets/default/manager/WindowManager.ts
getWindowWidth(): number {
if (this.mDisplayData == null) {
this.mDisplayData = this.getWindowDisplayData();
}
// 获取显示设备的屏幕宽度
return this.mDisplayData?.width as number;
}
private getWindowDisplayData(): display.Display | null {
let displayData: display.Display | null = null;
try {
// @ohos.display ==> 获取当前默认的 display 对象
displayData = display.getDefaultDisplaySync();
} catch(err) {
Log.showError(TAG, `display.getDefaultDisplaySync error: ${JSON.stringify(err)}`);
}
return displayData;
}
我现在手上的设备是 Pad,打个 Log 看看:
com.ohos.launcher I @@@ pepsimaxin : 屏幕宽度 ==> this.mScreenWidth = 1280
1.3 可用高度
let realHeight = this.mWorkSpaceHeight - this.mIndicatorHeight - this.mSysUITopHeight;
我们分别来跟踪下每一个数值:
this.mWorkSpaceHeight
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
calculateDock(): any {
this.mDockHeight = iconSize + 2 * dockPadding + marginBottom;
// 类似于 Android 的 Workspace,整个工作区的高度
this.mWorkSpaceHeight = this.mScreenHeight - this.mSysUIBottomHeight - this.mDockHeight;
}
- 先来看下屏幕高度:this.mScreenHeight
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
initScreen(navigationBarStatus?: string): void {
this.mScreenWidth = AppStorage.get('screenWidth');
this.mScreenHeight = AppStorage.get('screenHeight');
...
}
这里就跟之前获取屏幕宽度一样了,就不把相关代码重新写一遍了,我们看下 Log 数值:
pid-6687 D @@@ pepsimaxin : this.mScreenHeight = 720
- 再看:this.mSysUIBottomHeight
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
initScreen(navigationBarStatus?: string): void {
...
// 默认:false
if (!this.mNavigationBarStatus) {
if (this.mScreenWidth > this.mScreenHeight) {
this.mSysUIBottomHeight = this.mLauncherLayoutStyleConfig.mSysBottomHeight * this.mScreenWidth / 1280;
} else {
this.mSysUIBottomHeight = this.mLauncherLayoutStyleConfig.mSysBottomHeight * this.mScreenWidth / 360;
}
} else {
this.mSysUIBottomHeight = 0;
}
AppStorage.setOrCreate('sysUIBottomHeight', this.mSysUIBottomHeight);
...
}
因为手里是平板,所以会走第一个判断,直接看 Log:
pid-12018 D @@@ pepsimaxin : this.mSysUIBottomHeight = 44
- 最后看 Dock 区域高度: this.mDockHeight
this.mDockHeight = iconSize + 2 * dockPadding + marginBottom;
三个数值:Dock 区域图标大小、内间距、Dock 底部间距
(1) Dock 区域图标大小
let iconSize = this.mLauncherLayoutStyleConfig.mDockIconSize;
还是老样子:
// Phone
mDockIconSize: number = PhonePresetStyleConstants.DEFAULT_DOCK_ICON_SIZE;
static readonly DEFAULT_DOCK_ICON_SIZE = 54;
// Pad
mDockIconSize: number = PadPresetStyleConstants.DEFAULT_DOCK_ICON_SIZE;
static readonly DEFAULT_DOCK_ICON_SIZE = 54;
(2) 内间距
let dockPadding = this.mLauncherLayoutStyleConfig.mDockPadding;
还是老样子:
// Phone
mDockPadding: number = PhonePresetStyleConstants.DEFAULT_DOCK_PADDING;
static readonly DEFAULT_DOCK_PADDING = 12;
// Pad
mDockPadding: number = PadPresetStyleConstants.DEFAULT_DOCK_PADDING;
static readonly DEFAULT_DOCK_PADDING = 12;
(3) Dock 底部间距
let marginBottom = this.mLauncherLayoutStyleConfig.mDockMarginBottomHideBar;
if (!this.mNavigationBarStatus) {
marginBottom = this.mLauncherLayoutStyleConfig.mDockMarginBottom;
}
还是老样子:
// Phone
mDockMarginBottomHideBar: number = PhonePresetStyleConstants.DEFAULT_DOCK_MARGIN_BOTTOM;
static readonly DEFAULT_DOCK_MARGIN_BOTTOM = 9;
// Pad
mDockMarginBottomHideBar: number = PadPresetStyleConstants.DEFAULT_DOCK_MARGIN_BOTTOM;
static readonly DEFAULT_DOCK_MARGIN_BOTTOM = 10;
至此,综合得出 this.mDockHeight(Dock 栏的高度)为:
// Phone
com.ohos.launcher D @@@ pepsimaxin : iconSize = 54
com.ohos.launcher D @@@ pepsimaxin : dockPadding = 12
com.ohos.launcher D @@@ pepsimaxin : marginBottom = 9
com.ohos.launcher D @@@ pepsimaxin : this.mDockHeight = 87
// Pad
com.ohos.launcher D @@@ pepsimaxin : iconSize = 54
com.ohos.launcher D @@@ pepsimaxin : dockPadding = 12
com.ohos.launcher D @@@ pepsimaxin : marginBottom = 10
com.ohos.launcher D @@@ pepsimaxin : this.mDockHeight = 88
所以最终 this.mWorkSpaceHeight 的数值为:
pid-19975 D @@@ pepsimaxin : this.mWorkSpaceHeight = 588
this.mIndicatorHeight
现在整个 WorkSpace 的高度算出来了,接下来看下指示器区域的高度:
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
initScreen(navigationBarStatus?: string): void {
...
this.mIndicatorHeight = this.mLauncherLayoutStyleConfig.mIndicatorHeight;
...
}
老样子:
// Phone
mIndicatorHeight = PresetStyleConstants.DEFAULT_PHONE_INDICATOR_HEIGHT;
static readonly DEFAULT_PHONE_INDICATOR_HEIGHT = 32;
// Pad
mIndicatorHeight = PresetStyleConstants.DEFAULT_PAD_INDICATOR_HEIGHT;
static readonly DEFAULT_PAD_INDICATOR_HEIGHT = 32;
this.mSysUITopHeight
最后我们再来看下 SysUITopHeight:
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
initScreen(navigationBarStatus?: string): void {
...
this.mSysUITopHeight = this.mLauncherLayoutStyleConfig.mSysTopHeight;
...
}
老样子:
// Phone
mSysTopHeight = PhonePresetStyleConstants.DEFAULT_SYS_TOP_HEIGHT;
static readonly DEFAULT_SYS_TOP_HEIGHT = 44;
// Pad
mSysTopHeight = PadPresetStyleConstants.DEFAULT_SYS_TOP_HEIGHT;
static readonly DEFAULT_SYS_TOP_HEIGHT = 44;
可以看出这几个项的数值基本上一样,可以根据自己的需求自己配置。
至此,我们就可以得到最终我们需要的可用高度了:
pid-30046 I @@@ pepsimaxin : 屏幕高度 ==> this.realHeight = 512
二、计算列数、行数及间距
接下来我们继续分析第 2 部分代码:
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
calculateDesktop(): any {
Log.showInfo(TAG, 'calculateDesktop start');
/**
* 01. 计算桌面布局参数
*/
---------------------------------------------------
/**
* 02. 计算列数、行数及间距
*/
/**
* 获取每个应用图标的尺寸 mAppItemSize
*/
let itemSize = this.mLauncherLayoutStyleConfig.mAppItemSize;
/**
* 获取最小网格间距 mGridGutter
*/
let minGutter = this.mLauncherLayoutStyleConfig.mGridGutter;
/**
* 计算可以容纳多少列图标:~~ 是双重按位取反操作符,用于快速向下取整
*/
let column = ~~((realWidth + minGutter) / (itemSize + minGutter));
/**
* 计算列之间剩余的宽度 userWidth
*/
let userWidth = (realWidth + minGutter - (itemSize + minGutter) * column);
/**
* 重新计算列间距 gutter
*/
let gutter = (userWidth / (column - 1)) + minGutter;
/**
* 类似列的计算,计算可以容纳多少行图标
*/
let row = ~~((realHeight + gutter) / (itemSize + gutter));
/**
* 计算顶部边距 marginTop,用于使图标在垂直方向上居中
*/
let marginTop = ((realHeight + gutter - (itemSize + gutter) * row) / 2);
...
}
2.1 应用图标大小
let itemSize = this.mLauncherLayoutStyleConfig.mAppItemSize;
又碰到 LauncherLayoutStyleConfig 了,那么图标尺寸肯定不同设备配置不一样了:
// Phone
mAppItemSize = PhonePresetStyleConstants.DEFAULT_APP_LAYOUT_SIZE;
static readonly DEFAULT_APP_LAYOUT_SIZE = 80;
// Pad
mAppItemSize = PadPresetStyleConstants.DEFAULT_APP_LAYOUT_SIZE;
static readonly DEFAULT_APP_LAYOUT_SIZE = 96;
2.2 最小网格间距
let minGutter = this.mLauncherLayoutStyleConfig.mGridGutter;
同理,我们直接看配置信息:
// Phone
mGridGutter = PhonePresetStyleConstants.DEFAULT_APP_LAYOUT_MIN_GUTTER;
static readonly DEFAULT_APP_LAYOUT_MIN_GUTTER = 5;
// Pad
mGridGutter = PadPresetStyleConstants.DEFAULT_APP_LAYOUT_MIN_GUTTER;
static readonly DEFAULT_APP_LAYOUT_MIN_GUTTER = 6;
2.3 图标列数
// 计算容纳多少列图标
let column = ~~((realWidth + minGutter) / (itemSize + minGutter));
例如我手里的平板,我们打个 Log 看下数据:
pid-1360 I @@@ pepsimaxin : 桌面布局边距 ==> margin = 82
pid-1360 I @@@ pepsimaxin : 屏幕宽度 ==> this.mScreenWidth = 1280
com.ohos.launcher D @@@ pepsimaxin : realWidth = 1116, minGutter = 6, itemSize = 96
com.ohos.launcher D @@@ pepsimaxin : column = 11
也就是说,一行可以放置 11 个应用图标。
2.4 图标行数
/**
* 计算列之间剩余的宽度 userWidth
*/
let userWidth = (realWidth + minGutter - (itemSize + minGutter) * column);
/**
* 重新计算列间距 gutter
*/
let gutter = (userWidth / (column - 1)) + minGutter;
/**
* 类似列的计算,计算可以容纳多少行图标
*/
let row = ~~((realHeight + gutter) / (itemSize + gutter));
gutter
gutter 的作用是:为了使网格布局在视觉上更加一致,行和列之间使用相同的间距,我们直接看下数值:
com.ohos.launcher D @@@ pepsimaxin : gutter = 6
realHeight
在 1.3 可用高度中,我们已经得到了具体数值
pid-30046 I @@@ pepsimaxin : 屏幕高度 ==> this.realHeight = 512
itemSize
// 当前平板图标大小
mAppItemSize = PadPresetStyleConstants.DEFAULT_APP_LAYOUT_SIZE;
static readonly DEFAULT_APP_LAYOUT_SIZE = 96;
row 的计算方式跟 column 一样:
(512 + 6) / (96 + 6) = 5.07
~~ 向下取整
com.ohos.launcher D @@@ pepsimaxin : row = 5
看下平板效果:
三、特殊情况处理
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
calculateDesktop(): any {
Log.showInfo(TAG, 'calculateDesktop start');
/**
* 01. 计算桌面布局参数
*/
---------------------------------------------------
/**
* 02. 计算列数、行数及间距
*/
---------------------------------------------------
/**
* 03. 特殊情况处理与计算更新
*/
// 检查设备是否为平板。如果不是平板,执行以下操作
if (!this.mIsPad) {
// 对于非平板设备,行数超过 6 时,强制限制为 6 行
if (row > 6) {
row = 6;
}
// 如果导航栏存在,重新计算可用高度 realHeight
if (this.mNavigationBarStatus) {
realHeight = realHeight + this.mLauncherLayoutStyleConfig.mSysBottomHeight;
}
// 计算剩余的高度 remainHeight
let remainHeight = (realHeight + gutter - (itemSize + gutter) * row)
// 调整可用高度,去掉多余的高度
realHeight -= remainHeight
// 重新计算顶部边距,使其居中
marginTop = remainHeight / 2 + this.mSysUITopHeight
}
...
}
这段代码所有核心的参数上面全部解读过了,就是高度的数值更新,大家自行解读。
四、图标参数配置
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
calculateDesktop(): any {
Log.showInfo(TAG, 'calculateDesktop start');
/**
* 01. 计算桌面布局参数
*/
---------------------------------------------------
/**
* 02. 计算列数、行数及间距
*/
---------------------------------------------------
/**
* 03. 特殊情况处理与计算更新
*/
---------------------------------------------------
/**
* 04. 计算图标尺寸和其他配置
*/
let ratio = this.mLauncherLayoutStyleConfig.mIconRatio;
let lines = this.mLauncherLayoutStyleConfig.mNameLines;
let appTextSize = this.mLauncherLayoutStyleConfig.mNameSize;
let nameHeight = this.mLauncherLayoutStyleConfig.mNameHeight;
let iconNameMargin = this.mLauncherLayoutStyleConfig.mIconNameGap;
let iconMarginVertical = ratio * itemSize;
let iconHeight = itemSize - 2 * iconMarginVertical - nameHeight - iconNameMargin;
let iconMarginHorizontal = (itemSize - iconHeight) / 2;
...
}
这段代码就不用解读了,从不同 Config 里面读取配置,从命名就能看出来作用,大家自己跟代码调试吧。
五、更新布局
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
calculateDesktop(): any {
Log.showInfo(TAG, 'calculateDesktop start');
/**
* 01. 计算桌面布局参数
*/
---------------------------------------------------
/**
* 02. 计算列数、行数及间距
*/
---------------------------------------------------
/**
* 03. 特殊情况处理与计算更新
*/
---------------------------------------------------
/**
* 04. 计算图标尺寸和其他配置
*/
---------------------------------------------------
/**
* 05. 更新布局
*/
// 如果设备是纵向模式,强制设置行数和列数,一般不建议强制
MLog.showDebug(TAG, "Device isPortrait = " + AppStorage.get('isPortrait'))
if (AppStorage.get('isPortrait')) {
MLog.showDebug(TAG, "Device isPortrait")
row = 11;
column = 5;
}
// 调用更新网格的方法,设置行数和列数
this.updateGrid(row, column);
...
}
接下来,我们的重点就来到了 this.updateGrid(row, column):
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
private updateGrid(row: number, column: number): void {
// SettingsModel 单例
let settingsModel = SettingsModel.getInstance();
let gridConfig = settingsModel.getGridConfig();
gridConfig.row = row;
gridConfig.column = column;
const layoutDimension = `${row}X${column}`;
gridConfig.layout = layoutDimension;
gridConfig.name = layoutDimension;
}
重点:settingsModel.getGridConfig()
📄 common/src/main/ets/default/model/SettingsModel.ts
export class SettingsModel {
getGridConfig(): any {
this.mGridConfig = this.mPageDesktopModeConfig.getGridConfig();
let gridLayout = this.mGridLayoutTable[0];
for (let i = 0; i < this.mGridLayoutTable.length; i++) {
if (this.mGridLayoutTable[i].id == this.mGridConfig) {
gridLayout = this.mGridLayoutTable[i];
break;
}
}
return gridLayout;
}
}
猛然一看,感觉很懵是吧?我们拆解一下:
- 先来看第一行代码:
this.mGridConfig = this.mPageDesktopModeConfig.getGridConfig();
我们追踪一下:
📄 common/src/main/ets/default/layoutconfig/PageDesktopModeConfig.ts
export class PageDesktopModeConfig extends ILayoutConfig {
private mGridConfig: number = defaultLayoutConfig.defaultGridConfig;
getGridConfig(): number {
return this.mGridConfig;
}
}
const defaultLayoutConfig = {
defaultAppPageStartConfig: 'Grid',
defaultLayoutOptions: [
{ name: 'List', value: 'List', checked: false },
{ name: 'Grid', value: 'Grid', checked: false }
],
defaultGridConfig: 0, // 默认为 0
defaultRecentMissionsLimit: 20,
defaultRecentMissionsRowConfig: 'single',
defaultRecentMissionsLimitArray: [
{ name: '5', value: 5, checked: false },
{ name: '10', value: 10, checked: false },
{ name: '15', value: 15, checked: false },
{ name: '20', value: 20, checked: false }
],
defaultDeviceType: 'phone'
};
export default defaultLayoutConfig;
- 继续看代码:
let gridLayout = this.mGridLayoutTable[0];
this.mGridLayoutTable[0]
是什么呢?我们在代码里找找:
📄 common/src/main/ets/default/model/SettingsModel.ts
export class SettingsModel {
...
// 默认
private mGridLayoutTable = GridLayoutConfigs.GridLayoutTable;
...
private constructor() {
...
// 3. 判断当前设备类型
if (deviceType == CommonConstants.DEFAULT_DEVICE_TYPE) {
this.mGridLayoutTable = GridLayoutConfigs.GridLayoutTable;
} else if (deviceType == CommonConstants.PAD_DEVICE_TYPE) {
// 我手里的是 Pad
this.mGridLayoutTable = GridLayoutConfigs.PadGridLayoutTableHorizontal;
} else {
this.mGridLayoutTable = GridLayoutConfigs.GridLayoutTableHorizontal;
}
...
}
// 1. 单例
static getInstance(): SettingsModel {
if (globalThis.SettingsModelInstance == null) {
globalThis.SettingsModelInstance = new SettingsModel(); // 2. 构造函数
}
return globalThis.SettingsModelInstance;
}
}
所以涉及到两个核心的 Table:GridLayoutConfigs.GridLayoutTable 和 GridLayoutConfigs.PadGridLayoutTableHorizontal。
它们是什么?
📄 common/src/main/ets/default/configs/GridLayoutConfigs.ts
const GridLayoutConfigs = {
GridLayoutTable: [
{
id: 0,
layout: '4X4',
name: '4X4',
value: 1,
row: 4,
column: 4,
checked: false
},
{
id: 1,
layout: '5X4',
name: '5X4',
value: 0,
row: 5,
column: 4,
checked: false
},
{
id: 2,
layout: '6X4',
name: '6X4',
value: 2,
row: 6,
column: 4,
checked: false
},
],
...
PadGridLayoutTableHorizontal: [
{
id: 0,
layout: '5X11',
name: '5X11',
value: 0,
row: 5,
column: 11,
checked: false
},
{
id: 1,
layout: '4X10',
name: '4X10',
value: 1,
row: 4,
column: 10,
checked: false
},
{
id: 2,
layout: '4X9',
name: '4X9',
value: 2,
row: 4,
column: 9,
checked: false
}
],
原来就是两个数组,针对 Phone 和 Pad 分别存储了不同的网格布局配置。
所以,针对 Pad 而言:
this.mGridLayoutTable = GridLayoutConfigs.PadGridLayoutTableHorizontal;
进而:
let gridLayout = this.mGridLayoutTable[0];
==>
let gridLayout =
{
id: 0,
layout: '5X11',
name: '5X11',
value: 0,
row: 5,
column: 11,
checked: false
},
- 现在我们再来看 For 循环:
for (let i = 0; i < this.mGridLayoutTable.length; i++) {
if (this.mGridLayoutTable[i].id == this.mGridConfig) {
gridLayout = this.mGridLayoutTable[i];
break;
}
}
this.mGridLayoutTable.length
的长度,我们刚才看到了,为 3;
this.mGridConfig
为 0;
所以,最终 gridLayout 为:
let gridLayout =
{
id: 0,
layout: '5X11',
name: '5X11',
value: 0,
row: 5,
column: 11,
checked: false
},
现在让我们回到 updateGrid() 方法:
📄 common/src/main/ets/default/viewmodel/LayoutViewModel.ts
private updateGrid(row: number, column: number): void {
// SettingsModel 单例
let settingsModel = SettingsModel.getInstance();
// 现在我们知道了 gridConfig 是什么了
let gridConfig = settingsModel.getGridConfig();
gridConfig.row = row;
gridConfig.column = column;
const layoutDimension = `${row}X${column}`;
gridConfig.layout = layoutDimension;
gridConfig.name = layoutDimension;
}
剩余的代码全部都是更新网格配置里面对应的数据了。
六、Launcher 渲染
前面我们把桌面布局计算的流程全部更新了一遍,最终其实就是为了更新网格布局配置。那么就会存在一个问题,这个网格布局配置信息都是在哪用的?
如果你看了 Launcher 初体验 这篇文章,应该已经了解了 Launcher 首页的布局结果了,我们看张图:
很明显,所有的应用都是通过 Grid + GridItem 进行布局的,而网格布局最重要的就是要知道 row 和 column,这两个信息是不是就在网格布局配置里面?这不就串起来了?
我们不妨看在代码:
📄 feature/pagedesktop/src/main/ets/default/common/components/SwiperPage.ets
@Component
export default struct SwiperPage {
build() {
Grid() {
ForEach(this.mAppListInfo, (item: LauncherDragItemInfo, index: number) => {
GridItem() {
if (this.buildLog(item)) {
}
if (item.typeId === CommonConstants.TYPE_APP) {
AppItem({
item: item,
mPageDesktopViewModel: this.mPageDesktopViewModel,
mNameLines: this.mNameLines
}).id(`${TAG}_AppItem_${index}`)
} else if (item.typeId === CommonConstants.TYPE_FOLDER) {
...
} else if (item.typeId === CommonConstants.TYPE_CARD) {
...
}
}
.id(`${TAG}_GridItem_${index}`)
...
}, (item: LauncherDragItemInfo, index: number) => {
if (item.typeId === CommonConstants.TYPE_FOLDER) {
return JSON.stringify(item);
} else if (item.typeId === CommonConstants.TYPE_CARD) {
return JSON.stringify(item) + this.formRefresh;
} else if (item.typeId === CommonConstants.TYPE_APP) {
return JSON.stringify(item);
} else {
return '';
}
})
}
.id(`${TAG}_Grid_${this.swiperPage}`)
.columnsTemplate(this.ColumnsTemplate) // 列
.rowsTemplate(this.RowsTemplate) // 行
.columnsGap(this.mColumnsGap)
.rowsGap(this.mRowsGap)
.width(this.mGridWidth)
.height(this.mGridHeight)
.margin({
right: this.mMargin,
left: this.mMargin
})
...
}
}
我们来看下 this.ColumnsTemplate
和 this.RowsTemplate
:
📄 feature/pagedesktop/src/main/ets/default/common/components/SwiperPage.ets
@Component
export default struct SwiperPage {
@State ColumnsTemplate: string = '';
@State RowsTemplate: string = ''
aboutToAppear(): void {
Log.showInfo(TAG, 'aboutToAppear');
this.mPageDesktopViewModel = PageDesktopViewModel.getInstance();
this.updateDeskTopScreen();
}
updateDeskTopScreen(): void {
Log.showInfo(TAG, 'updateDeskTopScreen');
...
this.changeConfig();
...
}
private changeConfig(): void {
// 这里,核心代码,我们看看跳转到哪里
let mGridConfig = this.mPageDesktopViewModel?.getGridConfig() as GridLayout;
let column = mGridConfig.column as number;
let row = mGridConfig.row as number;
this.ColumnsTemplate = '';
this.RowsTemplate = '';
for (let i = 0;i < column; i++) {
this.ColumnsTemplate += '1fr '
}
for (let i = 0;i < row; i++) {
this.RowsTemplate += '1fr '
}
}
}
📄 feature/pagedesktop/src/main/ets/default/viewmodel/PageDesktopViewModel.ts
export class PageDesktopViewModel extends BaseViewModel {
getGridConfig() {
return this.mSettingsModel.getGridConfig();
}
}
📄 common/src/main/ets/default/model/SettingsModel.ts
export class SettingsModel {
getGridConfig(): any {
this.mGridConfig = this.mPageDesktopModeConfig.getGridConfig();
let gridLayout = this.mGridLayoutTable[0];
for (let i = 0; i < this.mGridLayoutTable.length; i++) {
if (this.mGridLayoutTable[i].id == this.mGridConfig) {
gridLayout = this.mGridLayoutTable[i];
break;
}
}
return gridLayout;
}
}
熟悉吗?我们刚刚才分析过这段代码,所以一切就串起来了!