• [技术干货] 鸿蒙应用示例: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
  • [技术干货] 鸿蒙应用示例:如何解决DevEco预览器上下白色间距问题及实现全屏沉浸式体验
    随着鸿蒙系统的不断更新迭代,新版DevEco Studio的预览器会有上下白色间距问题我们可以通过调用鸿蒙的API来修复这个问题。以下是如何手动调整界面以消除这些空白区域的方法:this.windowClass.setWindowLayoutFullScreen(true)这段代码会在页面加载时获取最后一个窗口并将其设为全屏模式,从而消除上下白色间距。实现全屏(沉浸式)页面与非全屏(非沉浸式)页面之间的跳转与切换为了实现全屏(沉浸式)页面与非全屏(非沉浸式)页面之间的无缝切换,我们可以使用鸿蒙的 ArkUI 库。以下是一个示例,展示了如何在两个页面之间进行切换,并保持全屏状态:全屏页面示例(Page01.ets)import { router, window } from '@kit.ArkUI'; @Entry @Component struct Page01 { windowClass?: window.Window async setFullScreen(flag: boolean) { if (!this.windowClass) { this.windowClass = await window.getLastWindow(getContext(this)) } this.windowClass.setWindowLayoutFullScreen(flag) } onPageShow(): void { this.setFullScreen(true) } onPageHide(): void { this.setFullScreen(false) } build() { Column() { Text('当前页全屏(沉浸式)').fontSize(30) Button('跳转到下一页').onClick(() => { router.pushUrl({ url: 'pages/Page02' }) }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) .backgroundColor(Color.Orange) } }非全屏页面示例(Page02.ets)import { router } from '@kit.ArkUI' @Entry @Component struct Page65 { build() { Column() { Text('当前页非全屏(非沉浸式)').fontSize(30) Button('返回上一页').onClick(() => { router.back() }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) .backgroundColor(Color.Blue) } }在这个例子中,我们定义了两个页面:一个是全屏页面(Page01),另一个是非全屏页面(Page02)。当用户点击“跳转到下一页”按钮时,页面会从全屏页面跳转到非全屏页面;而点击“返回上一页”按钮时,页面又会回到全屏页面。这样实现了全屏与非全屏页面之间的自由切换。注意,在全屏页面中,我们使用了 setFullScreen(true) 方法来开启全屏模式,而在非全屏页面中并没有设置全屏模式。这样就可以实现页面间的正常切换。结论通过上述方法,您可以轻松地解决DevEco预览器中的上下白色间距问题,并实现全屏沉浸式页面与非全屏页面之间的切换。鸿蒙系统提供了丰富的工具和库供开发者使用,帮助他们更好地控制应用程序的外观和行为。希望这篇文章能对您的鸿蒙系统开发工作有所帮助!转载自https://www.cnblogs.com/zhongcx/articles/18442825
  • [技术干货] 鸿蒙应用示例:购物车侧滑删除、侧滑收藏、计算价格
    在鸿蒙应用开发中,实现购物车功能并进行屏幕适配是一个常见的需求。通过侧滑删除、侧滑收藏和价格计算等功能,可以为用户提供便捷的购物体验。下面将介绍一个购物车示例的实现方法,并结合屏幕适配技术进行详细说明。示例代码解析以上代码实现了一个购物车功能的示例,包括商品展示、侧滑收藏、侧滑删除和价格计算等功能。通过定义BeanItem类和使用List和ListItem组件展示商品信息,同时通过swipeAction实现了侧滑收藏和侧滑删除的功能。在价格计算部分,通过遍历商品列表并计算总价,实现了价格的动态更新。屏幕适配技术应用在示例中,使用了lpx单位来设置字体大小、宽高等属性,以实现屏幕适配。通过合理设置单位和布局,可以在不同设备上实现一致的视觉效果和用户体验。同时,针对不同设备类型的展示效果,可以通过响应式布局和自适应布局进行调整,以适配不同屏幕尺寸和分辨率的设备。结语通过以上示例代码和屏幕适配技术的应用,可以为开发者提供一个实践的范例,帮助他们更好地理解和掌握鸿蒙应用的开发技巧。购物车功能作为一个常见的应用场景,结合屏幕适配技术的应用,可以为用户提供更加友好和一致的购物体验。通过合理使用不同单位和布局技术,可以有效地解决鸿蒙应用在不同设备上的屏幕适配问题,提供一致且优秀的用户体验。希望这篇文章能够帮助开发者更好地理解和掌握鸿蒙应用的屏幕适配技巧。以上是关于鸿蒙应用示例的相关介绍,希望对开发者在实际开发中有所帮助。import { promptAction } from '@kit.ArkUI' class BeanItem { name: string = "" price: number = 0 count: number = 0 img: string = '' constructor(name: string, price: number, count: number, img: string) { this.name = name this.price = price this.count = count this.img = img } } @Entry @Component struct test { @State dataArr: Array<BeanItem> = [ new BeanItem('苹果', 100, 0, 'https://s.boohee.cn/house/upload_food/2022/1/24/small_photo_url_83bdb4eb0c7f4ab9580d9ccff0bcdced.jpg'), new BeanItem('嘎啦果', 150, 0, 'https://s.boohee.cn/house/upload_food/2020/12/3/small_photo_url_f6a4dd01310addcf7e3e411915690b7a.jpg'), new BeanItem('苹果梨', 200, 0, 'https://s.boohee.cn/house/new_food/small/920d4a8b9c6149289cd9dc378830f551.jpg'), new BeanItem('苹果蕉', 50, 0, 'https://s.boohee.cn/house/upload_food/2021/7/7/small_photo_url_3bd7f3e283d2c6190c88dc6a84f2a17c.jpg'), new BeanItem('伏苹果', 48, 0, 'https://s.boohee.cn/house/new_food/small/aed4c667f79643e6ba3b756086124878.jpg'), new BeanItem('蒸苹果', 35, 0, 'https://s.boohee.cn/house/upload_food/2020/6/12/small_photo_url_90e4b6ab647f45029c52609296c1a7ac.jpg'), new BeanItem('旱苹果', 34, 0, 'https://s.boohee.cn/house/new_food/small/b255df59ed0149908bcf493a5f973c00.jpg'), new BeanItem('煮苹果', 70, 0, 'https://s.boohee.cn/house/upload_food/2021/7/23/small_photo_url_03.png'), new BeanItem('苹果酥', 157, 0, 'https://s.boohee.cn/house/upload_food/2021/7/23/small_photo_url_05.png'), new BeanItem('苹果糊', 53, 0, 'https://s.boohee.cn/house/upload_food/2019/7/19/small_photo_url_0329d3f0d77e104ebbf0c51d6e28b86c.jpg') ] @State priceCount: number = 0; getPriceCount() { let count = 0; for (let i = 0; i < this.dataArr.length; i++) { count += this.dataArr[i].count * this.dataArr[i].price } this.priceCount = count } @Builder itemStart(index: number) { Row() { Text('收藏').fontColor("#ffffff").fontSize('40lpx') .textAlign(TextAlign.Center) .width('180lpx') } .height('100%') .backgroundColor("#FFC107") .justifyContent(FlexAlign.SpaceEvenly) .borderRadius({ topLeft: 10, bottomLeft: 10 }) .onClick(() => { promptAction.showToast({ message: '【' + this.dataArr[index].name + '】收藏成功', duration: 2000, bottom: '400lpx' }); }) } @Builder itemEnd(index: number) { Row() { Text('删除').fontColor("#ffffff").fontSize('40lpx') .textAlign(TextAlign.Center) .width('180lpx') } .height('100%') .backgroundColor("#FF3D00") .justifyContent(FlexAlign.SpaceEvenly) .borderRadius({ topRight: 10, bottomRight: 10 }) .onClick(() => { promptAction.showToast({ message: '【' + this.dataArr[index].name + '】已删除', duration: 2000, bottom: '400lpx' }); this.dataArr.splice(index, 1) this.getPriceCount(); }) } build() { Column() { Text('购物车') .width('100%') .height('88lpx') .fontSize('38lpx') .backgroundColor("#ffffff") .textAlign(TextAlign.Center) List({ space: '44lpx' }) { ForEach(this.dataArr, (item: BeanItem, index: number) => { ListItem() { Row() { Image(item.img) .width('193lpx') .height('193lpx') .alt(item.img) .borderRadius(10) .padding('14lpx') Column() { Text(item.name) .fontSize('30lpx') .fontColor("#222B45") Text(item.price.toString() + '元') .fontSize("30lpx") .fontColor("#65DACC") Blank() Counter() { Text(item.count.toString()) .fontColor("#000000") .fontSize('26lpx') }.backgroundColor("#0F000000") .onInc(() => { item.count++ this.dataArr[index] = new BeanItem(item.name, item.price, item.count, item.img) this.getPriceCount() }) .onDec(() => { if (item.count == 0) { return; } item.count-- this.dataArr[index] = new BeanItem(item.name, item.price, item.count, item.img) this.getPriceCount() }) }.margin({ left: '56lpx' }) .height('167lpx') .alignItems(HorizontalAlign.Start) }.backgroundColor("#ffffff") .borderRadius(10) .width('100%') }.width('100%').margin({ top: index == 0 ? 20 : 0, bottom: index == this.dataArr.length - 1 ? 20 : 0 }) .swipeAction({ start: this.itemStart(index), end: this.itemEnd(index) }) }) } .width('648lpx') .layoutWeight(1) Row() { Column() { Text('合计').fontSize('26lpx').fontColor("#515C6F") Text(this.priceCount + '元').fontSize('38lpx').fontColor("#222B45") Text('免费送货') }.margin({ left: '50lpx' }) .justifyContent(FlexAlign.Start) .alignItems(HorizontalAlign.Start) .width('300lpx') Row() { Text('结账').fontColor("#FFFFFF").fontSize('28lpx') } .onClick(() => { promptAction.showToast({ message: '结算成功', duration: 2000, bottom: '400lpx' }); }) .width('316lpx') .height('88lpx') .backgroundColor("#65DACC") .borderRadius(10) .justifyContent(FlexAlign.Center) }.width('100%').height('192lpx').backgroundColor("#ffffff") } .backgroundColor("#F8FAFB") .width('100%') .height('100%') } }转载自https://www.cnblogs.com/zhongcx/articles/18441745
  • [热门活动] 云学堂新年开发者认证认证活动,赢取专属奖励!
    新年新技能,云学堂开发者认证学习等你来挑战。丰富的云实验带你快速体验华为云服务,轻松完成云上实践!涵盖了AI、鸿蒙、欧拉、GaussDB、云技术精髓等各项技术领域的课程、实战和认证一体化学习,帮助大家从入门到进阶,考证还可赢取专属奖励,云学堂助你成为更好的开发者!立即报名:cid:link_0福利一:分享有礼,邀请好友报名领取100-500元云资源券福利二:完成任意云实验抽好礼(云宝盲盒、定制帆布包、水杯)福利三:考证有礼,考取任意微认证和开发者认证可领取专属礼品,实物奖品、云资源代金券任你选择  
  • [分享交流] 基于OpenHarmony的Web编程技术及应用开发基础
    2024年8月3日,全国高校计算机类课程能力提升高级研修班第六期在华为上海研究所举行。来自西安交通大学的谢涛老师作了题为“基于OpenHarmony的Web编程技术及应用开发基础”的课程改革经验分享。谢涛老师分别从OpenHarmony概述及开发环境部署、两个OpenHarmony应用开发案例(查查词典、辞艺)等方面阐述了基于OpenHarmony的Web编程技术及应用开发的课程改革情况及实践效果,最后带领全体老师一起体验了AppGallery Connect 云数据库的连接实例。
  • [技术干货] 【HarmonyOS】公司鸿蒙项目收工总结之《API》
    HarmonyOS 项目 API 使用注意事项总结1.手动导包的重要性:* 自动导包(Alt + 回车)并不总是有效,某些包需要手动输入。例如:import fs from '@ohos.file.fs'; // 需手动输入2.使用 @Kit:* 从 API 12 开始,使用 @Kit 替代 @ohos。许多论坛资料仍使用 @ohos,可能导致功能不显示。例如:要把import hilog from '@ohos.hilog'; import deviceInfo from '@ohos.deviceInfo';改为import { hilog } from '@kit.PerformanceAnalysisKit'; import { deviceInfo } from '@kit.BasicServicesKit'; * 新建 API 12 项目时,查找资料时遇到 @ohos,请参考官方文档确认对应的 @Kit 版本。3.注意手机版本:* Beta 1 和 Beta 3 之间差异显著,尽管代码在 API 12 下不报错,但在真机上可能会出现错误。确保真机版本升级到 Beta 3 以上,官方文档可能未提供相关提示。常用 API 导入示例// 日志系统import { hilog } from '@kit.PerformanceAnalysisKit';// Web 控制能力import { webview } from '@kit.ArkWeb';// 图片处理import { image } from '@kit.ImageKit';// 相册管理import { photoAccessHelper } from '@kit.MediaLibraryKit';// 呼叫管理import { call } from '@kit.TelephonyKit';// 资源管理import { resourceManager } from '@kit.LocalizationKit';// 分享功能import { systemShare } from '@kit.ShareKit';// 用户首选项import { preferences } from '@kit.ArkData';// 事件处理import { emitter } from '@kit.BasicServicesKit';// 文件操作import { fileIo as fs } from '@kit.CoreFileKit';// 网络连接管理import { connection } from '@kit.NetworkKit';// 扫码功能import { scanBarcode } from '@kit.ScanKit';// 设备信息import { deviceInfo } from '@kit.BasicServicesKit';// 剪贴板管理import { pasteboard } from '@kit.BasicServicesKit';// 窗口管理import { window } from '@kit.ArkUI';// 动画插值曲线import { curves } from '@kit.ArkUI';// 组件内容封装import { ComponentContent } from '@kit.ArkUI';// 提示框import { promptAction } from '@kit.ArkUI';// 路由管理import { router } from '@kit.ArkUI';// 页签型标题栏import { TabTitleBar } from '@kit.ArkUI';示例代码// @ohos全面替换 @kit //hilog日志系统,使应用/服务可以按照指定级别、标识和格式字符串输出日志内容,帮助开发者了解应用/服务的运行状态,更好地调试程序。import { hilog } from '@kit.PerformanceAnalysisKit';//@ohos.web.webview提供web控制能力,Web组件提供网页显示的能力。访问在线网页时需添加网络权限:ohos.permission.INTERNET,具体申请方式请参考声明权限。import { webview } from '@kit.ArkWeb';//本模块提供图片处理效果,包括通过属性创建PixelMap、读取图像像素数据、读取区域内的图片数据等。import { image } from '@kit.ImageKit';//该模块提供相册管理模块能力,包括创建相册以及访问、修改相册中的媒体数据信息等。import { photoAccessHelper } from '@kit.MediaLibraryKit';//该模块提供呼叫管理功能,包括拨打电话、跳转到拨号界面、获取通话状态、格式化电话号码等。import { call } from '@kit.TelephonyKit';//资源管理模块,根据当前configuration:语言、区域、横竖屏、Mcc(移动国家码)和Mnc(移动网络码)、Device capability(设备类型)、Density(分辨率)提供获取应用资源信息读取接口。import { resourceManager } from '@kit.LocalizationKit'//本模块提供分享数据创建及分享面板拉起的功能,提供多种系统标准分享服务,例如分享数据给其他应用、复制、打印等。//// 分享接入应用需要配置、呈现和关闭分享面板。// 分享面板的配置包括数据对象、呈现视图的方式、预览方式等。import { systemShare } from '@kit.ShareKit';//本模块对标准化数据类型进行了抽象定义与描述。import { uniformTypeDescriptor as utd } from '@kit.ArkData';//@ohos.data.preferences (用户首选项)用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。//// 数据存储形式为键值对,键的类型为字符串型,值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。import { preferences } from '@kit.ArkData';//错误信息打印import { BusinessError } from '@kit.BasicServicesKit';//开发者可以通过该模块引用Ability公共模块类。import { common } from '@kit.AbilityKit';//需要校验的权限名称,合法的权限名取值可在应用权限列表中查询。import { Permissions } from '@kit.AbilityKit';//UIAbility是包含UI界面的应用组件,继承自Ability,提供组件创建、销毁、前后台切换等生命周期回调,同时也具备组件协同的能力,组件协同主要提供如下常用功能:import { UIAbility } from '@kit.AbilityKit';//本模块提供应用信息查询能力,支持BundleInfo、ApplicationInfo、AbilityInfo、ExtensionAbilityInfo等信息的查询。import { bundleManager } from '@kit.AbilityKit';//Want是对象间信息传递的载体, 可以用于应用组件间的信息传递。 Want的使用场景之一是作为startAbility的参数, 其包含了指定的启动目标, 以及启动时需携带的相关数据, 如bundleName和abilityName字段分别指明目标Ability所在应用的Bundle名称以及对应包内的Ability名称。当Ability A需要启动Ability B并传入一些数据时, 可使用Want作为载体将这些数据传递给Ability B。import { Want } from '@kit.AbilityKit';//本模块提供HTTP数据请求能力。应用可以通过HTTP发起一个数据请求,支持常见的GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT方法。import { http } from '@kit.NetworkKit';//网络连接管理提供管理网络一些基础能力,包括获取默认激活的数据网络、获取所有激活数据网络列表、开启关闭飞行模式、获取网络能力信息等功能。import { connection } from '@kit.NetworkKit';//本模块提供默认界面扫码能力。import { scanBarcode } from '@kit.ScanKit'//本模块提供扫码公共信息。import { scanCore } from '@kit.ScanKit'//本模块提供终端设备信息查询,开发者不可配置。其中deviceInfo.marketNames可以获取设备名称,比如mate60import { deviceInfo } from '@kit.BasicServicesKit';//本模块提供了在同一进程不同线程间,或同一进程同一线程内,发送和处理事件的能力,包括持续订阅事件、单次订阅事件、取消订阅事件,以及发送事件到事件队列的能力。import { emitter } from '@kit.BasicServicesKit';//本模块主要提供管理系统剪贴板的能力,为系统复制、粘贴功能提供支持。系统剪贴板支持对文本、HTML、URI、Want、PixelMap等内容的操作。import { pasteboard } from '@kit.BasicServicesKit';//该模块为基础文件操作API,提供基础文件操作能力,包括文件基本管理、文件目录管理、文件信息统计、文件流式读写等常用功能。import { fileIo as fs } from '@kit.CoreFileKit';//该模块提供空间查询相关的常用功能:包括对内外卡的空间查询,对应用分类数据统计的查询,对应用数据的查询等。import { storageStatistics } from '@kit.CoreFileKit';//选择器(Picker)是一个封装PhotoViewPicker、DocumentViewPicker、AudioViewPicker等API模块,具有选择与保存的能力。应用可以自行选择使用哪种API实现文件选择和文件保存的功能。该类接口,需要应用在界面UIAbility中调用,否则无法拉起photoPicker应用或FilePicker应用。import { picker } from '@kit.CoreFileKit';//本模块提供设置动画插值曲线功能,用于构造阶梯曲线对象、构造三阶贝塞尔曲线对象和构造弹簧曲线对象。import { curves } from '@kit.ArkUI';//ComponentContent表示组件内容的实体封装,其对象支持在非UI组件中创建与传递,便于开发者对弹窗类组件进行解耦封装。ComponentContent底层使用了BuilderNode,相关使用规格参考BuilderNode。import { ComponentContent } from '@kit.ArkUI';//用于设置长度属性,当长度单位为PERCENT时,值为1表示100%。import { LengthMetrics } from '@kit.ArkUI';//创建并显示文本提示框、对话框和操作菜单。import { promptAction } from '@kit.ArkUI';//窗口提供管理窗口的一些基础能力,包括对当前窗口的创建、销毁、各属性设置,以及对各窗口间的管理调度。//// 该模块提供以下窗口相关的常用功能://// Window:当前窗口实例,窗口管理器管理的基本单元。// WindowStage:窗口管理器。管理各个基本窗口单元。import { window } from '@kit.ArkUI';//一种普通标题栏,支持设置标题、头像(可选)和副标题(可选),可用于一级页面、二级及其以上界面配置返回键。import { ComposeTitleBar } from '@kit.ArkUI';//本模块提供通过不同的url访问不同的页面,包括跳转到应用内的指定页面、同应用内的某个页面替换当前页面、返回上一页面或指定的页面等。//// 推荐使用Navigation组件作为应用路由框架。import { router } from '@kit.ArkUI';//页签型标题栏,用于页面之间的切换。仅一级页面适用。import { TabTitleBar } from '@kit.ArkUI'; class MyUIAbility extends UIAbility {} @Builderfunction buildText(params: object) { Column() { }.backgroundColor('#FFF0F0F0')} @Entry@Componentstruct Page14 { controller: webview.WebviewController = new webview.WebviewController(); test() { router.pushUrl({ url: 'pages/routerpage2' }) let config: window.Configuration = { name: "test", windowType: window.WindowType.TYPE_DIALOG, ctx: getContext() }; let promise = window.createWindow(config); try { promptAction.showToast({ message: 'Hello World', duration: 2000 }); } catch (error) { let message = (error as BusinessError).message let code = (error as BusinessError).code console.error(`showToast args error code is ${code}, message is ${message}`); } ; } aboutToAppear(): void { LengthMetrics.vp(3) let uiContext = this.getUIContext(); let promptAction = uiContext.getPromptAction(); let contentNode = new ComponentContent(uiContext, wrapBuilder(buildText), Object); curves.initCurve(Curve.EaseIn) // 创建一个默认先慢后快插值曲线 let documentPicker = new picker.DocumentViewPicker(getContext()); storageStatistics.getCurrentBundleStats().then((BundleStats: storageStatistics.BundleStats) => { console.info("getCurrentBundleStats successfully:" + JSON.stringify(BundleStats)); }).catch((err: BusinessError) => { console.error("getCurrentBundleStats failed with error:" + JSON.stringify(err)); }); let filePath = "pathDir/test.txt"; fs.stat(filePath).then((stat: fs.Stat) => { console.info("get file info succeed, the size of file is " + stat.size); }).catch((err: BusinessError) => { console.error("get file info failed with error message: " + err.message + ", error code: " + err.code); }); let dataXml = new ArrayBuffer(256); let pasteData: pasteboard.PasteData = pasteboard.createData('app/xml', dataXml); let innerEvent: emitter.InnerEvent = { eventId: 1 }; console.info(`marketName:${deviceInfo.marketName}`) let netConnection = connection.createNetConnection(); http.RequestMethod.POST hilog.isLoggable(0x0001, "testTag", hilog.LogLevel.INFO); const color: ArrayBuffer = new ArrayBuffer(96); // 96为需要创建的像素buffer大小,取值为:height * width *4 let opts: image.InitializationOptions = { editable: true, pixelFormat: 3, size: { height: 4, width: 6 } } image.createPixelMap(color, opts).then((pixelMap: image.PixelMap) => { console.info('Succeeded in creating pixelmap.'); }).catch((error: BusinessError) => { console.error(`Failed to create pixelmap. code is ${error.code}, message is ${error.message}`); }) //此处获取的phAccessHelper实例为全局对象,后续使用到phAccessHelper的地方默认为使用此处获取的对象,如未添加此段代码报phAccessHelper未定义的错误请自行添加 let context = getContext(this); let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); call.makeCall("138xxxxxxxx", (err: BusinessError) => { if (err) { console.error(`makeCall fail, err->${JSON.stringify(err)}`); } else { console.log(`makeCall success`); } }); let systemResourceManager = resourceManager.getSystemResourceManager(); let data: systemShare.SharedData = new systemShare.SharedData({ utd: utd.UniformDataType.PLAIN_TEXT, content: 'Hello HarmonyOS' }); preferences.getPreferencesSync(getContext(), { name: 'myStore' }) let uiAbilityContext: common.UIAbilityContext; let permissionName: Permissions = 'ohos.permission.GRANT_SENSITIVE_PERMISSIONS'; let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_METADATA; let want: Want = { deviceId: '', // deviceId为空表示本设备 bundleName: 'com.example.myapplication', abilityName: 'EntryAbility', moduleName: 'entry' // moduleName非必选 }; // 定义扫码参数options let options: scanBarcode.ScanOptions = { scanTypes: [scanCore.ScanType.ALL], enableMultiMode: true, enableAlbum: true }; // 可调用getContext接口获取当前页面关联的UIAbilityContext scanBarcode.startScanForResult(context, options, (error: BusinessError, result: scanBarcode.ScanResult) => { }) } build() { Column() { ComposeTitleBar({ title: "标题", subtitle: "副标题", }) TabTitleBar({ // swiperContent: this.componentBuilder, // tabItems: this.tabItems, // menuItems: this.menuItems, }) } .height('100%') .width('100%') }}转载自https://www.cnblogs.com/zhongcx/articles/18440601
  • [技术干货] 【HarmonyOS】公司鸿蒙项目收工总结之《组件》
    在项目中使用的系统组件和属性总结如下:使用的系统组件:1.Column:纵向布局容器,用于垂直排列子组件。2.Row:横向布局容器,用于水平排列子组件。3.Stack:层叠布局容器,用于将子组件层叠显示。4.Flex:弹性布局容器,用于创建灵活的布局。5.Scroll:滚动布局容器,用于实现滚动效果。6.Image:图片组件,用于显示图片内容。7.Text:文本组件,用于显示文本内容。8.LoadingProgress:加载指示器组件,用于展示加载过程中的指示器。9.SaveButton:保存按钮组件,用于保存图片到相册。10.Swiper:轮播图组件,用于创建轮播图效果。11.TextInput:输入框组件,用于接收用户输入。12.Search:搜索条组件,用于实现搜索功能。13.Web:网页加载组件,用于加载网页内容。使用的属性记录:* width:设置容器或组件的宽度。* height:设置容器或组件的高度。* backgroundColor:设置容器或组件的背景色。* padding:设置容器或组件的内边距。* margin:设置容器或组件的外边距。* visibility:根据属性值控制容器或组件的显示或隐藏。* onClick:设置点击事件处理器。* onAreaChange:监听容器尺寸变化。* linearGradient:设置容器或组件的背景为线性渐变色。* shadow:设置容器的阴影效果。* align:设置内部组件的位置显示方式。* constraintSize:设置容器或组件的最大宽度或高度。* transition:为容器或组件添加过渡动画效果。* maxLines:设置文本组件最多显示的行数。* onChange:监听输入内容的变化。* indicator:设置是否显示轮播指示点。* loop:设置是否开启轮播图的循环播放。* src:设置网页加载组件的源地址。* javaScriptProxy:注册JS Bridge,用于与H5页面进行数据通信。* onLoadIntercept:拦截内部跳转、重定向等链接。以上是项目中使用的系统组件和属性记录,可以作为学习和开发项目时的参考。【代码示例】import { LengthMetrics } from '@kit.ArkUI'import web_webview from '@ohos.web.webview'; @Entry@Componentexport struct Page13 { controller: web_webview.WebviewController = new web_webview.WebviewController(); @State inviteQrCodeID: string = "inviteQrCodeID" @State isShow: boolean = true @State searchValue: string = "" scripts: Array<ScriptItem> = [ // { script: this.localStorage, scriptRules: ["*"] } ]; build() { Column() { Column({ space: 10 }) { // 纵向布局容器 // ForEach() 可配合使用以动态渲染子组件 } .width('90%') // 设置容器宽度为父容器的90% .height('100%') // 设置容器高度为父容器的100% .backgroundColor("#ffffff") // 设置容器背景色为白色 .layoutWeight(1) // 占据父容器的剩余空间 .alignItems(HorizontalAlign.Center) // 水平居中对齐 .justifyContent(FlexAlign.Center) // 垂直居中对齐 .align(Alignment.TopStart) // 内容对齐方式为顶部左端 .borderWidth({ bottom: 1 }) // 设置底部边框宽度为1 .borderColor("#E3E3E3") // 设置边框颜色 .padding({ left: '20lpx', right: '20lpx' }) // 设置左右内边距为20逻辑像素 .margin({ top: '44lpx' }) // 设置顶部外边距为44逻辑像素 .visibility(this.isShow ? Visibility.Visible : Visibility.None) // 根据isShow属性控制容器的显示或隐藏 .id(this.inviteQrCodeID) // 设置组件ID,方便后续截图保存到相册 .flexGrow(1) // 当父布局为Flex时,占据父容器的剩余空间 .gesture(LongPressGesture({ repeat: false })) // 长按手势监听,设置repeat为false表示长按时不会重复触发 .onClick(() => { // 点击事件处理器 // 处理点击逻辑 }) Row() { // 横向布局容器 // ForEach() 可用于动态渲染子组件列表 } .width('100%') // 设置容器宽度为父容器的100% .height('192lpx') // 设置容器高度为192逻辑像素 .justifyContent(FlexAlign.End) // 子元素在主轴方向上靠右对齐 .backgroundColor(Color.White) // 设置容器背景色为白色 .padding({ left: '28lpx', right: '28lpx' }) // 设置左右内边距为28逻辑像素 .margin({ top: '39lpx' }) // 设置顶部外边距为39逻辑像素 .transition(TransitionEffect.OPACITY.animation({ duration: 200 })) // 添加透明度过渡动画,持续时间为200毫秒 .visibility(this.isShow ? Visibility.None : Visibility.Visible) // 根据 `this.isShow` 属性来决定是否显示该容器 .onClick(() => { // 点击事件处理器 // 执行点击操作的逻辑 }) .onAreaChange((previousArea: Area, currentArea: Area) => { // 监听容器尺寸变化 // 在容器尺寸发生变化时执行相关逻辑 }) Stack({ alignContent: Alignment.Bottom }) { // 创建一个层叠布局容器,并设置内容对其方式为底部对齐 } .width(180) // 设置容器宽度为180逻辑像素 .height(180) // 设置容器高度为180逻辑像素 .backgroundColor(0x88000000) // 设置容器背景色为半透明黑色(RGB: #000000,Alpha: 0.53) .borderRadius(10) // 设置容器的圆角半径为10逻辑像素 .margin({ left: '25lpx', top: '6lpx', right: '25lpx' }) // 设置容器的左、顶和右外边距分别为25和6逻辑像素 .align(Alignment.Top) // 设置内部组件的位置显示方式为顶部对齐 .visibility(this.isShow ? Visibility.Visible : Visibility.None) // 根据 `this.isShow` 属性值来控制容器的显示或隐藏 .onClick(() => { }) // 定义点击事件处理函数 .linearGradient({ // 设置容器背景色为线性渐变色,注意与 `.backgroundColor` 互斥 angle: 90, // 渐变角度为90度 colors: [// 渐变颜色配置 [0xFF0000, 0.0], // 红色,位置0% [0xFFFF00, 0.2], // 黄色,位置20% [0x00FF00, 0.4], // 绿色,位置40% [0x00FFFF, 0.6], // 青绿色,位置60% [0x0000FF, 0.8], // 蓝色,位置80% [0xFF00FF, 1.0]// 紫色,位置100% ] }) .shadow({ // 设置容器的阴影效果 radius: 20, // 阴影模糊半径为20逻辑像素 offsetY: 25, // 阴影垂直偏移量为25逻辑像素 offsetX: 0, // 阴影水平偏移量为0逻辑像素 color: "#bfbfbf" // 阴影颜色为浅灰色 }) Flex({ wrap: FlexWrap.Wrap }) { // 创建一个允许换行的弹性布局容器 // ForEach 通常用于内部动态渲染子组件列表 } .width('100%') // 设置容器宽度为父容器的100% .height('88lpx') // 设置容器高度为88逻辑像素 .margin({ top: '20lpx', bottom: '30lpx' }) // 设置容器的顶部和底部外边距分别为20和30逻辑像素 .borderWidth({ top: 1 }) // 设置容器顶部边框宽度为1逻辑像素 .borderColor("#cccccc") // 设置容器边框颜色为浅灰色 .visibility(this.isShow ? Visibility.Visible : Visibility.None) // 根据 `this.isShow` 属性值来控制容器的显示或隐藏 Scroll() { // 创建一个滚动布局容器 // 通常内部配合 Row() 或 Column() 使用以实现滚动效果 } .width('100%') // 设置容器宽度为父容器的100% .layoutWeight(1) // 占据父容器的剩余空间 .scrollable(ScrollDirection.Horizontal) // 设置滚动方向为水平 .scrollBar(BarState.Off) // 关闭滚动条显示 .borderWidth({ bottom: 1 }) // 设置底部边框宽度为1逻辑像素 .borderColor("#e3e3e3") // 设置边框颜色 .align(Alignment.Start) // 设置内部组件的位置显示方式为起始位置对齐 .visibility(this.isShow ? Visibility.Visible : Visibility.None) // 根据 `this.isShow` 属性值来控制容器的显示或隐藏 Image($r('app.media.app_icon'))// 图片组件 .width('120lpx')// 设置图片宽度为120逻辑像素 .height('120lpx')// 设置图片高度为120逻辑像素 .padding('10lpx')// 设置图片的内边距为10逻辑像素 .margin({ top: '25lpx' })// 设置图片的顶部外边距为25逻辑像素 .draggable(false)// 设置图片不可拖动,默认情况下不设置时可能会受外层长按手势影响 .objectFit(ImageFit.Fill)// 设置图片显示方式为填充,可能会导致图片变形 .visibility(this.isShow ? Visibility.Visible : Visibility.Hidden)// 根据 `this.isShow` 属性值来控制图片的显示或隐藏 .alt($r('app.media.app_icon'))// 设置图片加载失败时的占位图 .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])// 使图片在系统安全区域内扩展,避免被状态栏遮挡 .onClick(() => { // 点击事件处理器 // 处理点击逻辑 }) Text() { // 文本组件,可结合 ForEach 和 Span 使用 ForEach(["a", "b"], (item: string, index: number) => { // 使用 ForEach 循环遍历数组 ["a", "b"] Span(item)// 创建一个 Span 子组件,并传入当前项作为文本内容 .fontColor("#FF1919")// 设置 Span 组件的文字颜色为红色 .fontSize('24lpx')// 设置 Span 组件的字体大小为24逻辑像素 .onClick(() => { }) // 设置 Span 组件的点击事件处理器 }) } .width('100%') // 设置 Text 组件的宽度为100%,即填充其父容器的宽度 .height('120lpx') // 设置 Text 组件的高度为120逻辑像素 .textAlign(TextAlign.Center) // 设置 Text 组件内的文本水平居中对齐 .fontColor("#2E2E2E") // 设置 Text 组件内的文本颜色为深灰色 .fontSize('36lpx') // 设置 Text 组件内的字体大小为36逻辑像素 .backgroundColor("#F9F9F9") // 设置 Text 组件的背景色为浅灰色 .padding(20) // 设置 Text 组件的内边距为20逻辑像素 .margin({ bottom: '44lpx' }) // 设置 Text 组件的底部外边距为44逻辑像素 .lineHeight('60lpx') // 设置 Text 组件内文本的行间距(行高)为60逻辑像素 .lineSpacing(LengthMetrics.lpx(15)) // 设置 Text 组件内文本行与行之间的实际空白距离为15逻辑像素 .borderRadius(8) // 设置 Text 组件的圆角半径为8逻辑像素 .borderWidth('1lpx') // 设置 Text 组件的边框宽度为1逻辑像素 .borderColor("#bbbbbb") // 设置 Text 组件的边框颜色为浅灰色 .borderStyle(BorderStyle.Solid) // 设置 Text 组件的边框样式为实线 .visibility(this.isShow ? Visibility.Visible : Visibility.None) // 根据 `this.isShow` 的值来决定 Text 组件是否可见 .constraintSize({ maxWidth: '80%' }) // 设置 Text 组件的最大宽度为其父容器宽度的80% .layoutWeight(1) // 设置 Text 组件在弹性布局中的权重为1,意味着它会占据剩余的空间 .transition(TransitionEffect.OPACITY.animation({ duration: 200 })) // 为 Text 组件添加一个持续200毫秒的透明度过渡动画 .maxLines(2) // 设置 Text 组件最多显示两行文本 .onClick(() => { // 设置 Text 组件的点击事件处理器 // 在此处添加点击事件的具体逻辑 }) .onAreaChange((previousArea: Area, currentArea: Area) => { // 监听 Text 组件的尺寸变化 // 在此处添加尺寸变化时的具体逻辑 }) .linearGradient({ // 设置 Text 组件的背景为线性渐变色,注意与 `.backgroundColor` 不能同时使用 angle: 90, // 设置渐变的角度为90度,从左到右 colors: [// 设置渐变的颜色配置数组 [0xFF0000, 0.0], // 红色,位于渐变起始点(0%) [0xFFFF00, 0.2], // 黄色,位于渐变的20%位置 [0x00FF00, 0.4], // 绿色,位于渐变的40%位置 [0x00FFFF, 0.6], // 青绿色,位于渐变的60%位置 [0x0000FF, 0.8], // 蓝色,位于渐变的80%位置 [0xFF00FF, 1.0]// 紫色,位于渐变终点(100%) ] }) LoadingProgress()// 创建一个加载指示器组件,通常用于页面加载过程中展示 .color(Color.White)// 设置加载指示器的颜色为白色 .width(100)// 设置加载指示器的宽度为100单位 .height(100) // 设置加载指示器的高度为100单位 SaveButton()// 用户点击此按钮可保存图片到相册 .onClick(() => { }) // 设置点击事件处理器 Swiper() { // 创建一个轮播图组件,通常内部结合 ForEach 使用 } .indicator(false) // 设置是否显示轮播指示点 .loop(false) // 设置是否开启轮播图的循环播放 TextInput({ placeholder: '请输入邀请码' })// 创建一个输入框,带有提示信息 .width('540lpx')// 设置输入框宽度为540逻辑像素 .height('76lpx')// 设置输入框高度为76逻辑像素 .placeholderColor("#CBCBCB")// 设置提示信息文字颜色为浅灰色 .maxLength(6)// 设置输入框最多可输入的字符长度为6 .fontColor("#2E2E2E")// 设置输入内容的颜色为深灰色 .fontSize('36lpx')// 设置输入框内字体大小为36逻辑像素 .padding({ left: 0 })// 设置输入框左侧内边距为0逻辑像素 .margin({ left: '105lpx', top: '28lpx' })// 设置输入框左侧和顶部的外边距分别为105和28逻辑像素 .borderRadius(0)// 设置输入框圆角为0,即直角 .backgroundColor("#ffffff")// 设置输入框背景色为白色 .inputFilter('^[0-9a-zA-Z]*$')// 设置输入过滤规则,仅允许数字和字母输入 .borderWidth({ bottom: 1 })// 设置输入框底部边框宽度为1逻辑像素 .borderColor("#CBCBCB")// 设置输入框边框颜色为浅灰色 .type(InputType.PhoneNumber)// 设置输入类型为电话号码 .caretColor('#FF1919')// 设置输入框光标颜色为红色 .onChange((value: string) => { }) // 监听输入内容的变化 Search({ value: $$this.searchValue, placeholder: '搜索当前列表显示的单位' })// 创建一个搜索条组件,并设置初始值和提示信息 .layoutWeight(1)// 在弹性布局中占据剩余空间 .backgroundColor(Color.Transparent)// 设置搜索条背景为透明 .fontColor("#2E2E2E")// 设置搜索内容的颜色为深灰色 .placeholderColor("#CBCBCB")// 设置提示信息文字颜色为浅灰色 .borderRadius('32lpx')// 设置搜索条的圆角半径为32逻辑像素 .textFont({ size: '28lpx' })// 设置搜索条内字体大小为28逻辑像素 .onChange((value: string) => { }) // 监听输入内容的变化 Web({ src: "https://xxx", controller: this.controller })// 创建一个加载网页的组件,并设置源地址和控制器 .width('100%')// 设置组件宽度为100%,即填充其父容器宽度 .height('100%')// 设置组件高度为100%,即填充其父容器高度 .domStorageAccess(true)// 开启DOM Storage API权限,允许本地存储功能 .javaScriptAccess(true)// 允许执行JavaScript脚本,默认允许执行 .databaseAccess(true)// 开启数据库存储API权限,默认不开启 .mixedMode(MixedMode.All)// 允许HTTP和HTTPS混合模式 .fileAccess(true)// 开启应用文件系统的访问权限,默认已开启 .imageAccess(true)// 允许自动加载图片资源,默认允许 .geolocationAccess(true)// 开启地理位置权限,默认开启 .onlineImageAccess(true)// 允许从网络加载图片资源,默认允许 .mediaPlayGestureAccess(true)// 允许有声视频播放无需用户手动点击,默认需要用户点击 .backgroundColor('#ffffff')// 设置组件背景色为白色 .javaScriptOnDocumentStart(this.scripts)// 在文档开始加载前注入JavaScript脚本 .javaScriptProxy({ // 注册JS Bridge,用于与H5页面进行数据通信 name: "xx", // 注册名称,注意避免使用关键字 object: Object(), methodList: Object(), controller: this.controller, }) .onLoadIntercept((event) => { return true })// 拦截内部跳转、重定向等链接 .onControllerAttached(() => { })// 在网页加载前设置自定义UA .onPageEnd((e) => { })// 页面加载完成后,可以设置隐藏loading布局 .onShowFileSelector((event) => { return true })// 网页请求拉起相机时,由系统调用返回用户选择的图片 .onVisibleAreaChange([0.0, 1.0], () => { }) // 监听网页显示区域的变化 } .height('100%') .width('100%') }}转载自https://www.cnblogs.com/zhongcx/articles/18440599
  • [技术干货] 【HarmonyOS】高仿华为阅读翻页
    【HarmonyOS】高仿华为阅读app翻页demosrc/main/ets/entryability/EntryAbility.etsimport { window } from '@kit.ArkUI';import { UIAbility } from '@kit.AbilityKit';export default class EntryAbility extends UIAbility { onWindowStageCreate(windowStage: window.WindowStage): void { let windowClass = windowStage.getMainWindowSync() let statusBarHeight = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height let navigationIndicatorHeight = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR).bottomRect.height AppStorage.setOrCreate('statusBarHeight', statusBarHeight) //保存状态栏高度,单位px AppStorage.setOrCreate('navigationIndicatorHeight', navigationIndicatorHeight) //保存底部导航条的高度,单位px windowClass.setWindowSystemBarEnable([]); //'status' | 'navigation' windowStage.loadContent('pages/Page40'); }}src/main/ets/pages/Page40.etsimport { promptAction } from '@kit.ArkUI'import { batteryInfo, systemDateTime } from '@kit.BasicServicesKit'@Entry@Componentstruct Page40 { // 页面信息 @Provide info: string = '设计理念\n在万物互联的时代,我们每天都会接触到很多不同形态的设备,每种设备在特定的场景下能够为我们解决一些特定的问题,表面看起来我们能够做到的事情更多了,但每种设备在使用时都是孤立的,提供的服务也都局限于特定的设备,我们的生活并没有变得更好更便捷,反而变得非常复杂。HarmonyOS 的诞生旨在解决这些问题,在纷繁复杂的世界中回归本源,建立平衡,连接万物。\n混沌初开,一生二、二生三、三生万物,我们希望通过 HarmonyOS 为用户打造一个和谐的数字世界——One Harmonious Universe。\nOne\n万物归一,回归本源。我们强调以人为本的设计,通过严谨的实验探究体验背后的人因,并将其结论融入到我们的设计当中。\nHarmonyOS 系统的表现应该符合人的本质需求。结合充分的人因研究,为保障全场景多设备的舒适体验,在整个系统中,各种大小的文字都清晰易读,图标精确而清晰、色彩舒适而协调、动效流畅而生动。同时,界面元素层次清晰,能巧妙地突出界面的重要内容,并能传达元素可交互的感觉。另外,系统的表现应该是直觉的,用户在使用过程中无需思考。因此系统的操作需要符合人的本能,并且使用智能化的技术能力主动适应用户的习惯。\nHarmonious\n一生为二,平衡共生。万物皆有两面,虚与实、阴与阳、正与反... 二者有所不同却可以很好地融合,达至平衡。\n在 HarmonyOS 中,我们希望给用户带来和谐的视觉体验。我们在物理世界中找到在数字世界中的映射,通过光影、材质等设计转化到界面设计中,给用户带来高品质的视觉享受。同时,物理世界中的体验记忆转化到虚拟世界中,熟悉的印象有助于帮助用户快速理解界面元素并完成相应的操作。\nUniverse\n三生万物,演化自如。HarmonyOS 是面向多设备体验的操作系统,因此,给用户提供舒适便捷的多设备操作体验是 HarmonyOS 区别于其他操作系统的核心要点。\n一方面,界面设计/组件设计需要拥有良好的自适应能力,可快速进行不同尺寸屏幕的开发。\n另一方面,我们希望多设备的体验能在一致性与差异性中取得良好的平衡。\n● 一致性:界面中的元素设计以及交互方式尽量保持一致,以便减少用户的学习成本。\n● 差异性:不同类型的设备在屏幕尺寸、交互方式、使用场景、用户人群等方面都会存在一定的差异性,为了给用户提供合适的操作体验,我们需要针对不同类型的设备进行差异化的设计。\n同时,HarmonyOS 作为面向全球用户的操作系统,为了让更多的用户享受便利的科技与愉悦的体验,我们将在数字健康、全球化、无障碍等方面进行积极的探索与思考。' @Provide lineHeight: number = 0 // 单行文本的高度 @Provide pageHeight: number = 0 // 每页的最大高度 @Provide totalContentHeight: number = 0 // 整个文本内容的高度 @Provide textContent: string = " " // 文本内容,默认一个空格是为了计算单行文本的高度 @Provide @Watch('totalPagesChanged') totalPages: number = 1 // 总页数 //=====页面切换动画===== @State currentPage: number = 0 // 当前页数 private DISPLAY_COUNT: number = 1 private MIN_SCALE: number = 0.75 @State pages: string[] = [] @State opacityList: number[] = [] @State scaleList: number[] = [] @State translateList: number[] = [] @State zIndexList: number[] = [] //=====定时器===== timeIntervalId: number = 0 @Provide timeStr: string = "" @Provide batterySOC: string = "" //======左右滑动判断====== @State screenStartX: number = 0 totalPagesChanged() { // 总页数变化时更新 this.pages = new Array(this.totalPages).fill(''); } aboutToDisappear(): void { clearInterval(this.timeIntervalId) } aboutToAppear(): void { this.timeIntervalId = setInterval(() => { let timestamp = systemDateTime.getTime(true) / 1000000 //因为获取的是纳秒 所以要 / 1000000 // console.info(`timestamp:${timestamp}`) const date = new Date(timestamp); const hours = ('0' + date.getHours()).slice(-2); const minutes = ('0' + date.getMinutes()).slice(-2); this.timeStr = `${hours}:${minutes}` this.batterySOC = `电量${batteryInfo.batterySOC}%` }, 1000, 0) for (let i = 0; i < this.pages.length; i++) { this.opacityList.push(1.0) this.scaleList.push(1.0) this.translateList.push(0.0) this.zIndexList.push(0) } } build() { Stack() { Page40Child()// 自定义动画变化透明度、缩放页面、抵消系统默认位移、渲染层级等 .width('100%').height('100%').visibility(Visibility.Hidden) Swiper() { ForEach(this.pages, (item: string, index: number) => { Page40Child({ index: index })// 自定义动画变化透明度、缩放页面、抵消系统默认位移、渲染层级等 .opacity(this.opacityList[index]) .scale({ x: this.scaleList[index], y: this.scaleList[index] }) .translate({ x: this.translateList[index] }) .zIndex(this.zIndexList[index]) }) } .onTouch((e) => { if (e.type === TouchType.Down && e.touches.length > 0) { // 触摸开始,记录初始位置 this.screenStartX = e.touches[0].x; } else if (e.type === TouchType.Up && e.changedTouches.length > 0) { // 当手指抬起时,更新最后的位置 let lastScreenX = e.changedTouches[0].x; if (this.screenStartX < lastScreenX && this.currentPage === 0) { promptAction.showToast({ message: "没有上一页了" }); } else if (this.screenStartX > lastScreenX && this.currentPage === this.totalPages - 1) { promptAction.showToast({ message: "没有下一页了" }); } } }) .onChange((index: number) => { console.info(index.toString()) this.currentPage = index }) .loop(false) // .height(300) .layoutWeight(1) .indicator(false) .displayCount(this.DISPLAY_COUNT, true) .customContentTransition({ // 页面移除视窗时超时1000ms下渲染树 // timeout: 1000, // 对视窗内所有页面逐帧回调transition,在回调中修改opacity、scale、translate、zIndex等属性值,实现自定义动画 transition: (proxy: SwiperContentTransitionProxy) => { if (proxy.position <= proxy.index % this.DISPLAY_COUNT || proxy.position >= this.DISPLAY_COUNT + proxy.index % this.DISPLAY_COUNT) { // 同组页面往左滑或往右完全滑出视窗外时,重置属性值 this.opacityList[proxy.index] = 1.0 this.scaleList[proxy.index] = 1.0 this.translateList[proxy.index] = 0.0 this.zIndexList[proxy.index] = 0 } else { // 同组页面往右滑且未滑出视窗外时,对同组中左右两个页面,逐帧根据position修改属性值,实现两个页面往Swiper中间靠拢并透明缩放的自定义切换动画 if (proxy.index % this.DISPLAY_COUNT === 0) { this.opacityList[proxy.index] = 1 - proxy.position / this.DISPLAY_COUNT this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - proxy.position / this.DISPLAY_COUNT) this.translateList[proxy.index] = -proxy.position * proxy.mainAxisLength + (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0 } else { this.opacityList[proxy.index] = 1 - (proxy.position - 1) / this.DISPLAY_COUNT this.scaleList[proxy.index] = this.MIN_SCALE + (1 - this.MIN_SCALE) * (1 - (proxy.position - 1) / this.DISPLAY_COUNT) this.translateList[proxy.index] = -(proxy.position - 1) * proxy.mainAxisLength - (1 - this.scaleList[proxy.index]) * proxy.mainAxisLength / 2.0 } this.zIndexList[proxy.index] = -1 } } }) .width('100%') .height('100%') }.width('100%').height('100%') }}@Componentstruct Page40Child { @Consume info: string @Consume lineHeight: number // 单行文本的高度 @Consume pageHeight: number // 每页的最大高度 @Consume totalContentHeight: number // 整个文本内容的高度 @Consume textContent: string // 文本内容,默认一个空格是为了计算单行文本的高度 @Consume totalPages: number // 总页数 @Consume timeStr: string @Consume batterySOC: string @State scrollOffset: number = 0 // 当前滚动偏移量 @Prop index: number = 0 scroller: Scroller = new Scroller() // 滚动条实例 resetMaxLineHeight() { if (this.lineHeight > 0 && this.pageHeight > 0 && this.totalContentHeight > 0) { this.pageHeight = (Math.floor(this.pageHeight / this.lineHeight)) * this.lineHeight this.totalPages = Math.ceil(this.totalContentHeight / this.pageHeight) //向上取整得到总页数 } } aboutToAppear(): void { this.scrollOffset = -(this.pageHeight * this.index) } build() { Column() { Text().width('100%').height(`${AppStorage.get('statusBarHeight')}px`) //顶部状态栏高度 Text('通用设计基础') .fontColor("#7a7a7a") .fontSize(10) .padding({ left: 30, top: 10, bottom: 10 }) .width('100%') Column() { Scroll(this.scroller) { Column() { Text(this.textContent) .fontSize(18) .lineHeight(36) .fontColor(Color.Black) .margin({ top: this.scrollOffset }) .onAreaChange((oldArea: Area, newArea: Area) => { if (this.lineHeight == 0 && newArea.height > 0) { this.lineHeight = newArea.height as number this.resetMaxLineHeight() //添加数据测试 this.textContent = this.info return } if (this.totalContentHeight != newArea.height) { console.info(`newArea.height:${newArea.height}`) this.totalContentHeight = newArea.height as number this.resetMaxLineHeight() } }) } .padding({ left: 25, right: 25 }) }.scrollBar(BarState.Off) .constraintSize({ maxHeight: this.pageHeight == 0 ? 1000 : this.pageHeight }) } .width('100%') .layoutWeight(1) .onAreaChange((oldArea: Area, newArea: Area) => { if (this.pageHeight == 0 && newArea.height > 0) { this.pageHeight = newArea.height as number this.resetMaxLineHeight() } }) Row() { Row() { Text(this.timeStr) .fontColor("#7a7a7a") .fontSize(10) Text(this.batterySOC) .fontColor("#7a7a7a") .fontSize(10) .margin({ left: 5 }) } Text(`${this.index + 1}/${this.totalPages}`) .fontColor("#7a7a7a") .fontSize(10) }.width('100%').padding({ left: 30, right: 30, top: 30 }).justifyContent(FlexAlign.SpaceBetween) Text().width('100%').height(`${AppStorage.get('navigationIndicatorHeight')}px`) //底部导航栏高度 } .width('100%') .height('100%') .backgroundColor("#CFE6D6") }}原理参考:cid:link_0
  • [技术干货] 【HarmonyOS】仿iOS线性渐变实现
    【HarmonyOS】仿照IOS中可以通过输入start=(0,0),end=(1,1)获取角度到.linearGradient,从而实现左上到右下渐变class Point { x: number = 0 y: number = 0}@Entry@Componentstruct Page57 { @State message: string = 'Hello World'; //输入start=(0,0),end=(1,1)实现左上到右下渐变 private calculateGradientAngle(start: Point, end: Point): number { // 计算两点之间的向量 const dx = end.x - start.x; const dy = end.y - start.y; // 使用 Math.atan2(dy, dx) 计算角度 // Math.atan2 返回的是弧度值,需要转换为角度 const radian = Math.atan2(dy, dx); const degree = radian * (180 / Math.PI); console.info(`degree:${degree}`) // 根据实际情况调整角度 // 从左上角到右下角的角度通常是 45 度 return (90 + degree) % 360; } build() { Column() { Text('背景渐变') Row() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) //.blendMode(BlendMode.DST_IN, BlendApplyType.OFFSCREEN) }.linearGradient({ angle: this.calculateGradientAngle({ x: 0, y: 0 }, { x: 1, y: 1 }), colors: [[0xff0000, 0.0], [0x0000ff, 1.0]] }) //.blendMode(BlendMode.SRC_OVER, BlendApplyType.OFFSCREEN) Text('文字渐变') Row() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) .blendMode(BlendMode.DST_IN, BlendApplyType.OFFSCREEN) }.linearGradient({ angle: this.calculateGradientAngle({ x: 0, y: 0 }, { x: 1, y: 1 }), colors: [[0xff0000, 0.0], [0x0000ff, 1.0]] }).blendMode(BlendMode.SRC_OVER, BlendApplyType.OFFSCREEN) } .width('100%') .height('100%') }}转载自https://www.cnblogs.com/zhongcx/articles/18433360
  • [技术干货] 【HarmonyOS】利用emitter封装工具类
    利用emitter封装工具类,使得父组件与子组件实现事件监听。注意:只能用真机调试,在预览器会提示The emitter.emit interface in the Previewer is a mocked implementation and may behave differently than on a real device.调用示例import { MyEmitterUtil } from '../MyEmitterUtil'@Componentstruct MyView { @Prop controller: MyEmitterUtil @State info: string = "" aboutToAppear(): void { this.controller.onChild((eventData) => { console.info('====eventData', JSON.stringify(eventData)) if (eventData.flag == "ABC") { this.info = eventData.data } }) } build() { Column() { Text('子组件接收到的数据:' + this.info) Button('向父组件发送数据').onClick(() => { this.controller.emitFather("CCC", "EEE") }) } }}@Entry@Componentstruct Page81 { controller: MyEmitterUtil = new MyEmitterUtil() @State info: string = "" aboutToAppear(): void { this.controller.onFather((eventData) => { console.info('====eventData', JSON.stringify(eventData)) if (eventData.flag == "CCC") { this.info = eventData.data } }) } build() { Row() { Column() { Text('父组件接收到的数据:' + this.info) Button('向子组件发送数据').onClick(() => { this.controller.emitChild("ABC", "conter") }) MyView({ controller: this.controller }) } .width('100%') } .height('100%') }}工具类import Emitter from '@ohos.events.emitter';/** * `MyEmitterUtil` 是一个针对 HarmonyOS 的事件驱动编程封装类,主要用于组件间的通信和数据传递。 * * 使用要求: * - API 版本:api 11 * 示例用法: * 1. 父组件绑定、解绑、向子组件发送事件: * ```typescript * aboutToAppear() { * this.myEmitterUtil.onFather((eventData: EmitterData) => { * console.info('父组件监听结果: ', JSON.stringify(eventData)); * // 判断事件类型并执行相应操作... * }); * } * * aboutToDisappear() { * this.myEmitterUtil.offFather(); * } * * // 向子组件发送事件 * this.myEmitterUtil.emitChild(MyEmitterUtil.UPDATE_DETAIL, "携带的测试数据"); * ``` * * 2. 子组件绑定、解绑、向父组件发送事件: * ```typescript * aboutToAppear() { * this.myEmitterUtil.onChild((eventData: EmitterData) => { * console.info('子组件监听结果: ', JSON.stringify(eventData)); * // 判断事件类型并执行相应操作... * }); * } * * aboutToDisappear() { * this.myEmitterUtil.offChild(); * } * * // 向父组件发送事件 * this.myEmitterUtil.emitFather(MyEmitterUtil.UPDATE_LIST, "测试"); * this.myEmitterUtil.emitFather(MyEmitterUtil.UPDATE_LIST_2, "测试2"); * ``` * * 参考文档: * - 请查阅 HarmonyOS 开发文档了解详细信息。 */export class MyEmitterUtil { private static EVENT_ID_COUNTER: number = 0; // 自动递增,生成唯一的事件ID private readonly eventIdFather: number; private readonly eventIdChild: number; constructor() { this.eventIdFather = MyEmitterUtil.EVENT_ID_COUNTER++; this.eventIdChild = MyEmitterUtil.EVENT_ID_COUNTER++; console.info(`事件ID(父组件): ${this.eventIdFather}`); console.info(`事件ID(子组件): ${this.eventIdChild}`); } // 定义业务状态标识常量 static readonly UPDATE_LIST = "UPDATE_LIST"; static readonly UPDATE_LIST_2 = "UPDATE_LIST_2"; static readonly UPDATE_DETAIL = "UPDATE_DETAIL"; /** * 在组件的`aboutToAppear`生命周期钩子中调用,监听父组件事件 * @param callback 事件回调函数,接受一个`EmitterData`对象作为参数 */ onFather(callback: (eventData: EmitterData) => void) { Emitter.on({ eventId: this.eventIdFather }, (event) => { if (event.data) { callback(new EmitterData(event.data.flag, event.data.data)); } }); } /** * 在组件的`aboutToDisappear`生命周期钩子中调用,解除父组件事件监听 */ offFather() { Emitter.off(this.eventIdFather); } /** * 在组件的`aboutToAppear`生命周期钩子中调用,监听子组件事件 * @param callback 事件回调函数,接受一个`EmitterData`对象作为参数 */ onChild(callback: (eventData: EmitterData) => void) { Emitter.on({ eventId: this.eventIdChild }, (event) => { if (event.data) { callback(new EmitterData(event.data.flag, event.data.data)); } }); } /** * 在组件的`aboutToDisappear`生命周期钩子中调用,解除子组件事件监听 */ offChild() { Emitter.off(this.eventIdChild); } /** * 向父组件发送事件 * @param flag 事件类型标识 * @param data 事件携带的数据 */ emitFather(flag: string, data: string) { Emitter.emit( { eventId: this.eventIdFather, priority: Emitter.EventPriority.IMMEDIATE }, { data: { flag, data } } ); } /** * 向子组件发送事件 * @param flag 事件类型标识 * @param data 事件携带的数据 */ emitChild(flag: string, data: string) { Emitter.emit( { eventId: this.eventIdChild, priority: Emitter.EventPriority.IMMEDIATE }, { data: { flag, data } } ); }}/** * 用于封装事件数据的类 */export class EmitterData { flag: string = ""; data: string = ""; constructor(flag: string, data: string) { this.flag = flag; this.data = data; }}转载自https://www.cnblogs.com/zhongcx/articles/18433359
总条数:446 到第
上滑加载中