• [技术干货] 鸿蒙NEXT开发案例:九宫格随机
     【引言】在鸿蒙NEXT开发中,九宫格抽奖是一个常见且有趣的应用场景。通过九宫格抽奖,用户可以随机获得不同奖品,增加互动性和趣味性。本文将介绍如何使用鸿蒙开发框架实现九宫格抽奖功能,并通过代码解析展示实现细节。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【思路】本案例中的“九宫格随机”应用旨在模拟一个简单的抽奖场景,用户点击抽奖按钮后,程序会从预先定义好的九个奖品中随机选择一个作为最终奖品。整个应用采用了响应式编程模式,结合鸿蒙NEXT提供的组件化开发方式,实现了交互流畅、视觉效果良好的用户体验。1. Prize类设计 应用首先定义了一个Prize类,用于表示奖品信息。该类使用了@ObservedV2装饰器,使得奖品属性(如标题、颜色、描述)的变化可以被自动追踪,从而实现UI的实时更新。构造函数允许创建具有特定属性值的奖品实例,便于后续管理。2. MyPrizeUpdate结构组件 为了提供奖品信息的编辑功能,我们创建了MyPrizeUpdate结构组件。它通过接收外部传入的数据(当前选中的奖品索引、抽奖顺序数组及所有奖品的数组),构建了一个包含文本输入框的界面,用户可以在其中修改奖品的标题、描述和颜色。任何对这些属性的更改都会即时反映到对应的奖品对象上,并触发UI的相应更新。3. LotteryPage入口组件LotteryPage是整个抽奖应用的核心组件,负责组织页面布局和处理用户交互逻辑。它初始化了一系列必要的状态变量,比如保存所有奖品的数组prizeArray、定义抽奖顺序的selectionOrder以及控制动画状态的isAnimating等。此外,该组件实现了抽奖过程的关键方法——startLottery(开始抽奖)、runAtConstantSpeed(匀速运行)和slowDown(减速),它们共同协作以模拟真实的抽奖体验。当用户点击抽奖按钮时,这些方法按照预定的速度模式依次调用,直到最终确定一个奖品为止。最后,通过弹出对话框的方式向用户展示抽奖结果。4. UI布局与样式 在构建UI方面,应用充分利用了鸿蒙NEXT提供的布局容器(如Column、Row、Flex)和样式属性(如宽度、高度、边距、背景色、圆角、阴影),精心设计了每个奖品项的外观。特别地,对于抽奖按钮,不仅设置了独特的背景颜色,还在点击事件中添加了动画效果,增强了用户的参与感。同时,考虑到不同设备屏幕尺寸的差异,所有布局元素均采用相对单位进行设置,确保了应用在各种终端上的良好适配性。5. 动画与交互优化 为了让抽奖过程看起来更加生动有趣,应用引入了加速、匀速、减速三个阶段的动画效果,使选中的奖品项能够以逐渐加快然后缓慢停止的方式出现在用户面前。这种变化不仅增加了悬念感,也提升了整体的娱乐性。此外,通过对点击事件的监听和处理,确保了即使是在动画过程中,用户的交互也不会受到影响,保证了良好的用户体验。【完整代码】// 定义一个可观察的Prize类,用于表示奖品信息。 @ObservedV2 class Prize { @Trace title: string // 奖品标题属性,使用@Trace进行追踪以便响应式更新UI @Trace color: string // 奖品颜色属性 @Trace description: string // 奖品描述属性 // 构造函数,用来初始化新的奖品实例 constructor(title: string, color: string, description: string = "") { this.title = title // 设置奖品标题 this.color = color // 设置奖品颜色 this.description = description // 设置奖品描述,默认为空字符串 } } // 定义MyPrizeUpdate结构组件,用于显示和编辑选中的奖品信息 @Component struct MyPrizeUpdate { @Consume selectedIndex: number // 当前选中的奖品索引 @Consume private selectionOrder: number[] // 保存抽奖顺序的数组 @Consume private prizeArray: Prize[] // 保存所有奖品的数组 build() { Column({ space: 20 }) { // 创建列布局容器,设置子元素之间的间距为20px Row() { // 创建行布局容器 Text('标题:') // 显示“标题”文本 TextInput({ text: this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].title }) .width('300lpx') // 设置输入框宽度 .onChange((value) => { // 监听输入框内容变化 this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].title = value // 更新奖品标题 }) } Row() { Text('描述:') TextInput({ text: `${this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].description}` }).width('300lpx').onChange((value) => { // 同上,但针对奖品描述 this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].description = value }) } Row() { Text('颜色:') TextInput({ text: `${this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].color}` }).width('300lpx').onChange((value) => { // 同上,但针对奖品颜色 this.prizeArray[this.selectionOrder[this.selectedIndex%this.selectionOrder.length]].color = value }) } } .justifyContent(FlexAlign.Start) // 设置内容左对齐 .padding(40) // 设置内边距 .width('100%') // 设置宽度为100% .backgroundColor(Color.White) // 设置背景颜色为白色 } } // 定义抽奖页面入口组件 @Entry @Component struct LotteryPage { @Provide private selectedIndex: number = 0 // 提供当前选中的索引,初始值为0 private isAnimating: boolean = false // 标记是否正在进行动画,初始值为false @Provide private selectionOrder: number[] = [0, 1, 2, 5, 8, 7, 6, 3] // 定义抽奖顺序 private cellWidth: number = 200 // 单元格宽度 private baseMargin: number = 10 // 单元格边距 @Provide private prizeArray: Prize[] = [ new Prize("红包", "#ff9675", "10元"), // 初始化奖品数组,创建各种奖品对象 new Prize("话费", "#ff9f2e", "5元"), new Prize("红包", "#8e7fff", "50元"), new Prize("红包", "#48d1ea", "30元"), new Prize("开始抽奖", "#fffdfd"), // 抽奖按钮,没有具体奖品描述 new Prize("谢谢参与", "#5f5f5f"), new Prize("谢谢参与", "#5f5f5f"), new Prize("超市红包", "#5f5f5f", "100元"), new Prize("鲜花", "#75b0fe"), ] private intervalID: number = 0 // 定时器ID,用于控制抽奖速度 @State isSheetVisible: boolean = false // 控制底部弹出表单的可见性 // 开始抽奖逻辑 startLottery(speed: number = 500) { setTimeout(() => { // 设置延时执行 if (speed > 50) { // 如果速度大于50,则递归调用startLottery以逐渐加速 speed -= 50 this.startLottery(speed) } else { this.runAtConstantSpeed() // 达到最高速度后进入匀速阶段 return } this.selectedIndex++ // 每次调用时更新选中索引 }, speed) } // 以恒定速度运行抽奖 runAtConstantSpeed() { let speed = 40 + Math.floor(Math.random() * this.selectionOrder.length) // 随机生成一个速度值 clearInterval(this.intervalID) // 清除之前的定时器 this.intervalID = setInterval(() => { // 设置新的定时器来更新选中索引 if (this.selectedIndex >= speed) { // 如果选中索引达到速度值,停止并进入减速阶段 clearInterval(this.intervalID) this.slowDown() return } this.selectedIndex++ }, 50) } // 减速逻辑 slowDown(speed = 50) { setTimeout(() => { // 设置延时执行 if (speed < 500) { // 如果速度小于500,则递归调用slowDown以逐渐减速 speed += 50 this.slowDown(speed) } else { this.selectedIndex %= this.selectionOrder.length // 确保索引在有效范围内 let index = this.selectionOrder[this.selectedIndex] // 获取最终选中的奖品索引 this.isAnimating = false // 动画结束 this.getUIContext().showAlertDialog({ // 显示结果对话框 title: '结果', message: `${this.prizeArray[index].title}${this.prizeArray[index].description}`, // 显示奖品信息 confirm: { defaultFocus: true, value: '我知道了', // 确认按钮文本 action: () => {} // 点击确认后的操作 }, alignment: DialogAlignment.Center, }); return } this.selectedIndex++ }, speed) } // 构建UI方法 build() { Column() { // 使用Column布局容器 Flex({ wrap: FlexWrap.Wrap }) { // 使用弹性布局,允许换行 ForEach(this.prizeArray, (item: Prize, index: number) => { // 遍历奖品数组,创建每个奖品的UI Column() { // 使用Column布局容器为每个奖品项 Text(`${item.title}`) // 显示奖品标题 .fontColor(index == 4 ? Color.White : item.color) // 设置字体颜色,对于抽奖按钮特殊处理 .fontSize(16) Text(`${item.description}`) // 显示奖品描述 .fontColor(index == 4 ? Color.White : item.color) // 设置字体颜色 .fontSize(20) } .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 添加点击效果 .onClick(() => { // 处理点击事件 if (this.isAnimating) { // 如果正在动画中,忽略点击 return } if (index == 4) { // 如果点击的是抽奖按钮,开始抽奖 this.isAnimating = true this.startLottery() } else { for (let i = 0; i < this.selectionOrder.length; i++) { if (this.selectionOrder[i] == index) { this.selectedIndex = i // 更新选中索引到对应位置 } } } }) .alignItems(HorizontalAlign.Center) // 设置水平居中对齐 .justifyContent(FlexAlign.Center) // 设置垂直居中对齐 .width(`${this.cellWidth}lpx`) // 设置单元格宽度 .height(`${this.cellWidth}lpx`) // 设置单元格高度 .margin(`${this.baseMargin}lpx`) // 设置单元格边距 .backgroundColor(index == 4 ? "#ff5444" : // 抽奖按钮背景颜色特殊处理 (this.selectionOrder[this.selectedIndex % this.selectionOrder.length] == index ? Color.Gray : Color.White)) .borderRadius(10) // 设置圆角 .shadow({ // 设置阴影效果 radius: 10, color: "#f98732", offsetX: 0, offsetY: 20 }) }) }.width(`${this.cellWidth * 3 + this.baseMargin * 6}lpx`) // 设置整体宽度 .margin({ top: 30 }) // 设置顶部边距 MyPrizeUpdate().margin({top:20}) // 插入MyPrizeUpdate组件,并设置其上边距 } .height('100%') // 设置高度为100% .width('100%') // 设置宽度为100% .backgroundColor("#ffb350") // 设置页面背景颜色 } } 转载自https://www.cnblogs.com/zhongcx/p/18602222
  • [技术干货] 鸿蒙NEXT开发案例:保质期计算
    【引言】保质期计算应用是一个基于鸿蒙NEXT框架开发的数字和文本统计组件。用户可以输入商品的生产日期和保质期天数,应用会自动计算并展示相关信息,包括保质状态、剩余天数、生产日期和到期日期。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【实现思路】1 组件定义在应用中,我们定义了一个名为ExpiryDateCalculator的组件,其中包含了各种状态变量和方法。通过监听输入文本变化和选择日期变化,实现了自动更新相关信息的功能。2 UI界面构建通过构建UI界面,我们使用了列布局和行布局来展示各个信息模块。包括标题展示、统计结果展示、示例和清空按钮、选择生产日期等功能。通过设置字体颜色、背景色、阴影效果等,使界面更加美观和易读。3 交互功能实现在交互功能方面,我们实现了输入框焦点状态的切换、清空按钮功能、选择日期功能等。用户可以方便地输入信息并查看计算结果,提升了用户体验和操作便捷性。【完整代码】@Entry @Component struct ExpiryDateCalculator { // 定义文本颜色的状态变量,初始值为深灰色 @State private textColor: string = "#2e2e2e"; // 定义阴影边框颜色的状态变量,初始值为浅灰色 @State private shadowColor: string = "#d5d5d5"; // 定义基础内边距的状态变量,初始值为30 @State private basePadding: number = 30; // 定义是否已过期的状态变量,初始值为false @State private isExpired: boolean = false; // 定义生产日期的状态变量,初始值为空字符串 @State private productionDate: string = ""; // 定义到期日期的状态变量,初始值为空字符串 @State private expiryDate: string = ""; // 定义剩余有效天数的状态变量,初始值为0 @State private remainingDays: number = 0; // 定义主题颜色,初始值为橙色 @State private themeColor: string | Color = Color.Orange; // 输入框是否获得了焦点的状态变量,初始值为false @State isInputFocused: boolean = false; // 定义监听输入文本变化的状态变量,初始值为"9" @State @Watch('inputChanged') private inputText: string = "9"; // 定义选择的日期状态变量,初始值为当前日期 @State @Watch('inputChanged') private selectedDate: Date = new Date() // 组件即将出现时的操作 aboutToAppear(): void { // 调用输入变化处理方法 this.inputChanged() } // 获取年月日的方法 getYearMonthDay(date: Date) { // 获取年份并格式化为4位数 const year: string = date.getFullYear().toString().padStart(4, '0'); // 获取月份并格式化为2位数 const month: string = (date.getMonth() + 1).toString().padStart(2, '0'); // 获取日期并格式化为2位数 const day: string = date.getDate().toString().padStart(2, '0'); // 返回格式化后的日期字符串 return `${year}年${month}月${day}日`; } // 输入变化时的处理方法 inputChanged() { // 打印当前选择的日期 console.info(`selectedDate:${this.selectedDate}`); // 更新生产日期为选择的日期 this.productionDate = this.getYearMonthDay(this.selectedDate); // 创建到期日期对象 let expiryDate: Date = new Date(this.selectedDate); // 根据输入的天数更新到期日期 expiryDate.setDate(expiryDate.getDate() + Number(this.inputText)); // 更新到期日期为格式化后的字符串 this.expiryDate = this.getYearMonthDay(expiryDate); // 判断是否已过期 this.isExpired = expiryDate.getTime() < new Date().getTime(); // 计算时间差 const timeDifference = expiryDate.getTime() - new Date().getTime(); // 计算剩余天数 this.remainingDays = Math.ceil(timeDifference / (1000 * 60 * 60 * 24)); } // 构建UI界面的方法 build() { // 创建一个列布局容器 Column() { // 添加标题 Text('保质期计算') .fontColor(this.textColor)// 设置字体颜色 .fontSize(18)// 设置字体大小 .width('100%')// 设置宽度为100% .height(50)// 设置高度为50 .textAlign(TextAlign.Center)// 设置文本对齐方式为居中 .backgroundColor(Color.White)// 设置背景颜色为白色 .shadow({ // 设置阴影效果 radius: 2, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 5 // Y轴偏移量 }); // 创建可滚动的容器 Scroll() { // 在可滚动容器内部创建列布局 Column() { // 添加统计结果展示 Column() { // 是否过期 Row() { Text() { Span(`保质状态:`) // 显示文本“保质状态:” Span(`${this.isExpired ? '已过期' : '未过期'}`)// 根据状态显示“已过期”或“未过期” .fontColor(this.isExpired ? "#e74c3c" : "#3ace7d") // 根据状态设置字体颜色 } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% Divider(); // 添加分隔线 // 剩余天数 Row() { Text() { Span(`剩余天数:`) // 显示文本“剩余天数:” Span(`${this.remainingDays < 0 ? 0 : this.remainingDays} `).fontColor(Color.Orange) // 显示剩余天数,负数显示为0 Span('天') // 显示单位“天” } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% Divider(); // 添加分隔线 // 生产日期 Row() { Text() { Span(`生产日期:`) // 显示文本“生产日期:” Span(`${this.productionDate} `) // 显示生产日期 } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% Divider(); // 添加分隔线 // 到期日期 Row() { Text() { Span(`到期日期:`) // 显示文本“到期日期:” Span(`${this.expiryDate} `) // 显示到期日期 } .fontColor(this.textColor) // 设置字体颜色 .fontSize(16) // 设置字体大小 .layoutWeight(1); // 设置布局权重 } .constraintSize({ minHeight: 45 }) // 设置最小高度 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% } .alignItems(HorizontalAlign.Start) // 设置子项对齐方式 .width('650lpx') // 设置宽度为650像素 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置上边距 .borderRadius(10) // 设置圆角 .backgroundColor(Color.White) // 设置背景颜色为白色 .shadow({ // 设置阴影效果 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 添加示例和清空按钮 Column() { Row() { // 添加文本输入区域 Row() { TextInput({ text: $$this.inputText, placeholder: `请输入保质期天数` })// 创建文本输入框 .type(InputType.Number)// 设置输入类型为数字 .layoutWeight(1)// 设置布局权重 .fontSize(16)// 设置字体大小 .textAlign(TextAlign.JUSTIFY)// 设置文本对齐方式 .backgroundColor(Color.Transparent)// 设置背景色为透明 .padding(0)// 设置内边距为0 .height('100%')// 设置高度为100% .placeholderColor(this.isInputFocused ? this.themeColor : Color.Gray)// 设置占位符颜色 .fontColor(this.isInputFocused ? this.themeColor : this.textColor)// 设置字体颜色 .caretColor(this.themeColor)// 设置光标颜色 .borderRadius(0)// 设置圆角为0 .onBlur(() => this.isInputFocused = false)// 失去焦点时更新状态 .onFocus(() => this.isInputFocused = true)// 获得焦点时更新状态 .width('100%'); // 设置宽度为100% } .padding(`${this.basePadding / 2}lpx`) // 设置内边距 .backgroundColor("#f2f1fd") // 设置背景色 .layoutWeight(1) // 设置布局权重 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角为10 .borderColor(this.isInputFocused ? this.themeColor : Color.Gray) // 根据焦点状态设置边框颜色 .margin({ right: `${this.basePadding / 2}lpx` }); // 设置右边距 Blank(); // 添加空白占位符 // 清空按钮 Text('清空')// 显示文本“清空” .fontColor("#e48742")// 设置字体颜色 .fontSize(16)// 设置字体大小 .padding(`${this.basePadding / 2}lpx`)// 设置内边距 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 })// 点击效果 .backgroundColor("#ffefe6")// 设置背景色 .borderRadius(5)// 设置圆角为5 .onClick(() => { // 点击事件处理 this.inputText = ""; // 清空输入文本 }); } .height(45) // 设置高度为45 .alignItems(VerticalAlign.Center) // 设置内容垂直居中 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% Divider(); // 添加分隔线 // 选择生产日期 Row() { Text('请选择生产日期')// 显示文本“请选择生产日期” .fontColor("#5871ce")// 设置字体颜色 .fontSize(16)// 设置字体大小 .padding(`${this.basePadding / 2}lpx`)// 设置内边距 .backgroundColor("#f2f1fd")// 设置背景色 .borderRadius(5)// 设置圆角为5 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 点击效果 Blank(); // 添加空白占位符 CalendarPicker({ hintRadius: 10, selected: this.selectedDate })// 创建日历选择器 .edgeAlign(CalendarAlign.END)// 设置对齐方式 .textStyle({ color: "#ff182431", font: { size: 20, weight: FontWeight.Normal } })// 设置文本样式 .margin(10)// 设置外边距 .onChange((date: Date) => { // 日期变化事件处理 this.selectedDate = date // 更新选择的日期 }) } .height(45) // 设置高度为45 .justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式 .width('100%'); // 设置宽度为100% } .alignItems(HorizontalAlign.Start) // 设置内容水平对齐方式 .width('650lpx') // 设置宽度为650像素 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置上边距 .borderRadius(10) // 设置圆角为10 .backgroundColor(Color.White) // 设置背景颜色为白色 .shadow({ // 设置阴影效果 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); // 添加工具介绍 Column() { Text('工具介绍')// 显示文本“工具介绍” .fontSize(18)// 设置字体大小为18 .fontWeight(600)// 设置字体粗细 .fontColor(this.textColor); // 设置字体颜色为状态变量中定义的文本颜色 Text('输入需要计算保质期商品的生产日期和保质期天数,工具将为您自动计算商品的保质期状态、剩余天数、到期日。')// 显示工具介绍文本 .textAlign(TextAlign.JUSTIFY)// 设置文本对齐方式为两端对齐 .fontSize(16)// 设置字体大小为16 .fontColor(this.textColor)// 设置字体颜色为状态变量中定义的文本颜色 .margin({ top: `${this.basePadding / 2}lpx` }); // 设置上边距为基础内边距的一半 } .alignItems(HorizontalAlign.Start) // 设置内容水平对齐方式 .width('650lpx') // 设置宽度为650像素 .padding(`${this.basePadding}lpx`) // 设置内边距 .margin({ top: `${this.basePadding}lpx` }) // 设置上边距 .borderRadius(10) // 设置圆角为10 .backgroundColor(Color.White) // 设置背景颜色为白色 .shadow({ // 设置阴影效果 radius: 10, // 阴影半径 color: this.shadowColor, // 阴影颜色 offsetX: 0, // X轴偏移量 offsetY: 0 // Y轴偏移量 }); } } .scrollBar(BarState.Off) // 关闭滚动条 .clip(false); // 不裁剪内容 } .height('100%') // 设置高度为100% .width('100%') // 设置宽度为100% .backgroundColor("#f4f8fb"); // 设置背景颜色为指定颜色 } }转载自https://www.cnblogs.com/zhongcx/p/18603215
  • [技术干货] 鸿蒙NEXT开发案例:世界时间表
    【引言】本案例将展示如何使用鸿蒙NEXT框架开发一个简单的世界时钟应用程序。该应用程序能够展示多个城市的当前时间,并支持搜索功能,方便用户快速查找所需城市的时间信息。在本文中,我们将详细介绍应用程序的实现思路,包括如何获取时区信息、更新城市时间、以及如何实现搜索高亮功能。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【实现思路】1. 组件结构设计我们的应用程序主要由两个核心组件构成:CityTimeInfo类和WorldClockApp组件。CityTimeInfo类用于存储每个城市的名称、当前时间和时区信息。WorldClockApp组件则负责管理城市时间列表、搜索功能以及用户界面。2. 获取时区信息在应用程序启动时,我们需要获取可用的时区信息。通过调用i18n.TimeZone.getAvailableIDs()方法,我们可以获取所有可用的时区ID。接着,我们使用这些ID创建CityTimeInfo实例,并将其添加到城市时间列表中。为了确保用户能够看到当前时间,我们还添加了北京的时间信息作为默认城市。3. 更新时间逻辑为了实时更新城市的当前时间,我们在updateAllCityTimes方法中实现了时间更新逻辑。通过获取系统的语言环境和相应的日历对象,我们可以根据城市的时区ID获取当前的年、月、日、时、分、秒,并将其格式化为字符串。这个方法会在页面显示时每秒调用一次,确保时间信息的准确性。4. 搜索功能实现为了提升用户体验,我们实现了搜索功能,允许用户通过输入关键词来筛选城市。在highlightSearchText方法中,我们对城市名称进行分段处理,将匹配的关键词高亮显示。通过这种方式,用户可以快速找到所需的城市,并且高亮的文本能够提供更好的视觉反馈。5. 用户界面构建最后,我们使用build方法构建用户界面。界面包括一个搜索框、城市名称和时间的显示区域。我们使用了Column和Row组件来布局,并通过设置样式属性来美化界面。滚动区域的实现使得用户可以方便地浏览多个城市的信息。【完整代码】import { i18n } from '@kit.LocalizationKit' // 导入国际化模块,用于处理多语言 import { inputMethod } from '@kit.IMEKit' // 导入输入法模块 @ObservedV2 // 观察者装饰器,用于观察状态变化 class CityTimeInfo { // 定义城市时间信息类 @Trace cityName: string = ""; // 城市名称,初始为空字符串 @Trace currentTime: string = ""; // 当前时间,初始为空字符串 timeZone: i18n.TimeZone; // 时区属性 constructor(cityName: string, timeZone: i18n.TimeZone) { // 构造函数,接收城市名称和时区 this.cityName = cityName; // 设置城市名称 this.timeZone = timeZone; // 设置时区 } @Trace isVisible: boolean = true; // 是否可见,初始为true } @Entry // 入口组件装饰器 @Component // 组件装饰器 struct WorldClockApp { // 定义世界时钟应用组件 @State private searchText: string = ''; // 搜索文本,初始为空字符串 @State private cityTimeList: CityTimeInfo[] = []; // 城市时间信息列表,初始为空数组 private lineColor: string = "#e6e6e6"; // 边框颜色 private titleBackgroundColor: string = "#f8f8f8"; // 标题背景色 private textColor: string = "#333333"; // 文字颜色 private basePadding: number = 4; // 内边距 private lineWidth: number = 2; // 边框宽度 private rowHeight: number = 50; // 行高 private ratio: number[] = [1, 1]; // 列宽比例 private textSize: number = 14; // 基础字体大小 private updateIntervalId = 0; // 更新间隔ID updateAllCityTimes() { // 更新所有城市的时间 const locale = i18n.System.getSystemLocale(); // 获取系统语言环境 for (const cityTime of this.cityTimeList) { // 遍历城市时间列表 const timeZoneId: string = cityTime.timeZone.getID(); // 获取时区ID const calendar = i18n.getCalendar(locale); // 获取日历对象 calendar.setTimeZone(timeZoneId); // 设置日历的时区 // 获取当前时间的各个部分 const year = calendar.get("year").toString().padStart(4, '0'); // 年 const month = (calendar.get("month")+1).toString().padStart(2, '0'); // 月 const day = calendar.get("date").toString().padStart(2, '0'); // 日 const hour = calendar.get("hour_of_day").toString().padStart(2, '0'); // 小时 const minute = calendar.get("minute").toString().padStart(2, '0'); // 分钟 const second = calendar.get("second").toString().padStart(2, '0'); // 秒 // 更新城市的当前时间字符串 cityTime.currentTime = `${year}年${month}月${day}日 ${hour}:${minute}:${second}`; } } onPageShow(): void { // 页面显示时的处理 clearInterval(this.updateIntervalId); // 清除之前的定时器 this.updateIntervalId = setInterval(() => { // 设置新的定时器 this.updateAllCityTimes(); // 每秒更新所有城市的时间 }, 1000); } onPageHide(): void { // 页面隐藏时的处理 clearInterval(this.updateIntervalId); // 清除定时器 } private highlightSearchText(cityTime: CityTimeInfo, keyword: string) { // 高亮搜索文本 let text = cityTime.cityName // 获取城市名称 if (!keyword) { // 如果没有关键词 cityTime.isVisible = true // 设置城市可见 return [text] // 返回城市名称 } let segments: string[] = []; // 存储分段文本 let lastMatchEnd: number = 0; // 上一个匹配结束的位置 while (true) { // 循环查找关键词 const matchIndex = text.indexOf(keyword, lastMatchEnd); // 查找关键词位置 if (matchIndex === -1) { // 如果没有找到 segments.push(text.slice(lastMatchEnd)); // 添加剩余文本 break; // 退出循环 } else { segments.push(text.slice(lastMatchEnd, matchIndex)); // 添加匹配前的文本 segments.push(text.slice(matchIndex, matchIndex + keyword.length)); // 添加匹配的关键词 lastMatchEnd = matchIndex + keyword.length; // 更新最后匹配结束位置 } } cityTime.isVisible = (segments.indexOf(keyword) != -1) // 设置城市可见性 return segments; // 返回分段文本 } aboutToAppear() { // 组件即将出现时的处理 const timeZoneIds: Array<string> = i18n.TimeZone.getAvailableIDs(); // 获取可用时区ID列表 this.cityTimeList.push(new CityTimeInfo('北京 (中国)', i18n.getTimeZone())); // 添加北京的城市时间信息 for (const id of timeZoneIds) { // 遍历时区ID const cityDisplayName = i18n.TimeZone.getCityDisplayName(id.split('/')[1], "zh-CN"); // 获取城市显示名称 if (cityDisplayName) { // 如果城市名称存在 this.cityTimeList.push(new CityTimeInfo(cityDisplayName, i18n.getTimeZone(id))); // 添加城市时间信息 } } this.updateAllCityTimes(); // 更新所有城市的时间 } build() { // 构建组件的UI Column({ space: 0 }) { // 创建一个垂直列 Search({ value: $$this.searchText })// 创建搜索框 .margin(this.basePadding)// 设置边距 .fontFeature("\"ss01\" on") // 设置字体特性 Column() { // 创建一个列 Row() { // 创建一行 Text('城市')// 显示“城市”文本 .height('100%')// 高度占满 .layoutWeight(this.ratio[0])// 设置布局权重 .textAlign(TextAlign.Center)// 文本居中 .fontSize(this.textSize)// 设置字体大小 .fontWeight(600)// 设置字体粗细 .fontColor(this.textColor) // 设置字体颜色 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 创建分隔线 Text('时间')// 显示“时间”文本 .height('100%')// 高度占满 .layoutWeight(this.ratio[1])// 设置布局权重 .textAlign(TextAlign.Center)// 文本居中 .fontSize(this.textSize)// 设置字体大小 .fontWeight(600)// 设置字体粗细 .fontColor(this.textColor) // 设置字体颜色 }.height(this.rowHeight).borderWidth(this.lineWidth).borderColor(this.lineColor) // 设置行高和边框 .backgroundColor(this.titleBackgroundColor) // 设置背景色 }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding }) // 设置列宽和内边距 Scroll() { // 创建可滚动区域 Column() { // 创建一个列 ForEach(this.cityTimeList, (item: CityTimeInfo) => { // 遍历城市时间列表 Row() { // 创建一行 Text() { // 创建文本 ForEach(this.highlightSearchText(item, this.searchText), (segment: string, index: number) => { // 高亮搜索文本 ContainerSpan() { // 创建容器 Span(segment)// 创建文本段 .fontColor(segment === this.searchText ? Color.White : Color.Black)// 设置字体颜色 .onClick(() => { // 点击事件 console.info(`高亮文本被点击:${segment}`); // 输出点击的文本 console.info(`点击索引:${index}`); // 输出点击的索引 }); }.textBackgroundStyle({ // 设置文本背景样式 color: segment === this.searchText ? Color.Red : Color.Transparent // 根据是否匹配设置背景色 }); }); } .height('100%') // 高度占满 .layoutWeight(this.ratio[0]) // 设置布局权重 .textAlign(TextAlign.Center) // 文本居中 .fontSize(this.textSize) // 设置字体大小 .fontColor(this.textColor) // 设置字体颜色 Line().height('100%').width(this.lineWidth).backgroundColor(this.lineColor) // 创建分隔线 Text(item.currentTime)// 显示当前时间 .height('100%')// 高度占满 .layoutWeight(this.ratio[1])// 设置布局权重 .textAlign(TextAlign.Center)// 文本居中 .fontSize(this.textSize)// 设置字体大小 .fontColor(this.textColor) // 设置字体颜色 } .height(this.rowHeight) // 设置行高 .borderWidth({ left: this.lineWidth, right: this.lineWidth, bottom: this.lineWidth }) // 设置边框宽度 .borderColor(this.lineColor) // 设置边框颜色 .visibility(item.isVisible ? Visibility.Visible : Visibility.None) // 根据可见性设置显示状态 }) }.width(`100%`).padding({ left: this.basePadding, right: this.basePadding }) // 设置宽度和内边距 } .width('100%') // 设置宽度占满 .layoutWeight(1) // 设置布局权重 .align(Alignment.Top) // 对齐方式 .onScrollStart(() => { // 滚动开始事件 this.onPageHide() // 页面隐藏处理 }) .onScrollStop(() => { // 滚动停止事件 this.onPageShow() // 页面显示处理 }) .onTouch((event) => { // 触摸事件 if (event.type == TouchType.Down) { // 如果是按下事件 inputMethod.getController().stopInputSession() // 停止输入会话 } }) } } }转载自https://www.cnblogs.com/zhongcx/p/18605192
  • 鸿蒙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.jsoncid:link_0代码// 引入必要的工具库 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); // 设置容器的宽高和背景颜色 } }转载自https://www.cnblogs.com/zhongcx/p/18606230
  • [技术干货] 鸿蒙NEXT开发案例:经纬度距离计算
     【引言】在鸿蒙NEXT平台上,我们可以轻松地开发出一个经纬度距离计算器,帮助用户快速计算两点之间的距离。本文将详细介绍如何在鸿蒙NEXT中实现这一功能,通过简单的用户界面和高效的计算逻辑,为用户提供便捷的服务。【环境准备】• 操作系统:Windows 10• 开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806• 目标设备:华为Mate60 Pro• 开发语言:ArkTS• 框架:ArkUI• API版本:API 12【思路】在本案例中,我们将创建一个名为“距离计算器”的组件,用户可以输入起点和终点的经纬度,系统将自动计算并显示两点之间的距离。以下是实现的主要思路:1 组件结构设计:使用Column和Row布局组件来组织界面元素,使其具有良好的可读性和用户体验。在界面顶部添加标题,明确应用的功能。2 输入区域:提供两个输入框,分别用于输入起点和终点的经纬度。用户可以手动输入,也可以通过点击示例按钮快速填充常用位置(如北京和上海)。设计清空按钮,方便用户快速重置输入。3 状态管理:使用@State装饰器管理组件的状态,包括输入框的聚焦状态、经纬度值和计算结果。通过@Watch装饰器监视输入变化,确保在用户输入经纬度时,能够实时更新计算结果。4 距离计算逻辑:在输入变化时,调用地图模块的calculateDistance方法,计算两点之间的距离,并将结果更新到界面上。结果以公里为单位显示,确保用户能够直观理解计算结果。5 界面美化:通过设置颜色、边框、圆角等样式,使界面更加美观和用户友好。使用适当的字体和大小,确保信息的清晰可读。【完整代码】import { mapCommon } from '@kit.MapKit'; // 导入地图通用模块 import { map } from '@kit.MapKit'; // 导入地图模块 @Entry // 入口装饰器,标识该组件为应用的入口 @Component // 组件装饰器,定义一个组件 struct DistanceCalculator { // 定义一个名为 DistanceCalculator 的结构体 @State private primaryColor: string = '#fea024'; // 定义主题颜色,初始值为橙色 @State private fontColor: string = "#2e2e2e"; // 定义字体颜色,初始值为深灰色 @State private isStartFocused: boolean = false; // 定义起点输入框的聚焦状态,初始为 false @State private isEndFocused: boolean = false; // 定义终点输入框的聚焦状态,初始为 false @State private isSecondStartFocused: boolean = false; // 定义第二起点输入框的聚焦状态,初始为 false @State private isSecondEndFocused: boolean = false; // 定义第二终点输入框的聚焦状态,初始为 false @State private baseSpacing: number = 30; // 定义基础间距,初始值为 30 @State @Watch('onInputChange') private startLongitude: string = ""; // 定义起点经度,初始为空,并监视输入变化 @State @Watch('onInputChange') private startLatitude: string = ""; // 定义起点纬度,初始为空,并监视输入变化 @State @Watch('onInputChange') private endLongitude: string = ""; // 定义终点经度,初始为空,并监视输入变化 @State @Watch('onInputChange') private endLatitude: string = ""; // 定义终点纬度,初始为空,并监视输入变化 @State distance: number = 0; // 定义两点之间的距离,初始值为 0 aboutToAppear(): void { // 生命周期钩子函数,组件即将显示时调用 this.onInputChange(); // 调用输入变化处理函数以初始化 } onInputChange() { // 输入变化处理函数 let fromLatLng: mapCommon.LatLng = { // 创建起点经纬度对象 latitude: Number(this.startLatitude), // 将起点纬度转换为数字 longitude: Number(this.startLongitude) // 将起点经度转换为数字 }; let toLatLng: mapCommon.LatLng = { // 创建终点经纬度对象 latitude: Number(this.endLatitude), // 将终点纬度转换为数字 longitude: Number(this.endLongitude) // 将终点经度转换为数字 }; this.distance = map.calculateDistance(fromLatLng, toLatLng); // 计算起点和终点之间的距离 } build() { // 构建界面函数 Column() { // 垂直布局容器 // 标题栏,展示应用名 Text("经纬度距离计算") // 创建文本组件,显示标题 .width('100%') // 设置宽度为 100% .height(54) // 设置高度为 54 像素 .fontSize(18) // 设置字体大小为 18 .fontWeight(600) // 设置字体粗细为 600 .backgroundColor(Color.White) // 设置背景颜色为白色 .textAlign(TextAlign.Center) // 设置文本对齐方式为居中 .fontColor(this.fontColor); // 设置字体颜色为定义的字体颜色 // 输入区域 Column() { // 垂直布局容器 Row() { // 水平布局容器 Text('示例(北京-->上海)') // 创建文本组件,显示示例信息 .fontColor("#5871ce") // 设置字体颜色为蓝色 .fontSize(18) // 设置字体大小为 18 .padding(`${this.baseSpacing / 2}lpx`) // 设置内边距 .backgroundColor("#f2f1fd") // 设置背景颜色 .borderRadius(5) // 设置圆角半径为 5 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .onClick(() => { // 点击事件处理 this.startLongitude = "116.4074"; // 设置起点经度为北京经度 this.startLatitude = "39.9042"; // 设置起点纬度为北京纬度 this.endLongitude = "121.4737"; // 设置终点经度为上海经度 this.endLatitude = "31.2304"; // 设置终点纬度为上海纬度 }); Blank(); // 占位符,用于占据空间 Text('清空') // 创建文本组件,显示“清空”按钮 .fontColor("#e48742") // 设置字体颜色为橙色 .fontSize(18) // 设置字体大小为 18 .padding(`${this.baseSpacing / 2}lpx`) // 设置内边距 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 设置点击效果 .backgroundColor("#ffefe6") // 设置背景颜色 .borderRadius(5) // 设置圆角半径为 5 .onClick(() => { // 点击事件处理 this.startLongitude = ""; // 清空起点经度 this.startLatitude = ""; // 清空起点纬度 this.endLongitude = ""; // 清空终点经度 this.endLatitude = ""; // 清空终点纬度 }); }.height(45) // 设置行高为 45 像素 .justifyContent(FlexAlign.SpaceBetween) // 设置子元素在主轴上的对齐方式 .width('100%'); // 设置宽度为 100% Divider().margin({ top: 5, bottom: 5 }); // 创建分隔符,设置上下边距 // 起点输入 Row() { // 水平布局容器 Text('起点') // 创建文本组件,显示“起点” .fontWeight(600) // 设置字体粗细为 600 .fontSize(18) // 设置字体大小为 18 .fontColor(this.fontColor); // 设置字体颜色为定义的字体颜色 } .margin({ bottom: `${this.baseSpacing}lpx`, top: `${this.baseSpacing}lpx` }); // 设置上下边距 Row() { // 水平布局容器 TextInput({ text: $$this.startLongitude, placeholder: '经度' }) // 创建起点经度输入框 .caretColor(this.primaryColor) // 设置光标颜色为主题颜色 .layoutWeight(1) // 设置布局权重 .type(InputType.NUMBER_DECIMAL) // 设置输入类型为小数 .placeholderColor(this.isStartFocused ? this.primaryColor : Color.Gray) // 设置占位符颜色 .fontColor(this.isStartFocused ? this.primaryColor : this.fontColor) // 设置字体颜色 .borderColor(this.isStartFocused ? this.primaryColor : Color.Gray) // 设置边框颜色 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角半径为 10 .backgroundColor(Color.White) // 设置背景颜色为白色 .showUnderline(false) // 不显示下划线 .onBlur(() => this.isStartFocused = false) // 失去焦点时设置聚焦状态为 false .onFocus(() => this.isStartFocused = true); // 获得焦点时设置聚焦状态为 true Line().width(10); // 创建分隔符,设置宽度为 10 像素 TextInput({ text: $$this.startLatitude, placeholder: '纬度' }) // 创建起点纬度输入框 .caretColor(this.primaryColor) // 设置光标颜色为主题颜色 .layoutWeight(1) // 设置布局权重 .type(InputType.NUMBER_DECIMAL) // 设置输入类型为小数 .placeholderColor(this.isEndFocused ? this.primaryColor : Color.Gray) // 设置占位符颜色 .fontColor(this.isEndFocused ? this.primaryColor : this.fontColor) // 设置字体颜色 .borderColor(this.isEndFocused ? this.primaryColor : Color.Gray) // 设置边框颜色 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角半径为 10 .backgroundColor(Color.White) // 设置背景颜色为白色 .showUnderline(false) // 不显示下划线 .onBlur(() => this.isEndFocused = false) // 失去焦点时设置聚焦状态为 false .onFocus(() => this.isEndFocused = true); // 获得焦点时设置聚焦状态为 true } // 终点输入 Text('终点') // 创建文本组件,显示“终点” .fontWeight(600) // 设置字体粗细为 600 .fontSize(18) // 设置字体大小为 18 .fontColor(this.fontColor) // 设置字体颜色为定义的字体颜色 .margin({ bottom: `${this.baseSpacing}lpx`, top: `${this.baseSpacing}lpx` }); // 设置上下边距 Row() { // 水平布局容器 TextInput({ text: $$this.endLongitude, placeholder: '经度' }) // 创建终点经度输入框 .caretColor(this.primaryColor) // 设置光标颜色为主题颜色 .layoutWeight(1) // 设置布局权重 .type(InputType.NUMBER_DECIMAL) // 设置输入类型为小数 .placeholderColor(this.isSecondStartFocused ? this.primaryColor : Color.Gray) // 设置占位符颜色 .fontColor(this.isSecondStartFocused ? this.primaryColor : this.fontColor) // 设置字体颜色 .borderColor(this.isSecondStartFocused ? this.primaryColor : Color.Gray) // 设置边框颜色 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角半径为 10 .backgroundColor(Color.White) // 设置背景颜色为白色 .showUnderline(false) // 不显示下划线 .onBlur(() => this.isSecondStartFocused = false) // 失去焦点时设置聚焦状态为 false .onFocus(() => this.isSecondStartFocused = true); // 获得焦点时设置聚焦状态为 true Line().width(10); // 创建分隔符,设置宽度为 10 像素 TextInput({ text: $$this.endLatitude, placeholder: '纬度' }) // 创建终点纬度输入框 .caretColor(this.primaryColor) // 设置光标颜色为主题颜色 .layoutWeight(1) // 设置布局权重 .type(InputType.NUMBER_DECIMAL) // 设置输入类型为小数 .placeholderColor(this.isSecondEndFocused ? this.primaryColor : Color.Gray) // 设置占位符颜色 .fontColor(this.isSecondEndFocused ? this.primaryColor : this.fontColor) // 设置字体颜色 .borderColor(this.isSecondEndFocused ? this.primaryColor : Color.Gray) // 设置边框颜色 .borderWidth(1) // 设置边框宽度 .borderRadius(10) // 设置圆角半径为 10 .backgroundColor(Color.White) // 设置背景颜色为白色 .showUnderline(false) // 不显示下划线 .onBlur(() => this.isSecondEndFocused = false) // 失去焦点时设置聚焦状态为 false .onFocus(() => this.isSecondEndFocused = true); // 获得焦点时设置聚焦状态为 true } } .width('650lpx') // 设置输入区域宽度为 650 像素 .padding(`${this.baseSpacing}lpx`) // 设置内边距 .margin({ top: 20 }) // 设置上边距为 20 像素 .backgroundColor(Color.White) // 设置背景颜色为白色 .borderRadius(10) // 设置圆角半径为 10 .alignItems(HorizontalAlign.Start); // 设置子元素在交叉轴上的对齐方式 // 显示计算结果 Column() { // 垂直布局容器 Text() { // 文本组件 Span(`两点之间的距离是:`) // 创建文本片段,显示提示信息 Span(`${(this.distance / 1000).toFixed(2)} `).fontColor(this.primaryColor) // 创建文本片段,显示距离(公里),并设置颜色 Span(`公里`) // 创建文本片段,显示单位“公里” } .fontWeight(600) // 设置字体粗细为 600 .fontSize(18) // 设置字体大小为 18 .fontColor(this.fontColor); // 设置字体颜色为定义的字体颜色 } .width('650lpx') // 设置结果显示区域宽度为 650 像素 .backgroundColor(Color.White) // 设置背景颜色为白色 .borderRadius(10) // 设置圆角半径为 10 .padding(`${this.baseSpacing}lpx`) // 设置内边距 .margin({ top: `${this.baseSpacing}lpx` }) // 设置上边距 .alignItems(HorizontalAlign.Start); // 设置子元素在交叉轴上的对齐方式 } .height('100%') // 设置整个组件高度为 100% .width('100%') // 设置整个组件宽度为 100% .backgroundColor("#eff0f3"); // 设置背景颜色为浅灰色 } } 转载自https://www.cnblogs.com/zhongcx/p/18607899
  • [课程学习] 开年第一课——HarmonyOS精品课程推荐
    又是一年开学、开工季人已在学校上课和工位上班脑子却在迷茫不知道做什么别再摸鱼啦!新的一年学习目标来啦!专为零基础小白提供的5门行业大会的HarmonyOS精品课程。这些课程由权威技术大咖讲解,深入浅出带你轻松理解鸿蒙应用基础概念,助力你全面掌握前沿技术,拥抱职业发展的无限可能!1.精品课程《鸿蒙:万物智联时代的操作系统》,请戳学习链接2.精品课程《鸿蒙应用开发:从Android到HarmonyOS》,请戳学习链接3.精品课程《鸿蒙分布式软总线的前世今生》,请戳学习链接4.精品课程《万物智联下的HarmonyOS开发框架》,请戳学习链接5.精品课程《Zelda :鸿蒙应用静态分析框架》,请戳学习链接
  • [技术干货] 鸿蒙应用示例:应用测试与上架全流程指南
    在开发HarmonyOS应用的过程中,从初始测试到最终上架,每一步都需要精心规划与实施。本文将按照实际开发流程,详细介绍从生成签名证书文件到完成小规模真机内测直至最终应用上架的各个步骤。官方交互式资料体验中心:AppGallery Connect交互式资料体验中心一、生成签名证书文件在开发阶段结束并准备进行真机测试或上架之前,首先需要生成签名证书文件。这是确保应用安全性的重要步骤之一。步骤如下:1. 生成密钥和证书请求文件(CSR)使用DevEco Studio中的“生成密钥和CSR”功能,输入相关信息以生成密钥文件和CSR文件。注意,填写信息时应避免使用中文字符,以免引起后续问题。参考:文档中心2. 上传CSR文件生成证书登录AppGallery Connect平台,上传之前生成的CSR文件,以生成相应的证书文件(.cer)。参考: 文档中心3. 生成P7B文件再次登录AppGallery Connect,进入“Profile”页面,使用之前生成的.cer文件生成.p7b文件。参考:文档中心4. 配置签名信息在DevEco Studio中配置签名信息:依次选择File > Project Structure > Signing Configs,在此处指定之前生成的密钥文件及其密码。二、小规模真机内测方案完成签名证书文件的准备工作之后,下一步是进行小规模的真机内测。这有助于在正式发布前发现并解决潜在的问题。方案包括但不限于:1. 开放式测试• 打包方式:使用发布证书打包成.app格式的安装包。• 上传流程:登录AppGallery Connect上传安装包。• 用户安装:用户收到邀请后,可以在真机上安装并使用该应用。• 前置条件:打包时需要使用发布证书,打.app格式;软件著作权、备案并填写相关信息,因为上传测试包也需要提交审核资质。参考:文档中心2. 发布企业内部应用• 打包方式:使用企业证书打包成.hap格式的安装包。• 上传流程:登录AppGallery Connect上传安装包。• 用户安装:用户收到邀请后,可以在真机上安装并使用该应用。• 前置条件:打包时需要使用企业证书,打.hap格式;需要额外准备一个账号,并填写申请信息,包括企业全称、应用信息、申请原因、使用人数等。不支持元服务也不支持个人申请。参考:文档中心3. DevEco Testing命令安装• 打包方式:使用调试证书打包成.hap格式的安装包。• 上传流程:通过DevEco Testing命令app install -r <路径>\xx.hap安装。• 用户安装:用户通过命令直接安装测试包。• 前置条件:打包时需要使用调试证书,打.hap格式;安装DevEco Testing软件,手机开启开发者模式。参考:下载中心 | 华为开发者联盟-HarmonyOS开发者官网,共建鸿蒙生态三、应用上架完成内测并确认应用无误后,接下来就是正式上架的步骤。流程如下:1. 构建并打包APP• 通过DevEco Studio的“Build > Build Hap(s)/APP(s) > Build APP(s)”菜单构建APP。构建完成后,在项目的相应目录下找到.app格式的正式签名安装包,用于上传至应用商店。2. 上传至AppGallery Connect参考:AppGallery Connect• 登录AppGallery Connect,上传之前构建好的.app格式安装包。• 按照提示填写应用的相关信息,如应用名称、描述、截图等。3. 提交审核• 提交应用后,等待华为团队的审核。审核期间,保持通讯畅通以便及时回应可能出现的问题。4. 完成上架• 审核通过后,应用正式上架,用户可以在AppGallery中搜索并下载使用。四、所需资料在整个流程中,还需要准备一些必要的资料:1. APP备案根据华为官方指南,APP备案需通过华为云、阿里云或腾讯云等接入服务商完成。填写备案信息时,重点关注包名、公钥、签名信息(证书MD5指纹)等。参考:文档中心2. 电子版软件著作权准备好《软件版权申请信息采集表.doc》、《源代码.doc》、《xxxapp 操作手册.doc》等相关文档,以供申请电子版软件著作权。转载自https://www.cnblogs.com/zhongcx/articles/18444615
  • [技术干货] 鸿蒙应用示例:应用开发中的动态获取属性与调用方法技巧
    随着HarmonyOS的发展,API版本的更新带来了许多新的特性和限制。在API 11及以后的版本中,直接赋值对象的语法不再被支持,这要求开发者们采用新的方式来处理对象的创建和属性的访问。同时,HarmonyOS支持ETS(Enhanced TypeScript)文件,这是一种扩展了TypeScript的文件格式,用于更好地支持HarmonyOS的特性。然而,ETS文件并不支持所有的TypeScript语法特性,这就需要开发者灵活运用不同的文件格式来实现所需的功能。【完整示例】src/main/ets/common/FactoryUtil.ts123export function createInstance<T>(constructor: new () => T): T {  return new constructor();}  src/main/ets/pages/Index.ets123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100import { createInstance } from '../common/FactoryUtil.ts'; class Person {  name: string = '张三';} class Person2 {  name: string = '李四';} function sum(a: number, b: number) {  console.info(`${a} + ${b} = ${a + b}`);} function subtract(a: number, b: number) {  console.info(`${a} - ${b} = ${a - b}`);} function printString(str: string) {  console.info(str);} class BirthInfo {  birthDate: string = "2020-02-03";} class UserInfo {  userName: string = '';  birthInfo?: BirthInfo = new BirthInfo();  calculateAge = (): number | string => {    if (!this.birthInfo) {      return '数据异常';    }    const today = new Date();    const birthDate = new Date(this.birthInfo.birthDate);    const age = today.getFullYear() - birthDate.getFullYear();    return age;  };} class Action {  description: string;  action: Function;   constructor(description: string, action: Function) {    this.description = description;    this.action = action;  }} @Entry@Componentstruct MainPage {  @State actions: Action[] = [    new Action('加法计算', sum),    new Action('减法计算', subtract),    new Action('打印字符串', printString),  ];   build() {    Column({ space: 10 }) {      Button('创建对象并获取属性').onClick(() => {        const person: object = Object({ name: '张三', age: 30 });        console.info(`person['name']:${person['name']}`);        console.info(`person['age']:${person['age']}`);      });       Button('执行动作').onClick(() => {        this.actions.forEach(action => {          if (action.description.includes('加法')) {            action.action(1, 2);          } else if (action.description.includes('减法')) {            action.action(1, 2);          } else if (action.description.includes('打印')) {            action.action('Hello World');          }        });      });       Button('从TS文件创建实例').onClick(() => {        const person1 = createInstance(Person);        console.info('person1.name', person1.name);         const person2 = createInstance(Person2);        console.info('person2.name', person2.name);      });       Button('获取用户信息').onClick(() => {        const userInfo = new UserInfo();        Object.keys(userInfo).forEach(key => {          console.info(`key: ${key}`);        });         console.info(`年龄: ${userInfo.calculateAge()}`);      });    }    .width('100%')    .height('100%');  }}  打印1234567891011121314person['name']:张三person['age']:30 1 + 2 = 31 - 2 = -1Hello World person1.name 张三person2.name 李四 key: userNamekey: birthInfokey: calculateAge年龄: 4  技术要点解析1. 创建对象并获取属性使用Object()创建对象并使用索引访问属性,以确保兼容性与正确性。123const person:object = Object({ name: '张三', age: 30 });console.info(`person['name']:${person['name']}`);console.info(`person['age']:${person['age']}`);  2. 执行动作将方法定义为对象属性,并通过动态方式调用它们。123456this.actions.forEach(action => {  if (action.description.includes('加法')) {    action.action(1, 2);  }  // 其他条件分支...});  3. 从TS文件创建实例在某些情况下,ETS文件不支持特定的TypeScript语法特性,如new () => T语法。这时可以将这部分逻辑移到TS文件中,并在ETS文件中导入使用。例如,创建一个通用的工厂函数来实例化类:12const person1 = createInstance(Person);console.info('person1.name', person1.name);  4. 遍历对象的属性可以使用Object.keys()遍历对象的属性,并利用面向对象的思想通过实例方法计算年龄。123456const userInfo = new UserInfo();Object.keys(userInfo).forEach(key => {  console.info(`key: ${key}`);}); console.info(`年龄: ${userInfo.calculateAge()}`);  结论本文介绍了HarmonyOS应用开发中的几个关键技巧,包括使用Object()创建对象、动态调用方法、使用TS文件中的工厂函数创建实例,以及遍历对象属性。通过遵循良好的命名规范和代码组织结构,可以使代码更加清晰易懂,便于后期维护。掌握了这些技巧后,开发者能够更加高效地开发出高质量的HarmonyOS应用程序。转载自https://www.cnblogs.com/zhongcx/articles/18445830
  • [技术干货] 鸿蒙应用示例:ArkTS中设置颜色透明度与颜色渐变方案探讨
    /** * 根据比例混合两个十六进制颜色值。 * @param colorA 第一个颜色的十六进制值,例如红色 '#ff0000'。 * @param colorB 第二个颜色的十六进制值,例如黑色 '#000000'。 * @param ratio 混合比例,0 表示仅返回 colorA,1 表示仅返回 colorB,介于 0 和 1 之间的值会混合两个颜色。 * @returns 混合后的颜色的十六进制值。 */ function mixColors(colorA: string, colorB: string, ratio: number): string { let mix = (a: number, b: number, ratio: number) => Math.round(a + (b - a) * ratio).toString(16).padStart(2, '0'); let a = parseInt(colorA.slice(1), 16); let b = parseInt(colorB.slice(1), 16); let red = mix((a >> 16) & 255, (b >> 16) & 255, ratio); let green = mix((a >> 8) & 255, (b >> 8) & 255, ratio); let blue = mix(a & 255, b & 255, ratio); return `#${red}${green}${blue}`; } function convertDecimalColorToHex(decimalColorValue: number): string { if (decimalColorValue < 0 || decimalColorValue > 0xFFFFFFFF) { throw new Error('Color value must be within the range of 0 to 0xFFFFFFFF.'); } const paddedHexColor = ('00000000' + decimalColorValue.toString(16).toUpperCase()).slice(-8); return '#' + paddedHexColor.slice(-6); } @Entry @Component struct Index { @State resourceColor: Resource | undefined = undefined @State convertedColorHex: string = "" @State displayText: string = "测试测试" aboutToAppear(): void { this.resourceColor = $r(`app.color.start_window_background`); const colorFromResourceManager = getContext(this).resourceManager.getColorByNameSync('start_window_background'); console.info('从ResourceManager获取的颜色(十进制):', colorFromResourceManager); this.convertedColorHex = convertDecimalColorToHex(colorFromResourceManager); console.info('转换后的十六进制颜色:', this.convertedColorHex); } build() { Column() { Text('测试1').backgroundColor('rgba(0, 0, 0, 0.5)') Text('测试2').backgroundColor('#80000000') Text(mixColors('#ff0000', '#000000', 0)) .fontColor(Color.White) .backgroundColor(mixColors('#ff0000', '#000000', 0)) Text(mixColors('#ff0000', '#000000', 1)) .fontColor(Color.White) .backgroundColor(mixColors('#ff0000', '#000000', 1)) Text(mixColors('#ff0000', '#000000', 0.5)) .fontColor(Color.White) .backgroundColor(mixColors('#ff0000', '#000000', 0.5)) Text(mixColors('#ff0000', '#000000', 0.8)) .fontColor(Color.White) .backgroundColor(mixColors('#ff0000', '#000000', 0.8)) Text(this.displayText) .backgroundColor($r(`app.color.start_window_background`)) .fontSize(50) .fontWeight(FontWeight.Bold) Text(this.displayText) .backgroundColor(this.resourceColor) .fontSize(50) .fontWeight(FontWeight.Bold) Text(this.displayText) .backgroundColor(this.convertedColorHex) .fontSize(50) .fontWeight(FontWeight.Bold) } .height('100%') .width('100%') .backgroundColor(Color.Orange) } }在HarmonyOS应用开发中,颜色设置是一项基本但重要的功能。本文将介绍在ArkTS中如何设置颜色的透明度,并探讨如何在两种颜色之间进行混合以得到新的颜色值。一、设置颜色透明度在HarmonyOS的ArkTS框架中,可以通过多种方式来设置颜色的透明度。以下是两种常见的方法:1. 使用RGBA格式:通过直接指定RGBA值来设置颜色的透明度。1Text('测试1').backgroundColor('rgba(0, 0, 0, 0.5)')2. 使用十六进制颜色值:通过在颜色值前加上表示透明度的十六进制数来设置透明度。1Text('测试2').backgroundColor('#80000000')在此示例中,80表示半透明,00表示不透明,ff表示完全透明。二、颜色混合方案在一些应用场景中,需要实现两种颜色之间的混合效果。下面是一个示例,演示如何根据给定的比例混合两种颜色:/** * 根据比例混合两个十六进制颜色值。 * @param colorA 第一个颜色的十六进制值,例如红色 '#ff0000'。 * @param colorB 第二个颜色的十六进制值,例如黑色 '#000000'。 * @param ratio 混合比例,0 表示仅返回 colorA,1 表示仅返回 colorB,介于 0 和 1 之间的值会混合两个颜色。 * @returns 混合后的颜色的十六进制值。 */ mixColors(colorA: string, colorB: string, ratio: number): string { let mix = (a: number, b: number, ratio: number) => Math.round(a + (b - a) * ratio).toString(16).padStart(2, '0'); let a = parseInt(colorA.slice(1), 16); let b = parseInt(colorB.slice(1), 16); let red = mix((a >> 16) & 255, (b >> 16) & 255, ratio); let green = mix((a >> 8) & 255, (b >> 8) & 255, ratio); let blue = mix(a & 255, b & 255, ratio); return `#${red}${green}${blue}`; }三、解决预览器颜色显示问题在使用resourceManager获取颜色值时,可能会遇到在预览器中无法正确显示颜色的问题。为了避免这类问题,建议直接定义颜色常量:如果只是简单的颜色值,建议直接定义字符串类颜色值,如"#000000",这样预览器也能正确显示颜色转载自https://www.cnblogs.com/zhongcx/articles/18447243
  • [技术干货] 鸿蒙应用示例:ArkTS中实现一键置灰功能
    引言在特殊情况下,如国难日或其他重要事件期间,应用程序可能需要将界面转换为灰度显示以示尊重或表达特定的情感。比如android环境下的代码为12345Paint paint = new Paint();ColorMatrix cm = new ColorMatrix();cm.setSaturation( 0);//0:表示灰度显示,1:表示彩色显示paint.setColorFilter(new ColorMatrixColorFilter(cm));view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);  方案一:使用 saturate 属性通过设置页面根容器的饱和度为0来实现灰度效果:123456789101112131415@Entry@Componentstruct Index {  build() {    Column() {      Image($r("app.media.app_icon"))        .autoResize(true)        .width(100)        .height(100)    }    .width('100%')    .height('100%')    .saturate(0)  }}  方案二:使用 grayscale 属性通过设置页面根容器的灰度效果为1来实现灰度效果:123456789101112131415@Entry@Componentstruct Index {  build() {    Column() {      Image($r("app.media.app_icon"))        .autoResize(true)        .width(100)        .height(100)    }    .width('100%')    .height('100%')    .grayscale(1)  }}转载自https://www.cnblogs.com/zhongcx/articles/18447277
  • [技术干货] 鸿蒙应用示例: flexGrow 与 layoutWeight 布局属性比较
     @Entry @Component struct Page11 { @State message: string = 'Hello World'; build() { Column() { Row(){ Text("呵呵").flexGrow(1).backgroundColor(Color.Red) Text("呵呵").flexGrow(1).backgroundColor(Color.Blue) } Flex(){ Text("呵呵").layoutWeight(1).backgroundColor(Color.Red) Text("呵呵").flexGrow(1).backgroundColor(Color.Blue) } Flex(){ Text("呵呵").flexGrow(1).backgroundColor(Color.Red) Text("呵呵呵呵呵呵呵呵呵呵").flexGrow(1).backgroundColor(Color.Blue) }.width('100%') Flex(){ Text("呵呵").layoutWeight(1).backgroundColor(Color.Red) Text("呵呵呵呵呵呵呵呵呵呵").layoutWeight(1).backgroundColor(Color.Blue) }.width('100%') } .height('100%') .width('100%') } }引言在HarmonyOS的应用开发过程中,我们需要对不同类型的布局属性有所了解,以便更好地组织和管理我们的界面。本文将探讨两种重要的布局属性:flexGrow 和 .layoutWeight,并分析它们之间的区别以及各自的适用场景。1. 使用场景flexGrow 是一种Flex容器特有的布局属性,它决定了子元素如何根据剩余空间进行扩展。.layoutWeight 则可以在 Flex、Row 和 Column 等多种容器中使用,更加灵活。2. 混合使用如果在非 Flex 容器中同时使用 flexGrow 和 .layoutWeight,则只有 .layoutWeight 起作用。这是因为 flexGrow 只在 Flex 容器中有意义,而 .layoutWeight 的兼容性更强。3. 分配方式flexGrow 根据每个子元素的权重分配剩余空间,但会优先保留子元素自身的宽度。例如,在有两个子元素的 Flex 容器中,如果两者都设置了 flexGrow: 1,那么它们将平均分配剩余空间。然而,如果其中某个子元素的内容较长,则实际宽度可能会大于另一个。相比之下,.layoutWeight 根据每个子元素的权重分配整个组件的宽度。这意味着具有相同权重的子元素将获得相同的宽度,无论其内容长度如何。4. 推荐使用由于 .layoutWeight 具有简单直观的宽度分配方式,因此在需要均匀分配空间的情况下,推荐使用此属性。转载自https://www.cnblogs.com/zhongcx/articles/18447321
  • [技术干货] 鸿蒙应用示例:工作中常用的日期时间处理方法
    import { systemDateTime } from '@kit.BasicServicesKit'; @Entry @Component struct Index { @State formattedTimeNow: string = ""; @State formattedTimeAgo: string = ""; @State timestampSecs: string = ""; @State timestampSecsAlt: string = ""; @State fullDateTime: string = ""; @State nanosecondsTimestamp: string = ""; @State timezoneOffsetHours: string = ""; @State currentDate: string = ""; @State formattedSpecifiedDateTime: string = ""; formatTimeAgo(dateTime: Date): string { const now = new Date(); const diff = now.getTime() - dateTime.getTime(); const SECONDS = 1000; const MINUTES = SECONDS * 60; const HOURS = MINUTES * 60; const DAYS = HOURS * 24; if (diff < SECONDS) { return '刚刚'; } else if (diff < MINUTES) { return '不到一分钟'; } else if (diff < HOURS) { return Math.round(diff / MINUTES) + '分钟前'; } else if (diff < DAYS) { return Math.round(diff / HOURS) + '小时前'; } else if (diff < DAYS * 2) { return '昨天'; } else if (diff < DAYS * 3) { return '前天'; } else { return this.formatDate(dateTime); } } formatDate(dateTime: Date): string { const year = dateTime.getFullYear(); const month = String(dateTime.getMonth() + 1).padStart(2, '0'); const day = String(dateTime.getDate()).padStart(2, '0'); return `${year}年${month}月${day}日`; } getFullDateTime(): string { const formatter = new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); return formatter.format(new Date()); } formatCustomDateTime(dateTime: Date): string { const formatter = new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }); const parts = formatter.formatToParts(dateTime); let formattedDate = ''; for (const part of parts) { switch (part.type) { case 'month': formattedDate += `${part.value}月`; break; case 'day': formattedDate += `${part.value}日`; break; case 'year': formattedDate = `${part.value}年${formattedDate}`; break; default: break; } } return formattedDate; } getFormattedSpecifiedDateTime(dateTime: Date): string { return this.formatCustomDateTime(dateTime); } getNanosecondsTimestamp(): void { const time = systemDateTime.getTime(true); this.nanosecondsTimestamp = time.toString(); } getTimezoneOffsetHours(): void { try { const now = new Date(); const offsetMinutes = now.getTimezoneOffset(); const offsetHours = Math.floor(-offsetMinutes / 60); this.timezoneOffsetHours = offsetHours.toString(); } catch (error) { console.error('获取时区偏移量失败:', error); this.timezoneOffsetHours = '未知'; } } getCurrentYearMonthDay(): string { const date = new Date(); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}年${month}月${day}日`; } build() { Column({ space: 10 }) { Button('获取当前时间戳(秒)').onClick(() => { this.timestampSecs = Math.floor(Date.now() / 1000).toString() this.timestampSecsAlt = Math.floor(new Date().getTime() / 1000).toString() }) Text(`当前时间戳(秒):${this.timestampSecs}`) Text(`当前时间戳(秒):${this.timestampSecsAlt}`) Button('获取当前时间戳(纳秒)').onClick(() => { this.getNanosecondsTimestamp(); }) Text(`当前时间戳(纳秒):${this.nanosecondsTimestamp}`) Button('获取时间间隔显示').onClick(() => { this.formattedTimeNow = this.formatTimeAgo(new Date()); this.formattedTimeAgo = this.formatTimeAgo(new Date('2023-04-01T12:00:00')); }) Text(`当前时间间隔显示:${this.formattedTimeNow}`) Text(`指定时间间隔显示:${this.formattedTimeAgo}`) Button('获取当前时区偏移量').onClick(() => { this.getTimezoneOffsetHours(); }) Text(`当前时区偏移量:${this.timezoneOffsetHours}小时`) Button('获取当前年-月-日').onClick(() => { this.currentDate = this.getCurrentYearMonthDay(); }) Text(`当前年-月-日:${this.currentDate}`) Button('获取当前完整时间').onClick(() => { this.fullDateTime = this.getFullDateTime(); }) Text(`当前完整时间:${this.fullDateTime}`) Button('获取指定日期时间').onClick(() => { this.formattedSpecifiedDateTime = this.getFormattedSpecifiedDateTime(new Date('2024-10-02T09:30:00')); }) Text(`指定日期时间:${this.formattedSpecifiedDateTime}`) } .width('100%') .height('100%') } }转载自https://www.cnblogs.com/zhongcx/articles/18447576
  • [技术干货] 鸿蒙应用示例:镂空效果实现教程
    在鸿蒙系统中,为了给用户带来更加生动的视觉体验,我们可以使用不同的技术手段来实现图像和文字的镂空效果。本文将通过三个具体的示例来展示如何在鸿蒙系统中实现实心矩形镂空、实心圆镂空以及文字镂空的效果。示例代码// 定义一个名为Index的应用入口组件 @Entry @Component struct Index { // 初始化绘图上下文所需的设置 private settings: RenderingContextSettings = new RenderingContextSettings(true); // 创建两个用于绘制不同图案的Canvas绘图上下文 private contextForRectangle: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); private contextForCircle: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); // 构建UI build() { // 使用Column布局,设置每个子元素之间的间隔为5 Column({ space: 5 }) { // 第一个示例:实心矩形镂空 Text('1. 实心矩形镂空') Stack() { // 在Stack中放置一个图像作为背景 Image($r('app.media.startIcon')).width(200).height(200).enableAnalyzer(true) // 创建一个Canvas,并在准备就绪后执行绘图逻辑 Canvas(this.contextForRectangle).width(80).height(80).backgroundColor(undefined).onReady(async () => { // 设置填充色 this.contextForRectangle.fillStyle = 'rgba(0, 255, 255, 1)'; // 绘制一个覆盖整个Canvas的矩形 this.contextForRectangle.fillRect(0, 0, 80, 80); // 在矩形中心位置创建一个镂空矩形 this.contextForRectangle.clearRect(10, 20, 50, 40); }); }.width(300) // 第二个示例:实心圆镂空 Text('2. 实心圆镂空') Stack() { Image($r('app.media.startIcon')).width(200).height(200).enableAnalyzer(true) // 创建一个Canvas,并在准备就绪后执行绘图逻辑 Canvas(this.contextForCircle).width(80).height(80).backgroundColor(undefined).onReady(async () => { // 清除背景 this.contextForCircle.clearRect(0, 0, 80, 80); // 设置填充色 this.contextForCircle.fillStyle = 'rgba(0, 255, 255, 1)'; // 绘制一个覆盖整个Canvas的矩形 this.contextForCircle.fillRect(0, 0, 80, 80); // 画一个圆形镂空 this.contextForCircle.beginPath(); this.contextForCircle.arc(40, 40, 30, 0, Math.PI * 2); // 圆心坐标为(40, 40),半径为30 this.contextForCircle.globalCompositeOperation = 'destination-out'; this.contextForCircle.fill(); }); } // 第三个示例:文字镂空 Text('3. 文字镂空') Stack() { // 背景图像 Image($r('app.media.startIcon')).width(200).height(200).enableAnalyzer(true) Stack() { // 在内部Stack中绘制镂空文字 Text('鸿蒙') .fontSize(30) .fontWeight(FontWeight.Bold) .blendMode(BlendMode.XOR, BlendApplyType.OFFSCREEN) }.blendMode(BlendMode.SRC_OVER, BlendApplyType.OFFSCREEN) .backgroundColor('rgba(0, 255, 255, 1)') .width(80).height(80) }.width(200).height(200) }.width('100%') } }案例解析1. 实心矩形镂空在这个例子中,我们首先创建了一个Canvas实例,并在准备好后绘制了一个完全覆盖Canvas的矩形。接着,我们在该矩形的中央清除了一块矩形区域,从而实现了矩形镂空的效果。2. 实心圆镂空对于圆形镂空,我们同样先绘制一个覆盖整个Canvas的矩形,然后在一个新路径中绘制圆形并设置 globalCompositeOperation 为 'destination-out',这样就会在矩形中挖出一个圆形的镂空。3. 文字镂空最后,在文字镂空的例子中,我们先绘制了一个背景图像,然后在图像上绘制了“鸿蒙”这两个字。通过设置 blendMode 为 'XOR' 和 'SRC_OVER',使得文字呈现镂空效果。转载自https://www.cnblogs.com/zhongcx/articles/18448377
  • [技术干货] 鸿蒙应用示例:实现文本高亮与自动换行
    在设计应用界面时,我们常常需要对某些重要的文本进行高亮显示,以引起用户的注意。同时,在一些场景中,我们需要确保长文本能够自动换行,以适应不同的屏幕尺寸和布局需求。本文将通过两个示例,分别展示如何在HarmonyOS应用中实现这些功能。【示例一】文本高亮显示@Entry @Component struct Page01 { @State originalText: string = '混沌未分天地乱,茫茫渺渺无人见。自从盘古破鸿蒙,开辟从兹清浊辨。'; @State highlightKeyword: string = '鸿蒙'; // 需要高亮显示的关键字 @State highlightedSegments: string[] = []; // 分割原始文本并保留关键字 private splitAndHighlight(text: string, keyword: string): string[] { let segments: string[] = []; let lastMatchEnd: number = 0; while (true) { const matchIndex = text.indexOf(keyword, lastMatchEnd); if (matchIndex === -1) { segments.push(text.slice(lastMatchEnd)); break; } else { segments.push(text.slice(lastMatchEnd, matchIndex)); segments.push(text.slice(matchIndex, matchIndex + keyword.length)); lastMatchEnd = matchIndex + keyword.length; } } return segments; } // 页面即将出现时进行文本分割 aboutToAppear() { this.highlightedSegments = this.splitAndHighlight(this.originalText, this.highlightKeyword); console.info(`分割后的文本段落:${JSON.stringify(this.highlightedSegments)}`); } build() { Column({ space: 20 }) { Text() { ForEach(this.highlightedSegments, (segment: string, index: number) => { ContainerSpan() { ImageSpan($r('app.media.app_icon')).width(0).height(1); Span(segment).fontSize(30) .fontColor(segment === this.highlightKeyword ? Color.White : Color.Black) .onClick(() => { console.info(`高亮文本被点击:${segment}`); console.info(`点击索引:${index}`); }); }.textBackgroundStyle({ color: segment === this.highlightKeyword ? Color.Red : Color.Transparent }); }); } }.width('100%').height('100%'); } }在这个示例中,我们首先定义了一个字符串originalText作为原始文本,并指定了需要高亮显示的关键字highlightKeyword。然后,我们定义了一个splitAndHighlight函数来分割原始文本,并将包含关键字的部分与其他部分分开。在页面加载时,我们调用这个函数来获得分割后的文本段落,并使用Span组件来显示文本。需要注意的是,由于Span组件本身不支持直接设置背景颜色(即Span不支持.backgroundColor(Color.Orange)),因此设置背景色需要在Span外部嵌套ContainerSpan组件,并使用textBackgroundStyle属性来实现。对于需要高亮显示的关键字部分,我们通过ContainerSpan组件的textBackgroundStyle属性来改变其背景颜色,同时保持字体颜色为白色,以确保高亮效果明显。【示例二】文本自动换行接下来,我们来看一个文本自动换行的示例。在这个例子中,我们需要将多行文本按照一定的规则自动换行。@Entry @Component struct Page02 { @State poemLines: string[] = [ '混沌未分天地乱,', '茫茫渺渺无人见。', '自从盘古破鸿蒙,', '开辟从兹清浊辨。', ]; build() { Column({ space: 10 }) { Text('Text + Span,文本无法自动换行').backgroundColor(Color.Orange); Text() { ForEach(this.poemLines, (line: string) => { Span(line); }); } .fontSize(20); Text('Flex + Span,可以实现文本换行').backgroundColor(Color.Orange); Flex({ wrap: FlexWrap.Wrap }) { ForEach(this.poemLines, (line: string) => { Text(line).fontSize(20); }); } } .width('100%').height('100%'); } }在这个示例中,我们定义了一个字符串数组poemLines,其中包含了多行诗句。我们展示了两种不同的方式来显示这些诗句:一种是使用Text和Span组件直接显示,这种方式默认不会自动换行;另一种是使用Flex容器,并设置wrap属性为FlexWrap.Wrap,这样可以使得子元素在超出容器宽度时自动换行。【技术要点总结】1. 文本高亮:• 使用splitAndHighlight函数分割文本,并标记关键字。• 使用ContainerSpan和Span组件组合实现背景高亮。• 注意Span不支持直接设置背景颜色,需通过ContainerSpan的textBackgroundStyle属性实现。2. 文本换行:• 使用Flex容器并设置wrap属性为FlexWrap.Wrap,实现自动换行。• 多行文本可以通过ForEach循环动态生成。
  • [技术干货] 鸿蒙应用示例:系统退出应用的最佳实践
    在鸿蒙系统(HarmonyOS)的应用开发中,有时需要提供退出应用的功能。鸿蒙系统提供了多种方法来实现这一目标,包括 terminateSelf()、killAllProcesses() 和 clearUpApplicationData()。本文将详细介绍这些方法及其适用场景,并提供一种较为优雅的退出应用的方式,以提升用户体验。退出应用的方法方案一:terminateSelf()描述terminateSelf() 方法用于停止当前 Ability 自身。这是一种较为常见的退出方式,适用于那些只有一个 Activity 或者 Ability 的应用。用法在 EntryAbility 中使用:this.context.terminateSelf();在 Pages 页面中使用:import { common } from '@kit.AbilityKit'; (getContext(this) as common.UIAbilityContext)?.terminateSelf();优点• 退出时有动画过渡,用户体验较好。• 适合单 Activity 或 Ability 应用。缺点• 仅能终止当前 Ability,对于多 Ability 应用可能不够用。方案二:killAllProcesses()描述killAllProcesses() 方法用于杀死应用所在的整个进程。这是一种更为激进的退出方式,会立即结束应用的所有活动。用法getContext(this).getApplicationContext().killAllProcesses();优点• 可以彻底结束应用的所有活动,适用于需要完全清理资源的场景。缺点• 无动画过渡,用户体验较差。• 可能会导致一些未保存的数据丢失。方案三:clearUpApplicationData()描述clearUpApplicationData() 方法用于清理应用本身的数据,并撤销应用向用户申请的权限。这是一种更为极端的退出方式,不仅终止了应用,还清除了所有应用数据。用法 getContext(this).getApplicationContext().clearUpApplicationData(); 优点• 清理了所有应用数据,适用于需要完全重置应用的状态。缺点• 退出时可能会有短暂的卡顿现象(大约 1-2 秒),用户体验不佳。• 清除数据的过程可能会导致一些副作用,如未保存的数据丢失。推荐方案推荐使用 terminateSelf() 方法来退出应用,因为它提供了动画过渡,用户体验较好。而对于需要清除应用数据并退出的情况,可以使用 clearUpApplicationData(),但在调用之前增加一个加载提示,以提高用户体验。示例代码 @Entry @Component struct Page03 { @State isExit: boolean = false build() { Stack() { Column({ space: 10 }) { Button('清除应用数据并退出APP').margin({ bottom: 200 }).onClick(() => { this.isExit = true setTimeout(() => { getContext(this).getApplicationContext().clearUpApplicationData() }, 500) }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) Text('处理中...') .textAlign(TextAlign.Center) .visibility(this.isExit ? Visibility.Visible : Visibility.None) .width('200lpx') .fontColor("#ffffff") .height('200lpx') .backgroundColor("#80000000") .borderRadius(20) }.width('100%').height('100%') } }总结在鸿蒙系统中,根据应用的具体需求选择合适的退出方法非常重要。对于多数情况,推荐使用 terminateSelf() 来提供优雅的退出体验。而对于需要清除应用数据的情况,可以使用 clearUpApplicationData(),并在调用前加入加载提示,以避免用户感到困惑或不满。通过合理选择和设计,可以显著提升应用的整体用户体验。转载自https://www.cnblogs.com/zhongcx/articles/18442826
总条数:462 到第
上滑加载中