第19次:CodeBlock 代码块组件

从这一篇开始,我们进入“课程内容展示体验优化”的部分。当前项目里真正承担代码展示任务的组件是 entry/src/main/ets/components/CodeBlock.ets,它会被 LessonDetail.ets 调用,用来展示标题、语言标签、代码正文、说明文字和复制能力。


为什么教程类应用一定要做代码块组件

如果你在课时详情页里直接用一个普通 Text 显示代码,马上就会遇到这些问题:

  • 代码和正文混在一起,视觉层次很差。
  • 用户不知道这段代码是什么语言。
  • 没办法一键复制去试验。
  • 代码解释没有固定位置,内容会很乱。

所以一个成熟一点的学习类应用,都会把代码展示抽成单独组件。这个项目的 CodeBlock 虽然不复杂,但结构很完整,非常适合入门项目学习。

LessonDetail

CodeBlock

标题栏

语言标签

复制按钮

可滚动代码区

解释说明区


一、CodeBlock 组件到底接收了哪些参数

当前组件接收四个核心属性:

  • code
  • language
  • title
  • explanation

这四个参数看起来简单,但已经足够覆盖大多数教学场景了。

code

真正要展示的代码内容,是组件的核心主体。

language

用于生成右上角语言标签,比如 TSXJSXTS。哪怕当前版本没有做真正的语法高亮,用户也能第一眼知道这段代码是什么类型。

title

告诉用户这段代码在讲什么,例如“函数组件示例”“状态更新写法”“Effect 清理函数示例”。这能显著提升可读性。

explanation

这是很多教程最容易忽略、却最有价值的一部分。代码展示出来以后,必须有人话解释,不然用户只能“看见”,不能“理解”。


二、这个组件的结构为什么很合理

CodeBlock 不是简单地上下堆几个控件,它分成了三个稳定层次:

  1. 顶部标题栏
  2. 中间代码区域
  3. 底部解释说明

其中标题栏里还继续拆分成:

  • 左侧标题
  • 右侧语言标签
  • 复制按钮

这样的结构非常符合用户阅读代码时的习惯:

  • 先知道这段代码叫什么
  • 再看正文
  • 最后看解释

这比把说明塞在代码上面、或者把复制按钮放到页面角落,都更自然。


三、为什么代码区要放在 Scroll()

教学类应用的代码长度是不稳定的。有时候只有三五行,有时候可能几十行。如果不用 Scroll(),代码会把整页高度撑爆,尤其在手机上更明显。

当前实现把代码区包在 Scroll() 中,并限制最大高度:

  • .constraintSize({ maxHeight: 300 })
  • .scrollBar(BarState.Auto)

这两个设置配合起来就很实用:

  • 短代码不会浪费空间。
  • 长代码也不会撑破布局。
  • 用户知道这里是可以滚动的。

对于鸿蒙手机端来说,这种“限制高度 + 局部滚动”的代码展示方式几乎是标配。


四、复制能力是怎么实现的

组件用的是:

  • @kit.BasicServicesKit 中的 pasteboard
  • @kit.ArkUI 中的 promptAction.showToast

整体流程很清晰:

promptAction pasteboard CodeBlock 用户 promptAction pasteboard CodeBlock 用户 点击复制按钮 createData + getSystemPasteboard setData(code) showToast("代码已复制")

这套实现的教学意义很强,因为它不是纯 UI,而是把鸿蒙的系统服务能力真正用起来了。对于初学者来说,这也是一个很好的例子:组件并不只是负责显示,它也可以承担轻量交互和系统能力调用。


五、为什么解释说明单独放在底部

很多教程喜欢把代码解释写在代码上方,但当前项目把解释说明放到代码下面,这种顺序其实更适合学习:

  • 先整体看代码长什么样
  • 再通过说明理解“这段代码要表达什么”

同时,解释区使用了和代码区不同的底色,让用户能明确区分:

  • 这里不是代码正文
  • 这里是作者的解释

这在教学产品中非常重要。用户在读教程时最怕“哪部分是代码,哪部分是说明”混在一起。


六、它和 LessonDetail 的关系一定要看清楚

CodeBlock 本身只是一个渲染组件,并不关心课时、模块、进度这些业务。真正把它放进教学流程的是 LessonDetail.ets

也就是说,两者分工很明确:

  • LessonDetail 决定展示哪些代码示例
  • CodeBlock 决定这些示例怎么显示

这种拆分特别值得你学习,因为它体现了组件化的核心思想:

  • 页面负责业务组织
  • 组件负责局部展示

如果以后你要给题库页、源码详情页、项目说明页也加代码展示,只要继续复用 CodeBlock 就行,不需要重复造轮子。


七、自己动手验证时怎么做

  1. 打开任意课时详情页,找到代码示例区域。
  2. 观察标题、语言标签、代码正文、解释说明是否都能正确显示。
  3. 点击复制按钮,确认系统提示是否出现。
  4. 选一段较长代码,测试局部滚动是否正常。
  5. 切换深色模式,观察标题栏、正文区、说明区颜色是否仍然清晰。

只要这五步你自己跑一遍,就会对这个组件的职责非常清楚。


八、本篇常见坑

1. 把代码块写死在页面里

这样后面每个页面都要重复写样式,维护成本会快速上升。

2. 代码区不做高度限制

一旦代码稍长,手机页面就会非常难看。

3. 只展示代码,不提供说明

教程不是代码仓库,代码一定要配解释。

4. 忽略复制功能

用户看到可运行示例后,往往第一反应就是复制去试。没有复制按钮会很影响体验。


本篇小结

CodeBlock 组件看似小,但它把“教学代码展示”这件事做得非常完整:

  • 有标题
  • 有语言标签
  • 有可滚动代码区
  • 有解释说明
  • 还能一键复制

你以后做任何知识型、教程型、文档型鸿蒙应用,都几乎离不开这样一个基础组件。


跟着真实源码继续往下看

下面这段就是项目里 CodeBlock.ets 的真实核心实现:

if (this.title) {
  Row() {
    Text(this.title)
    Blank()
    Text(this.language.toUpperCase())
    Text('📋')
      .onClick(() => this.copyCode())
  }
}

Scroll() {
  Text(this.code)
    .fontSize(13)
    .fontFamily('monospace')
}
.constraintSize({ maxHeight: 300 })

复制逻辑的真实代码如下:

private copyCode(): void {
  try {
    const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, this.code);
    const systemPasteboard = pasteboard.getSystemPasteboard();
    systemPasteboard.setData(pasteboardData);
    promptAction.showToast({ message: '代码已复制' });
  } catch (error) {
    console.error('[CodeBlock] Failed to copy:', error);
    promptAction.showToast({ message: '复制失败' });
  }
}

按这个顺序动手

  1. 打开 entry/src/main/ets/components/CodeBlock.ets
  2. 先看标题栏如何把标题、语言和复制按钮放到一行。
  3. 再看 copyCode(),理解鸿蒙剪贴板能力怎么接入组件。

课后练习

  1. 思考如果你要给 CodeBlock 增加“折叠/展开”功能,最适合新增什么状态字段。
  2. 观察当前组件并没有真正做关键字级别语法高亮,想一想如果后续要加,应该在组件层做还是在数据预处理层做。
  3. 总结 CodeBlock 为什么适合作为通用组件,而不该耦合 Lesson 或 Module 业务字段。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐