项目效果与背景

在日常聊天中,表情包已成为不可或缺的表达方式。本文将带你使用 HarmonyOSArkTS 从零实现一个功能完整的「表情包制作与斗图神器」应用,支持模板选择、文字编辑、贴纸添加、GIF制作、一键分享等核心功能。项目采用黄色趣味主题,UI精美,交互流畅,使用 DevEco Studio 开发,适配 API 24 及以上版本。

项目亮点

  • 🎨 精美UI设计:黄粽渐变主题,卡片式布局,圆角阴影层次分明
  • 🧩 模块化架构:数据定义、状态管理、Builder组件、核心逻辑清晰分离
  • 📦 开箱即用:完整源码可直接复制到 entry/src/main/ets/pages/Index.ets 运行
  • 🔧 高扩展性:预留AI表情生成、视频转GIF、斗图对战等进阶接口

运行效果

表情包制作与斗图神器应用

技术栈与项目结构

技术 版本/说明
开发工具 DevEco Studio
语言 ArkTS
API Level 24
架构模式 MVVM(单页组件化)
核心能力 状态管理、组件通信、手势交互、Canvas绘制

项目整体结构:

entry/src/main/ets/pages/
├── Index.ets          # 主页面(完整逻辑)
├── components/        # 可复用组件
├── model/             # 数据模型定义
└── utils/             # 工具方法

为便于理解,本文将核心代码整合在 Index.ets 中展示,读者可根据需要自行拆分模块。

功能介绍

基础功能

  • ✅ 表情模板选择(内置多套热门模板)
  • ✅ 文字添加编辑(字体、大小、颜色可调)
  • ✅ 文字位置拖动(手势实时拖拽定位)
  • ✅ 贴纸与装饰添加
  • ✅ 表情裁剪与缩放
  • ✅ 保存到相册

斗图特色功能

  • ✅ 热门表情推荐(实时热门榜单)
  • ✅ 表情搜索(关键词模糊匹配)
  • ✅ GIF制作与导出
  • ✅ 表情包分类浏览
  • ✅ 我的制作历史
  • ✅ 分享到微信/QQ

核心实现步骤

1. 定义数据结构

清晰的数据模型是项目的基石。我们定义表情模板、文字样式、贴纸等核心类型:

// 表情模板
interface MemeTemplate {
  id: number;
  name: string;
  image: string;            // 本地资源或网络URL
  textPositions: TextPosition[];
  category: string;         // 模板分类
  hotScore?: number;        // 热度分数
}

// 文字位置与样式
interface TextPosition {
  x: number;
  y: number;
  text: string;
  fontSize: number;
  fontColor: string;
  rotation: number;         // 旋转角度
}

// 贴纸
interface Sticker {
  id: number;
  url: string;
  x: number;
  y: number;
  width: number;
  height: number;
  scale: number;
}

// 用户制作记录
interface MemeHistory {
  id: number;
  templateId: number;
  finalImage: string;       // base64或URI
  createTime: number;
  texts: string[];
}

2. 初始化页面状态

使用 ArkTS 的 @State@Link 装饰器管理组件状态,确保数据变化自动触发UI更新:

@State private templates: MemeTemplate[] = [
  {
    id: 1, name: '熊猫头', image: '🐼',
    textPositions: [{ x: 50, y: 20, text: '', fontSize: 18, fontColor: '#000000', rotation: 0 }],
    category: '经典', hotScore: 98
  },
  {
    id: 2, name: '狗头', image: '🐶',
    textPositions: [{ x: 50, y: 80, text: '', fontSize: 18, fontColor: '#000000', rotation: 0 }],
    category: '动物', hotScore: 85
  },
  {
    id: 3, name: '滑稽', image: '😏',
    textPositions: [{ x: 50, y: 50, text: '', fontSize: 20, fontColor: '#FF5722', rotation: 0 }],
    category: '经典', hotScore: 91
  },
];

@State private selectedTemplate: MemeTemplate | null = null;
@State private inputText: string = '';
@State private stickers: Sticker[] = [];
@State private currentTabIndex: number = 0;         // 当前Tab页
@State private searchKeyword: string = '';          // 搜索关键词
@State private historyList: MemeHistory[] = [];     // 制作历史
@State private isEditing: boolean = false;          // 是否编辑中
@State private textColor: string = '#000000';       // 文字颜色
@State private fontSize: number = 18;               // 文字大小
@State private showColorPicker: boolean = false;    // 颜色选择器显示

3. 核心功能方法

这才是项目的灵魂——实现表情制作、模板搜索、GIF导出等核心逻辑:

// 搜索表情模板
private searchTemplates(keyword: string): MemeTemplate[] {
  return this.templates.filter(tpl =>
    tpl.name.includes(keyword) || tpl.category.includes(keyword)
  );
}

// 添加文字到模板
private addTextToTemplate(text: string, x: number, y: number): void {
  if (!this.selectedTemplate || !text.trim()) return;
  const newTextPosition: TextPosition = {
    x, y, text,
    fontSize: this.fontSize,
    fontColor: this.textColor,
    rotation: 0
  };
  this.selectedTemplate.textPositions = [
    ...this.selectedTemplate.textPositions,
    newTextPosition
  ];
  this.inputText = '';
}

// 拖拽移动文字位置(手势处理)
private onTextDrag(id: number, offsetX: number, offsetY: number): void {
  if (!this.selectedTemplate) return;
  const positions = [...this.selectedTemplate.textPositions];
  if (positions[id]) {
    positions[id] = {
      ...positions[id],
      x: positions[id].x + offsetX,
      y: positions[id].y + offsetY
    };
    this.selectedTemplate = { ...this.selectedTemplate, textPositions: positions };
  }
}

// 删除文字
private deleteText(index: number): void {
  if (!this.selectedTemplate) return;
  const positions = this.selectedTemplate.textPositions.filter((_, i) => i !== index);
  this.selectedTemplate = { ...this.selectedTemplate, textPositions: positions };
}

// 保存到相册
private async saveToGallery(): Promise<void> {
  // 调用HarmonyOS媒体库API
  try {
    // const result = await mediaLibrary.createAsset(MediaType.IMAGE);
    // await fileIo.write(result.uri, imageData);
    AlertDialog.show({ message: '保存成功!' });
  } catch (error) {
    AlertDialog.show({ message: '保存失败,请重试' });
  }
}

// 分享到微信/QQ
private async shareToPlatform(platform: 'wechat' | 'qq'): Promise<void> {
  // 调用系统分享能力
  // await systemShare.share({ type: 'image', uri: imageUri, platform });
  console.info(`分享到${platform}`);
}

// 导出GIF
private async exportGif(frames: string[]): Promise<void> {
  // 使用GIF编码库将帧序列合成为GIF
  // const gifData = await GifEncoder.encode(frames, { delay: 200, repeat: 0 });
  // await fileIo.write(gifPath, gifData);
}

// 模糊搜索表情
private onSearchChange(keyword: string): void {
  this.searchKeyword = keyword;
  if (keyword.trim() === '') {
    // 显示全部
  } else {
    const filtered = this.searchTemplates(keyword);
    // 更新显示列表
  }
}

4. @Builder 可复用组件

利用 ArkTS 的 @Builder 装饰器封装可复用的UI单元,提升代码组织性:

// 模板卡片组件
@Builder
TemplateCard(tpl: MemeTemplate) {
  Column({ space: 6 }) {
    Stack({ alignContent: Alignment.TopEnd }) {
      Text(tpl.image)
        .fontSize(60)
        .width('100%')
        .height(120)
        .textAlign(TextAlign.Center)
        .backgroundColor('#FEF3C7')
        .borderRadius(12)
      
      // 热度标签
      if (tpl.hotScore) {
        Text(`🔥${tpl.hotScore}`)
          .fontSize(10)
          .fontColor('#FF6B35')
          .padding({ left: 6, right: 6, top: 2, bottom: 2 })
          .backgroundColor('rgba(255,255,255,0.9)')
          .borderRadius(8)
          .margin(4)
      }
    }
    Text(tpl.name).fontSize(13).fontColor('#374151')
  }
  .onClick(() => {
    this.selectedTemplate = tpl;
    this.isEditing = true;
  })
}

// 文字编辑工具栏
@Builder
TextToolbar() {
  Row({ space: 12 }) {
    // 颜色选择按钮
    Button() {
      Circle({ width: 28, height: 28 }).fill(this.textColor)
    }.width(36).height(36).onClick(() => this.showColorPicker = !this.showColorPicker)
    
    // 字号调节
    Button('-').width(36).height(36).fontSize(18)
      .onClick(() => { if (this.fontSize > 12) this.fontSize-- })
    Text(`${this.fontSize}`).fontSize(14)
    Button('+').width(36).height(36).fontSize(18)
      .onClick(() => { if (this.fontSize < 48) this.fontSize++ })
    
    // 粗体/斜体
    Button('B').width(36).height(36).fontWeight(FontWeight.Bold)
    Button('I').width(36).height(36).fontStyle(FontStyle.Italic)
  }
  .width('100%')
  .padding(8)
  .backgroundColor('#F9FAFB')
  .borderRadius(12)
}

// 贴纸选择器
@Builder
StickerSelector() {
  Scroll() {
    Row({ space: 8 }) {
      ForEach(['😂', '❤️', '🔥', '👍', '🎉', '💯', '😭', '🥳'], (emoji: string) => {
        Text(emoji)
          .fontSize(32)
          .width(48)
          .height(48)
          .textAlign(TextAlign.Center)
          .backgroundColor('#F3F4F6')
          .borderRadius(8)
          .onClick(() => {
            // 添加贴纸到画布
            this.addSticker(emoji);
          })
      })
    }.padding(8)
  }.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)
}

// 表情分类Tab
@Builder
CategoryTab(label: string, icon: string, index: number) {
  Column({ space: 4 }) {
    Text(icon).fontSize(22)
    Text(label).fontSize(12).fontColor(this.currentTabIndex === index ? '#FBBF24' : '#9CA3AF')
  }
  .width('100%')
  .padding({ top: 10, bottom: 10 })
  .onClick(() => this.currentTabIndex = index)
}

5. 完整build()页面布局

整合所有组件,构建包含模板选择、制作编辑、历史记录三大Tab的完整界面:

build() {
  Column() {
    // ======== 顶部导航栏 ========
    Row() {
      Text('表情包制作')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1F2937')
      Blank()
      // 搜索按钮
      Button() {
        Image($r('app.media.ic_search')).width(22).height(22)
      }
      .width(40).height(40)
      .backgroundColor('#F3F4F6')
      .borderRadius(20)
      Text('📁').fontSize(22).margin({ left: 8 })
    }
    .width('100%')
    .padding(20)
    
    // ======== 底部Tab切换 ========
    Tabs({ barPosition: BarPosition.Bottom }) {
      // --- Tab1: 模板选择 ---
      TabContent() {
        Column() {
          // 分类横向滚动
          Row({ space: 12 }) {
            ForEach(['推荐', '经典', '动物', '搞笑', '动漫'], (cat: string) => {
              Text(cat).fontSize(14).fontColor('#6B7280').onClick(() => {})
            })
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 8, bottom: 8 })
          
          // 模板网格
          Scroll() {
            Grid() {
              ForEach(this.templates, (tpl: MemeTemplate) => {
                GridItem() {
                  this.TemplateCard(tpl)
                }
              })
            }
            .columnsTemplate('1fr 1fr 1fr')
            .columnsGap(10)
            .rowsGap(12)
            .width('100%')
            .padding(16)
          }
          .scrollBar(BarState.Off)
        }
      }
      .tabBar(this.CategoryTab('模板', '🎨', 0))
      
      // --- Tab2: 制作编辑 ---
      TabContent() {
        Column() {
          // 编辑画布 + 工具栏 + 贴纸选择器
          this.TextToolbar()
          Divider().margin({ top: 8, bottom: 8 })
          this.StickerSelector()
        }.width('100%')
      }
      .tabBar(this.CategoryTab('制作', '✏️', 1))
      
      // --- Tab3: 我的 ---
      TabContent() {
        Column() {
          if (this.historyList.length === 0) {
            Column({ space: 12 }) {
              Text('📭').fontSize(48)
              Text('还没有制作记录').fontSize(14).fontColor('#9CA3AF')
              Text('快去制作你的第一张表情包吧~').fontSize(12).fontColor('#D1D5DB')
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
          } else {
            List() {
              ForEach(this.historyList, (history: MemeHistory) => {
                ListItem() {
                  Row({ space: 12 }) {
                    Image(history.finalImage).width(60).height(60).borderRadius(8)
                    Column({ space: 4 }) {
                      Text(`模板ID: ${history.templateId}`).fontSize(14).fontColor('#374151')
                      Text(new Date(history.createTime).toLocaleDateString()).fontSize(12).fontColor('#9CA3AF')
                    }
                    Blank()
                    Button('分享').fontSize(12).height(30)
                  }
                  .width('100%')
                  .padding(12)
                }
              })
            }
          }
        }.width('100%')
      }
      .tabBar(this.CategoryTab('我的', '👤', 2))
    }
    .width('100%')
    .layoutWeight(1)
    .barHeight(60)
    
    // ======== 制作弹窗(选择模板后弹出) ========
    if (this.selectedTemplate && this.isEditing) {
      Column() {
        Blank()
        Column({ space: 20 }) {
          // 弹窗标题行
          Row() {
            Text('制作表情包')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
            Blank()
            Text('✕')
              .fontSize(20)
              .fontColor('#9CA3AF')
              .onClick(() => {
                this.selectedTemplate = null;
                this.isEditing = false;
              })
          }.width('100%')
          
          // 画布区域(多文字叠加)
          Stack({ alignContent: Alignment.Center }) {
            Text(this.selectedTemplate.image)
              .fontSize(120)
              .width(280)
              .height(280)
              .backgroundColor('#FEF9C3')
              .borderRadius(16)
            
            // 渲染所有文字
            ForEach(this.selectedTemplate.textPositions, (pos: TextPosition, index: number) => {
              Text(pos.text || '预览文字')
                .fontSize(pos.fontSize)
                .fontColor(pos.fontColor)
                .fontWeight(FontWeight.Bold)
                .position({ x: pos.x, y: pos.y })
                .gesture(
                  PanGesture({ direction: PanDirection.All })
                    .onActionUpdate((event: GestureEvent) => {
                      this.onTextDrag(index, event.offsetX, event.offsetY);
                    })
                )
                .onClick(() => this.deleteText(index))
            })
          }
          
          // 文字输入区
          TextInput({ placeholder: '输入文字,支持多行', text: this.inputText })
            .width('100%')
            .height(50)
            .backgroundColor('#F3F4F6')
            .borderRadius(12)
            .onChange((v: string) => this.inputText = v)
            .onSubmit(() => {
              this.addTextToTemplate(this.inputText, 50, 120);
            })
          
          // 操作按钮
          Row({ space: 16 }) {
            Button('取消')
              .width('45%')
              .height(48)
              .backgroundColor('#E5E7EB')
              .fontColor('#374151')
              .borderRadius(24)
              .onClick(() => {
                this.selectedTemplate = null;
                this.isEditing = false;
              })
            Button('保存到相册')
              .width('45%')
              .height(48)
              .backgroundColor('#FBBF24')
              .fontColor('#78350F')
              .borderRadius(24)
              .onClick(() => this.saveToGallery())
          }
        }
        .width('100%')
        .padding(24)
        .backgroundColor(Color.White)
        .borderRadius({ topLeft: 24, topRight: 24 })
      }
      .width('100%')
      .height('100%')
      .backgroundColor('rgba(0,0,0,0.5)')
    }
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#FFFBEB')
}

页面设计说明

整体UI设计遵循以下原则:

  • 主题色:采用暖黄色系 #A16207#FBBF24 渐变,底色 #FFFBEB 营造轻松趣味氛围
  • 布局方式:卡片式设计 + 圆角阴影,视觉层次清晰,符合Material Design规范
  • 交互反馈:按钮按压态、弹窗过渡动画、拖拽手势实时响应
  • 适配策略:百分比宽度 + 弹性布局,适配不同屏幕尺寸的HarmonyOS设备

SDK配置

build-profile.json5 中配置如下:

{
  "apiType": "stageMode",
  "buildOption": {},
  "targets": [
    {
      "name": "default",
      "runtimeOS": "HarmonyOS"
    }
  ],
  "products": [
    {
      "name": "default",
      "compileSdkVersion": 24,
      "compatibleSdkVersion": "6.1.1(24)",
      "runtimeOS": "HarmonyOS"
    }
  ]
}

运行项目

  1. 使用 DevEco Studio 打开或新建 HarmonyOS 项目
  2. 将完整代码复制到 entry/src/main/ets/pages/Index.ets
  3. 连接真机或启动模拟器(API ≥ 24)
  4. 点击运行即可体验表情包制作与斗图功能

提示:首次运行如遇权限问题,请在 module.json5 中添加相册读写权限配置。

进阶扩展方向

本项目已完成基础框架,读者可在此基础上继续实现以下进阶功能:

扩展方向 技术方案 难度
AI表情生成 接入大模型API,根据文字描述生成表情 ⭐⭐⭐⭐
人脸表情合成 使用OpenCV/MediaPipe识别人脸并替换表情 ⭐⭐⭐⭐⭐
视频转GIF 调用FFmpeg或HarmonyOS多媒体编解码能力 ⭐⭐⭐
表情包社区 搭建云函数后端,实现表情上传/点赞/评论 ⭐⭐⭐⭐
斗图对战 WebSocket实时通信,回合制表情PK ⭐⭐⭐⭐
表情抠图 使用图像分割模型去除背景 ⭐⭐⭐⭐
花字特效 Canvas自定义绘制阴影/描边/渐变文字 ⭐⭐⭐
表情压缩 图片压缩算法,优化分享速度 ⭐⭐
批量制作 多模板批量套用文字,一键生成表情包合集 ⭐⭐⭐

项目总结

本文从零搭建了一个功能完整的 HarmonyOS ArkTS 表情包制作与斗图神器应用,涵盖了以下核心技术点:

  1. ArkTS声明式UI:使用 @State@Builder 实现响应式界面
  2. 组件化架构:将模板卡片、工具栏、贴纸选择器封装为可复用Builder
  3. 手势交互PanGesture 实现文字拖拽定位,提升用户体验
  4. 状态管理:多状态联动,弹窗、Tab切换、搜索过滤一气呵成
  5. 工程化思维:数据结构设计、功能分层、扩展接口预留

📦 完整源码 已在上方提供,可直接复制运行。如需获取更多 HarmonyOS 实战项目,欢迎关注收藏,后续将持续更新 ArkTS 进阶教程!作等。

Logo

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

更多推荐