【 OpenHarmony 系统应用源码解析 】-- Launcher 桌面布局

前言

阅读本篇文章之前,有几个需要说明一下:

  1. 调试设备:平板,如果你是开发者手机,一样可以加 Log 调试,源码仍然是手机和平板一起分析;
  2. 文章中的 Log 信息所显示的数值可能跟你的设备不一样,以你调试的数据为准。
  3. 装个逼:目前好像 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 类,它有两个子类:

在这里插入图片描述

  1. 如果你的设备是 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;
  1. 如果你的设备是 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() 了。

  1. 如果设备是 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}`);
  }
}
  1. 如果设备是 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;
}
  1. 先来看下屏幕高度: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
  1. 再看: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
  1. 最后看 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;
  }

}

猛然一看,感觉很懵是吧?我们拆解一下:

  1. 先来看第一行代码:
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;
  1. 继续看代码:
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.GridLayoutTableGridLayoutConfigs.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
			    },
  1. 现在我们再来看 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.ColumnsTemplatethis.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;
  }

}

熟悉吗?我们刚刚才分析过这段代码,所以一切就串起来了!

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值