#API12
is api12 gonna be a big thing? gonna just give up all my plogon but like three of them at this rate lmao
March 25, 2025 at 11:43 PM
Huawei first party ArkWeb Ad Blocking!
Third party developers can implement on 3rd party browsers on HarmonyOS Next/5.x
#HarmonyOS #HarmonyOS5 #HarmonyOSNext #Beta #OpenHarmony #China #OpenBeta #API12
November 19, 2024 at 12:26 AM
鸿蒙开发:异步并发操作
## **前言** > 本文基于Api12 所谓异步,说白了,就是不必等到上一个的执行结果,我们就可以继续执行下一个操作,相对于同步而言,异步操作可以同时处理多个任务,不会因为一个任务的耗时而影响其他任务的执行,从而提高了程序的并发处理能力‌,在实际的开发中,特别是应用启动初始化的时候,为了不阻塞程序,异步的初始化往往是开发中的首选,在鸿蒙当中,我们如何进行异步的并发操作呢?使用系统提供的Promise和async/await便可以轻松搞定。 ## 简单案例 使用setTimeout函数模拟了一个异步操作。 private getMessage(): Promise<string> { return new Promise((resolve: Function) => { setTimeout(() => { resolve("===1秒之后打印") }, 1000) }) } 书写执行逻辑 console.log("===调用前打印") this.getMessage().then((result) => { console.log(result) }) console.log("===调用后打印") 由于getMessage方法是一个异步操作,打印的情况如下,从以上的模拟代码,就印证了开头所说的,异步不回阻塞下面的程序执行。 本文简单从以下三个方向概述: **1、什么是** **Promise** **2、异步回调接收成功和失败** **3、结合async/await进行使用** **4、相关总结** ## 一、什么是Promise 首先,Promise它是一种用于处理异步操作的对象,并且提供了一个状态机制来管理异步操作的不同阶段,使用Promise有两个特点,第一个特点,它可以结合async/await,将异步操作转换为类似于同步操作的风格,第二个特点就是,正常的异步操作,使用回调函数来处理成功或失败的结果。 Promise提供了三种状态,当Promise对象创建后处于pending(进行中)状态,并在异步操作完成后转换为fulfilled(已完成)或rejected(已拒绝)状态,具体的简单案例如下: promise.then((result) => { //结果 }).catch((error: BusinessError) => { //错误 }); ## 二、异步回调接收成功和失败 异步的回调,主要使用then方法来接收结果,catch方法回调失败结果,如上面的案例一样,需要说明的是then方法可以接收一个参数,也可以接收两个参数,一个参数,表示当Promise对象状态变为fulfilled时,then方法会自动调用这个回调函数,两个参数时,一个处理fulfilled状态的函数,另一个处理rejected状态的函数。 promise.then((result) => { //结果 }).catch((error: BusinessError) => { //错误 }); 大家可以去官方api查看,大部分的方法,或多或少都提供了这种方式的操作,比如网络请求,比如数据库操作,等等,可以说司空见惯。 ## 三、结合async/await进行使用 async/await是一种用于处理异步操作的Promise语法糖,正如上面所说的,结合使用,可以将异步操作转换为类似于同步操作的风格,比如前言中的案例中,我们使用async/await,简单改造一下: private async printLog() { console.log("===调用前打印") let message = await this.getMessage() //获取消息 console.log(message) console.log("===调用后打印") } 以上的代码改为async/await形式后,打印日志如下: 可以清晰的发现,最后的打印,在等待上一个结束完后再执行,这就实现了同步操作。 async函数是一个返回Promise对象的函数,用于表示一个异步操作,在其内部,可以使用await关键字等待一个Promise对象的解析,然后再返回其解析的结果,如果抛出异常,最终会传递给Promise对象的onRejected()方法。 ## 四、相关总结 在结合async/await进行使用的时候,有一点需要注意,await关键字必须结合async,这两个是搭配使用的,缺一不可,同步风格在使用的时候,如何获取到错误呢,毕竟没有catch方法,其实,我们可以自己创建try/catch来捕获异常。 private async printLog() { try { console.log("===调用前打印") let message = await this.getMessage() //获取消息 console.log(message) console.log("===调用后打印") } catch (e) { console.error("===异常信息:" + e); } }
forem.com
March 19, 2025 at 8:44 AM
鸿蒙开发:走进stateStyles多态样式
## **前言** > 本文基于Api12 一个组件,多种状态下,我们如何实现呢?举一个很简单的案例,一个按钮,默认状态下是黑色背景,点击后是红色,手指放开后还原黑色。 我们自然而然的就会想到利用手势的按下和抬起,改变其背景颜色即可,代码如下: Button("点击") .backgroundColor(this.clickBackgroundColor) .onTouch((event: TouchEvent) => { if (event.type == TouchType.Down) { this.clickBackgroundColor = Color.Red } else if (event.type == TouchType.Up) { this.clickBackgroundColor = Color.Black } }) 除了onTouch之外,gesture也可以实现其效果,无论哪种实现,我们都是要定义变量,改变某一个属性,有没有一种方式,直接更改属性呢?当然是有的,这就是stateStyles。 同样是上一个案例,我们使用stateStyles来实现一下,代码如下: Button("点击") .stateStyles({ pressed: { //按压 .backgroundColor(Color.Red) }, normal: { //正常态 .backgroundColor(Color.Black) } }) 可以发现,效果是和上述一模一样的。 ## **简单概述** stateStyles为多态样式,可以依据组件的内部状态的不同,快速设置不同样式,比如背景颜色,颜色、大小等等常见的通用属性,此种行为,很类似于css中的伪类,但语法稍有不同,目前支持的有以下五种状态: 状态 | 概述 ---|--- focused | 获焦态 normal | 正常态 pressed | 按压态 disabled | 不可用态 selected | 选中态 ## **实际场景** 具体的场景主要适用于多状态的切换,比如前言中的案例,当然了,它是支持多个属性的,比如,我们再增加宽高属性: Button("点击") .stateStyles({ pressed: { //按压 .backgroundColor(Color.Red) .width(200) .height(100) }, normal: { //正常态 .backgroundColor(Color.Black) .width(100) .height(50) } }) 除了多个属性之外,另外也可以直接传递样式: 定义Styles: @Styles normalStyle() { .backgroundColor(Color.Black) .width(100) .height(50) } @Styles pressedStyle() { .backgroundColor(Color.Red) .width(200) .height(100) } 使用Styles: Button("点击") .stateStyles({ pressed: this.pressedStyle, normal: this.normalStyle }) ## **总结** selected状态一般用于可选择的组件,比如Checkbox,比如Radio,目前支持的组件如下: 支持组件 | 支持的参数/属性 | 起始API版本 ---|---|--- Checkbox | select | 10 CheckboxGroup | selectAll | 10 Radio | checked | 10 Toggle | isOn | 10 ListItem | selected | 10 GridItem | selected | 10 MenuItem | selected | 10 **简单案例:** Radio({ value: 'Radio1', group: 'radioGroup1' }) .checked(this.value) .height(50) .width(50) .borderRadius(50) .radioStyle({ checkedBackgroundColor: Color.Red }) .onClick(() => { this.value = !this.value }) .stateStyles({ normal: { .backgroundColor(Color.Black) }, selected: { .backgroundColor(Color.Red) }, })
forem.com
March 15, 2025 at 4:11 AM
深入探索HarmonyOS NEXT自定义组件与样式,提升应用个性化水平
深入探索HarmonyOS NEXT自定义组件与样式,提升应用个性化水平 在HarmonyOS NEXT平台上开发应用程序时,创建自定义组件和有效应用样式是实现独特用户体验的关键。本文将详细介绍如何从零开始构建自定义组件,并探讨如何利用样式系统来统一整个应用的视觉风格。我们将重点介绍API12版本中支持的功能和技术,帮助开发者提升应用的个性化水平。 创建自定义组件:封装特定功能和外观 自定义组件允许开发者封装特定功能和外观,从而促进代码复用和模块化开发。通过组合基础组件或继承现有组件,可以构建新的复合组件,使复杂界面变得更加简洁和易于管理。 基本流程:定义组件结构、设置属性和事件处理逻辑。 高级技巧:使用插槽(slot)机制传递内容,增强组件灵活性;结合状态管理和动画效果来丰富交互体验。 import { defineComponent, h } from 'harmonium'; const MyCustomComponent = defineComponent({ props: { title: String, onClick: Function, }, setup(props) { return () => h('div', { class: 'custom-style', onClick: props.onClick }, [props.title]); } }); exportdefault MyCustomComponent; 样式定义:打造一致且美观的视觉风格 HarmonyOS NEXT提供了类似于CSS的DSL语言或专有的样式系统,用于设定颜色、字体、边距等属性。开发者可以通过全局样式和局部样式的混合使用,确保应用具有一致的视觉风格。 全局样式:为整个应用定义一套默认的样式规则,如主题色、字体系列等。 局部样式:针对特定页面或组件设置独特的样式,保持灵活性的同时避免影响其他部分。 /* 全局样式 */ body { font-family: Arial, sans-serif; background-color: #f5f5f5; } /* 局部样式 */ .custom-style { padding: 16px; background-color: #007bff; color: white; border-radius: 8px; } 主题切换:动态改变应用主题 为了提供更加个性化的用户体验,许多现代应用程序都支持动态改变主题的功能,例如日间模式和夜间模式的切换。这不仅提升了用户的舒适度,还展示了应用的设计美感。 实现方式:通过编程方式修改根节点的class名称或内联样式,触发全局样式表的变化。 用户控制:添加按钮或其他UI元素,让用户能够轻松切换主题。 import { ref } from '@ohos/reactivity'; import { Button, Column } from '@ohos/ace'; exportdefault { setup() { const theme = ref('light'); const toggleTheme = () => { theme.value = theme.value === 'light' ? 'dark' : 'light'; }; return { theme, toggleTheme, }; }, render() { return ( Toggle Theme ); } } 组织和管理样式文件:提高可维护性和扩展性 随着项目的增长,有效地组织和管理样式文件变得越来越重要。合理的文件结构可以帮助开发者更轻松地找到和修改样式,同时也便于团队协作。 分层结构:按照功能模块划分样式文件,如base.css、components.css、pages.css等。 预处理器:考虑使用SASS或LESS这样的预处理器语言,编写更具表现力的样式规则,如变量、嵌套选择器等。 // 变量示例 $primary-color: #007bff; // 嵌套选择器 .button { background-color: $primary-color; &:hover { background-color: darken($primary-color, 10%); } } 动态样式修改:实现灵活变化的效果 除了静态样式外,有时还需要根据用户操作或数据状态动态修改样式。HarmonyOS NEXT支持多种方式来实现这一点,包括条件渲染、样式绑定以及JavaScript直接操作DOM。 条件渲染:基于布尔值或其他条件表达式决定是否显示某个元素。 样式绑定:通过数据绑定的方式实时更新元素的样式属性。 import { ref, computed } from '@ohos/reactivity'; import { Text } from '@ohos/ace'; export default { setup() { const isActive = ref(true); const activeClass = computed(() => ({ active: isActive.value, })); return { isActive, activeClass, }; }, render() { return ( This text has dynamic styles. ); } } 结语 通过深入探索HarmonyOS NEXT的自定义组件与样式功能,开发者不仅可以创建出更加个性化和富有吸引力的应用程序,还能极大地提高开发效率和代码质量。掌握这些技能后,你可以更好地应对各种设计挑战,满足不同用户群体的需求。持续关注官方文档和技术博客,学习最新的设计理念和技术趋势,将有助于你在这个快速发展的领域中保持领先地位。 文章转自:https://zhuanlan.zhihu.com/p/14550845802
dev.to
March 27, 2025 at 12:43 PM
鸿蒙NEXT开发教程:浅谈@ComponentV2装饰器
听说今天的广州车展上有一部分人已经看到华为汽车的最后一“界”,尊界超豪华大轿车,应该很快就要正式亮相,可以期待一波。 在api12之后,鸿蒙系统推出一个V2版本的状态管理装饰器,不过目前还在开发试用状态,幽蓝君仔细研究了一下,今天跟大家做一个简单的介绍。 幽蓝君对V2版本装饰器的总结是,它是为了修补目前版本状态管理的不足,新增了一些功能更加精细的状态变量装饰器,等到开发试用完善之后应该会和当前装饰器合并到一起,不再使用V2区分。 下面跟大家详细介绍一下@ComponentV2装饰器。 @ComponentV2和@Component一样,都用于装饰自定义组件,但是@ComponentV2内部只能使用全新的状态变量装饰器,比如@Local、@param、@Once、@Event、@provider、@Consumer等。这几个装饰器有什么作用呢,请往下看: @local:组件内部状态 @local的作用和现在的@State类似,但是鸿蒙研发团队认为@State装饰器可以从外部初始化是一个缺陷,所以@Local是一个组件内部状态,它不接受从外部进行的初始化,下面我写了一段demo,让我们来感受一下: @ComponentV2 struct TextV2{ @Local text:string = 'v2' build() { Text(this.text) .fontColor(Color.Brown) .fontSize(20) } } @Component struct TextV1{ @State text:string = 'v1' build() { Text(this.text) .fontColor(Color.Grey) .fontSize(20) } } 可以看到,当我向子组件传值时,@local修饰符是不接受的。 @param:组件外部输入 和@Local相反,@param表示组件从外部传入的状态,它是为了父子组件之间的数据能够进行同步。但是它是不支持组件内部进行修改的: @Once:初始化同步一次 这个装饰器看单词就比较好理解,只能修改一次,不过它的局限性比较大,必须和@Param配合使用,不能单独使用,它是为了实现仅从外部初始化一次、不接受后续同步变化的能力。 @Param @Once text:string = 'v2' @Event组件输出 @Event也和@Param有关系,因为@Param修饰的变量无法从内部修改,但是有时它又需要修改,那怎么办呢,@Event装饰的事件负责告诉父组件,再由父组件进行修改。好像有点多此一举: @ComponentV2 struct TextV2{ @Param text:string = 'v2' @Event changeText: (x: string) => void = (x: string) => {}; build() { Column(){ Text(this.text) .fontColor(Color.Brown) .fontSize(20) .onClick(()=>{ this.changeText('hahaha') }) } } @provider装饰器和@Consumer装饰器:跨组件层级双向同步 我们在V1版本里都使用过@Provide和@Consume,是用来跨组件层级双向传递的装饰器,这两个长的很像,功能也类似,不过有一些细小的区别,直接跟大家截个图吧: @Monitor:监听 我们之前使用@Watch来监听变量的变化,不过@Watch无法实现对对象、数组中某一单个属性或数组项变化的监听,且无法获取变化之前的值。而@Monitor可以: @Local text:string = 'v2' //监听text @Monitor('text') onStrChange(monitor: IMonitor){ monitor.dirty.forEach((path:string)=>{ console.log('修改前:',monitor.value(path)?.before) console.log('修改后:',monitor.value(path)?.now ) }) } @Computed:计算属性 @Computed用来装饰getter方法,并且会随着被计算的值的变化而变化,并且只会计算一次,另外,@Computed的计算性能更佳。文字比较难理解,举个例子: @Local a:number = 10 @Local b:number = 20 @Computed get c(){ return this.a + this.b }
dev.to
March 23, 2025 at 12:21 PM
鸿蒙开发:异步并发操作
## **前言** > 本文基于Api12 所谓异步,说白了,就是不必等到上一个的执行结果,我们就可以继续执行下一个操作,相对于同步而言,异步操作可以同时处理多个任务,不会因为一个任务的耗时而影响其他任务的执行,从而提高了程序的并发处理能力‌,在实际的开发中,特别是应用启动初始化的时候,为了不阻塞程序,异步的初始化往往是开发中的首选,在鸿蒙当中,我们如何进行异步的并发操作呢?使用系统提供的Promise和async/await便可以轻松搞定。 ## 简单案例 使用setTimeout函数模拟了一个异步操作。 private getMessage(): Promise<string> { return new Promise((resolve: Function) => { setTimeout(() => { resolve("===1秒之后打印") }, 1000) }) } 书写执行逻辑 console.log("===调用前打印") this.getMessage().then((result) => { console.log(result) }) console.log("===调用后打印") 由于getMessage方法是一个异步操作,打印的情况如下,从以上的模拟代码,就印证了开头所说的,异步不回阻塞下面的程序执行。 本文简单从以下三个方向概述: **1、什么是** **Promise** **2、异步回调接收成功和失败** **3、结合async/await进行使用** **4、相关总结** ## 一、什么是Promise 首先,Promise它是一种用于处理异步操作的对象,并且提供了一个状态机制来管理异步操作的不同阶段,使用Promise有两个特点,第一个特点,它可以结合async/await,将异步操作转换为类似于同步操作的风格,第二个特点就是,正常的异步操作,使用回调函数来处理成功或失败的结果。 Promise提供了三种状态,当Promise对象创建后处于pending(进行中)状态,并在异步操作完成后转换为fulfilled(已完成)或rejected(已拒绝)状态,具体的简单案例如下: promise.then((result) => { //结果 }).catch((error: BusinessError) => { //错误 }); ## 二、异步回调接收成功和失败 异步的回调,主要使用then方法来接收结果,catch方法回调失败结果,如上面的案例一样,需要说明的是then方法可以接收一个参数,也可以接收两个参数,一个参数,表示当Promise对象状态变为fulfilled时,then方法会自动调用这个回调函数,两个参数时,一个处理fulfilled状态的函数,另一个处理rejected状态的函数。 promise.then((result) => { //结果 }).catch((error: BusinessError) => { //错误 }); 大家可以去官方api查看,大部分的方法,或多或少都提供了这种方式的操作,比如网络请求,比如数据库操作,等等,可以说司空见惯。 ## 三、结合async/await进行使用 async/await是一种用于处理异步操作的Promise语法糖,正如上面所说的,结合使用,可以将异步操作转换为类似于同步操作的风格,比如前言中的案例中,我们使用async/await,简单改造一下: private async printLog() { console.log("===调用前打印") let message = await this.getMessage() //获取消息 console.log(message) console.log("===调用后打印") } 以上的代码改为async/await形式后,打印日志如下: 可以清晰的发现,最后的打印,在等待上一个结束完后再执行,这就实现了同步操作。 async函数是一个返回Promise对象的函数,用于表示一个异步操作,在其内部,可以使用await关键字等待一个Promise对象的解析,然后再返回其解析的结果,如果抛出异常,最终会传递给Promise对象的onRejected()方法。 ## 四、相关总结 在结合async/await进行使用的时候,有一点需要注意,await关键字必须结合async,这两个是搭配使用的,缺一不可,同步风格在使用的时候,如何获取到错误呢,毕竟没有catch方法,其实,我们可以自己创建try/catch来捕获异常。 private async printLog() { try { console.log("===调用前打印") let message = await this.getMessage() //获取消息 console.log(message) console.log("===调用后打印") } catch (e) { console.error("===异常信息:" + e); } }
dev.to
March 19, 2025 at 8:40 AM
鸿蒙开发:Navigation路由组件使用由繁入简
## 前言 > 本文基于Api12 之前的文章,针对Navigation做了简单的分析,无论是静态配置还是动态配置,都有着手动配置的需要,本篇文章就是要解决手动配置,利用路由库和插件实现自动配置的需求。 本篇文章大纲如下: **1、主页面和子页面模版(了解)** **2、封装统一路由静态库(了解)** **3、分析哪些需要插件自动完成(了解)** **4、路由库和插件依赖** **5、结合标题栏组件简化模版** **6、路由库功能调用** **7、简单总结** ## 一、主页面和子页面模版(了解) Navigation是路由容器组件,一般作为首页的根容器,也就是说,我们只在首页面使用即可,对于所有的子页面使用NavDestination即可,项目中的页面,无外乎首页面和子页面,首页面还好,书写一次即可,而子页面就非常非常的多了,所以,需要针对模版进行抽取。 以下简单贴出两个模版,此模版必须在有统一路由库的基础之上进行使用。 主页面模版: @Entry @Component struct Index { private pageStack: NavPathStack = new NavPathStack() aboutToAppear() { RouterModule.createRouter(RouterNameConstants.ENTRY_HAP, this.pageStack); } @Builder routerMap(builderName: string, param: object) { RouterModule.getBuilder(builderName).builder(param); } build() { Navigation(this.pageStack) { RelativeContainer() { //其他UI } .width('100%') .height('100%') }.width('100%') .height('100%') .hideTitleBar(true) .navDestination(this.routerMap); } } 子页面模版: @Component struct TestPage { build() { Column() { Text("子页面") } .width('100%') .height('100%') } } @Builder export function TestBuilder(value: object) { NavDestination() { TestPage() } .hideTitleBar(true) } const builderName = BuilderNameConstants.Test; if (!RouterModule.getBuilder(builderName)) { const builder: WrappedBuilder<[object]> = wrapBuilder(TestBuilder); RouterModule.registerBuilder(builderName, builder); } 以上两个模版,特别是子页面,我们不可能每创建一个页面都要书写这么多冗余代码,显然是不合理的,在路由库和插件依赖一项中,会针对其进行抽取。 ## 二、封装统一路由静态库(了解) 要实现各个动态组件的交互,比如主模块和动态共享包,动态共享包和动态共享包,动态共享包和静态共享包等等之间进行跳转,必须需要一个统一的路由组件库进行桥梁作用,这个库,可以直接放到业务层,主要的作用就是路由的相关功能配置,具体的相关工具,可以看之前的文章。 ## 三、分析哪些需要插件自动完成 在正常的开发中,针对页面,我们正常书写即可,无须其他的额外配置,路由的操作应该和移动端的路由操作一致,只需要一个简单的注解就可以,那么基于此逻辑,其一、子页面中注册配置,NavDestination配置等等都是可以抽取的。 正常的子页面应该清洁无瑕,就是简单的UI组件,其他的都统统交给插件自己生成,自己实现配置。 @RouterPath("entry_test") @Component export struct TestPage { build() { //UI组件 } } 其二,就是每个子页面的动态导入import,在上一篇文章中,是需要手动的在初始化中进行配置,针对这个繁琐的程序,也应该交由插件进行配置。 ## 四、路由库和插件依赖 以上三点只是做一个简单的了解,以哪些模版作为生成,以哪些代码需要抽取,这些步骤都已经做了封装,我们简单看下,封装后的路由功能实现。 ### 第一步,配置依赖项 方式一:在需要Module中的oh-package.json5中设置三方包依赖,配置示例如下: "dependencies": { "@abner/router": "^1.0.2"} 方式二:在Terminal窗口中,执行如下命令安装三方包,DevEco Studio会自动在工程的oh-package.json5中自动添加三方包依赖。建议:在使用的模块路径下进行执行命令。 ohpm install @abner/router ### 第二步,配置插件 配置插件前,请务必保障你的所有用到路由的Module都依赖了@abner/router,当然你也可以采用中间件的方式进行依赖。 依赖插件 找到项目中的hvigor目录,在hvigor-config.json5文件中dependencies配置插件。 代码如下:当前版本为:1.0.8 "dependencies": { "ohos-router": "1.0.8" } 执行插件方法,打开根目录中的hvigorfile.ts文件,在plugins数组中导入方法: plugins:[ abnerRouter() ] 导包: import { abnerRouter } from 'ohos-router/router-plugin'; 插件完成之后,编译项目,你会发现,每个Module中,都会生成一个路由配置文件,以Module名字+RouterConfig为文件命名。 此路由配置文件为自动生成,无特殊情况下无须改动,当然,再有特殊情况下,你可以进行手动更正。 主页面模版 @Entry @Component struct Index { private navPathStack: NavPathStack = new NavPathStack() aboutToAppear() { routerInitNavPathStack(this.navPathStack) } @Builder routerMap(builderName: string, param: Object) { routerReturnBuilder(builderName).builder(param) } build() { Navigation(this.navPathStack) { RelativeContainer() { //其他组件 } .height('100%') .width('100%') }.navDestination(this.routerMap) } } 子页面模版 子页面没有模版,正常书写UI即可,需要配置一个注解@RouterPath设置一个别名即可,此别名需要模块名字+别名。 @RouterPath("entry_test") @Component export struct TestPage { build() { //UI组件 } } ## 五、路由库功能调用 ### 1、普通跳转 startPage支持自己Module下跳转,同样也支持跨组件模式跳转。 startPage("entry_main") //entry_main和页面注解要一一对应 ### 2、带数据跳转 支持常见的数据传递,比如对象、字符串、数组等等 #### 字符串 startPage("shared_show_params", { param: "一个字符串" }) #### number startPage("shared_show_params", { param: 100 }) #### boolean startPage("shared_show_params", { param: true }) #### 数组 startPage("shared_show_params", { param: [1, 2, 3, 4, 5, 6] }) #### 对象 startPage("shared_show_params", { param: new SharedBean() }) #### 直接传递对象 startPage("shared_show_params", { param: { "name": "AbnerMing", "age": 20 } }) #### 传递Map let map = new HashMap<string, Object>() map.set("name", "AbnerMing") map.set("age", 18) startPage("shared_show_params", { param: map }) ### 3、接收参数 通过getRouterParams()方法获取上一个页面传递的数据。 aboutToAppear(): void { let params = routerGetParams() // let map = params as number//number // let map = params?.toString() //string // let map = params as boolean //boolean // let map = params as string[] //数组 // let map = params as SharedBean //对象 // let map = params as HashMap<string, Object>//如果是传递的map,自己遍历即可 } ### 4、返回上一页面 finishPage() ### 5、返回指定页面 传递页面路由即可。 finishPageToName("entry_back_01") ### 6、直接返回首页 routerClearPage() ### 7、返回带数据 跳转时,使用result进行监听返回的参数 startPage("static_return_params", { result: (params) => { //获取返回的数据 console.log("==========" + params?.toString()) } }) 销毁页面时携带返回数据即可。 finishPage({ param: "我是返回的数据" }) ### 8、关于生命周期 当子组件使用了NavDestination之后,会和组件的生命周期发生冲突,为解决这个问题,大家可以使用NavDestination的生命周期进行解决。 目前提供了setRouterLifeCycle方法用于监听其生命周期,可以在子页面中进行实现。 案例如下: aboutToAppear(): void { setRouterLifeCycle({ onShown: () => { console.log("==========显示") }, onHidden: () => { console.log("==========隐藏") } }) } 相关方法一览: 方法 | 概述 ---|--- onWillAppear | NavDestination创建后,挂载到组件树之前执行,在该方法中更改状态变量会在当前帧显示生效 onAppear | 通用生命周期事件,NavDestination组件挂载到组件树时执行。 onWillShow | NavDestination组件布局显示之前执行,此时页面不可见(应用切换到前台不会触发)。 onShown | NavDestination组件布局显示之后执行,此时页面已完成布局。 onWillHide | NavDestination组件触发隐藏之前执行(应用切换到后台不会触发)。 onHidden | NavDestination组件触发隐藏后执行(非栈顶页面push进栈,栈顶页面pop出栈或应用切换到后台) onWillDisappear | NavDestination组件即将销毁之前执行,如果有转场动画,会在动画前触发(栈顶页面pop出栈) onDisappear | 通用生命周期事件,NavDestination组件从组件树上卸载销毁时执行。 ## 三、常见方法汇总 方法 | 参数 | 概述 ---|---|--- startPage | builderName: string, model?: RouterModel | 跳转页面 replacePage | builderName: string, model?: RouterModel | 替换页面栈操作 startPageByName | name: string, param: Object, onPop: Callback, animated?: boolean | 将name指定的NavDestination页面信息入栈,传递的数据为param,添加onPop回调接收入栈页面出栈时的返回结果,并进行处理。 startPageDestination | info: NavPathInfo, options?: NavigationOptions | 将info指定的NavDestination页面信息入栈,使用Promise异步回调返回接口调用结果,具体根据options中指定不同的LaunchMode,有不同的行为。 startPageDestinationByName | name: string, param: Object, onPop: Callback,animated?: boolean | 将name指定的NavDestination页面信息入栈,传递的数据为param,并且添加用于页面出栈时处理返回结果的OnPop回调,使用Promise异步回调返回接口调用结果。 finishPage | model?: PopModel | 销毁页面 finishPageToName | name: string, model?: PopModel | 回退路由栈到由栈底开始第一个名为name的NavDestination页面。 routerSetInterception | interception: NavigationInterception | 设置Navigation页面跳转拦截回调。 routerClearPage | animated?: boolean | 清除栈中所有页面 routerGetParentNavPathStack | 无参 | 清除栈中所有页面 routerDisableAnimation | value: boolean | 关闭(true)或打开(false)当前Navigation中所有转场动画。 routerMoveIndexToTop | index: number, animated?: boolean | 将index指定的NavDestination页面移到栈顶。 routerMoveToTop | name: string, animated?: boolean | 将index指定的NavDestination页面移到栈顶 routerPopToIndex | index: number, result: Object, animated?: boolean | 回退路由栈到index指定的NavDestination页面,并触发onPop回调传入页面处理结果。 routerRemoveByName | name: string | 将页面栈内指定name的NavDestination页面删除。 routerRemoveByIndexes | indexes: Array<number> | 将页面栈内索引值在indexes中的NavDestination页面删除。 routerReplacePathByName | name: string, param: Object, animated?: boolean | 将当前页面栈栈顶退出,将name指定的页面入栈 routerRemoveByNavDestinationId | navDestinationId: string | 将页面栈内指定navDestinationId的NavDestination页面删除。navDestinationId可以在NavDestination的onReady回调中获取,也可以在NavDestinationInfo中获取。 routerInitBuilder | builderName: string, builder: WrappedBuilder<[Object]> | 初始化Builder routerInitConfig | routerConfig?: RouterConfig[] | 初始化配置 routerGetParams | 无参 | 获取传递的参数 ## 六、结合标题栏组件简化模版 如果你想结合我的另一个开源库bar进行使用,那么需要更换插件,由ohos-router切换到abner-router,其他都不变 "abner-router": "1.0.0" 依赖之后的模版,就非常的简单了,对于首页面,只需要使用MainView包裹即可。 首页模版 @Entry @Component struct Index { build() { RelativeContainer() { MainView() { //其他组件 } } .height('100%') .width('100%') } } 子页面模版 @RouterPath("entry_test") @Component struct TestPage { build() { TitleLayout({ title:"我是标题" }) { //任意组件 } } } ## 七、简单总结 使用了插件和路由库之后,在每个Module下都会生成一个路由配置文件,以Module名字+RouterConfig为文件命名,此路由配置文件,也会在AbilityStage中,通过routerInitConfig方法进行自动配置。 子页面注解的作用很简单,用来标记页面的唯一标识,也就是别名,要求格式为:Module名字+下划线+定义的别名。 如entry Module下:entry_test1,entry_test2等等 如test Module下:test_test1,test_test2等等 如果你想对Module下的路由进行统一管理,便于查找路由和修改路由,目前进支持在Module根目录下进行配置。 比如entry目录下,你可以创建任意的管理类EntryRouter: export class EntryRouter { static EntryTest= "entry_test"//测试页面 } 使用不变,直接获取即可。 @RouterPath(EntryRouter.EntryTest) @Component export struct TestPage { build() { //UI组件 } }
dev.to
March 15, 2025 at 4:06 AM
鸿蒙开发:实现一个超简单的网格拖拽
## 前言 > 本文基于Api12 网格拖拽,此功能很是常见,一般用于频道的编辑或者条目顺序的排列,在鸿蒙的开发中,针对网格的编辑,系统也给出了相关的Api,通过onItemDragStart和在onItemDrop即可轻松实现,onItemDragStart用于设置拖拽过程中的显示,onItemDrop是进行数据交换逻辑处理。 根据官方提供,我们随便实现了一个简单的拖拽效果: @Entry @Component struct Index { @State numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @Builder itemLayout(text: string) { Text(text) .fontSize(16) .backgroundColor(Color.Pink) .width(80) .height(80) .textAlign(TextAlign.Center) } changeIndex(index1: number, index2: number) { //交换数组位置 let temp = this.numbers[index1]; this.numbers[index1] = this.numbers[index2]; this.numbers[index2] = temp; } build() { Column() { Grid() { ForEach(this.numbers, (n: number) => { GridItem() { this.itemLayout(n.toString()) } }) } .columnsTemplate('1fr 1fr 1fr') .columnsGap(10) .rowsGap(10) .editMode(true) //设置Grid是否进入编辑模式 .onItemDragStart((event: ItemDragInfo, itemIndex: number) => { //第一次拖拽此事件绑定的组件时,触发回调。 return this.itemLayout(this.numbers[itemIndex].toString()) //设置拖拽过程中显示 }) .onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => { if (!isSuccess || insertIndex >= this.numbers.length) { return } this.changeIndex(itemIndex, insertIndex) }) }.width('100%') .height("100%") } } 以上代码我们就很简单的实现了一个网格拖拽,实际运行之后,长按确实发生了拖拽,和对应的item也进行了交换,但是并没有一个移动交换的过程,这是因为没有设置拖拽动画方法,如果想要拖拽过程中有移动动画,只需要设置supportAnimation为true即可,除此之外,还有一个非常重要的属性,那就是editMode,为true即为编辑模式,false即不可编辑。 拖拽的代码很是简单,毕竟官方也给出了相关案例,其中有两个点是未给出的,那就是禁止拖拽,和禁止与其交换。 比如,第一个和第二个条目,不能拖拽,如何进行设置呢?再比如,任何条目都不能和第一个进行交换,又该如何设置呢? ## 禁止拖拽 还是上述的代码,例如,把第一个条目禁止,不让其执行拖拽,实现起来很是简单,在onItemDragStart方法里,如果触摸的是指定索引,不让其执行即可。 .onItemDragStart((event: ItemDragInfo, itemIndex: number) => { //第一次拖拽此事件绑定的组件时,触发回调。 if (itemIndex == 0) { //禁止拖拽 return } return this.itemLayout(this.numbers[itemIndex].toString()) //设置拖拽过程中显示 }) 需要注意的是,以上的代码仅仅是做到了禁止拖拽,但是,有一个潜在的问题是,别的条目是可以和它进行交换的,交换过之后,由于它的索引发生了变化,就变得可以拖拽了,如果想实现真正的,即便被交换后也不能拖拽,那就不能判断索引了,可以以唯一值进行判断,比如item数据换成对象,在对象里定义唯一值。 ## 禁止交换 实际的开发中,除了某个条目禁止拖拽之外,也有不能和它进行交换的逻辑,比如第一个条目,就是固定的,不仅仅禁止拖拽,也不能和其进行交换,这种情况下如何进行实现呢? onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => { //第一个禁止交换 if (insertIndex <= 0) { return } if (!isSuccess || insertIndex >= this.numbers.length) { return } this.changeIndex(itemIndex, insertIndex) }) 很是简单,如果是第一个,直接不执行即可,以上的代码虽然实现了禁止交换,但是有一个问题,那就是实际的动画已经执行了,只是最后的交换没有执行而已,在视觉上仍然存在很大的问题。 我们想要的效果是,保留移动拖拽的动画,只是拖拽到第一个时,动画不执行,其他的该怎么执行就怎么执行。 那么,在设置supportAnimation动画的时候,就不能直接设置为true了,而是移动到条目一的位置后,设置为fasle,其他再设置为true。 如何拿到第一个item的坐标呢,其实每一个组件都有一个方法,onAreaChange,通过这个属性,我们就可以拿到任何一个组件的宽高还有,xy的坐标,当然,你也可以通过计算的方式,毕竟,屏幕的宽高,还有条目的宽高你都知道。 手势移动的坐标可以通过onTouch方法进行获取,在Move事件中进行判断,如果移动到了条目一的范围之内,取消动画,否则就执行动画。 ## 简单实现 目前呢这个网格拖拽功能已经做了一层封装,放到了refresh库中,大家如果想直接使用,可以依赖这个库: "dependencies": { "@abner/refresh": "^1.3.6"} 简单案例如下: import { GridDropView } from '@abner/refresh' @Entry @Component struct Index { @State numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @Builder itemLayout(item: Object, _: number) { Text(item.toString()) .fontSize(16) .backgroundColor(Color.Pink) .width(80) .height(80) .textAlign(TextAlign.Center) } build() { Column() { GridDropView({ columnsTemplate:"1fr 1fr 1fr", items: this.numbers, itemLayout: this.itemLayout, dropLayout: this.itemLayout, rowsGap: 10, columnsGap: 10, isEditMode: true, prohibitDrop: [0, 1], //禁止拖拽的索引 prohibitMaxSwap: 0, //禁止交换 onDropData: (items) => { console.log("拖拽结束:" + JSON.stringify(items)) } }) }.width('100%') .height("100%") } } ## 注意事项 实现拖拽,最重要的三个方法就是,打开编辑状态editMode,实现onItemDragStart和onItemDrop,设置拖拽移动动画和交换数据,如果想到开启补位动画,还需要实现supportAnimation方法。
dev.to
March 15, 2025 at 4:06 AM
鸿蒙开发:走进stateStyles多态样式
## **前言** > 本文基于Api12 一个组件,多种状态下,我们如何实现呢?举一个很简单的案例,一个按钮,默认状态下是黑色背景,点击后是红色,手指放开后还原黑色。 我们自然而然的就会想到利用手势的按下和抬起,改变其背景颜色即可,代码如下: Button("点击") .backgroundColor(this.clickBackgroundColor) .onTouch((event: TouchEvent) => { if (event.type == TouchType.Down) { this.clickBackgroundColor = Color.Red } else if (event.type == TouchType.Up) { this.clickBackgroundColor = Color.Black } }) 除了onTouch之外,gesture也可以实现其效果,无论哪种实现,我们都是要定义变量,改变某一个属性,有没有一种方式,直接更改属性呢?当然是有的,这就是stateStyles。 同样是上一个案例,我们使用stateStyles来实现一下,代码如下: Button("点击") .stateStyles({ pressed: { //按压 .backgroundColor(Color.Red) }, normal: { //正常态 .backgroundColor(Color.Black) } }) 可以发现,效果是和上述一模一样的。 ## **简单概述** stateStyles为多态样式,可以依据组件的内部状态的不同,快速设置不同样式,比如背景颜色,颜色、大小等等常见的通用属性,此种行为,很类似于css中的伪类,但语法稍有不同,目前支持的有以下五种状态: 状态 | 概述 ---|--- focused | 获焦态 normal | 正常态 pressed | 按压态 disabled | 不可用态 selected | 选中态 ## **实际场景** 具体的场景主要适用于多状态的切换,比如前言中的案例,当然了,它是支持多个属性的,比如,我们再增加宽高属性: Button("点击") .stateStyles({ pressed: { //按压 .backgroundColor(Color.Red) .width(200) .height(100) }, normal: { //正常态 .backgroundColor(Color.Black) .width(100) .height(50) } }) 除了多个属性之外,另外也可以直接传递样式: 定义Styles: @Styles normalStyle() { .backgroundColor(Color.Black) .width(100) .height(50) } @Styles pressedStyle() { .backgroundColor(Color.Red) .width(200) .height(100) } 使用Styles: Button("点击") .stateStyles({ pressed: this.pressedStyle, normal: this.normalStyle }) ## **总结** selected状态一般用于可选择的组件,比如Checkbox,比如Radio,目前支持的组件如下: 支持组件 | 支持的参数/属性 | 起始API版本 ---|---|--- Checkbox | select | 10 CheckboxGroup | selectAll | 10 Radio | checked | 10 Toggle | isOn | 10 ListItem | selected | 10 GridItem | selected | 10 MenuItem | selected | 10 **简单案例:** Radio({ value: 'Radio1', group: 'radioGroup1' }) .checked(this.value) .height(50) .width(50) .borderRadius(50) .radioStyle({ checkedBackgroundColor: Color.Red }) .onClick(() => { this.value = !this.value }) .stateStyles({ normal: { .backgroundColor(Color.Black) }, selected: { .backgroundColor(Color.Red) }, })
dev.to
March 15, 2025 at 4:05 AM