乐趣区

关于前端:公开揭密团队成员开发鸿蒙-OpenHarmony-的完整过程收获官方7k奖金和开发板等2w字用心总结

背景

随着 OpenHarmony 组件开发大赛后果颁布,咱们的团队成员被告知取得了二等奖,在开心之余也想将咱们这段时间贵重的开发教训写下来与大家分享,当咱们看到参赛告诉的时候曾经是 9 月中旬的时候,此时曾经是作品能够提交的工夫了,参考了一些其余作品发现,基于 Canvas 开发的组件目前还没有,那咱们就开始打算写一个基于 Canvas 和通用组件一起开发的组件,在这之前因为并没有开发过 OpenHarmony 利用,咱们团队成员都没有相干的教训,大家从零开始在摸索,咱们首先分工合作,有的成员负责去下载 IDE 和调试设施,有的成员负责钻研和浏览官网文档。先附上源代码,分享和总结不易,求点赞关注一下⭐️:

https://github.com/Wscats/openharmony-sheet

配置

在浏览完官网文档之后,咱们成员别离在本人本地电脑和设施上做了以下的环境配置:

  1. 下载并装置好 DevEco Studio 2.1 Release 及以上版本
  2. 获取 OpenHarmony SDK 包并解压
  3. 配置 OpenHarmony SDK

DevEco 主界面,点击工具栏中的 File > Settings > Appearance & Behavior > System Settings > HarmonyOS SDK 界面,点击 HarmonyOS SDK Location 加载 SDK

而后始终点击 NextFinish 实现环境配置。

  1. 装置额定包,进入 OpenHarmony-SDK-2.0-Canary/js/2.2.0.0/build-tools/ace-loader 目录,而后在该目录下运行命令行工具,别离执行如下命令,直至装置实现
npm cache clean -f
npm install
  1. 下载 OpenHarmonyJSDemos 我的项目工程,将工程导入 DevEco Studio
  2. 申请并配置证书,留神 OpenHarmonyHarmonyOS 的证书不通用,所以须要额定进行申请
  3. 进行编译构建,生成一个 HAP 利用安装包,生成 HAP 利用安装包,装置到 OpenHarmony 开发板
  4. 装置运行后,在开发板屏幕上点击利用图标即可关上利用,即可在设施上查看利用示例运行成果,以及进行相干调试
  5. 除了应用真机调试,咱们还能够应用近程调试和本地的 Previewer 调试,尽管十分相当不便,但理论体现必定和真机是有略微差别的

前言

在实现 Canvas 利用之前,咱们通过一些磋商和探讨,首先是心愿能借助这一次开发晋升对 OpenHarmony 的了解,不便后续业务的反对,其次咱们团队成员也是心愿能拿到比拟好的名次和处分,咱们留神到较量的评分由评委打分,满分为 100 分,这里会依据作品的创意性、实用性、用户体验、代码标准等四个维度点评打分,Canvas 的利用首先实现老本会比一般利用难度略微大点,并且不好调试,在创意性和实用性上咱们劣势不大,因为大部分前端开发者接触到的 Canvas 利用都是游戏相干的,所以这条路注定是会绝对艰巨的,用户体验也是一个很大的难点,咱们真机测试发现 Canvas 的体现也不是很好,比原生一些组件的体验差很多,对于团队成员的代码品质是有信念的,然而代码标准的评分比重却是起码的,所以在立项的时候咱们有比拟大的一致。

评比维度 阐明 分值
创意性 作品的翻新水平 30%
实用性 作品在利用场景中的理论利用水平 30%
用户体验 用户体验价值,用户可能轻松应用组件,并取得良好体验感 25%
代码标准 代码的品质,好看度,是否符合规范 15%

打算

正因为由下面总总的疑虑,咱们先制订了三个打算和一个指标:

  • 应用根底组件和容器组件等实现通用组件 – OpenHarmonyGallery
  • 应用画布组件实现 Canvas 游戏 – OpenHarmonyFlappyBird
  • 应用根底组件,容器组件和画布组件实现 Canvas 渲染引擎 – OpenHarmonySheet

渲染引擎是咱们最终目标,尽管难度偏大,但咱们团队成员决定离开三步来实现该指标,首先至多先学会应用根底组件和容器组件,而后再学会应用画布组件,最初综合这些教训实现一个渲染引擎。

初体验

咱们首先实现了一个通用的画廊组件来作为练手我的项目,它次要应用了四个根底组件和容器组件:

咱们搁置一个按钮来触发 showGallery 办法,该办法管制 panel 弹出式组件的显示和暗藏,这里的 divbutton 标签就是 hml 内置的组件,跟咱们平时写 html 很类似,它反对咱们大部分的惯例属性如 idclasstype 等,不便咱们用来设置组件根本标识和外观特色显示。

<div class="btn-div">
  <button type="capsule" value="Click Here" onclick="showGallery"></button>
</div>

而后咱们 panel 组件中搁置可变更的画廊内容展现窗口,并让 modesrc 变成可设置的变量,这样画廊组件就能依据模式让画廊组件显示不同的状态,依据传入的图片地址显示不同的图片内容,这里的语法跟微信小程序很和 Vue 框架类似,都能够应用 Mustache 语法来管制属性值。

<panel
  id="gallery"
  class="gallery"
  type="foldable"
  mode="{{modeFlag}}}"
  onsizechange="changeMode"
>
  <div class="panel-div" onclick="closeGallery">
    <image class="panel-image" onclick="closeGallery" src="{{galleryUrl}}}"></image>
    <button
      class="panel-circle"
      onclick="closeGallery"
      type="circle"
      icon="/common/images/close.svg"
    ></button>
  </div>
</panel>

实现完视图和布局之后,咱们就能够在同级目录下 index.js 中补充画廊组件的逻辑,因为反对 ES6 语法,咱们写的也很难受很高效,这里的 data 是画廊组件的数据模型,类型能够是对象或者函数,如果类型是函数,返回值必须是对象,留神属性名不能以 $_ 结尾,不要应用保留字,咱们在这里给 modeFlaggalleryUrl 设置默认值。

export default {
  data: {
    modeFlag: "full",
    galleryUrl:
      "https://pic1.zhimg.com/v2-3be05963f5f3753a8cb75b6692154d4a_1440w.jpg?source=172ae18b",
  },
};

而显示和暗藏逻辑比较简单,只须要获取 panel 的节点,而后触发 show 或者 hide 办法即可,当然除了该办法,咱们还能够应用 渲染属性 来实现:

  • for 依据设置的数据列表,开展以后元素
  • if 依据设置的 boolean 值,增加或移除以后元素
  • show 依据设置的 boolean 值,显示或暗藏以后元素
showGallery(e) {this.$element('gallery').show()},
closeGallery(e) {if(e.target.type==='image') return
    this.$element('gallery').close()},

咱们还能够在同级目录下在 index.css 补充组件的款式,能够让咱们的画廊出现更好的成果,这里动画款式还反对动静的旋转、平移、缩放和突变成果,均可在 stylecss 中设置。

.panel-div {
  width: 100%;
  height: 100%;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

整体实现的成果如下图所示,成果简略粗犷,写完了这个 DEMO 之后,咱们团队成员对 OpenHarmony 的根底组件使用有了最根本的理解:



进阶

尽管下面咱们把握了最根底的组件应用,但咱们还是没应用到 Canvas 画布组件,所以咱们持续翻阅官网文档,发现 OpenHarmony 是提供了齐全的画布接口:

咱们应用经典 FlappyBird 游戏作为咱们画布组件的第一次尝试。

收集素材

首先咱们先筹备好游戏的图片和动画素材:

素材
背景
小鸟
高空
水管

而后咱们筹备好画布,设置好高度和宽度,并监听画布按下的办法 ontouchend

<div class="container">
  <canvas ref="canvas" style="width: 280px; height: 512px;" ontouchend="moveUp"></canvas>
</div>

数据初始化

筹备好画布之后,咱们就须要初始化游戏的初始数据,外围的次要波及几个:

el 画布元素
gap 管道间距
score 得分
bX 小鸟 X 轴坐标
bY 小鸟 Y 轴坐标
gravity 重力指数
pipe 管道数据
birdHeight 小鸟高度
birdWidth 小鸟宽度
pipeNorthHeight 上侧管道高度
pipeNorthWidth 下侧管道高度
cvsHeight 画布高度
cvsWidth 画布宽度
fgHeight 高空高度
fgWidth 高空宽度

实现这个游戏之前,咱们岂但须要把握根底的组件,还须要理解一部分生命周期,OpenHarmony 有两种生命周期,别离是利用生命周期和页面生命周期,咱们这里第一次使用到生命周期 onShow,它是在页面关上的时候触发,并且利用处于前台时触发,咱们须要它在开始的时候帮咱们初始化一些要害数据,获取画布的节点,保留画布的上下文作用域 ctx,清空管道数据和触发游戏帧绘制。

onShow() {
    this.el = this.$refs.canvas;
    this.ctx = this.el.getContext('2d');
    this.pipe[0] = {
        x: this.cvsWidth,
        y: 0,
    };
    requestAnimationFrame(this.draw);
},

这里的 this.draw 办法是整个游戏的外围逻辑,波及小鸟的航行动画,静止轨迹,边界解决和得分计算。

首先咱们从画布的左上角 XY 轴的起始地位开始绘制游戏的背景。

const ctx = this.ctx;
ctx.drawImage(this.bg, 0, 0);

而后咱们绘制小鸟航行过程中呈现在天空和高空的管道,这里须要计算天空的管道地位,上管道的地位须要用两个管道预设的间距加上下管道的高度的进去的,当地位计算出来后,只须要配合定时器或者 requestAnimationFrame 来实时更新管道和鸟的地位就能让用户感知游戏动静画面的成果,这里我应用了 requestAnimationFrame 申请动画帧体验会更好,然而它从 API Version 6 才开始反对,并且不须要你导入,所以读者须要注意你的 SDK 是否是比拟新的版本。

for (let i = 0; i < this.pipe.length; i++) {
  this.constant = this.pipeNorthHeight + this.gap;
  ctx.drawImage(this.pipeNorth, this.pipe[i].x, this.pipe[i].y);
  ctx.drawImage(this.pipeSouth, this.pipe[i].x, this.pipe[i].y + this.constant);
  this.pipe[i].x--;
}

碰撞检测

这里咱们应用一个条件判断来做边界解决即碰撞检测,也就是小鸟如果碰到高空,碰到天空的管道或者高空的管道就会使所有动画进行,即游戏完结,如果游戏完结则结算问题,并且应用 OpenHarmony 内置的弹窗揭示玩家是否须要从新开始新的游戏。

if ((this.bX + this.birdWidth >= this.pipe[i].x &&
    this.bX <= this.pipe[i].x + this.pipeNorthWidth &&
    (this.bY <= this.pipe[i].y + this.pipeNorthHeight ||
      this.bY + this.birdHeight >= this.pipe[i].y + this.constant)) ||
  this.bY + this.birdHeight >= this.cvsHeight - this.fgHeight
) {
  prompt.showDialog({buttons: [{ text: "重来一次"}],
    success: (data) => this.restart(),});
  clearInterval(this.interval);
}

当解决完边界,咱们还须要解决当小鸟始终飞下去的时候,要一直创立新的管道,回收旧管道算得分,这个逻辑也相当之简略,实质上也算是一种碰撞检测,当管道地位变更到画面左侧 X 轴为 5 的地位,即小鸟曾经平安通过,则胜利得分,当最新的管道变更到画面右侧 X 轴为 125 的地位,即小鸟将要飞跃的下一个管道,则提前创立好下一个新的管道,如果小鸟飞跃的间隔比拟长,咱们还须要思考优化管道数组,不能让数组无限度的增长上来,咱们还能够优化,所以当旧管道曾经齐全隐没在画面中的时候,咱们能够思考把旧管道的数据从数组中删除。

if (this.pipe[i].x == 125) {
  this.pipe.push({
    x: this.cvsWidth,
    y: Math.floor(Math.random() * this.pipeNorthHeight) - this.pipeNorthHeight,
  });
}
if (this.pipe[i].x == 5) {this.score++;}

下面所有的这些逻辑实质都是绘制管道的动画,咱们配合偏移量和重力因素,很轻易的就能绘制出小鸟的航行轨迹,咱们这里还顺便把得分绘制到屏幕的左下角,以便实时展现玩家得分。

ctx.drawImage(this.fg, 0, this.cvsHeight - this.fgHeight);
ctx.drawImage(this.bird, this.bX, this.bY);
this.bY += this.gravity;
ctx.fillStyle = "#000";
ctx.font = "20px Verdana";
ctx.fillText("Score :" + this.score, 10, this.cvsHeight - 20);

操作和计分

而咱们玩家参加整个游戏只须要一个操作,就是用手指点击屏幕,尽量让小鸟平安飞过管道之间,所以咱们须要监听屏幕的点击事件,实质也就是画布的点击事件,当用户点击一下的时候,咱们就让小鸟往上方挪动一点间隔。

moveUp() {this.bY -= 25;},

而重置游戏跟初始化的逻辑很类似,只有把玩家得分,鸟的地位和管道的数据全副复原到默认状态即可:

restart() {this.pipe = [];
    this.pipe[0] = {
        x: this.cvsWidth,
        y: 0,
    };
    this.constant = 0;
    this.score = 0;
    this.bY = 150;
},

封装组件


因为较量要求咱们是实现一个通用组件,所以在案例 2 中咱们心愿更进一步,尝试把这个把这个游戏封装成一个通用的组件,查阅官网文档发现实现起来很简略,详情在自定义组件,所谓自定义组件就是是用户依据业务需要,将已有的组件组合,封装成的新组件,能够在工程中屡次调用,从而进步代码的可读性。综上所述,咱们只须要应用 <element> 组件把咱们方才实现的组件引入到宿主页面即可。

<element name="Flappy" src="./flappy//pages//index/index.hml"></element>
<div class="container">
  <Flappy></Flappy>
</div>

终极挑战

有了后面两个案例的积攒,咱们团队对 OpenHarmony 开发有了更清晰的意识,就要进入最初激动人心的终极挑战了,咱们要残缺的移植一个 Canvas 引擎,咱们一开始思考的是实现一个游戏引擎,但思考到较量剩余时间并不足够,并且游戏引擎的实用性和创意性不利于展示,所以通过咱们团队综合考量,咱们最终决定实现一个文档表格渲染引擎。

思考

可能有人疑难为什么会抉择移植一个文档渲染引擎,这里想起外网知乎有过相似的探讨,中国要用多久能力研发出相似 Excel,且性能涵盖 Excel 95% 性能的代替软件?,这条路很起伏很艰巨,援用最高赞一些大 V 的答复吧:

  • 微软轮子哥 :做不进去的,那么多货色,要把需要文档写好都得好几年。
    微软的 Belleve:各位程序员能够试试先实现下 recalc(依据公式更新单元格数值),就晓得难度了,文档我的项目作为国内最简单的 C++ 我的项目绝非浪得虚名。
  • 微软的妖怪弟弟:作为 Excel 的工程师,哥认真的答一个,不能,因为咱们隔壁组曾经尝试过了,两年大略笼罩了 40% 高低吧。
  • IBM 的 Caspar Cui:如果是开发罕用的 Excel 性能的话,WPS 曾经是很好的替代品了。而且微软和金山也有穿插受权。然而说要提到 95% 性能的 Excel 曾经做到了这种事儿。。。还是有点小瞧 Excel 了。就一个帮忙文档量,WPS 也得多致力。
  • 中科大的 Sixue:如果微软脑抽,把 Excel 源码弄丢了,不可复原了。那就是世界末日,大家一起完蛋。哪怕微软把 Excel 团队原班人马找回来,到职的反聘,英年早逝的复活,而后从新开发一个 Excel。他也没方法保障把 Excel 的性能复原到 95%,没法保障 95% 的 Excel 文件失常关上。
  • Bbcallen:不可能的,微软本人都做不到。

不论任何人怎么说,这条路咱们也必须走,就如鸿蒙诞生背地的意义,咱们抉择去迎接这个挑战,这外面的每一个坎每一个坑都值得留下一个中国人的足迹。

从技术和指标角度感性去看,咱们更应该实现的不是曾经固化了市场和用户习惯的本地集体文档而是在线协同文档,本地文档只需思考集体,不须要思考多人协同场景,只须要思考离线,不须要思考在线场景,只须要思考客户端场景,不须要思考服务器场景等 …

在线文档的宿主环境是浏览器,本地文档背地是零碎,国内任何在线文档背地都没有像谷歌文档基于谷歌浏览器的反对,没有微软 Office 基于微软 Windows 零碎的反对,事实上基于这所有咱们也该清醒认识到,做到 95% 是很难的。要晓得谷歌为了开发浏览器前后投入了十几年上千人上百亿,微软 Windows 零碎就更不用说了,在国内咱们可能领有不了这样的技术背景,但咱们仍在致力缩小差距倔强追赶。

实现计划

在谈谈实现计划之前,咱们先讲讲表格渲染有多简单,表格的渲染一般来说有两种实现计划:

  • DOM 渲染。
  • Canvas 渲染。

业界比拟闻名的 handsontable 开源库就是基于 DOM 实现渲染,等同渲染后果,须要对 DOM 节点进行精心的设计与结构,但不言而喻十万、百万单元格的 DOM 渲染会产生较大的性能问题。因而,现在很多在线表格实现都是基于 Canvas 和叠加 DOM 来实现的,但应用 Canvas 实现须要思考可视区域、滚动操作、画布层级关系,也有 Canvas 本身面临的一些性能问题,包含 Canvas 如何进行直出等,对开发的要求较高,但为了更好的用户体验,更偏向于 Canvas 渲染的实现计划。

因为大部分前端我的项目渲染层是应用框架依据排版模型树结构逐层渲染的,整棵渲染树也是与排版模型树一一对应。因而,整个渲染的节点也十分多。我的项目较大时,性能会受到较大的影响。

[外链图片转存失败, 源站可能有防盗链机制, 倡议将图片保留下来间接上传(img-ACtSdVX6-1637535201140)(https://p3-juejin.byteimg.com… “ 屏幕截图.png”)]

为了晋升渲染性能,提供更优质的编辑体验从 DOM 更换成 Canvas 渲染,不便开发者构建重前端大型在线文档我的项目,在国内外实现相似引擎的公司仅仅只有几家,如:腾讯文档,金山文档和谷歌文档等。

顶层
DOM 容器插件输入框等
Canvas 高亮选区等
Canvas 内容字体背景色等
底层

咱们通过分类收集视图元素,再进行逐类别渲染的形式,缩小 Canvas 绘图引擎切换状态机的次数,升高性能损耗,优化渲染耗时,整个外围引擎代码管制在 1500 行左右,另补充演示代码 500 行,不便大家了解浏览和进行二次开发。

这里移植的引擎次要参考了一个商业我的项目和一个开源我的项目:

  • X Spreadsheet@MyLiang
  • Tencent Doc@AlloyTeam

画布初始化

咱们结构一个 table 类,在初始化的时候创立画布,并设置好高度和宽度,并放入 DOM 中,并把罕用的属挂载到原型链上并裸露到全局 window 变量上。

class Table {constructor(container, width, height) {const target = document.createElement("canvas");
    document.querySelector(container).appendChild(target);
    this.$target = target;
    this.$draw = Canvas2d.create(target);
    this.$width = width;
    this.$height = height;
  }
}

咱们就能够,页面中在 <div id="table"></div> 搁置好表格元素,全局环境中间接实例化该画布,这里组件通过 id 属性标识后,能够应用该 id 获取组件对象并调用相干组件办法。

const table = new Table("#table", 800, 500);
左上区域 col 列 col 列
row 行 cell 单元格 cell 单元格
row 行 cell 单元格 cell 单元格

坐标系建设

有了画布,咱们就要开始筹备渲染,咱们 table 类外面封装 render 办法,render 办法次要绘制四个区域,也就是相似数学上的 笛卡尔直角坐标系 的四个设想,波及格子线段,格子的信息,列头部(A-Z 列),行头部和解冻区域。

2 – 左上区域 1 – 右上区域
3 - 左下区域 4 – 右下区域

这些 area 都是通过 area.jsArea 初始化而来

  • 2 区域至 1 区域就是 A-Z 列头部
  • 2 区域至 3 区域就是行头部
  • 4 区域最罕用,是可编辑的单元格
renderBody.call(this, draw, area4);
renderRowHeader.call(this, draw, iarea3);
renderColHeader.call(this, draw, iarea1);
renderFreezeLines.call(this, draw, area4.x, area4.y);

而这个四个区域大部分的外围思路实质都是绘制格子,所有都独特用到 renderLinesAndCells 办法,外面别离有用于绘制区域的线条和格子信息的办法,外面的 renderCells 会遍历区域而后触发 renderCell 绘制每一个独自的单元格,这里还会解决一些非凡的单元格,比方合并的单元格和选中的单元格,而 renderLines 则会遍历每行每列去绘制所有行列的距离线。

function renderLinesAndCells() {renderLines(draw, area, lineStyle);
  renderCells(draw, type, area, cell, cellStyle, selection, selectionStyle, merges);
}

单元格渲染

绘制了表格的单元格之后,就须要往每个单元格渲染数据和格局了,这里在 Table 原型链上挂载了一个 cell 办法,它承受一个回调函数并把它存到动态属性 $cell 上,当 renderCell 函数触发的时候就会调用这个办法并把行列号传入 $cell 办法中获取单元格的信息。

Table.prototype["cell"] = function (callback) {this[`$$cell`] = callback;
  return this;
};
const sheet = [["1", "1", "1"],
  ["1", "0", "1"],
  ["1", "1", "1"],
];
table.cell((ri, ci) => sheet?.[ri]?.[ci] || "").render();

<img width=”250″ src=”https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6d88f654f3b04af1a1b7fdb54eb89472~tplv-k3u1fbpfcp-zoom-1.image” />

所以咱们能够通过裸露的 $cell 办法管制每个单元格的文字信息和单元格款式,当然这里反对你只传入纯文本,也反对你传入简单数据结构来装璜单元格,这里的 style 会有默认的值来自于 $cellStyle

bgcolor #ffffff
align left
valign middle
textwrap true
underline false
color #0a0a0a
bold false
italic false
rotate 0
fontSize 9
fontName Source Sans Pro

这些款式实质是应用 Canvas 提供的接口 draw.attr() 来展现的。

const c = cell(ri, ci);
let text = c.text || "";
let style = c.style;
cellRender(draw, text, cellRect, style);

有下面最根底的办法,咱们曾经领有表格数据展现的性能,此时咱们能够裸露更丰盛的接口给第三方应用,比方罕用的合并单元格,咱们调用 merges 办法告知 table 类,咱们在 XG9H11YB9D11 的范畴外面的单元格是被合并的状态,

Table.create("#table", 800, 500).merges(["G9:H11", "B9:D11"]);

那此时 renderCells 就会将该区域的格子做非凡的解决,把它渲染成合并单元格的状态。

eachRanges(merges, (it) => {if (it.intersects(area)) {renderCell(draw, it.startRow, it.startCol, cell, area.rect(it), cellStyle);
  }
});

数据可编辑

除了单元格合并,罕用的还有画布的事件处理,因为方才所有的办法都只是表格只查看状态,表格还会被用户所编辑,所以就得监听用户点击和输出的事件,所以咱们在表格渲染的时候绑定了 clickmousedownmousemovemouseup 事件等,咱们能够通过监听用户点击行为,在对应的单元格的画布的上方,即 DOM 元素 Z 轴显示输入框,给用户提供输出批改单元格性能。

bind($target, "click", (evt) => {});
bind($target, "mousedown", (evt) => {});

所以在 DOM 节点中咱们除了放 <canvas> 元素,还能够安顿上 <textarea> 元素,这里能够留意到咱们内联款式中有 areaTopareaLeft 来管制咱们输入框的具体位置,这个地位获取也是非常容易,咱们只须要拿到点击事件对象 evt.offsetXevt.offsetY,而后依据坐标的地位算出是否在四个象限区域外面并返回所在的行列信息,联合行列的信息就能够精确算出输入框的偏移值 areaTopareaLeft,而后再让输入框切换为可显示的状态,用户就能够在表格的对应单元格上看到输入框。

<div
  if="{{isShowArea}}"
  style="width: 100px; height: 25px; top: {{areaTop}}px; left: {{areaLeft}}px"
>
  <textarea
    if="{{isShowArea}}"
    focus="{{isFocus}}"
    value="{{content}}"
    selectchange="change"
    onchange="change"
  ></textarea>
</div>

有了输入框咱们就能够监听用户的输出操作,咱们把输出事件绑定在 textarea 组件上,当组件达到事件触发条件时,会执行 JS 中对应的事件回调函数,实现页面 UI 视图和页面 JS 逻辑层的交互,事件回调函数中通过参数能够携带额定的信息,如组件上的数据对象 dataset 事件特有的回调参数,当组件触发事件后,事件回调函数默认会收到一个事件对象,通过该事件对象能够获取相应的信息,咱们通过事件对象失去用户输出的值,并调用 cell 办法从新更新表格外面对应单元格的值,当然理论状况有时候比较复杂,比方用户是批改单元格文字的色彩,所以这里会判断数据格式。

textarea.addEventListener("input", (e) => {
  let input = e.target.value;
  table.cell((ri, ci) => {if (ri === row && ci === col) {return (window.data[ri][ci] =
        typeof value === "string" || typeof value === "number"
          ? input
          : {...value, text: input});
    }
    return window.data[ri][ci];
  });
});

下图是咱们真机的实测成果,能够看到咱们引入了内置库 @system.prompt,点击对应的单元格弹窗显示对应的行列信息,不便咱们开发调试,咱们应用手机的内置输入法输出内容测试,输入框会精确获取到信息并更新到表格上,而应用 IDE 内置的 Previewer 预览则有效,猜想是 PC 端键盘的输出事件没有被触发。

工具栏实现

有了最根本的查看和编辑表格的性能,下一步咱们就能够思考实现工具栏了,工具栏的实现,个别会提供设置行列高度,文本加粗,居中,斜体,下划线和背景色等设置,其实就是下面单元格 style 办法配合行列地位或者范畴信息的再封装各种接口实现。

咱们这里介绍几个罕用的,colHeader 能够设置你的列表行头和其高度,如果你不对它进行设值,他也会有默认的高度。

table.colHeader({height: 50, rows: 2}).render();

某些状况,咱们在查阅表格的时候,咱们可能须要固定某些行和某些列的单元格来进步表格浏览性,此时 .freeze 就能够派上用场,以下设置它会帮你解冻 C6 以内的列。

table.freeze("C6").render();

scrollRows 个别配合解冻区域应用,让解冻区域以外的选区能够做滚动操作。

table.scrollRows(2).scrollCols(1).render();

咱们能够应用以下办法更新单元格第二行第二列的数据为 8848,色彩为红色:

table
  .cell((ri, ci) => {if (ri === 2 && ci === 2) {
      return {
        text: "8848",
        style: {color: "red",},
      };
    }
    return this.sheet?.[ri]?.[ci] || "";
  })
  .render();

因为可设置单元格的模式太多了,这里不一一开展,具体能够参考以下接口,反对各种丰盛的多样的改变,能够看进去其实跟咱们设置 CSS 款式是很类似的:

{
  cell: {
    text,
    style: {
      border, fontSize, fontName,
      bold, italic, color, bgcolor,
      align, valign, underline, strike,
      rotate, textwrap, padding,
    },
    type: text | button | link | checkbox | radio | list | progress | image | imageButton | date,
  }
}

咱们将下面常见的接口做了一些演示,运行 OpenHarmonySheet,长按 任一单元格弹出 对话框 并点击对应选项即可查看罕用接口的运行后果,此演示仅供参考,更多理论应用场景请参考文档实现:




生命周期和事件

在实现下面上述的性能之后,咱们就须要思考裸露生命周期和事件并封装成一个通用组件给接入方应用。

  • @sheet-show 表格显示
  • @sheet-hide 表格暗藏
  • @click-cell-start 单元格点击前
  • @click-cell-end 单元格点击后
  • @click-cell-longpress 长按表格
  • @change 批改单元格数据

因为 OpenHarmony 为自定义组件提供了一系列生命周期回调办法,便于开发者治理自定义组件的外部逻辑。生命周期次要包含:onInitonAttachedonDetachedonLayoutReadyonDestroyonPageShowonPageHide。咱们的表格组件能够利用各个生命周期回调的机会裸露本身的生命周期。

this.$emit("eventName", data);

这里 name 属性指自定义组件名称,组件名称对大小写不敏感,默认应用小写。src 属性指自定义组件 hml 文件门路,如果没有设置 name 属性,则默认应用 hml 文件名作为组件名。而自定义组件中绑定子组件事件应用 onXXX@XXX 语法,子组件中通过 this.$emit触发事件并进行传值,通过绑定的自定义事件向上传递参数,父组件执行 bindParentVmMethod 办法并接管子组件传递的参数。

<element name="Sheet" src="../../components/index.hml"></element>
<Sheet
  sheet="{{sheet}}"
  @sheet-show="sheetShow"
  @sheet-hide="sheetHide"
  @click-cell-start="clickCellStart"
  @click-cell-end="clickCellEnd"
  @click-cell-longpress="clickCellLongpress"
  @change="change"
></Sheet>

咱们把下面这些统统打包,并欠缺了介绍文档和接入文档上传到 Gitee – [OpenHarmonySheet
](https://github.com/Wscats/sheet) 仓库中,就实现了咱们的表格引擎组件。

回顾整个过程尽管有难度有挑战,但咱们团队还是的集思广益解决了,在整个较量过程中咱们团队也学习了很多对于鸿蒙的货色,在以前始终没有这个机会去理解,借着这次较量的机会能重新认识鸿蒙,也意识了一些气味相投的开发者,暂且做个总结吧,作品在参赛前要找准一个方向,最好是这个畛域本人相熟的,并且与其余参赛作品是不重合的,这样对本人的作品是负责,对他人的作品是敌对,对较量也是尊重的,在把握好方向之后就要制订每一个小打算和最终的指标,做好前中后期的具体布局,步子不能跨地太大,不能好高骛远,要好高鹜远的去实现每一个小打算,这个过程对于团队和本人都是双赢的,尽管咱们不肯定能达到起点,但回头看咱们每一个脚印都是本人致力的回报,团队成员之间要团结,有针对性地实现好每一个工作,认真负责,相互帮忙,共同进步,正如鸿蒙所经验的一样,一个欠缺的零碎须要千万开发者群策群力一起去构建和打磨,心愿能有越来越多好的 OpenHarmony 开源我的项目,不积跬步,无以至千里,不积小流,无以成江海,一起去打造属于咱们的生态。

最初衷心希望 OpenHarmony 能倒退的越来越弱小,越来越顺利,这条路尽管很难,但值得,长风破浪会有时,直挂云帆济桑田!

退出移动版