Flutter 到 OpenHarmony,不是有手就行吗 (下拉刷新)_flutter openharmony(1)
(组件都继承于该类,里面是一些公共的属性,事件),虽然这样可以让编辑器有提示,并且不报错,但是运行起来依然会提示。中一样,父组件里面存放管理下拉刷新的状态,然后子组件里面监听状态,达到局部刷新的效果。在给组件定义参数的时候,会遇到这个参数不必须设置,但后续需要根据情况给它一个默认值。在给组件定义参数的时候,会遇到这个参数不必须设置,但后续需要根据情况给它一个默认值。我遇到的上架的问题主要是组织名称
先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新HarmonyOS鸿蒙全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。




既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip204888 (备注鸿蒙)
正文
package
–example
而在 OpenHarmony 里面你只能使用下面的结构,这样才能方便你修改代码。
example
–package
2 种结构的区别是, package 下面肯定会需要加 README,LICENSE,但是 github,gitee 默认只会显示根目录下面的 README,第二种结构就要多复制一份到 example 目录下面。
但是 OpenHarmony 三方库中心仓 却要求,有点难顶啊。

ide啥时候支持下第一种结构呀!
创建组件演示项目
创建一个项目。

创建组件项目
创建一个 Static Libray (至于其他 Module 是什么意思,请自行查看文档)

创建好的目录长这样子

oh-package.json5 中是你的组件的信息。
这里你需要把名字改成 @candies/pull_to_refresh 即 (@你的组织名字/组件名字)
一个完整的 oh-package.json5 是这样的
{
“license”: “Apache-2.0”,
“devDependencies”: {},
“keywords”: [
“pull”,
“refresh”,
“pulltorefresh”
],
“author”: “zmtzawqlp”,
“name”: “@candies/pull_to_refresh”,
“description”: “Harmony plugin for building pull to refresh effects with PullToRefresh quickly.”,
“main”: “index.ets”,
“repository”: “https://github.com/HarmonyCandies/pull_to_refresh”,
“version”: “1.0.0”,
“homepage”: “https://github.com/HarmonyCandies/pull_to_refresh”,
“dependencies”: {}
}
组件项目中 Index.ets 是入口,用于导出组件。跟 Flutter 中 lib 下面带 library 组件名; 标识的 dart 文件效果一样。
export { MainPage } from ‘./src/main/ets/components/mainpage/MainPage’
引用组件项目
要想 Example 能引用到 pull_to_refresh, 你还需要到
{
“license”: “”,
“devDependencies”: {},
“author”: “”,
“name”: “entry”,
“description”: “Please describe the basic information.”,
“main”: “”,
“version”: “1.0.0”,
“dependencies”: {
“@candies/pull_to_refresh”: “file:…/pull_to_refresh”
}
}
发布
在准备发布之前,请先阅读 贡献三方库 下面内容。

阅读操作完毕之后,你就可以打你的 har 包了。选中你的组件项目,在 Build 下面选择 Make Module 你的组件名字。编译完成之后,你就可以在组件项目路径 build\default\outputs\default\ 中找到你即将发布的包。

最后执行 ohpm publish xxx.har(xxx.har 为上传包的本地路径)。上传成功之后,你就可以看到你的个人中心里面的消息和状态了,耐心等待审核。
我遇到的上架的问题主要是组织名称(当然,这是我自己猜的,后面会聊到这个),ohos 不是普通三方开发者使用的前缀, ohos 的库都在 OpenHarmony-TPC: OpenHarmony third party components (gitee.com)下面。按道理你可以 pr 到这个下面,并且加入到 ohos 中,再发布。当然更欢迎大家能加入 candies 组织,大家一起生产有趣的小组件。


@Provide/@Consume
第一眼看到这个状态管理装饰器的时候,好亲切的感觉。这不是就是 Flutter 里面的 (provider | Flutter Package (flutter-io.cn)) 吗?
最开始设计 pull\_to\_refresh 的时候,想着跟 Flutter 中一样,父组件里面存放管理下拉刷新的状态,然后子组件里面监听状态,达到局部刷新的效果。
第一版的设计结构如下:
CustomWidget中提供了@Provide('a')CustomWidgetChild中使用@Consume('a')获取状态变化。
@Entry
@Component
struct HomePage {
@Builder
builder2(KaTeX parse error: Can't use function '$' in math mode at position 26: …ing }) { Text(`$̲{.a}测试`)
}
build() {
Column() {
CustomWidget() {
CustomWidgetChild({ builder: this.builder2 })
}
}
}
}
@Component
struct CustomWidget {
@Provide(‘a’) a: string = ‘abc’;
@BuilderParam
builder: () => void;
build() {
Column() {
Button(‘你好’).onClick((x) => {
if (this.a == ‘ddd’) {
this.a = ‘abc’;
}
else {
this.a = ‘ddd’;
}
})
this.builder()
}
}
}
@Component
struct CustomWidgetChild {
@Consume(‘a’) a: string;
@BuilderParam
builder: ($$: { a: string }) => void;
build() {
Column() {
this.builder({ a: this.a })
}
}
}
运行会报找不到 Provide 的错误。
通过分析由 ArkTS 生成的 js 文件(生成的 js 在 entry\build\default\cache\default\default@CompileArkTS\esmodule\debug 路径下面) ,我们可以分析得出:CustomWidgetChild 其父组件实际上是 HomePage,其内部 this 指向的也是 HomePage,因此找不到 CustomWidget 的 @Provide 变量。
class HomePage extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1) {
super(parent, localStorage, elmtId);
this.setInitiallyProvidedValue(params);
}
setInitiallyProvidedValue(params) {
}
updateStateVars(params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
}
aboutToBeDeleted() {
SubscriberManager.Get().delete(this.id());
this.aboutToBeDeletedInternal();
}
builder2(KaTeX parse error: Can't use function '$' in math mode at position 149: …; Text.create(`$̲{.a}测试`);
if (!isInitialRender) {
Text.pop();
}
ViewStackProcessor.StopGetAccessRecording();
});
Text.pop();
}
initialRender() {
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
Column.create();
if (!isInitialRender) {
Column.pop();
}
ViewStackProcessor.StopGetAccessRecording();
});
{
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
if (isInitialRender) {
ViewPU.create(new CustomWidget(this, {
builder: () => {
{
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
if (isInitialRender) {
ViewPU.create(new CustomWidgetChild(this, { builder: this.builder2 }, undefined, elmtId));
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {});
}
ViewStackProcessor.StopGetAccessRecording();
});
}
}
}, undefined, elmtId));
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {});
}
ViewStackProcessor.StopGetAccessRecording();
});
}
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}
class CustomWidget extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1) {
super(parent, __localStorage, elmtId);
this.__a = new ObservedPropertySimplePU(‘abc’, this, “a”);
this.addProvidedVar(“a”, this.__a);
this.builder = undefined;
this.setInitiallyProvidedValue(params);
}
setInitiallyProvidedValue(params) {
if (params.a !== undefined) {
this.a = params.a;
}
if (params.builder !== undefined) {
this.builder = params.builder;
}
}
updateStateVars(params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
}
aboutToBeDeleted() {
this.a.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id());
this.aboutToBeDeletedInternal();
}
get a() {
return this.__a.get();
}
set a(newValue) {
this.__a.set(newValue);
}
initialRender() {
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
Column.create();
if (!isInitialRender) {
Column.pop();
}
ViewStackProcessor.StopGetAccessRecording();
});
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
Button.createWithLabel(‘你好’);
Button.onClick((x) => {
if (this.a == ‘ddd’) {
this.a = ‘abc’;
}
else {
this.a = ‘ddd’;
}
});
if (!isInitialRender) {
Button.pop();
}
ViewStackProcessor.StopGetAccessRecording();
});
Button.pop();
this.builder.bind(this)();
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}
class CustomWidgetChild extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1) {
super(parent, __localStorage, elmtId);
this.__a = this.initializeConsume(“a”, “a”);
this.builder = undefined;
this.setInitiallyProvidedValue(params);
}
setInitiallyProvidedValue(params) {
if (params.builder !== undefined) {
this.builder = params.builder;
}
}
updateStateVars(params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
}
aboutToBeDeleted() {
this.a.aboutToBeDeleted();
SubscriberManager.Get().delete(this.id());
this.aboutToBeDeletedInternal();
}
get a() {
return this.__a.get();
}
set a(newValue) {
this.__a.set(newValue);
}
initialRender() {
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
Column.create();
if (!isInitialRender) {
Column.pop();
}
ViewStackProcessor.StopGetAccessRecording();
});
this.builder.bind(this)(makeBuilderParameterProxy(“builder”, { a: () => (this[“__a”] ? this[“__a”] : this[“a”]) }));
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}
ViewStackProcessor.StartGetAccessRecordingFor(ViewStackProcessor.AllocateNewElmetIdForNextComponent());
loadDocument(new HomePage(undefined, {}));
ViewStackProcessor.StopGetAccessRecording();
export {};
//# sourceMappingURL=Index.js.map
意思就是你只能写成下面的这种形式。虽然说 CustomWidgetChild 是看起来是通过 CustomWidget 的 builder 创建出来的,但是它们依然没有父子关系,这跟 Flutter 完全不是一套原理。
@Entry
@Component
struct HomePage {
@Provide(‘a’) test: string = ‘abc’;
@Builder
builder2(KaTeX parse error: Can't use function '$' in math mode at position 26: …ing }) { Text(`$̲{.a}测试`)
}
build() {
Column() {
CustomWidget() {
CustomWidgetChild({ builder: this.builder2 })
}
}
}
}
@Component
struct CustomWidget {
@Consume(‘a’) a: string;
@BuilderParam
builder: () => void;
build() {
Column() {
Button(‘你好’).onClick((x) => {
if (this.a == ‘ddd’) {
this.a = ‘abc’;
}
else {
this.a = ‘ddd’;
}
})
this.builder()
}
}
}
@Component
struct CustomWidgetChild {
@Consume(‘a’) a: string;
@BuilderParam
builder: ($$: { a: string }) => void;
build() {
Column() {
this.builder({ a: this.a })
}
}
}
@Builder/@BuilderParam
在自定义组件中,如果你想传入其他的组件,你需要使用到 @Builder 和 @BuilderParam, 代码如下:
@Component
struct Child {
@BuilderParam aBuilder0: () => void;
build() {
Column() {
this.aBuilder0()
}
}
}
@Entry
@Component
struct Parent {
@Builder componentBuilder() {
Text(Parent builder )
}
build() {
Column() {
Child({ aBuilder0: this.componentBuilder })
}
}
}
但是实际中写一个自定义组件的时候,会有这种需求。需要为 BuilderParam 修饰的内容的返回增加一些事件或者设置。比如下面例子,为 BuilderParamChild 的 builder 的返回增加 hitTestBehavior 设置。我这里将 builder 的返回修改为了 CommonMethod<any>(组件都继承于该类,里面是一些公共的属性,事件),虽然这样可以让编辑器有提示,并且不报错,但是运行起来依然会提示 hitTestBehavior 找不到。
@Component
struct BuilderParamTestDemo {
build() {
Column(){
BuilderParamChild(){
Text(‘测试’)
}
}
}
}
@Component
struct BuilderParamChild {
@BuilderParam
builder: () => CommonMethod;
build() {
this.builder().hitTestBehavior(HitTestMode.None)
}
}
错误堆栈如下:
E Error message: Cannot read property hitTestBehavior of undefined
E SourceCode:
E this.builder().hitTestBehavior.bind(this)(HitTestMode.None);
E ^
E Stacktrace:
E at initialRender (entry/src/main/ets/pages/Index.ets:20:5)
从生成的 js 中也能看到对应的代码。
“use strict”;
class BuilderParamTestDemo extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1) {
super(parent, localStorage, elmtId);
this.setInitiallyProvidedValue(params);
}
setInitiallyProvidedValue(params) {
}
updateStateVars(params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
}
aboutToBeDeleted() {
SubscriberManager.Get().delete(this.id());
this.aboutToBeDeletedInternal();
}
initialRender() {
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
Column.create();
if (!isInitialRender) {
Column.pop();
}
ViewStackProcessor.StopGetAccessRecording();
});
{
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
if (isInitialRender) {
ViewPU.create(new BuilderParamChild(this, {
builder: () => {
this.observeComponentCreation((elmtId, isInitialRender) => {
ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
Text.create(‘测试’);
if (!isInitialRender) {
Text.pop();
}
ViewStackProcessor.StopGetAccessRecording();
});
Text.pop();
}
}, undefined, elmtId));
}
else {
this.updateStateVarsOfChildByElmtId(elmtId, {});
}
ViewStackProcessor.StopGetAccessRecording();
});
}
Column.pop();
}
rerender() {
this.updateDirtyElements();
}
}
class BuilderParamChild extends ViewPU {
constructor(parent, params, __localStorage, elmtId = -1) {
super(parent, localStorage, elmtId);
this.builder = undefined;
this.setInitiallyProvidedValue(params);
}
setInitiallyProvidedValue(params) {
if (params.builder !== undefined) {
this.builder = params.builder;
}
}
updateStateVars(params) {
}
purgeVariableDependenciesOnElmtId(rmElmtId) {
}
aboutToBeDeleted() {
SubscriberManager.Get().delete(this.id());
this.aboutToBeDeletedInternal();
}
initialRender() {
this.builder().hitTestBehavior.bind(this)(HitTestMode.None);
}
rerender() {
this.updateDirtyElements();
}
}
ViewStackProcessor.StartGetAccessRecordingFor(ViewStackProcessor.AllocateNewElmetIdForNextComponent());
loadDocument(new BuilderParamTestDemo(undefined, {}));
ViewStackProcessor.StopGetAccessRecording();
//# sourceMappingURL=Index.js.map
对应这个问题,官方的解释是
1.ArkUI 没有类似安卓基类组件。
2.目前 ArkUI 组件是没有具体的类型,也不支持组件继承。
3.如果需要给自定义构建方法添加属性,只能是套一层容器组件之后再给容器组件设置属性
4.从语法规范上来讲,BuilderParam 的方法类型就是 () => void
话虽然这样说,但我还是提出了疑问,那么有没有那种单纯的容器组件, 不管是用 Row,还是 Column 或者其他功能容器,这里的含义都蛮奇怪的。
回答是,暂时没有。希望官方以后还是考虑一下这个,虽然我包个 Row/Column 是可以,但是感觉怪怪的。
@Component
struct BuilderParamChild {
@BuilderParam
builder: () => void;
build() {
Column() {
this.builder()
}.hitTestBehavior(HitTestMode.None)
}
}
状态装饰器
在给组件定义参数的时候,会遇到这个参数不必须设置,但后续需要根据情况给它一个默认值。
在 Flutter 中,我们可以通过定义参数为可空,然后在后续流程中判断这个参数是否为 null,再给它默认值。
在 ArkTS 中我第一反应是这样写:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注鸿蒙)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
还是提出了疑问,那么有没有那种单纯的容器组件, 不管是用 Row,还是 Column 或者其他功能容器,这里的含义都蛮奇怪的。
回答是,暂时没有。希望官方以后还是考虑一下这个,虽然我包个 Row/Column 是可以,但是感觉怪怪的。
@Component
struct BuilderParamChild {
@BuilderParam
builder: () => void;
build() {
Column() {
this.builder()
}.hitTestBehavior(HitTestMode.None)
}
}
状态装饰器
在给组件定义参数的时候,会遇到这个参数不必须设置,但后续需要根据情况给它一个默认值。
在 Flutter 中,我们可以通过定义参数为可空,然后在后续流程中判断这个参数是否为 null,再给它默认值。
在 ArkTS 中我第一反应是这样写:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注鸿蒙)
[外链图片转存中…(img-KEyhTJWY-1713124548440)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
更多推荐

所有评论(0)