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

打印 上一主题 下一主题

主题 792|帖子 792|积分 2376


【引言】
本文将先容一个名为“颜文字搜索器”的开发案例,该应用是基于鸿蒙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
代码
  1. // 引入必要的工具库 util 用于文本解码等操作
  2. import { util } from '@kit.ArkTS'
  3. // 引入 BusinessError 类用于处理业务逻辑错误
  4. import { BusinessError } from '@kit.BasicServicesKit'
  5. // 引入 inputMethod 模块用于管理输入法行为
  6. import { inputMethod } from '@kit.IMEKit'
  7. // 定义一个可以被观察的数据模型 EmoticonBean 表示单个表情符号的信息
  8. @ObservedV2
  9. class EmoticonBean {
  10.   // 定义风格属性,并初始化为空字符串
  11.   style: string = ""
  12.   // 定义类型属性,并初始化为空字符串
  13.   type: string = ""
  14.   // 定义表情符号本身,并初始化为空字符串
  15.   emoticon: string = ""
  16.   // 定义含义属性,并初始化为空字符串
  17.   meaning: string = ""
  18.   // 构造函数,允许在创建对象时设置上述属性
  19.   constructor(style: string, type: string, emoticon: string, meaning: string) {
  20.     this.style = style
  21.     this.type = type
  22.     this.emoticon = emoticon
  23.     this.meaning = meaning
  24.   }
  25.   // 定义是否显示的表情符号状态标记,默认为 true,使用 @Trace 装饰器使其可追踪变化
  26.   @Trace isShown: boolean = true
  27. }
  28. // 使用 @Entry 和 @Component 装饰器定义 Index 组件作为应用入口
  29. @Entry
  30. @Component
  31. struct Index {
  32.   // 定义一个状态变量 textInput 用于存储搜索框中的文本内容,默认为空字符串
  33.   @State private textInput: string = ''
  34.   // 定义一个状态变量 emoticonList 用于存储表情符号列表,默认为空数组
  35.   @State private emoticonList: EmoticonBean[] = []
  36.   // 定义线条颜色属性
  37.   private lineColor: string = "#e6e6e6"
  38.   // 定义标题背景色属性
  39.   private titleBackground: string = "#f8f8f8"
  40.   // 定义文本颜色属性
  41.   private textColor: string = "#333333"
  42.   // 定义基础填充大小
  43.   private basePadding: number = 4
  44.   // 定义线条宽度
  45.   private lineWidth: number = 2
  46.   // 定义单元格高度
  47.   private cellHeight: number = 50
  48.   // 定义列权重比例
  49.   private weightRatio: number[] = [1, 1, 5, 4]
  50.   // 定义基础字体大小
  51.   private baseFontSize: number = 14
  52.   // 定义一个方法 splitAndHighlight 用于分割并高亮表情符号含义中的关键词
  53.   private splitAndHighlight(item: EmoticonBean, keyword: string): string[] {
  54.     let text = item.meaning // 获取表情符号的含义文本
  55.     if (!keyword) { // 如果没有关键词,则直接返回整个文本,并显示该表情符号
  56.       item.isShown = true
  57.       return [text]
  58.     }
  59.     let segments: string[] = []; // 用于存储分割后的文本片段
  60.     let lastMatchEnd: number = 0; // 记录上一次匹配结束的位置
  61.     while (true) { // 循环查找关键词在文本中的位置
  62.       const matchIndex = text.indexOf(keyword, lastMatchEnd); // 查找关键词出现的位置
  63.       if (matchIndex === -1) { // 如果找不到关键词,将剩余文本加入到segments中并退出循环
  64.         segments.push(text.slice(lastMatchEnd));
  65.         break;
  66.       } else { // 如果找到关键词,将非关键词部分和关键词部分分别加入到segments中
  67.         segments.push(text.slice(lastMatchEnd, matchIndex)); // 非关键词部分
  68.         segments.push(text.slice(matchIndex, matchIndex + keyword.length)); // 关键词部分
  69.         lastMatchEnd = matchIndex + keyword.length;
  70.       }
  71.     }
  72.     // 如果有关键词出现,则设置表情符号为显示状态
  73.     item.isShown = (segments.indexOf(keyword) != -1)
  74.     return segments;
  75.   }
  76.   // 当组件即将出现在屏幕上时调用此方法,用于加载表情符号数据
  77.   aboutToAppear() {
  78.     // 从资源管理器中读取本地文件 emoticons.json 的内容
  79.     getContext().resourceManager.getRawFileContent("emoticons.json", (err: BusinessError, data) => {
  80.       if (err) { // 如果读取失败,打印错误信息
  81.         console.error('getRawFileContent error: ' + JSON.stringify(err))
  82.         return
  83.       }
  84.       // 创建一个文本解码器来将二进制数据转换为字符串
  85.       let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })
  86.       let jsonString = textDecoder.decodeToString(data, { stream: false })
  87.       let jsonObjectArray: object[] = JSON.parse(jsonString) // 将 JSON 字符串解析为对象数组
  88.       for (let i = 0; i < jsonObjectArray.length; i++) { // 遍历对象数组,填充 emoticonList
  89.         let item = jsonObjectArray[i]
  90.         this.emoticonList.push(new EmoticonBean(item['s'], item['t'], item['e'], item['m']))
  91.       }
  92.       try {
  93.         // 打印 emoticonList 到控制台以供调试
  94.         console.info(`this.emoticonList:${JSON.stringify(this.emoticonList, null, '\u00a0\u00a0')}`)
  95.       } catch (err) {
  96.         console.error('parse error: ' + JSON.stringify(err))
  97.       }
  98.     })
  99.   }
  100.   // 定义 build 方法构建组件的UI结构
  101.   build() {
  102.     Column({ space: 0 }) { // 创建一个列容器,内部元素之间没有间距
  103.       // 搜索框组件,绑定到 textInput 状态变量
  104.       Search({ value: $$this.textInput })
  105.         .margin(this.basePadding) // 设置外边距
  106.         .fontFeature(""ss01" on") // 设置字体特征
  107.       // 创建一个列容器用于表头
  108.       Column() {
  109.         Row() { // 创建一行用于放置表头
  110.           // 表头文字:风格
  111.           Text('风格')
  112.             .height('100%') // 设置高度为父容器的100%
  113.             .layoutWeight(this.weightRatio[0]) // 根据权重分配宽度
  114.             .textAlign(TextAlign.Center) // 文本居中对齐
  115.             .fontSize(this.baseFontSize) // 设置字体大小
  116.             .fontWeight(600) // 设置字体粗细
  117.             .fontColor(this.textColor) // 设置文本颜色
  118.           // 分割线
  119.           Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
  120.           // 表头文字:类型
  121.           Text('类型')
  122.             .height('100%')
  123.             .layoutWeight(this.weightRatio[1])
  124.             .textAlign(TextAlign.Center)
  125.             .fontSize(this.baseFontSize)
  126.             .fontWeight(600)
  127.             .fontColor(this.textColor)
  128.           // 分割线
  129.           Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
  130.           // 表头文字:表情
  131.           Text('表情')
  132.             .height('100%')
  133.             .layoutWeight(this.weightRatio[2])
  134.             .textAlign(TextAlign.Center)
  135.             .fontSize(this.baseFontSize)
  136.             .fontWeight(600)
  137.             .fontColor(this.textColor)
  138.           // 分割线
  139.           Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
  140.           // 表头文字:含义
  141.           Text('含义')
  142.             .height('100%')
  143.             .layoutWeight(this.weightRatio[3])
  144.             .textAlign(TextAlign.Center)
  145.             .fontSize(this.baseFontSize)
  146.             .fontWeight(600)
  147.             .fontColor(this.textColor)
  148.         }.height(this.cellHeight).borderWidth(this.lineWidth).borderColor(this.lineColor)
  149.         .backgroundColor(this.titleBackground) // 设置背景颜色
  150.       }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding })
  151.       // 创建一个滚动容器 Scroll 包含表情符号列表
  152.       Scroll() {
  153.         Column() {
  154.           // ForEach 循环遍历 emoticonList 数组,创建每一行代表一个表情符号条目
  155.           ForEach(this.emoticonList, (item: EmoticonBean) => {
  156.             Row() {
  157.               // 显示表情符号的风格
  158.               Text(item.style)
  159.                 .height('100%')
  160.                 .layoutWeight(this.weightRatio[0])
  161.                 .textAlign(TextAlign.Center)
  162.                 .fontSize(this.baseFontSize)
  163.                 .fontColor(this.textColor)
  164.               // 分割线
  165.               Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
  166.               // 显示表情符号的类型
  167.               Text(item.type)
  168.                 .height('100%')
  169.                 .layoutWeight(this.weightRatio[1])
  170.                 .textAlign(TextAlign.Center)
  171.                 .fontSize(this.baseFontSize)
  172.                 .fontColor(this.textColor)
  173.               // 分割线
  174.               Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
  175.               // 显示表情符号
  176.               Text(item.emoticon)
  177.                 .height('100%')
  178.                 .layoutWeight(this.weightRatio[2])
  179.                 .textAlign(TextAlign.Center)
  180.                 .fontSize(this.baseFontSize)
  181.                 .fontColor(this.textColor)
  182.                 .copyOption(CopyOptions.LocalDevice) // 允许复制到剪贴板
  183.               // 分割线
  184.               Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor)
  185.               // 显示表情符号的含义,支持关键字高亮
  186.               Text() {
  187.                 ForEach(this.splitAndHighlight(item, this.textInput), (segment: string, index: number) => {
  188.                   ContainerSpan() {
  189.                     Span(segment)
  190.                       .fontColor(segment === this.textInput ? Color.White : Color.Black) // 根据是否是关键词设置字体颜色
  191.                       .onClick(() => { // 设置点击事件监听器
  192.                         console.info(`Highlighted text clicked: ${segment}`); // 打印点击的文本信息
  193.                         console.info(`Click index: ${index}`); // 打印点击的索引信息
  194.                       });
  195.                   }.textBackgroundStyle({
  196.                     color: segment === this.textInput ? Color.Red : Color.Transparent // 根据是否是关键词设置背景颜色
  197.                   });
  198.                 });
  199.               }
  200.               .height('100%')
  201.               .layoutWeight(this.weightRatio[3])
  202.               .textAlign(TextAlign.Center)
  203.               .fontSize(this.baseFontSize)
  204.               .fontColor(this.textColor)
  205.               .padding({ left: this.basePadding, right: this.basePadding })
  206.             }
  207.             .height(this.cellHeight)
  208.             .borderWidth({ left: this.lineWidth, right: this.lineWidth, bottom: this.lineWidth })
  209.             .borderColor(this.lineColor)
  210.             // 根据表情符号的状态(是否显示)来决定其可见性
  211.             .visibility(item.isShown ? Visibility.Visible : Visibility.None)
  212.           })
  213.         }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding })
  214.       }.width('100%').layoutWeight(1).align(Alignment.Top)
  215.       // 触摸事件处理,当用户点击空白区域时,关闭键盘输入
  216.       .onTouch((event) => {
  217.         if (event.type == TouchType.Down) { // 如果是按下事件
  218.           inputMethod.getController().stopInputSession() // 停止当前的输入会话
  219.         }
  220.       })
  221.     }.width('100%').height('100%').backgroundColor(Color.White); // 设置容器的宽高和背景颜色
  222.   }
  223. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

涛声依旧在

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表