【 OpenHarmony 系统应用源码解析 】-- Launcher 初体验

news2024/9/28 11:24:16

前言

最近因为业务需要,需要做一款 UI 定制的鸿蒙 Launcher,于是就开始了「找到代码」、「研究代码」、「魔改代码」的套路流程,仅以此文章作为知识备份和技术探讨所用,也希望能给其他小伙伴提供一些源码的解析思路,方法大家各自魔改!


一、官方简介

Gitee codes:应用子系统/Launcher

Launcher 作为系统人机交互的首要入口,提供应用图标的显示、点击启动、卸载应用,并提供桌面布局设置以及最近任务管理等功能。

Launcher 采用扩展的 TS 语言(ArkTS)开发

1.1 主要结构

在这里插入图片描述

1.2 分层说明

Module层级说明
product业务形态层区分不同产品、不同屏幕的各形态桌面,含有桌面窗口、个性化业务,组件的配置,以及个性化资源包。
feature公共特性层抽象的公共特性组件集合,可以被各桌面形态引用。
common公共能力层基础能力集,每个桌面形态都必须依赖的模块。

1.3 目录结构

/applications/standard/launcher/
├── common                    # 公共能力层目录
├── docs                      # 开发指南
├── feature                   # 公共特性层目录
│   └── appcenter             # 应用中心
│   └── bigfolder             # 智能文件夹
│   ├── form                  # 桌面卡片管理功能
│   ├── gesturenavigation     # 手势导航
│   ├── pagedesktop           # 工作区
│   ├── recents               # 最近任务
│   ├── settings              # 桌面设置
│   ├── smartdock             # dock工具栏
├── product                   # 业务形态层目录
├── signature                 # 签名证书

1.4 开发调试

IDE 下载:建议大家直接下载 OpenHarmony 4.1 Release DevEco-Studio 吧,API 支持 8 ~ 11

在这里插入图片描述

1.5 SDK

Launcher 应用的编译需使用相对应版本的 ohos-sdk-full \ mac-sdk-full 来进行开发调试。

IDE 上是 Public SDK,故 full sdk 需要重新下载,下载地址:

新版本界面:http://ci.openharmony.cn/workbench/cicd/dailybuild/dailylist

老版本界面:http://ci.openharmony.cn/dailys/dailybuilds

具体下载及如何替换这边就不啰嗦了,大家直接看 Gitee 介绍自行替换。

1.6 签名配置

关于签名配置,也不啰嗦了,下载的代码自带的文件都已经配置好,无需自己手动签名。

1.7 替换 Launcher

使用以下命令来更新编译出来的 Launcher 部件 hap 包:

ren phone_launcher-default-signed.hap Launcher.hap
ren launcher_settings-phone_launcher-default-signed.hap Launcher_Settings.hap

hdc target mount
hdc shell rm -rf /data/misc_de/0/mdds/0/default/bundle_manager_service
hdc shell rm -rf /data/accounts
hdc shell mount -o remount,rw /
hdc file send .\Launcher.hap /system/app/com.ohos.launcher/Launcher.hap
hdc file send .\Launcher_Settings.hap /system/app/com.ohos.launcher/Launcher_Settings.hap

pause

hdc shell mount -o remount,rw /
hdc shell rm /data/ -rf
hdc shell sync /system/bin/udevadm trigger
hdc shell reboot

二、编译运行

2.1 分支选择

拉完官方示例代码后,可以看到很多分支,我选了 OpenHarmony-4.1-Release 作为魔改的基础分支,当然你也可以根据需要选择别的分支(我是着实看不懂,搞这么多分支干什么,而且基本上彼此分支的 UI 效果大差不差,几乎所有 Openharmony 自带的系统应用 Demo UI 及功能逻辑都很 low,所以凡事靠自己,自己魔改吧!)

在这里插入图片描述

2.2 打开工程 / 编译 hap

切到对应分支后,即可打开工程,等待同步完成,如下图即可。

在这里插入图片描述

接下来可以编译 hap 包:

在这里插入图片描述

接着找到需要的 hap 包,重命名,替换后重启:

在这里插入图片描述

默认 Launcher 效果:(我手里有一台平板,所以就以平板为示例,效果要比手机少一点)

在这里插入图片描述


三、Launcher 首页

3.1 MainAbility

export default class MainAbility extends ServiceExtension {
  onCreate(want: Want): void {
    Log.showInfo(TAG,'onCreate start');
    this.context.area = 0;
    this.initLauncher();
  }

  async initLauncher(): Promise<void> {
    /**
     * 1. init Launcher context
     *    初始化上下文
     */
    globalThis.desktopContext = this.context;

    /**
     * 2. init global const
     *    初始化全局变量
     */
    this.initGlobalConst();

    /**
     * 3. init Gesture navigation
     *    初始化手势导航
     */
    this.startGestureNavigation();

    /**
     * 4. init rdb
     *    初始化 rdb
     */
    let dbStore = RdbStoreManager.getInstance();
    await dbStore.initRdbConfig();
    await dbStore.createTable();

    let registerWinEvent = (win: window.Window) => {
      win.on('windowEvent', (stageEventType) => {
        // 桌面获焦或失焦时,通知桌面的卡片变为可见状态
        if (stageEventType === window.WindowEventType.WINDOW_ACTIVE) {
          localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_FORM_ITEM_VISIBLE, null);
          Log.showInfo(TAG, `lifeCycleEvent change: ${stageEventType}`);
        }
      })
    };
    
	/**
	 * 5. 注册窗口事件
	 */
    windowManager.registerWindowEvent();
    
    /**
     * 6. 注册导航栏事件
     */
    navigationBarCommonEventManager.registerNavigationBarEvent();

    /**
     * 7. create Launcher entry view
     *    创建桌面窗口
     *    WindowManager.ts --> DESKTOP_WINDOW_NAME = 'EntryView';
     *    加载 pages/EntryView
     */
    windowManager.createWindow(globalThis.desktopContext, windowManager.DESKTOP_WINDOW_NAME,
      windowManager.DESKTOP_RANK, 'pages/' + windowManager.DESKTOP_WINDOW_NAME, true, registerWinEvent);

    /**
     * 8. load recent,加载 Recent 窗口
     */
    windowManager.createRecentWindow();
    this.registerInputConsumer();
  }

  ...
}

MainAbility 创建了桌面窗口:pages/EntryView

3.2 EntryView

📄 EntryView.ets

@Entry
@Component
struct EntryView {

  build() {
    Stack() {
      Flex({ direction: FlexDirection.Column, ... }) {
        Column() {
          // 1. 桌面布局,类似于 Android Launcher 的 CellLayout
          PageDesktopLayout();
        }
        .height(this.workSpaceHeight)
        .onAreaChange((oldValue: Area, newValue: Area) => {
          Log.showDebug(TAG, `onAreaChange navigationBarStatus: ${this.navigationBarStatus}`);
          if (JSON.stringify(oldValue) == JSON.stringify(newValue)) return;
          if (this.navigationBarStatus == "1") {
            setTimeout(() => {
              SettingsModel.getInstance().setValue(this.navigationBarStatus);
            }, 50)
          }
        })

        Column() {
          // 2. Dock 区域,类似于 Android 的 Hotseat
          SmartDock();
        }
        .height(this.dockHeight)
      }

      FolderOpenComponent();
    }
    .backgroundImage(StyleConstants.DEFAULT_BACKGROUND_IMAGE)
    .backgroundImageSize(ImageSize.Cover)
    .backgroundImagePosition(Alignment.Center)
    .width('100%')
    .height('100%')
  }

}

3.3 PageDesktopLayout()

所以,我们再来看看 PageDesktopLayout() 的源码:

@Component
export struct PageDesktopLayout {

  build() {
    // 自定义的 GridSwiper 组件
    GridSwiper({
      gridConfig: this.gridConfig,
      mPageDesktopViewModel: mPageDesktopViewModel,
      dialogController: this.deviceType == CommonConstants.PAD_DEVICE_TYPE ? null : this.dialogController
    }).id(`${TAG}`)
      .width(StyleConstants.PERCENTAGE_100)
      .height(StyleConstants.PERCENTAGE_100)
  }

}

3.4 GridSwiper

继续跟踪源码:

@Component
export default struct GridSwiper {

  build() {
    Column() {
      if (this.buildLog()) {}
        if (this.desktopLoadFinished) {
          // 1. 轮播布局
          Swiper(this.swiperController) {
            ForEach(this.pageList, (item: number, index: number) => {
              // 判断设备类型
              if (AppStorage.get('deviceType') == CommonConstants.DEFAULT_DEVICE_TYPE) {
                Column() {
                  SwiperPage({
                    appListInfo: $appListInfo,
                    swiperPage: index.valueOf(),
                    gridConfig: this.gridConfig,
                    mPageDesktopViewModel: this.mPageDesktopViewModel
                  }).id(`SwiperPage_${item}${index}`)
                }
                .gesture(
                LongPressGesture({ repeat: false })
                  .onAction((event: GestureEvent) => {
                    this.dialogController?.open();
                  })
                )
                .bindContextMenu(this.MenuBuilder, ResponseType.RightClick)
              } else {
                SwiperPage({
                  appListInfo: $appListInfo,
                  swiperPage: index.valueOf(),
                  gridConfig: this.gridConfig,
                  mPageDesktopViewModel: this.mPageDesktopViewModel
                }).id(`SwiperPage_${item}${index}`)
                  .bindContextMenu(this.MenuBuilder, ResponseType.LongPress)
                  .bindContextMenu(this.MenuBuilder, ResponseType.RightClick)
              }
            }, (item: number, index: number) => {
              return `${item}${index}`;
            })
          }
          .id(`${TAG}_Swiper`)
          ...
        }
    }
    .id(`${TAG}`)
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .height(StyleConstants.PERCENTAGE_100)
    .width(StyleConstants.PERCENTAGE_100)
  }

}

我们忽略掉一些多余的代码,只看核心部分,发现都会调用 SwiperPage 组件,我们继续跟:

3.5 SwiperPage

@Component
export default struct SwiperPage {

  build() {
    // 1. 网格布局
    Grid() {
      ForEach(this.mAppListInfo, (item: LauncherDragItemInfo, index: number) => {
        // 2. 自组件
        GridItem() {
          if (this.buildLog(item)) {
          }
          // 3. 如果类型是 APP
          if (item.typeId === CommonConstants.TYPE_APP) {
            // 4. 具体每一个应用
            AppItem({
              item: item,
              mPageDesktopViewModel: this.mPageDesktopViewModel,
              mNameLines: this.mNameLines
            }).id(`${TAG}_AppItem_${index}`)
          } else if (item.typeId === CommonConstants.TYPE_FOLDER) {
            FolderItem({
              folderItem: item,
              mPageDesktopViewModel: this.mPageDesktopViewModel,
              mNameLines: this.mNameLines
            }).id(`${TAG}_FolderItem_${index}`)
          } else if (item.typeId === CommonConstants.TYPE_CARD) {
            FormItem({
              formItem: item
            }).id(`${TAG}_FormItem_${index}`)
          }
        }
        .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}`)
    ...
  }

}

3.6 AppItem

@Component
export default struct AppItem {

build() {
    Column() {
      // 又是一个 AppBubble
      AppBubble({
        iconSize: this.mIconSize,
        nameSize: this.mAppNameSize,
        nameHeight: this.mAppNameHeight,
        nameFontColor: this.mPageDesktopViewModel?.getPageDesktopStyleConfig().mNameFontColor as string,
        appName: this.item.appName,
        bundleName: this.item.bundleName,
        abilityName: this.item.abilityName,
        moduleName: this.item.moduleName,
        appIconId: this.item.appIconId,
        appLabelId: this.item.appLabelId,
        badgeNumber: this.item.badgeNumber,
        isSelect: this.selectDesktopAppItem == this.item.keyName,
        getMenuInfoList: this.getMenuInfoList,
        mPaddingTop: this.mMarginVertical,
        nameLines: this.mNameLines,
        mIconNameMargin: this.mIconNameMargin,
        dragStart: this.dragStart
      })
    }
    .visibility(...)
    .onMouse((event: MouseEvent) => {
      ...
    })
    .onClick((event) => {
      ...
    })
    .onTouch((event: TouchEvent) => {
      ...
    })
    .width(this.mAppItemWidth)
    .height(this.mAppItemWidth)
  }

}

3.7 AppBubble

@Component
export struct AppBubble {

  build() {
    Column() {
      Column() {
        Column() {
          // 应用图标
          AppIcon({
            iconSize: this.iconSize,
            iconId: this.appIconId,
            bundleName: this.bundleName,
            moduleName: this.moduleName,
            icon: ResourceManager.getInstance().getCachedAppIcon(this.appIconId, this.bundleName, this.moduleName),
            badgeNumber: this.badgeNumber,
            useCache: this.useCache
          })
        }
        .onDragStart((event: DragEvent, extraParams: string) => {
          return this.dragStart(event);
        })
        .bindContextMenu(this.MenuBuilder, ResponseType.LongPress)
        .onDragEnd((event: DragEvent, extraParams: string) => {
          ...
        })

		// 应用名称
        AppName({
          nameHeight: this.nameHeight,
          nameSize: this.nameSize,
          nameFontColor: this.nameFontColor,
          bundleName: this.bundleName,
          moduleName: this.moduleName,
          appName: this.appName,
          labelId: this.appLabelId,
          useCache: this.useCache,
          nameLines: this.nameLines,
          marginTop: this.mIconNameMargin
        })
      }
      .bindContextMenu(this.MenuBuilder, ResponseType.RightClick)
      ...
    }
    .parallelGesture(
      ...
    )
  }

}

看到这,是不是整个桌面的图标区域结构豁然开朗?看个图:

在这里插入图片描述

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

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

相关文章

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——8.stackqueuepriority_queue(模拟实现)

1.stack 可通过模板使用其他类来建立stack&#xff08;如vector&#xff0c;list&#xff09; #include<vector>namespace zone {template<class T,class container> //两个模板参数class stack{public:void push(const T& x){it.push_back(x); //使用it的p…

【Linux】命令简介------迅速掌握Linux命令

目录 Linux 命令 &#x1f354; ls命令 &#x1f354; cd 和 pwd命令 &#x1f354; 相对路径和绝对路径 &#x1f354; 文件/文件夹的创建以及文件内容的浏览 &#x1f354; 文件的复制,移动和删除 &#x1f354; 文件的查找 &#x1f354; grep 和管道 &#x1f354…

Windows11安装SqlLite、Navicat Premium 15连接SqlLite、Springboot集成SqlLite

一、Windows11安装SqlLite 1、下载安装包 地址&#xff1a;SQLite Download Page 2、压缩包解压 3、配置系统环境变量 4、验证安装是否成功 打开命令提示符&#xff0c;输入 sqlite3 5、创建数据库文件 新建文件重命名为你想要的数据库名称&#xff0c;文件后缀改为.db 二、…

【微信小程序】如何触发按钮事件,例如调起微信客服

需求 实现一个如下图的效果, 点击客服按钮, 调起微信客服功能, 需要和button组合使用 效果图 实现思路 客服只能通过button按钮调起, 所以我们需要写一个button按钮, open-type“contact”, 然后把它隐藏起来。给客服图标加一个label, 设置for“btnId”, 这样点击图片就会触…

微服务即时通讯系统环境搭建(客户端)

微服务即时通讯系统环境搭建(客户端) 前言 今天开始&#xff0c;我们要开一个新坑&#xff0c;我们将它称作微服务即时通讯系统。说到即时通讯系统&#xff0c;大家肯定能想到如同“微信”这样的app。那么没错&#xff0c;这次这个项目就会像微信一样&#xff0c;当然功能肯定…

Linux(CentOS8)系统安装mysql-8.0.26-linux-glibc2.12-x86_64.tar.xz

一、下载获取 mysql安装包&#xff1b; MySQL :: Download MySQL Community Server (Archived Versions) 二、安装步骤 1、切换到安装目录下&#xff0c;并解压 tar -zxvf mysql-8.0.26-linux-glibc2.12-x86_64.tar.xz 2.移动解压后的文件并且重命名为mysql mv mysql-8.0.26…

Mybatis:基础巩固-DCL

目录 一、概述二、用户管理2.1 查询用户2.2 创建用户2.3 修改用户密码2.4 删除用户 三、权限控制3.1 查询权限3.2 赋予权限3.3 撤销权限 一、概述 DCL数据控制语言&#xff0c;用来管理数据库用户、控制数据库的访问和权限。简单来说就是可以让哪些用户可以访问哪些数据库。 二…

LiveQing视频点播流媒体RTMP推流服务功能-支持OBS推流摄像机RTMP推流支持无人机RTMP推流解决大疆无人机推流花屏问题完美解决大疆无人机花屏

LiveQing-支持OBS推流摄像机RTMP推流支持无人机RTMP推流解决大疆无人机推流花屏问题完美解决大疆无人机花屏 1、流媒体服务搭建2、推流工具准备3、创建鉴权直播间4、获取推流地址5、配置OBS推流6、推流及播放7、获取播放地址7.1 页面查看视频源地址7.2 接口查询 8、更多问题8.1…

黑屏环境下,如何利用OBD部署OceanBase企业版集群

一、前言 OBD&#xff0c;作为OceanBase官方推出的部署工具&#xff0c;显著简化了OB单机及集群的部署流程。此前&#xff0c;OBD能够支持对社区版OB进行一键部署&#xff0c;那OBD是否同样支持OB企业版的部署呢&#xff1f; 本文为大家介绍通过OBD&#xff0c;在OB企业版集群…

短视频矩阵系统怎么开发搭建使用?解决内容创作分发效率问题的工具系统

目录 前言 &#xff1a; 一、短视频矩阵系统开发目的 系统主要功能 二、怎么开发 前言 &#xff1a; 短视频矩阵系统是一种综合性的短视频营销工具&#xff0c;它集成了短视频创作、管理、分发、数据分析等多种功能于一体。 一、短视频矩阵系统开发目的 在帮助创作者和企…

C++中类的相关学习

动态内存分配和回收&#xff08;堆区&#xff09; C语言中动态内存分配和回收使用malloc函数和free函数完成的 C依旧可以使用上述的两个函数完成&#xff0c;动态内存分配和回收 C也可以使用两个关键字new和delete来完成动态内存的分配和回收 内存分配 单个申请 格式&…

Chapter 07 watch侦听器

欢迎大家订阅【Vue2Vue3】入门到实践 专栏&#xff0c;开启你的 Vue 学习之旅&#xff01; 文章目录 前言一、基本用法二、深度侦听 前言 在 Vue 中&#xff0c;watch 侦听器是一个非常实用的工具&#xff0c;用于处理自定义数据的变化。本文详细讲解了 watch 侦听器的基本用法…

Pytorch实现多层LSTM模型,并增加emdedding、Dropout、权重共享等优化

简述 本文是 Pytorch封装简单RNN模型&#xff0c;进行中文训练及文本预测 一文的延申&#xff0c;主要做以下改动&#xff1a; 1.将nn.RNN替换为nn.LSTM&#xff0c;并设置多层LSTM&#xff1a; 既然使用pytorch了&#xff0c;自然不需要手动实现多层&#xff0c;注意nn.RNN…

JVM1-初识JVM

目录 什么是JVM JVM的功能 解释和运行 内存管理 即时编译 Java性能低的主要原因和跨平台特性 常见的JVM 什么是JVM JVM 全称是 Java Virtual Machine&#xff0c;中文译名&#xff1a;Java虚拟机 JVM本质上是一个运行在计算机上的程序&#xff0c;它的职责是运行Java字…

AI大模型编写多线程并发框架(六十三):监听器优化·下

系列文章目录 文章目录 系列文章目录前言一、项目背景二、第十一轮对话-修正运行时数据三、修正任务计数器四、第十二轮对话-生成单元测试五、验证通过七、参考文章 前言 在这个充满技术创新的时代&#xff0c;AI大模型正成为开发者们的新宠。它们可以帮助我们完成从简单的问答…

C++,如何写单元测试用例?

文章目录 1. 概述1.1 什么是单元测试&#xff1f;1.2 为什么要做单元测试&#xff1f; 2. 写测试用例的方法3. 编写测试用例的通用原则3.1 目的性原则3.2 独立性原则3.3 可重复性原则3.4 小规模原则3.5 一致性原则3.6 自动化原则3.7 边界条件原则3.8 错误检测原则3.9 性能原则3…

西门子PLC控制激光读头,profient转Ethernet IP网关应用

在智能制造的浪潮下&#xff0c;企业对于生产线的灵活性、智能化水平以及数据交互能力提出了更高要求。西门子PLC以其高可靠性和丰富的功能模块&#xff0c;广泛应用于各种自动化生产线中。而激光读头作为精密测量与定位的关键设备&#xff0c;其高精度、非接触式测量特性在自动…

力扣862.和至少为K的最短子数组

力扣862.和至少为K的最短子数组 双端单调队列 前缀和 用单调队列存遍历过的前缀和&#xff0c;同时两个优化 1. 2. class Solution {public:int shortestSubarray(vector<int>& nums, int k) {int n nums.size(),ans n 1;long s[n1];s[0] 0L;for(int i0;i…

1999-2023年上市公司年报文本数据(PDF+TXT)

1999-2023年上市公司年报文本数据&#xff08;PDFTXT&#xff09; 1、时间&#xff1a;1999-2023年 2、来源&#xff1a;上市公司年度报告 3、范围&#xff1a;A股上市公司&#xff0c;5600企业&#xff0c;6.3W份 4、格式&#xff1a;PDFTXT 5、下载链接&#xff1a; 199…

东方通Web服务器(TongWeb)控制台部署改自动部署操作

首先将控制台部署改自动部署的应用进行解除部署&#xff0c;具体如下&#xff1a;登录TongWeb管理控制台&#xff0c;在左侧导航栏中点击“应用管理”&#xff0c;通过应用列表中第一列复选框选中要解除部署的应用&#xff0c;点击“解部署”&#xff0c;完成应用解除部署操作。…