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

前言

最近因为业务需要,需要做一款 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.haphdc 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.happausehdc 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 的 CellLayoutPageDesktopLayout();}.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 的 HotseatSmartDock();}.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. 如果类型是 APPif (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() {// 又是一个 AppBubbleAppBubble({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.rhkb.cn/news/413016.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp小程序怎么判断滑动的方向

项目场景&#xff1a; 获取手机上手指滑动的距离超过一定距离 来操作一些逻辑 解决方案&#xff1a; 在uniapp中&#xff0c;可以通过监听触摸事件来判断滑动的方向。常用的触摸事件包括touchstart, touchmove, 和 touchend。通过这些事件的参数&#xff0c;可以计算出用户的滑…

【Android】最好用的网络库:Retrofit

最好用的网络库&#xff1a;Retrofit 文章目录 最好用的网络库&#xff1a;RetrofitRetrofit的基本用法Retrofit的使用逻辑Retrofit的基本操作处理复杂的接口地址类型进阶删除提交header中指定参数 Retrofit构建器的最佳写法Retrofit的使用封装 Retrofit的基本用法 Retrofit是一…

html2Canvas和jspdf导出长pdf

续使用html2canvas和jspdf导出pdf包含跨页以及页脚_jspdf.umd.min.js-CSDN博客我的这篇文章再写一种情况因为最近我也使用到了 具体的html2Canvas和jspdf的我就不说了&#xff0c;直接开始了&#xff0c; 在公共方法的文件夹中建立一个新的文件htmlToPdf.js用来写咱们得方法然…

SpringBoot SSM vue在线作业考试系统

SpringBoot SSM vue在线作业考试系统 首页 图片轮播 作业信息 通知公告 登录注册 留言板 个人中心 我的收藏 后台管理 登录注册 个人中心 教师信息管理 学生信息管理 学院信息管理 专业信息管理 班级信息管理 作业信息管理 作业提交管理 通知公告管理 试卷管理 试题管理 系统…

关于LLC知识14

1、LLC必须工作在感性区 2、为了降低LLC进入容性区后MOS管的电流应力&#xff0c;必须要选择快管&#xff0c;对体二极管的反向恢复参数有要求&#xff1a;trr<200ns 3、对于上下管的死区时间不能太短&#xff0c;否则电容无法充放电完成&#xff0c;就无法实现ZVS导通 如…

Nginx简单的安全性配置

文章目录 引言I Nginx简单的安全性配置禁止特定的HTTP方法限制URL长度禁止某些用户代理限制请求速率连接限制禁止访问某些文件类型II 常见的安全规则防御CC攻击User-Agent过滤GET-URL过滤GET-参数过滤POST过滤(sql注入、xss攻击 )引言 Nginx本身并不具备复杂的防火墙规则定制…

LeetCode题集-1- 两数之和

这个题目是什么意思呢&#xff1f;简单来说就是在一个数组中找出两个元素&#xff0c;使其和为我们设定的值&#xff0c;并且每个元素只能用一次。 如下图具体示例&#xff1a; 到这里不知道你是否已经有解题思路了呢&#xff1f; 解法一&#xff1a;双层循环 我第一反应就是…

2024了,Neo4j能显示节点图片吗?

经过一番调研&#xff0c;答案是官方的是不能的.但有一个中文版可以显示网络图片作为节点背景 如通义千问说说&#xff1a; Neo4j 图数据库本身并不直接支持在节点中存储和显示图片。但是&#xff0c;你可以通过几种方式间接实现这一功能&#xff1a;1. 存储图片URL 最简单的…

【数据结构】关于哈希表内部原理,你到底了解多少???(超详解)

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解关于哈希表的内部实现原理&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;http://t.csdnimg.cn/7D225 &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 目录 &a…

6.2K star!推荐一款开源混沌工程测试平台:Chaos Mesh

1、Chaos Mesh 介绍 Chaos Mesh是一个开源的混沌工程平台&#xff0c;旨在帮助用户在生产环境中测试、验证和优化其应用程序的可靠性和稳定性。通过引入故障注入和混沌工程原则&#xff0c;Chaos Mesh可以模拟各种故障场景&#xff0c;如网络延迟、节点故障、磁盘故障等&#…

JavaWeb JavaScript ⑧ DOM编程

在光芒万丈之前&#xff0c;我们都要欣然接受眼下的难堪和不易&#xff0c;接受一个人的孤独和无助&#xff0c;认真做好眼前的每一件事&#xff0c;你想要的都会有 —— 24.8.29 一、什么是DOM编程 简单来说&#xff1a;DOM(Document obiect Model)编程就是使用document对象的…

重大内幕!揭秘数据“零丢失”,全靠它

2017年&#xff0c;某运营商设备扩容&#xff0c;误删80万用户数据… 2020年初疫情期间&#xff0c;某电商公司恶意删库事件&#xff0c;导致业务停机3天&#xff0c;公司赔付1.5亿元人民币 “链家程序员删库”事件&#xff0c;恶意删除公司 9TB 数据&#xff0c;造成公司财务…

鸿蒙HarmonyOS开发:如何灵活运用服务卡片提升用户体验

文章目录 一、ArkTS卡片相关模块二、卡片事件能力说明三、卡片事件的主要使用场景3.1、使用router事件跳转到指定UIAbility3.1.1、卡片内按钮跳转到应用的不同页面3.1.2、服务卡片的点击跳转事件 3.2、通过message事件刷新卡片内容3.2.1、在卡片页面调用postCardAction接口触发…

网络安全的历史

如今&#xff0c;网络安全几乎成为各大公司和利益相关者关注的焦点。但在早期&#xff0c;网络安全的概念非常模糊。 直到多年以后&#xff0c;由于网络攻击和危险实体威胁的频繁发生&#xff0c;网络安全的发展才受到重视。这些措施的发展成为了网络安全的演变。 网络安全起…

CentOS全面停服,国产化提速,央国企信创即时通讯/协同门户如何选型?

01. CentOS停服带来安全新风险&#xff0c; 国产操作系统迎来新的发展机遇 2024年6月30日&#xff0c;CentOS 7版本全面停服&#xff0c;于2014年发布的开源类服务器操作系统——CentOS全系列版本生命周期画上了句号。国内大量基于CentOS开发和适配的服务器及平台&#xff0c…

500Kg载重无线遥控履带式无人车技术详解

500Kg载重无线遥控履带式无人车是一种专为复杂环境与多样化任务设计的高科技产品&#xff0c;具备卓越的机动性、承载能力和无线遥控功能。以下是对其技术特点的详细解析&#xff1a; 一、技术特点 履带式驱动系统 地形适应性&#xff1a;采用履带式驱动&#xff0c;能够…

实时图像编辑大革新!Adobe发布TurboEdit:可以通过文本来编辑图像,编辑时间<0.5秒!

今天给大家介绍Adobe研究院新的研究TurboEdit&#xff0c;可以通过文本来编辑图像&#xff0c;通过一句话就能改变图像中的头发颜色、衣服、帽子、围巾等等。而且编辑飞快&#xff0c;<0.5秒。简直是图像编辑的利器。 相关链接 项目&#xff1a;betterze.github.io/TurboE…

问题-解决

1. 在collection中的SetTest4_Student上面 此时我想解决LinkedHashSet的自定义降序身高问题是现在不行了 难道只能在构造器里面完成吗 还是说明只能为int类型 Double.compare(o1.getHeight(),o2.getHeight()) 在这道题中我在使用为什么不通过 Map<String, Integer>…

视频结构化从入门到精通——图像算法类型介绍

视频结构化主要图像算法 1 认识“数组、矩阵和张量” 1.1 什么是维度 在图像算法中&#xff0c;“维度”这个概念非常重要&#xff0c;它描述了数据的结构和形状。在不同的上下文中&#xff0c;维度可能有不同的含义&#xff0c;但总体来说&#xff0c;它们都与数据的排列方式…

【WiFi主要技术学习2】

WiFi协议学习2 WiFi SPEC理解频段信道带宽协商速率安全与加密WiFi主要技术理解BP直接序列扩频(Direct Sequence Spread Spectrum,DSSS)BPSKQPSK正交幅度调制(Quadrature Amplitude Modulation,QAM)互补码键控(Complementary Code Keying,CCK)正交频分复用(Orthogonal…