鸿蒙NEXT开发案例:颜文字搜索器

【引言】

本文将介绍一个名为“颜文字搜索器”的开发案例,该应用是基于鸿蒙NEXT平台构建的,旨在帮助用户快速查找和使用各种风格的表情符号。通过本案例的学习,读者可以了解如何在鸿蒙平台上进行数据处理、UI设计以及交互逻辑的实现。

【环境准备】

• 操作系统:Windows 10

• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806

• 目标设备:华为Mate60 Pro

• 开发语言:ArkTS

• 框架:ArkUI

• API版本:API 12

【开发思路】

1. 数据模型设计

为了表示单个表情符号的信息,我们定义了一个 EmoticonBean 类,它包含了表情符号的风格(style)、类型(type)、表情符号本身(emoticon)及其含义(meaning)。此外,还添加了一个布尔属性 isShown 来追踪表情符号是否应该显示给用户,这有助于在搜索时动态更新列表。

2. UI 组件与布局

应用的主界面由一个 Index 组件构成,它负责整体布局的设计。界面上部是一个搜索框,允许用户输入关键词来过滤表情符号列表。下方则以表格形式展示了所有符合条件的表情符号,每一行包括四个部分:风格、类型、表情符号和含义。为了提升用户体验,当用户点击某个表情符号或其含义中的高亮文本时,会触发相应的点击事件,并输出日志信息。

3. 数据加载与处理

表情符号的数据来源于一个本地 JSON 文件 (emoticons.json),该文件在组件初次渲染之前被读取并解析为 EmoticonBean 对象数组。每次用户修改搜索框中的内容时,都会调用 splitAndHighlight 方法对每个表情符号的含义进行分割,并检查是否存在匹配的关键字。如果存在,则设置 isShown 属性为 true,否则为 false,以此控制表情符号是否在界面上显示。

4. 搜索与高亮

splitAndHighlight 函数用于将表情符号的含义按关键字分割成多个片段,并返回这些片段组成的数组。对于包含关键字的片段,会在界面上以不同的颜色高亮显示,从而直观地指出匹配的部分。此外,此函数还会根据是否有匹配项来决定表情符号是否可见,确保只有相关的表情符号才会展示给用户。

5. 用户交互

为了让用户有更好的操作体验,我们在界面上实现了触摸事件监听,当用户点击非输入区域时,自动关闭键盘。这样既保证了界面整洁,又简化了用户的操作流程。

【完整代码】

数据源:src/main/resources/rawfile/emoticons.json

https://download.csdn.net/download/zhongcongxu01/90126325

代码

// 引入必要的工具库 util 用于文本解码等操作
import { util } from '@kit.ArkTS'
// 引入 BusinessError 类用于处理业务逻辑错误
import { BusinessError } from '@kit.BasicServicesKit'
// 引入 inputMethod 模块用于管理输入法行为
import { inputMethod } from '@kit.IMEKit'

// 定义一个可以被观察的数据模型 EmoticonBean 表示单个表情符号的信息
@ObservedV2
class EmoticonBean {
  // 定义风格属性,并初始化为空字符串
  style: string = ""
  // 定义类型属性,并初始化为空字符串
  type: string = ""
  // 定义表情符号本身,并初始化为空字符串
  emoticon: string = ""
  // 定义含义属性,并初始化为空字符串
  meaning: string = ""

  // 构造函数,允许在创建对象时设置上述属性
  constructor(style: string, type: string, emoticon: string, meaning: string) {
    this.style = style
    this.type = type
    this.emoticon = emoticon
    this.meaning = meaning
  }

  // 定义是否显示的表情符号状态标记,默认为 true,使用 @Trace 装饰器使其可追踪变化
  @Trace isShown: boolean = true
}

// 使用 @Entry 和 @Component 装饰器定义 Index 组件作为应用入口
@Entry
@Component
struct Index {
  // 定义一个状态变量 textInput 用于存储搜索框中的文本内容,默认为空字符串
  @State private textInput: string = ''
  // 定义一个状态变量 emoticonList 用于存储表情符号列表,默认为空数组
  @State private emoticonList: EmoticonBean[] = []

  // 定义线条颜色属性
  private lineColor: string = "#e6e6e6"
  // 定义标题背景色属性
  private titleBackground: string = "#f8f8f8"
  // 定义文本颜色属性
  private textColor: string = "#333333"
  // 定义基础填充大小
  private basePadding: number = 4
  // 定义线条宽度
  private lineWidth: number = 2
  // 定义单元格高度
  private cellHeight: number = 50
  // 定义列权重比例
  private weightRatio: number[] = [1, 1, 5, 4]
  // 定义基础字体大小
  private baseFontSize: number = 14

  // 定义一个方法 splitAndHighlight 用于分割并高亮表情符号含义中的关键词
  private splitAndHighlight(item: EmoticonBean, keyword: string): string[] {
    let text = item.meaning // 获取表情符号的含义文本
    if (!keyword) { // 如果没有关键词,则直接返回整个文本,并显示该表情符号
      item.isShown = true
      return [text]
    }
    let segments: string[] = []; // 用于存储分割后的文本片段
    let lastMatchEnd: number = 0; // 记录上一次匹配结束的位置
    while (true) { // 循环查找关键词在文本中的位置
      const matchIndex = text.indexOf(keyword, lastMatchEnd); // 查找关键词出现的位置
      if (matchIndex === -1) { // 如果找不到关键词,将剩余文本加入到segments中并退出循环
        segments.push(text.slice(lastMatchEnd));
        break;
      } else { // 如果找到关键词,将非关键词部分和关键词部分分别加入到segments中
        segments.push(text.slice(lastMatchEnd, matchIndex)); // 非关键词部分
        segments.push(text.slice(matchIndex, matchIndex + keyword.length)); // 关键词部分
        lastMatchEnd = matchIndex + keyword.length;
      }
    }
    // 如果有关键词出现,则设置表情符号为显示状态
    item.isShown = (segments.indexOf(keyword) != -1)
    return segments;
  }

  // 当组件即将出现在屏幕上时调用此方法,用于加载表情符号数据
  aboutToAppear() {
    // 从资源管理器中读取本地文件 emoticons.json 的内容
    getContext().resourceManager.getRawFileContent("emoticons.json", (err: BusinessError, data) => {
      if (err) { // 如果读取失败,打印错误信息
        console.error('getRawFileContent error: ' + JSON.stringify(err))
        return
      }
      // 创建一个文本解码器来将二进制数据转换为字符串
      let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })
      let jsonString = textDecoder.decodeToString(data, { stream: false })
      let jsonObjectArray: object[] = JSON.parse(jsonString) // 将 JSON 字符串解析为对象数组
      for (let i = 0; i < jsonObjectArray.length; i++) { // 遍历对象数组,填充 emoticonList
        let item = jsonObjectArray[i]
        this.emoticonList.push(new EmoticonBean(item['s'], item['t'], item['e'], item['m']))
      }
      try {
        // 打印 emoticonList 到控制台以供调试
        console.info(`this.emoticonList:${JSON.stringify(this.emoticonList, null, '\u00a0\u00a0')}`)
      } catch (err) {
        console.error('parse error: ' + JSON.stringify(err))
      }
    })
  }

  // 定义 build 方法构建组件的UI结构
  build() {
    Column({ space: 0 }) { // 创建一个列容器,内部元素之间没有间距
      // 搜索框组件,绑定到 textInput 状态变量
      Search({ value: $$this.textInput })
        .margin(this.basePadding) // 设置外边距
        .fontFeature("\"ss01\" on") // 设置字体特征
      // 创建一个列容器用于表头
      Column() {
        Row() { // 创建一行用于放置表头
          // 表头文字:风格
          Text('风格')
            .height('100%') // 设置高度为父容器的100%
            .layoutWeight(this.weightRatio[0]) // 根据权重分配宽度
            .textAlign(TextAlign.Center) // 文本居中对齐
            .fontSize(this.baseFontSize) // 设置字体大小
            .fontWeight(600) // 设置字体粗细
            .fontColor(this.textColor) // 设置文本颜色
          // 分割线
          Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
          // 表头文字:类型
          Text('类型')
            .height('100%')
            .layoutWeight(this.weightRatio[1])
            .textAlign(TextAlign.Center)
            .fontSize(this.baseFontSize)
            .fontWeight(600)
            .fontColor(this.textColor)
          // 分割线
          Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
          // 表头文字:表情
          Text('表情')
            .height('100%')
            .layoutWeight(this.weightRatio[2])
            .textAlign(TextAlign.Center)
            .fontSize(this.baseFontSize)
            .fontWeight(600)
            .fontColor(this.textColor)
          // 分割线
          Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
          // 表头文字:含义
          Text('含义')
            .height('100%')
            .layoutWeight(this.weightRatio[3])
            .textAlign(TextAlign.Center)
            .fontSize(this.baseFontSize)
            .fontWeight(600)
            .fontColor(this.textColor)
        }.height(this.cellHeight).borderWidth(this.lineWidth).borderColor(this.lineColor)
        .backgroundColor(this.titleBackground) // 设置背景颜色
      }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding })

      // 创建一个滚动容器 Scroll 包含表情符号列表
      Scroll() {
        Column() {
          // ForEach 循环遍历 emoticonList 数组,创建每一行代表一个表情符号条目
          ForEach(this.emoticonList, (item: EmoticonBean) => {
            Row() {
              // 显示表情符号的风格
              Text(item.style)
                .height('100%')
                .layoutWeight(this.weightRatio[0])
                .textAlign(TextAlign.Center)
                .fontSize(this.baseFontSize)
                .fontColor(this.textColor)
              // 分割线
              Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
              // 显示表情符号的类型
              Text(item.type)
                .height('100%')
                .layoutWeight(this.weightRatio[1])
                .textAlign(TextAlign.Center)
                .fontSize(this.baseFontSize)
                .fontColor(this.textColor)
              // 分割线
              Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
              // 显示表情符号
              Text(item.emoticon)
                .height('100%')
                .layoutWeight(this.weightRatio[2])
                .textAlign(TextAlign.Center)
                .fontSize(this.baseFontSize)
                .fontColor(this.textColor)
                .copyOption(CopyOptions.LocalDevice) // 允许复制到剪贴板
              // 分割线
              Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
              // 显示表情符号的含义,支持关键字高亮
              Text() {
                ForEach(this.splitAndHighlight(item, this.textInput), (segment: string, index: number) => {
                  ContainerSpan() {
                    Span(segment)
                      .fontColor(segment === this.textInput ? Color.White : Color.Black) // 根据是否是关键词设置字体颜色
                      .onClick(() => { // 设置点击事件监听器
                        console.info(`Highlighted text clicked: ${segment}`); // 打印点击的文本信息
                        console.info(`Click index: ${index}`); // 打印点击的索引信息
                      });
                  }.textBackgroundStyle({
                    color: segment === this.textInput ? Color.Red : Color.Transparent // 根据是否是关键词设置背景颜色
                  });
                });
              }
              .height('100%')
              .layoutWeight(this.weightRatio[3])
              .textAlign(TextAlign.Center)
              .fontSize(this.baseFontSize)
              .fontColor(this.textColor)
              .padding({ left: this.basePadding, right: this.basePadding })
            }
            .height(this.cellHeight)
            .borderWidth({ left: this.lineWidth, right: this.lineWidth, bottom: this.lineWidth })
            .borderColor(this.lineColor)
            // 根据表情符号的状态(是否显示)来决定其可见性
            .visibility(item.isShown ? Visibility.Visible : Visibility.None)
          })
        }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding })
      }.width('100%').layoutWeight(1).align(Alignment.Top)
      // 触摸事件处理,当用户点击空白区域时,关闭键盘输入
      .onTouch((event) => {
        if (event.type == TouchType.Down) { // 如果是按下事件
          inputMethod.getController().stopInputSession() // 停止当前的输入会话
        }
      })
    }.width('100%').height('100%').backgroundColor(Color.White); // 设置容器的宽高和背景颜色
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/935941.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【报错记录】Ubuntu22.04解决开机卡在 /dev/sda5 : clean , *files , *blocks

一个愿意伫立在巨人肩膀上的农民...... 一、错误现象 本人的电脑安装Windows10和Ubuntu22.04双系统&#xff0c;一次训练中电脑死机无法开机&#xff0c;重启之后便出现如下错误&#xff0c;在网上寻找过很多方法均无效&#xff0c;在root下禁用了samba服务&#xff0c;也无济…

关于睡懒觉

我们经常听到一个词&#xff1a;睡懒觉。 我认为&#xff0c;睡懒觉这个词&#xff0c;是错误的。 人&#xff0c;是需要睡眠的&#xff0c;睡不够&#xff0c;就不会醒。睡够了&#xff0c;自然会醒&#xff0c;也不想继续睡。不信你试试&#xff0c;睡够了&#xff0c;你…

数据地图怎么做?推荐这款数据可视化地图生成器

在数字化与信息化高速发展的今天&#xff0c;企业迎来了前所未有的发展机遇&#xff0c;规模迅速扩张&#xff0c;市场版图不断延伸。然而&#xff0c;伴随着这种快速的发展&#xff0c;一个不容忽视的问题逐渐浮出水面——如何精准高效地掌握分布在各地的分公司、业务点乃至整…

访问者模式的理解和实践

在软件开发过程中&#xff0c;设计模式为我们提供了解决常见问题的最佳实践。访问者模式&#xff08;Visitor Pattern&#xff09;是行为设计模式之一&#xff0c;它将数据操作与数据结构分离&#xff0c;使得在不修改数据结构的前提下&#xff0c;能够定义作用于这些元素的新的…

【Java 学习】面向程序的三大特性:封装、继承、多态

引言 1. 封装1.1 什么是封装呢&#xff1f;1.2 访问限定符1.3 使用封装 2. 继承2.1 为什么要有继承&#xff1f;2.2 继承的概念2.3 继承的语法2.4 访问父类成员2.4.1 子类中访问父类成员的变量2.4.2 访问父类的成员方法 2.5 super关键字2.6 子类的构造方法 3. 多态3.1 多态的概…

Burp与小程序梦中情缘

前言 在日常渗透工作中&#xff0c;有时需要对微信小程序进行抓包渗透&#xff0c;通过抓包&#xff0c;我们可以捕获小程序与服务器之间的通信数据&#xff0c;分析这些数据可以帮助我们发现潜在的安全漏洞&#xff0c;本文通过讲述三个方法在PC端来对小程序抓包渗透 文章目…

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 原模型 多图推理

基于Qwen2-VL模型针对LaTeX OCR任务进行微调训练 - 原模型 多图推理 flyfish 输入 输出 [‘第一张图片是一幅中国山水画&#xff0c;描绘了一座山峰和周围的树木。第二张图片是一张现代照片&#xff0c;展示了一座山峰和周围的自然景观&#xff0c;包括水体和植被。’] fro…

R语言学习笔记-1

1. 基础操作和函数 清空环境&#xff1a;rm(list ls()) 用于清空当前的R环境。 打印输出&#xff1a;print("Hello, world") 用于输出文本到控制台。 查看已安装包和加载包&#xff1a; search()&#xff1a;查看当前加载的包。install.packages("package_na…

基本分页存储管理

一、实验目的 目的&#xff1a;熟悉并掌握基本分页存储管理的思想及其实现方法&#xff0c;熟悉并掌握基本分页存储管理的分配和回收方式。 任务&#xff1a;模拟实现基本分页存储管理方式下内存空间的分配和回收。 二、实验内容 1、实验内容 内存空间的初始化——可以由用户输…

如何将CSDN的文章保存为PDF?

目录 1、打开CSDN文章2、按F12或者鼠标右键选择检查并进入控制台3、在控制台输入以下代码4、然后回车&#xff08;Enter&#xff09;如果纵向显示不全就横向 1、打开CSDN文章 2、按F12或者鼠标右键选择检查并进入控制台 3、在控制台输入以下代码 (function(){ $("#side&q…

ur机器人ros-urdf

新建工作空间 mkdir -p ~/catkin_ws/src cd catkin_ws_ur5/src git clone -b melodic-devel https://github.com/ros-industrial/universal_robot.git cd .. rosdep update rosdep install --rosdistro melodic --ignore-src --from-paths src catkin_make source ~/catkin_ws…

A6688 JSP+MYSQL+LW+二手物品网上交易系统

二手物品网上交易系统的设计与实现 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 摘 要 随着社会经济快速发展&#xff0c;互联网推动了电子商务业的迅速崛起。越来越多的人们喜欢在线进行商品的交易&#xff0c;尤其是对于二手物品的处理&#xff0…

深度学习day4|用pytorch实现猴痘病识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 &#x1f37a;要求&#xff1a; 训练过程中保存效果最好的模型参数。 加载最佳模型参数识别本地的一张图片。 调整网络结构使测试集accuracy到达88%&#x…

ISP(Image Signal Processor)——HDR技术总结

传统多帧融合技术 拍摄一系列不同曝光时长的图像帧&#xff08;LDR&#xff09;&#xff0c;然后使用融合算法进行融合成HDR图像。 融合算法可以分为两种 基于照度图估计的融合 基于照度估计需要拟合相机响应函数&#xff0c;详细可以参考如下论文&#xff1a; Recovering H…

首程控股再发停车资产类REITs,行业走向“资产管理模式”

伴随着资产证券化的快速发展&#xff0c;交易结构设置和底层资产类型也愈加丰富多样。 近日&#xff0c;首程控股完成“国君-首程控股智慧停车第二期资产支持专项计划”的正式发行。据了解&#xff0c;该产品的优先级利率为2.40%&#xff0c;发行规模达到人民币3.70亿元&#…

如何写出优秀的单元测试?

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 写出优秀的单元测试需要考虑以下几个方面&#xff1a; 1. 测试用例设计 测试用例应该覆盖被测试代码的不同场景和边界情况&#xff0c;以尽可能发现潜在的问题。…

PostgreSQL的学习心得和知识总结(一百六十四)|深入理解PostgreSQL数据库之在 libpq 中支持负载平衡

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

批量合并多个Excel到一个文件

工作中&#xff0c;我们经常需要将多个Excel的数据进行合并&#xff0c;很多插件都可以做这个功能。但是今天我们将介绍一个完全免费的独立软件【非插件】&#xff0c;来更加方便的实现这个功能。 准备Excel 这里我们准备了两张待合并的Excel文件 的卢易表 打开的卢易表软件…

shell编程(完结)

shell编程&#xff08;完结&#xff09; 声明&#xff01; 学习视频来自B站up主 ​泷羽sec​​ 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章 笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其…

docker 容器相互访问

目前采用 network 方式 1. 创建自定义网络 docker network create network-group 如下 2. 相互访问的容器更改&#xff08;目前演示redis 以及netcore api 访问redis &#xff09; //redis 原有容器删除 跟之前区别就是加入 --network network-group docker run \ -p 6379:…