img

目录

  • 案例介绍
  • 代码实现
  • 代码详解
  • 数据结构设计
  • 文件树数据模型
  • 当前文件数据模型
  • 界面构建
  • 工具栏实现
  • 文件列表实现
  • 文件/文件夹项的实现
  • 子文件显示
  • 交互实现
  • 文件选择
  • 总结

案例介绍

在开发专业代码编辑器时,文件树区域是用户与代码文件交互的重要入口。本篇文章将详细介绍如何使用HarmonyOS NEXT的UI组件实现一个功能完善的文件树区域,包括文件夹和文件的层级显示、交互响应等功能。通过本案例,您将学习到List、ForEach等组件的嵌套使用,以及如何构建专业的文件管理界面。

代码实现

@Entry
@Component
struct CodeEditor {
  // 文件树数据
  @State fileTree: Array<{
    name: string,
    type: 'file' | 'folder',
    children?: Array<{ name: string, type: 'file' | 'folder' }>
  }> = [
    {
      name: 'src',
      type: 'folder',
      children: [
        { name: 'main.ets', type: 'file' },
        { name: 'app.ets', type: 'file' }
      ]
    },
    {
      name: 'resources',
      type: 'folder',
      children: [
        { name: 'index.html', type: 'file' },
        { name: 'styles.css', type: 'file' }
      ]
    },
    { name: 'package.json', type: 'file' }
  ]

  // 当前编辑的文件内容
  @State currentFile: {
    name: string,
    content: string
  } = {
    name: 'main.ets',
    content: '@Entry\n@Component\nstruct MainPage {\n  build() {\n    Column() {\n      Text(\'Hello World\')\n        .fontSize(20)\n    }\n  }\n}'
  }

  build() {
    Row() {
      // 文件树区域
      Column() {
        // 工具栏
        Row() {
          Text('文件')
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
          Blank()
          Button({
            type: ButtonType.Circle,
            stateEffect: true
          }) {
            Image($r('app.media.add'))
              .width(16)
              .height(16)
          }
          .width(24)
          .height(24)
          .margin({ left: 8 })
        }
        .width('100%')
        .height(40)
        .padding({ left: 16, right: 16 })
        .backgroundColor('#F8F9FA')

        // 文件列表
        List() {
          ForEach(this.fileTree, (item) => {
            ListItem() {
              Row() {
                Image(item.type === 'folder' ? $r('app.media.folder') : $r('app.media.file'))
                  .width(16)
                  .height(16)
                Text(item.name)
                  .fontSize(14)
                  .margin({ left: 8 })
              }
              .width('100%')
              .padding(8)

              if (item.type === 'folder' && item.children) {
                Column() {
                  ForEach(item.children, (child) => {
                    Row() {
                      Image(child.type === 'folder' ? $r('app.media.folder') : $r('app.media.file'))
                        .width(16)
                        .height(16)
                      Text(child.name)
                        .fontSize(14)
                        .margin({ left: 8 })
                    }
                    .width('100%')
                    .padding(8)
                    .padding({ left: 24 })
                    .onClick(() => {
                      if (child.type === 'file') {
                        this.currentFile.name = child.name
                      }
                    })
                  })
                }
              }
            }
            .onClick(() => {
              if (item.type === 'file') {
                this.currentFile.name = item.name
              }
            })
          })
        }
        .width('100%')
        .layoutWeight(1)
      }
      .width(250)
      .height('100%')
      .backgroundColor('#F8F9FA')
    }
  }
}

代码详解

数据结构设计

文件树数据模型
@State fileTree: Array<{
  name: string,
  type: 'file' | 'folder',
  children?: Array<{ name: string, type: 'file' | 'folder' }>
}>

这段代码定义了文件树的数据结构:

  • name:文件或文件夹的名称
  • type:类型标识,可以是'file'或'folder'
  • children:可选属性,当type为'folder'时,包含子文件和子文件夹

使用TypeScript的类型系统确保了数据结构的严谨性,通过联合类型'file' | 'folder'限制了type字段的取值范围。

当前文件数据模型
@State currentFile: {
  name: string,
  content: string
}

这个状态变量存储当前选中文件的信息:

  • name:文件名
  • content:文件内容

使用@State装饰器使这些数据具有响应式特性,当数据变化时UI会自动更新。

界面构建

工具栏实现
Row() {
  Text('文件')
    .fontSize(16)
    .fontWeight(FontWeight.Medium)
  Blank()
  Button({
    type: ButtonType.Circle,
    stateEffect: true
  }) {
    Image($r('app.media.add'))
      .width(16)
      .height(16)
  }
  .width(24)
  .height(24)
  .margin({ left: 8 })
}
.width('100%')
.height(40)
.padding({ left: 16, right: 16 })
.backgroundColor('#F8F9FA')

工具栏使用Row组件实现水平布局:

  • 左侧是标题文本,使用fontSizefontWeight设置样式
  • 中间使用Blank()组件自动填充空间
  • 右侧是一个圆形按钮,内部包含一个添加图标
  • 通过padding属性设置内边距,backgroundColor设置背景色
文件列表实现
List() {
  ForEach(this.fileTree, (item) => {
    ListItem() {
      // 文件/文件夹项的UI实现
    }
  })
}
.width('100%')
.layoutWeight(1)

文件列表使用List组件实现:

  • 通过ForEach遍历fileTree数组,为每个项目创建一个ListItem
  • layoutWeight(1)使列表占据剩余空间
文件/文件夹项的实现
Row() {
  Image(item.type === 'folder' ? $r('app.media.folder') : $r('app.media.file'))
    .width(16)
    .height(16)
  Text(item.name)
    .fontSize(14)
    .margin({ left: 8 })
}
.width('100%')
.padding(8)

每个文件/文件夹项使用Row组件实现:

  • 根据item.type显示不同的图标(文件夹或文件)
  • 显示文件/文件夹名称
  • 设置适当的内边距和宽度
子文件显示
if (item.type === 'folder' && item.children) {
  Column() {
    ForEach(item.children, (child) => {
      Row() {
        // 子文件/文件夹的UI实现
      }
      .onClick(() => {
        if (child.type === 'file') {
          this.currentFile.name = child.name
        }
      })
    })
  }
}

当项目是文件夹且有子项时:

  • 使用条件渲染显示子项列表
  • 通过ForEach遍历子项
  • 子项使用缩进(padding({ left: 24 }))表示层级关系
  • 为子项添加点击事件,选中文件时更新currentFile

交互实现

文件选择
.onClick(() => {
  if (item.type === 'file') {
    this.currentFile.name = item.name
  }
})

为文件项添加点击事件:

  • 判断点击的是文件而非文件夹
  • 更新currentFile.name为选中的文件名

这种交互模式使用户可以通过点击文件树中的文件来切换当前编辑的文件。

总结

本篇文章详细介绍了代码编辑器文件树区域的实现方法。通过使用HarmonyOS NEXT的UI组件,我们构建了一个具有层级结构的文件树界面,实现了文件和文件夹的显示、层级关系的表达以及文件选择功能。

关键技术点包括:

  1. 使用TypeScript定义严谨的数据结构
  2. 利用List和ForEach组件实现动态列表
  3. 通过条件渲染实现文件夹展开功能
  4. 使用事件处理实现文件选择交互
  5. 合理设置样式属性创建专业的视觉效果

这些技术和设计模式不仅适用于代码编辑器,也可以应用于其他需要展示层级数据的场景,如文件管理器、目录浏览器等应用。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐