Flutter 到 OpenHarmony,不是有手就行吗 (下拉刷新)_flutter openharmony

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

完整开源地址:https://docs.qq.com/doc/DSkNLaERkbnFoS0ZF

.backgroundColor('#22808080')

}
}


### 学废了


虽然练习时长只有一个月,但通过编写第一个 `ArtUI` 组件,还是学到了不少东西。


#### 创建发布一个组件


##### 创建组织


先到 [OpenHarmony 三方库中心仓]( ) 上面注册个账号,到 `个人中心` =》`组织管理` 中,申请一个组织。这个组织名字以后要用到,因为普通三方作者,是不能使用 `ohos` 前缀的。


比如我注册的是组织名为 `candies`,组件为 `pull_to_refresh`。那么组件最终的名字就是 `@candies/pull_to_refresh`


最后用户可以通过 `ohpm install @candies/pull_to_refresh`,来安装使用组件。



> 
> 为啥这个要先做,因为审核很慢。
> 
> 
> 


##### 创建项目


写一个组件,必然也会给这个组件创建一个演示例子,在 `Flutter` 中发布一个组件,你可以使用下面的结构。



package
–example


而在 `OpenHarmony` 里面你只能使用下面的结构,这样才能方便你修改代码。



example
–package


`2` 种结构的区别是, `package` 下面肯定会需要加 `README`,`LICENSE`,但是 `github`,`gitee` 默认只会显示根目录下面的 `README`,第二种结构就要多复制一份到 `example` 目录下面。


但是 [OpenHarmony 三方库中心仓]( ) 却要求,有点难顶啊。


![](https://img-blog.csdnimg.cn/img_convert/7fc94048e27e0b1aa682422304f90be8.webp?x-oss-process=image/format,png)



> 
> `ide` 啥时候支持下第一种结构呀!
> 
> 
> 


###### 创建组件演示项目


创建一个项目。


![](https://img-blog.csdnimg.cn/img_convert/7acc1aa0ba15ac44590290941249c625.webp?x-oss-process=image/format,png)


###### 创建组件项目


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


![](https://img-blog.csdnimg.cn/img_convert/cc0a616af735dafae7562694f65b7637.webp?x-oss-process=image/format,png)


创建好的目录长这样子


![](https://img-blog.csdnimg.cn/img_convert/bf90c1ed622087877cfdd2b45fe3fad0.webp?x-oss-process=image/format,png)


`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”
}
}


##### 发布


在准备发布之前,请先阅读 [贡献三方库]( ) 下面内容。


![](https://img-blog.csdnimg.cn/img_convert/a814ad1b7a04960b661033ac3c7e58c3.webp?x-oss-process=image/format,png)


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


![](https://img-blog.csdnimg.cn/img_convert/770475efb52498cbb721ebd0cd09fcad.webp?x-oss-process=image/format,png)


最后执行 `ohpm publish xxx.har`(`xxx.har` 为上传包的本地路径)。上传成功之后,你就可以看到你的个人中心里面的消息和状态了,耐心等待审核。


我遇到的上架的问题主要是组织名称(当然,这是我自己猜的,后面会聊到这个),`ohos` 不是普通三方开发者使用的前缀, `ohos` 的库都在 `OpenHarmony-TPC: OpenHarmony third party components (gitee.com)`下面。按道理你可以 `pr` 到这个下面,并且加入到 `ohos` 中,再发布。当然更欢迎大家能加入 `candies` 组织,大家一起生产有趣的小组件。


![](https://img-blog.csdnimg.cn/img_convert/7ef3dc151ab182f541a362aa87383c11.webp?x-oss-process=image/format,png)


![](https://img-blog.csdnimg.cn/img_convert/512219cbe4e057c727176f40aa17e577.webp?x-oss-process=image/format,png)


#### @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 30: …}) { 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 181: … 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 30: …}) { 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

  • 17
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值