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

news2024/9/25 1:16:26

前言

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

  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;
  }

}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2093379.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言中的“#”和“##”

目录 开头1.什么是#?2.什么是##?3.#和##的实际应用输出变量的名字把两个符号连接成一个符号输出根据变量的表达式…… 下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。在今天&#xff0c;我们要学一下关于C语言中的#和##的一些知识。 1.什么是#? #&#xff0…

Datawhale X 李宏毅苹果书 AI夏令营-深度学入门task2:线性模型

1.线性模型 把输入的特征 x 乘上一个权重&#xff0c;再加上一个偏置就得到预测的结果&#xff0c;这样的模型称为线性模型&#xff08;linear model&#xff09; 2.分段线性模型 线性模型也许过于简单&#xff0c;x1 跟 y 可能中间有比较复杂的关系。线性模型有很大的限制&…

上书房信息咨询:商标相似性评估调研

商标相似性评估调研可以帮助确定商标之间的相似性程度&#xff0c;为商标注册、商标权利维护和商标侵权案件提供参考依据。以下是进行商标相似性评估调研的一般步骤&#xff1a; 1、收集商标信息&#xff1a;收集相关商标的注册证书、商标注册申请、商标注册公告等信息&#x…

【测试】——开发模型与测试模型

&#x1f4d6; 前言&#xff1a;在软件开发过程中&#xff0c;理解和应用合适的开发模型与测试模型至关重要。本文将详细介绍几种常见的开发模型&#xff0c;如瀑布模型、螺旋模型、增量模型和敏捷过程&#xff0c;以及测试模型如V模型和W模型。 目录 &#x1f552; 1. 开发模型…

Mobile-Agent赛题分析和代码解读笔记(DataWhale AI夏令营)

前言 你好&#xff0c;我是GISer Liu&#xff0c;一名热爱AI技术的GIS开发者&#xff0c;本文是DataWhale 2024 AI夏令营的最后一期——Mobile-Agent赛道&#xff0c;关于赛题分析和代码解读的学习文档总结&#xff1b;这边作者也会分享自己的思路&#xff1b; 本文是对原视频的…

18045 前一个和后一个字符

### 思路 1. 读取输入的字符。 2. 判断输入是否为数字字符&#xff1a; - 如果不是数字字符&#xff0c;输出“error”。 - 如果是数字字符&#xff0c;进行以下判断&#xff1a; - 如果输入是0&#xff0c;输出“first”和1。 - 如果输入是9&#xff0c;输出8…

MACOS安装配置前端开发环境

官网下载安装Mac版本的谷歌浏览器以及VS code代码编辑器&#xff0c;还有在App Store中直接安装Xcode&#xff08;里面自带git&#xff09;&#xff1b; node.js版本管理器nvm的下载安装如下&#xff1a; 参考B站&#xff1a;https://www.bilibili.com/video/BV1M54y1N7fx/?sp…

【Python 千题 —— 基础篇】评论倾向分析

Python 千题持续更新中 …… 脑图地址 👉:⭐https://twilight-fanyi.gitee.io/mind-map/Python千题.html⭐ 题目描述 在某个电商平台的评论系统中,用户可以提交商品评论。为了分析评论的情感倾向,我们需要编写一个程序来处理用户评论,并对评论内容进行简单的分析和处理。…

在java中如何使用etcd的v2 和v3 api获取配置,并且对配置的变化进行监控和监听

etcd 和zookeeper 很像&#xff0c;都可以用来做配置管理。并且etcd可以在目前流行的Kubernetes中使用。 但是etcd 提供了v2版本合v3的版本的两种api。我们现在分别来介绍一下这两个版本api的使用。 一、Etcd V2版本API 1、java工程中使用maven引入 etcd v2的java api操作ja…

Pytorch实现自然风光图像场景分类识别(含训练代码和数据集)

Pytorch实现自然风光图像场景分类识别(含训练代码和数据集) 目录 Pytorch实现自然风光图像场景分类识别(含训练代码和数据集) 1. 前言 2. 自然风光图像场景分类数据集 &#xff08;1&#xff09;自然风光图像场景分类数据集 &#xff08;2&#xff09;自定义数据集 3. 自…

震惊!更换GPU会改变LLM的行为

文章目录 新发现前言1. Why this article ?2. Setup the experimentation3. The experiment results&#xff1a;A100/A10/30904. Why is it different?5. Why do the calculation differ depending on the GPU ?结论 新发现 最近在做RAG相关的工作&#xff0c;偶然间发现&a…

swift自定义数据集微调Qwen-7B大模型,转换模型后使用ollama跑起来

前文&#xff1a;swift微调Qwen-7B大模型-CSDN博客 我详细介绍了swift如何进行微调&#xff0c;但数据集均来自魔搭社区&#xff0c;如何想训练自定义数据集&#xff0c;实际上也很简单。 一、自定义数据集微调 export MKL_THREADING_LAYERGNU \ CUDA_VISIBLE_DEVICES0,1,2…

STM32:TIM定时中断配置的最全库函数讲解笔记

声明&#xff1a;本博客为哔哩哔哩up主江协科技 “STM32入门教程”的听课笔记&#xff0c;仅供学习、参考使用&#xff0c;不得用作其他用途&#xff0c;违者必究。如有版权问题&#xff0c;请联系作者修改。 目录 一、综述 二、TIM库 初始化 2.1、TIM_DeInit 恢复缺省值 …

经典文献阅读之--ParkingE2E(基于摄像头的端到端停车网络:从图像到规划)

0. 简介 自动泊车是智能驾驶领域的一项关键任务。传统泊车算法通常采用基于规则的方案来实现。然而&#xff0c;由于算法设计的复杂性&#xff0c;这些方法在复杂的泊车场景中效果欠佳。相比之下&#xff0c;基于神经网络的方法往往比基于规则的方法更加直观且功能多样。通过收…

中国的铁路订票系统在世界上属于什么水平?

每到节假日&#xff0c;中国的铁路订票系统总会成为人们热议的焦点。无论是“抢票大战”还是“秒杀特价票”&#xff0c;这一系统似乎总是牵动着亿万乘客的心。那么&#xff0c;中国的铁路订票系统到底有多强大&#xff1f;在全球范围内&#xff0c;它处于什么水平&#xff1f;…

Java_ElasticSearch(ES)——分布式搜索引擎

介绍&#xff1a; Elasticsearch是一个开源的分布式搜索和分析引擎&#xff0c;最初由Elastic公司开发。它构建在Apache Lucene搜索引擎库之上&#xff0c;提供了一个强大的全文搜索和分析引擎&#xff0c; 它结合kibana、Logstash、Beats&#xff0c;是一整套技术栈&#xff0…

C语言——简单的do while循环找100~999之间的水仙花数(所有的三位水仙花数)

这道题的关键是如何把这个三位数的个位、十位、百位表示出来 这里用到了 / &#xff08;整除&#xff09;和 % &#xff08;取余&#xff09;这两个运算符 #include<stdio.h> int main() { int num 100; do { int a; int b; int …

手把手教你:用sentence-transformers库进行文本嵌入

在 Python 中使用 sentence-transformers 库进行实操&#xff0c;你可以按照以下步骤进行&#xff1a; 1. 安装 sentence-transformers 库 首先&#xff0c;确保你已经安装了 sentence-transformers。如果没有&#xff0c;可以通过 pip 安装&#xff1a; pip install sentenc…

图像去噪评论:从经典到最先进的方法

系列文章目录 文章目录 系列文章目录前言摘要1 引言1.1.噪声抑制 2. 空间域过滤2.1.局部滤波器2.2.非局部滤波器 3.变换域滤波3.1.阈值3.1.1. 通用阈值3.1.2. VISUShrink3.1.3.SURE收缩3.1.4.BayesShrink3.1.5.概率收缩3.1.6.SURELET3.1.7.Neigh Shrink Sure&#xff08;NSS&am…

十一头像红旗怎么弄的?3个方法轻松教会你!

国庆佳节渐行渐至&#xff0c;朋友圈里早已掀起了一股更换国庆主题头像的热潮&#xff01;那些五彩斑斓、光彩夺目的渐变国旗头像&#xff0c;既美观又富有节日气氛。如果你也想加入这个行列&#xff0c;那么如何动手制作呢&#xff1f;别担心&#xff0c;接下来我将为你介绍三…