【 OpenHarmony 4.1 Launcher 源码解析 】-- 初体验

news2024/11/24 18:46:31

前言

最近因为业务需要,需要做一款 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/2101030.html

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

相关文章

【Anaconda】修改jupyter notebook默认打开的工作目录、jupyter notebook快捷键

jupyter notebook快捷键 针对单元格的颜色蓝色命令行模式绿色编辑模式 两种模式的切换编辑模式切换到命令行模式 >>> esc键命令行模式切换到编辑模式 >>> 鼠标左键或者直接按enter键1.标题的书写方式1:1.esc进入命令行模式2.按m键3.写内容4.运行单元格即可方…

读懂以太坊源码(2)-重要概念Gas

在以太坊中&#xff0c;gasLimit、gasUsed和gasPrice是三个重要的概念&#xff0c;它们之间有特定的含义和关系。 一、含义 gasLimit&#xff1a; 含义&#xff1a;每个区块或每笔交易都有一个 gas 限制。对于一个区块来说&#xff0c;gasLimit是该区块中所有交易可以消耗的最…

日常避坑指南:如何合理利用Swap优化MongoDB内存管理

MongoDB作为一款高性能的NoSQL数据库,广泛应用于大数据处理和实时应用中。然而,面对批量数据写入时,MongoDB对内存的需求极为苛刻,尤其是在测试服务器或资源受限的环境下,容易引发系统性能问题。本文将探讨如何通过合理利用Swap来优化MongoDB的内存管理,确保系统的稳定运…

Unity(2022.3.41LTS) - UI详细介绍-Scroll View(滚动视图)

目录 零.简介 一、基本功能与用途 二、主要组件 Rect Transform&#xff08;矩形变换&#xff09;&#xff1a; Scroll Rect&#xff08;滚动矩形&#xff09;组件&#xff1a; Scrollbar&#xff08;滚动条&#xff09;组件&#xff1a; Mask&#xff08;遮罩&#xff…

算法笔试-编程练习-M-01-24

t这套题&#xff0c;偏向灵活&#xff0c;更多的考察了数学、贪心 一、质因数 题目描述 小乖对 gcd (最大公约数) 很感兴趣, 他会询问你t次。 每次询问给出一个大于 1 的正整数 n, 你是否找到一个数字m(2 ≤m ≤ n)&#xff0c;使得 gcd(n,m)为素数. 注&#xff1a;原题为给…

构建高效智慧水务平台的关键要素有哪些?

在推进智慧水务平台建设的过程中&#xff0c;需着重注意以下几点&#xff0c;以确保系统的先进性与实用性并重&#xff1a; 数据集成与标准化‌&#xff1a;构建统一的数据采集与处理标准&#xff0c;实现多源水务数据的无缝集成与高效管理&#xff0c;为精准决策提供坚实的数据…

逆向工程核心原理 Chapter24 | DLL卸载

DLL卸载技术的学习。 DLL卸载原理 DLL注入&#xff08;Injection&#xff09;是将DLL强制加载进进程的技术。 DLL卸载&#xff08;Ejection&#xff09;则是将强制插入进程的DLL弹出的技术。 两者的原理都十分类似&#xff0c;都用CreateRemoteThread来实现&#xff1a; I…

拼图软件推荐哪个好?日常拼图用这5个就够了

夏日悠长&#xff0c;暑假悄然过去&#xff0c;那些阳光灿烂的日子&#xff0c;是不是已经装满了你的相机和手机&#xff1f; 现在&#xff0c;是时候把这些珍贵的记忆碎片&#xff0c;用创意的拼图方式&#xff0c;编织成一本独一无二的暑假相册了&#xff01; 那么&#xf…

聚鼎装饰画:怎么做好一家装饰画店铺

在当今这个讲究美学和个性化的时代&#xff0c;拥有一家装饰画店铺无疑蕴含着巨大的潜力。然而&#xff0c;要想在这个竞争激烈的市场中崭露头角&#xff0c;不仅需要对艺术有着深厚的理解&#xff0c;还需要具备一定的商业头脑。本文将探讨几个关键的策略&#xff0c;帮助装饰…

行业首家!百度智能云通过中国信通院「H5 端人脸识别安全能力」测评

2024 年 6 月&#xff0c;在中国信通院组织的 H5 端人脸识别产品安全能力评测中&#xff0c;百度智能云「H5 实时活体检测产品-V3.0」在人脸识别算法安全能力、人脸数据传输安全能力、H5 端应用安全能力、身份认证业务安全能力、安全管理能力 5 个方面表现优异&#xff0c;相应…

NameNode 的 Web 界面

http://127.0.0.1:50070/ 图片显示的是Hadoop的Web界面导航栏。导航栏包含以下选项&#xff1a; Hadoop&#xff1a;Hadoop的主页。Overview&#xff1a;集群的概览信息。Datanodes&#xff1a;数据节点的状态和信息。Datanode Volume Failures&#xff1a;数据节点的卷故障信…

SAP B1 三大基本表单标准功能介绍-物料主数据(下)

背景 在 SAP B1 中&#xff0c;科目表、业务伙伴主数据、物料主数据被称为三大基本表单&#xff0c;其中的标准功能是实施项目的基础。本系列文章将逐一介绍三大基本表单各个字段的含义、须填内容、功能等内容。 附上 SAP B1 10.0 的帮助文档&#xff1a;SAP Business One 10…

给大模型加上“记忆”,深入探索 Mem0 项目

背景介绍 在之前的软件应用中&#xff0c;我们总会在应用中保留大量的用户历史操作记录&#xff0c;方便用户下次使用时可以快速查看和复用&#xff0c;甚至基于这些用户记录可以为用户提供个性化的服务。而这些记录往往都保存在传统的结构化或非结构化数据库中。 在大模型的…

高校为什么需要AIGC大数据实验室?

AIGC大数据实验室是一个专注于人工智能生成内容&#xff08;AIGC&#xff09;和大数据相关技术研究、开发与应用的创新实验平台。 AIGC主要研究方向包括&#xff1a;AIGC技术创新、大数据处理与分析、AIGC 与大数据融合应用。 AIGC 技术创新&#xff1a;探索如何利用人工…

企业微信hook协议接口,聚合群聊客户管理工具开发

服务提供了丰富的API和SDK&#xff0c;可以在企微的功能之上进行应用开发和功能扩展 自建应用可以调用企微hook或协议提供的接口来实现数据交互&#xff0c;可以直接调用hook或协议接口提供的功能来进行消息的发送与接收、用户管理、应用管理等操作&#xff0c;通过接口可以实…

线性代数教材书籍推荐

INTRODUCTION TO LINEAR ALGEBRA, 线性代数导论&#xff0c;GILBERT STRANG &#xff0c;有第六版中译本&#xff0c;网上也有第五版英文电子版&#xff0c;个人认为讲理论最好的教材 Practical Linear Algebra for Data Science&#xff0c;From Core Concepts to Applicatio…

相机常见名词详解

本文主要参考超人视觉课程做的笔记&#xff0c;有讲解不太懂的&#xff0c;又做了详细的解释 1、物距&#xff1a;物体到镜片的距离&#xff1b; 2、像距&#xff1a;像到镜片的距离&#xff1b; 3、焦距&#xff1a;镜片到焦点的距离&#xff1b; (1)二倍焦距以外&#xff…

LLM指令微调实践与分析

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

毛辊清洗机的优势:

毛辊清洗机作为一种高效的清洗设备&#xff0c;在食品加工、农产品处理等多个领域得到了广泛应用。其主要优点可以归纳如下&#xff1a; 一、清洗效率高 有效容积大&#xff1a;毛辊清洗机设计有足够大的清洗空间&#xff0c;能够一次性处理大量的物料&#xff0c;如土豆、胡…

智能视频监控平台LntonAIServer安防监控视频平台视频质量诊断功能使用说明

LntonAIServer视频质量诊断功能是一种先进的技术&#xff0c;旨在通过智能分析来评估和优化视频流的质量。这种功能通常集成在视频传输和管理平台中&#xff0c;以提供对视频内容的实时监控和质量控制。以下是关于LntonAIServer视频质量诊断功能的使用说明阐述&#xff1a; 首…