diff --git a/README.md b/README.md index 8f183cd21c11c6ee4d1b32a9b9399b87ca4b7b09..1875c376f110c9e27af7fd43853875a615e61407 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,53 @@ ohpm install @ohos/pulltorefresh ### Quick Start ```typescript +// How to use the V1 decorator import { PullToRefresh } from '@ohos/pulltorefresh' // Create a scroller instance. private scroller: Scroller = new Scroller(); PullToRefresh({ + // (Mandatory) Data source of the list component. + data: $data, + // (Mandatory) List or grid component in the main layout. + scroller: this.scroller, + // (Mandatory) Custom list or grid view. + customList: () => { + // A UI method decorated by @Builder. + this.getListView(); + }, + // (Optional) Callback used to handle the pull-down refresh action. + onRefresh: () => { + return new Promise((resolve, reject) => { + // Simulate a network request operation. When data is obtained 2 seconds after the network request, inform the component to refresh the list data. + setTimeout(() => { + resolve ('Refresh successful'); + this.data = [...this.dataNumbers]; + }, 2000); + }); + }, + // (Optional) Callback used to handle the pull-up loading action. + onLoadMore: () => { + return new Promise((resolve, reject) => { + // Simulate a network request operation. When data is obtained 2 seconds after the network request, inform the component to update the list data. + setTimeout(() => { + resolve(''); + this.data.push ("Added items" + this.data.length); + }, 2000); + }); + }, + customLoad: null, + customRefresh: null, +}) + +// How to use the V2 decorator +import { PullToRefreshV2 } from '@ohos/pulltorefresh' + +// Create a scroller instance. +private scroller: Scroller = new Scroller(); + +PullToRefreshV2({ // (Optional) Data source of the list component. data: this.data, // (Mandatory) List or grid component in the main layout. @@ -116,7 +157,7 @@ interface DataChangeListener { For details, see [LazyForEach: Lazy Data Loading](https://docs.openharmony.cn/pages/v4.1/en/application-dev/quick-start/arkts-rendering-control-lazyforeach.md/). ## Available APIs -### PullToRefresh Properties +### PullToRefresh/PullToRefreshV2 Properties | Name | Type | Description | Default Value | | :------------------|:----------------------------------------------------------------------------------|:---------------------| :---------------------------- | diff --git a/README_zh.md b/README_zh.md index 11ee01603dca788c9da028fcf2edb254bf6b8bc0..950f87feb6c8c2fd3c949e40db194fc5377b8f49 100644 --- a/README_zh.md +++ b/README_zh.md @@ -22,14 +22,15 @@ ohpm install @ohos/pulltorefresh ### 快速使用 ```typescript +// V1装饰器下的使用方式 import { PullToRefresh } from '@ohos/pulltorefresh' // 需绑定列表或宫格组件 private scroller: Scroller = new Scroller(); PullToRefresh({ -// 可选项,列表组件所绑定的数据 -data: this.data, +// 必传项,列表组件所绑定的数据 +data: $data, // 必传项,需绑定传入主体布局内的列表或宫格组件 scroller: this.scroller, // 必传项,自定义主体布局,内部有列表或宫格组件 @@ -60,6 +61,46 @@ onLoadMore: () => { customLoad: null, customRefresh: null, }) + +// V2装饰器下的使用方式 +import { PullToRefreshV2 } from '@ohos/pulltorefresh' + +// 需绑定列表或宫格组件 +private scroller: Scroller = new Scroller(); + +PullToRefreshV2({ + // 可选项,列表组件所绑定的数据 + data: this.data, + // 必传项,需绑定传入主体布局内的列表或宫格组件 + scroller: this.scroller, + // 必传项,自定义主体布局,内部有列表或宫格组件 + customList: () => { + // 一个用@Builder修饰过的UI方法 + this.getListView(); + }, + // 可选项,下拉刷新回调 + onRefresh: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve('刷新成功'); + this.data = [...this.dataNumbers]; + }, 2000); + }); + }, + // 可选项,上拉加载更多回调 + onLoadMore: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(''); + this.data.push("增加的条目" + this.data.length); + }, 2000); + }); + }, + customLoad: null, + customRefresh: null, +}) ``` 设置属性示例和设置自定义动画示例请看[示例entry](https://gitee.com/openharmony-sig/PullToRefresh/tree/master/entry/src/main/ets/pages) @@ -113,7 +154,7 @@ interface DataChangeListener { 具体使用请看 openharmony:[LazyForEach:数据懒加载](https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/quick-start/arkts-rendering-control-lazyforeach.md/) ## 属性(接口)说明 -### PullToRefresh组件属性 +### PullToRefresh/PullToRefreshV2组件属性 | 属性 | 类型 | 释义 | 默认值 | | :------------------:|:----------------------------------------------------------------------------------:|:---------------------:| :----------------------------: | diff --git a/changelog.md b/changelog.md index 5a204c7fc0206780d1847058a1922bc48f5f2a7f..9d5ad463eb56123c444925dcd3836e78c75c5e84 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,7 @@ +## 2.1.1-rc.1 +1、Addressed an issue where V1 and V2 decorators were incompatible with PullToRefresh +2、Added the PullToRefreshV2 API for the component + ## 2.1.1-rc.0 1、添加中英文README.md修改以及demo国际化处理 diff --git a/entry/oh-package.json5 b/entry/oh-package.json5 index a302e646681f8bb7be73b82156bab103e7d74d58..d9952a0a7aaed0337dfa943fe47167144d8e37ed 100644 --- a/entry/oh-package.json5 +++ b/entry/oh-package.json5 @@ -4,7 +4,7 @@ "name": "entry", "description": "example description", "repository": {}, - "version": "2.1.1-rc.0", + "version": "2.1.1-rc.1", "dependencies": { "@ohos/pulltorefresh": "file:../library" } diff --git a/entry/src/main/ets/pages/customConfig.ets b/entry/src/main/ets/pages/customConfig.ets index 678d89591c70b4d84dbc23d21c1c550cd1066b91..63b1ab766d5aa6f8cdff2a6ed2a56db06f3fe488 100644 --- a/entry/src/main/ets/pages/customConfig.ets +++ b/entry/src/main/ets/pages/customConfig.ets @@ -15,14 +15,14 @@ import { PullToRefresh, PullToRefreshConfigurator } from '@ohos/pulltorefresh' @Entry -@ComponentV2 +@Component struct Index { private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; private dataStrings: Array = [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), $r("app.string.Settings"), $r("app.string.LogOut")]; - @Local data: Array = this.dataStrings; + @State data: Array = this.dataStrings; private scroller: Scroller = new Scroller(); private refreshConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator(); @@ -58,8 +58,8 @@ struct Index { build() { Column() { PullToRefresh({ - // 可选项,列表组件所绑定的数据 - data: this.data, + // 必传项,列表组件所绑定的数据 + data: $data, // 必传项,需绑定传入主体布局内的列表或宫格组件 scroller: this.scroller, // 必传项,自定义主体布局,内部有列表或宫格组件 diff --git a/entry/src/main/ets/pages/customConfigV2.ets b/entry/src/main/ets/pages/customConfigV2.ets new file mode 100644 index 0000000000000000000000000000000000000000..103887c387cfea29fb703a820fc57ed7de1f56c0 --- /dev/null +++ b/entry/src/main/ets/pages/customConfigV2.ets @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PullToRefreshV2, PullToRefreshConfigurator } from '@ohos/pulltorefresh' + +@Entry +@ComponentV2 +struct Index { + private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; + private dataStrings: Array = + [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), + $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), + $r("app.string.Settings"), $r("app.string.LogOut")]; + @Local data: Array = this.dataStrings; + private scroller: Scroller = new Scroller(); + private refreshConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator(); + + private getResourceString(res: Resource) { + return getContext().resourceManager.getStringSync(res.id) + } + + aboutToAppear() { + // 设置属性 + this.refreshConfigurator + .setHasRefresh(true)// 是否具有下拉刷新功能 + .setHasLoadMore(true)// 是否具有上拉加载功能 + .setMaxTranslate(150)// 可下拉上拉的最大距离 + .setSensitivity(1)// 下拉上拉灵敏度 + .setListIsPlacement(false)// 滑动结束后列表是否归位 + .setAnimDuration(300)// 滑动结束后,回弹动画执行时间 + .setRefreshHeight(80)// 下拉动画高度 + .setRefreshColor('#ff0000')// 下拉动画颜色 + .setRefreshBackgroundColor('#ffbbfaf5')// 下拉动画区域背景色 + .setRefreshTextColor('red')// 下拉加载完毕后提示文本的字体颜色 + .setRefreshTextSize(25)// 下拉加载完毕后提示文本的字体大小 + .setRefreshAnimDuration(1000)// 下拉动画执行一次的时间 + .setLoadImgHeight(50)// 上拉图片高度 + .setLoadBackgroundColor('#ffbbfaf5')// 上拉动画区域背景色 + .setLoadTextColor('blue')// 上拉文本的字体颜色 + .setLoadTextSize(25)// 上拉文本的字体大小 + .setLoadTextPullUp1(this.getResourceString($r("app.string.Please_continue_pull_up")))// 上拉1阶段文本 + .setLoadTextPullUp2(this.getResourceString($r("app.string.Release_refresh")))// 上拉2阶段文本 + .setLoadTextLoading(this.getResourceString($r("app.string.Loading")))// 上拉加载更多中时的文本 + .setRefreshCompleteTextHoldTime(500) //上拉刷新后停留的时间, 默认一秒, 建议设置500 + } + + build() { + Column() { + PullToRefreshV2({ + // 可选项,列表组件所绑定的数据 + data: this.data, + // 必传项,需绑定传入主体布局内的列表或宫格组件 + scroller: this.scroller, + // 必传项,自定义主体布局,内部有列表或宫格组件 + customList: () => { + // 一个用@Builder修饰过的UI方法 + this.getListView(); + }, + // 可选项,组件属性配置,具有默认值 + refreshConfigurator: this.refreshConfigurator, + // 可选项,容器宽,默认自适应 + mWidth: '80%', + // 可选项,容器高,默认自适应 + mHeight: '80%', + // 可选项,下拉刷新回调 + onRefresh: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(this.getResourceString($r("app.string.RefreshSuccessful"))); + this.data = [...this.dataNumbers]; + }, 2000); + }); + }, + // 可选项,上拉加载更多回调 + onLoadMore: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(''); + this.data.push(`${this.getResourceString($r("app.string.AddedEntry"))} ${this.data.length}`); + }, 2000); + }); + }, + customLoad: null, + customRefresh: null, + }) + } + .width('100%') + } + + @Builder + private getListView() { + List({ space: 20, scroller: this.scroller }) { + ForEach(this.data, (item: string) => { + ListItem() { + Text(item) + .width('100%') + .height(150) + .fontSize(20) + .textAlign(TextAlign.Center) + .backgroundColor('#95efd2') + } + }) + } + .backgroundColor('#eeeeee') + .divider({ strokeWidth: 1, color: 0x222222 }) + .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果 + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/customRefreshAnim.ets b/entry/src/main/ets/pages/customRefreshAnim.ets index 2db9c3a3770b8f17e92416f97a64cff52eace1f6..ae0c2a966d24957e6b48acc03e06197e228c3a0a 100644 --- a/entry/src/main/ets/pages/customRefreshAnim.ets +++ b/entry/src/main/ets/pages/customRefreshAnim.ets @@ -18,14 +18,14 @@ const pointSpace = 30; const pointJitterAmplitude = 10; @Entry -@ComponentV2 +@Component struct Index { private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; private dataStrings: Array = [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), $r("app.string.Settings"), $r("app.string.LogOut")]; - @Local data: Array = this.dataStrings; + @State data: Array = this.dataStrings; private scroller: Scroller = new Scroller(); private refreshConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator(); private canvasSetting: RenderingContextSettings = new RenderingContextSettings(true); @@ -46,8 +46,8 @@ struct Index { build() { Column() { PullToRefresh({ - // 可选项,列表组件所绑定的数据 - data: this.data, + // 必传项,列表组件所绑定的数据 + data: $data, // 必传项,需绑定传入主体布局内的列表或宫格组件 scroller: this.scroller, // 必传项,自定义主体布局,内部有列表或宫格组件 diff --git a/entry/src/main/ets/pages/customRefreshAnimV2.ets b/entry/src/main/ets/pages/customRefreshAnimV2.ets new file mode 100644 index 0000000000000000000000000000000000000000..30fb29cbe7abb9c3b3ee10114b825a2b06a24c1f --- /dev/null +++ b/entry/src/main/ets/pages/customRefreshAnimV2.ets @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PullToRefreshV2, PullToRefreshConfigurator } from '@ohos/pulltorefresh' + +const pointSpace = 30; +const pointJitterAmplitude = 10; + +@Entry +@ComponentV2 +struct Index { + private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; + private dataStrings: Array = + [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), + $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), + $r("app.string.Settings"), $r("app.string.LogOut")]; + @Local data: Array = this.dataStrings; + private scroller: Scroller = new Scroller(); + private refreshConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator(); + private canvasSetting: RenderingContextSettings = new RenderingContextSettings(true); + private canvasRefresh: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.canvasSetting); + private value1: number[] = []; + private value2: number[] = []; + + private getResourceString(res: Resource) { + return getContext().resourceManager.getStringSync(res.id) + } + + aboutToAppear() { + // 设置属性 + this.refreshConfigurator + .setRefreshAnimDuration(600) // 下拉动画执行一次的时间 + } + + build() { + Column() { + PullToRefreshV2({ + // 可选项,列表组件所绑定的数据 + data: this.data, + // 必传项,需绑定传入主体布局内的列表或宫格组件 + scroller: this.scroller, + // 必传项,自定义主体布局,内部有列表或宫格组件 + customList: () => { + // 一个用@Builder修饰过的UI方法 + this.getListView(); + }, + // 可选项,组件属性配置,具有默认值 + refreshConfigurator: this.refreshConfigurator, + // 可选项,下拉刷新回调 + onRefresh: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(''); + this.data = [...this.dataNumbers]; + }, 2000); + }); + }, + // 可选项,上拉加载更多回调 + onLoadMore: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(''); + this.data.push(`${this.getResourceString($r("app.string.AddedEntry"))} ${this.data.length}`); + }, 2000); + }); + }, + // 可选项,自定义下拉刷新动画布局 + customRefresh: () => { + this.customRefreshView(); + }, + // 可选项,下拉中回调 + onAnimPullDown: (value, width, height) => { + if (value !== undefined && width !== undefined && height !== undefined) { + this.canvasRefresh.clearRect(0, 0, width, height); + if (value <= 0.33) { + this.drawPoint(width / 2, height / 2); + } else if (value <= 0.75) { + this.drawPoint(width / 2 - (pointSpace / 2 * (value - 0.33) / (0.75 - 0.33)), height / 2); + this.drawPoint(width / 2 + (pointSpace / 2 * (value - 0.33) / (0.75 - 0.33)), height / 2); + } else { + this.drawPoint(width / 2, height / 2); + this.drawPoint(width / 2 - pointSpace / 2 - (pointSpace / 2 * (value - 0.75) / (1 - 0.75)), height / 2); + this.drawPoint(width / 2 + pointSpace / 2 + (pointSpace / 2 * (value - 0.75) / (1 - 0.75)), height / 2); + } + } + }, + // 可选项,刷新中回调 + onAnimRefreshing: (value, width, height) => { + if (value !== undefined && width !== undefined && height !== undefined) { + this.canvasRefresh.clearRect(0, 0, width, height); + // 将value值由0到1循环变化变为-1到1反复变化 + value = Math.abs(value * 2 - 1) * 2 - 1; + // 绘制第1个点 + this.drawPoint(width / 2 - pointSpace, height / 2 + pointJitterAmplitude * value); + // 绘制第2个点 + if (this.value1.length == 7) { + this.drawPoint(width / 2, height / 2 + pointJitterAmplitude * this.value1[0]); + this.value1 = this.value1.splice(1, this.value1.length); + } else { + this.drawPoint(width / 2, height / 2 + pointJitterAmplitude); + } + this.value1.push(value); + // 绘制第3个点 + if (this.value2.length == 14) { + this.drawPoint(width / 2 + pointSpace, height / 2 + pointJitterAmplitude * this.value2[0]); + this.value2 = this.value2.splice(1, this.value2.length); + } else { + this.drawPoint(width / 2 + pointSpace, height / 2 + pointJitterAmplitude); + } + this.value2.push(value); + } + }, + customLoad: null, + }) + } + } + + private drawPoint(x: number, y: number): void { + this.canvasRefresh.beginPath(); + this.canvasRefresh.arc(x, y, 3, 0, Math.PI * 2); + this.canvasRefresh.fill(); + } + + @Builder + private customRefreshView() { + Canvas(this.canvasRefresh) + .width('100%') + .height('100%') + .onReady(() => { + this.canvasRefresh.fillStyle = 'red'; + }) + } + + @Builder + private getListView() { + List({ space: 20, scroller: this.scroller }) { + ForEach(this.data, (item: string) => { + ListItem() { + Text(item) + .width('100%') + .height(150) + .fontSize(20) + .textAlign(TextAlign.Center) + .backgroundColor('#95efd2') + } + }) + } + .backgroundColor('#eeeeee') + .divider({ strokeWidth: 1, color: 0x222222 }) + .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果 + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/demoV1.ets b/entry/src/main/ets/pages/demoV1.ets new file mode 100644 index 0000000000000000000000000000000000000000..160bbfdf96cef4d50e7cc6a597ac858fa651d95f --- /dev/null +++ b/entry/src/main/ets/pages/demoV1.ets @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import router from '@ohos.router'; + +@Entry +@Component +struct Index { + build() { + Row() { + Column() { + Button($r("app.string.quickStart")) + .onClick(() => { + router.push({ url: 'pages/quickStart' }) + }) + Button($r("app.string.customConfig")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/customConfig' }) + }) + Button($r("app.string.customRefreshAnim")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/customRefreshAnim' }) + }) + Button($r("app.string.fullScreen")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/fullScreen' }) + }) + Button($r("app.string.tabsTestPage")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/tabsTestPage' }) + }) + Button($r("app.string.lazyForEachGuide")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/lazyForEachGuide' }) + }) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/demoV2.ets b/entry/src/main/ets/pages/demoV2.ets new file mode 100644 index 0000000000000000000000000000000000000000..cd968ad11db4866f98f6ecded808d22dab42297c --- /dev/null +++ b/entry/src/main/ets/pages/demoV2.ets @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import router from '@ohos.router'; + +@Entry +@Component +struct Index { + build() { + Row() { + Column() { + Button($r("app.string.quickStart")) + .onClick(() => { + router.push({ url: 'pages/quickStartV2' }) + }) + Button($r("app.string.customConfig")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/customConfigV2' }) + }) + Button($r("app.string.customRefreshAnim")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/customRefreshAnimV2' }) + }) + Button($r("app.string.fullScreen")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/fullScreenV2' }) + }) + Button($r("app.string.tabsTestPage")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/tabsTestPageV2' }) + }) + Button($r("app.string.lazyForEachGuide")) + .margin({ top: 10 }) + .onClick(() => { + router.push({ url: 'pages/lazyForEachGuideV2' }) + }) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/fullScreen.ets b/entry/src/main/ets/pages/fullScreen.ets index bf36f6d21c4e9380dff273844f6e01bd577b6acc..e06a0a46f6b64bd162e869ee3d439797b4e29003 100644 --- a/entry/src/main/ets/pages/fullScreen.ets +++ b/entry/src/main/ets/pages/fullScreen.ets @@ -15,14 +15,14 @@ import { PullToRefresh } from '@ohos/pulltorefresh' @Entry -@ComponentV2 +@Component struct Index { private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; private dataStrings: Array = [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), $r("app.string.Settings"), $r("app.string.LogOut")]; - @Local data: Array = this.dataStrings; + @State data: Array = this.dataStrings; // 需绑定列表或宫格组件 private scroller: Scroller = new Scroller(); @@ -33,8 +33,8 @@ struct Index { build() { Column() { PullToRefresh({ - // 可选项,列表组件所绑定的数据 - data: this.data, + // 必传项,列表组件所绑定的数据 + data: $data, // 必传项,需绑定传入主体布局内的列表或宫格组件 scroller: this.scroller, // 必传项,自定义主体布局,内部有列表或宫格组件 diff --git a/entry/src/main/ets/pages/fullScreenV2.ets b/entry/src/main/ets/pages/fullScreenV2.ets new file mode 100644 index 0000000000000000000000000000000000000000..ec902a3eb3f43022bad90c31b6fc857ba4a3ec3c --- /dev/null +++ b/entry/src/main/ets/pages/fullScreenV2.ets @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PullToRefreshV2 } from '@ohos/pulltorefresh' + +@Entry +@ComponentV2 +struct Index { + private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; + private dataStrings: Array = + [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), + $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), + $r("app.string.Settings"), $r("app.string.LogOut")]; + @Local data: Array = this.dataStrings; + // 需绑定列表或宫格组件 + private scroller: Scroller = new Scroller(); + + private getResourceString(res: Resource) { + return getContext().resourceManager.getStringSync(res.id) + } + + build() { + Column() { + PullToRefreshV2({ + // 可选项,列表组件所绑定的数据 + data: this.data, + // 必传项,需绑定传入主体布局内的列表或宫格组件 + scroller: this.scroller, + // 必传项,自定义主体布局,内部有列表或宫格组件 + customList: () => { + // 一个用@Builder修饰过的UI方法 + this.getListView(); + }, + // 可选项,下拉刷新回调 + onRefresh: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(this.getResourceString($r("app.string.RefreshSuccessful"))); + this.data = [...this.dataNumbers]; + }, 2000); + }); + }, + // 可选项,上拉加载更多回调 + onLoadMore: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(''); + this.data.push(`${this.getResourceString($r("app.string.AddedEntry"))} ${this.data.length}`); + }, 2000); + }); + }, + customLoad: null, + customRefresh: null, + }) + } + } + + @Builder + private getListView() { + List({ space: 0, scroller: this.scroller }) { + ForEach(this.data, (item: string) => { + ListItem() { + Text(item) + .fontSize(20) + } + .width('100%') + .height('100%') + .backgroundColor('#95efd2') + }) + } + .backgroundColor('#eeeeee') + .divider({ strokeWidth: 1, color: 0x222222 }) + .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果 + .scrollSnapAlign(ScrollSnapAlign.CENTER) + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/index.ets b/entry/src/main/ets/pages/index.ets index 160bbfdf96cef4d50e7cc6a597ac858fa651d95f..57e5877744a39a18d1bb92962a9174f23edfa193 100644 --- a/entry/src/main/ets/pages/index.ets +++ b/entry/src/main/ets/pages/index.ets @@ -20,34 +20,14 @@ struct Index { build() { Row() { Column() { - Button($r("app.string.quickStart")) + Button("DemoV1") .onClick(() => { - router.push({ url: 'pages/quickStart' }) + router.push({ url: 'pages/demoV1' }) }) - Button($r("app.string.customConfig")) + Button("DemoV2") .margin({ top: 10 }) .onClick(() => { - router.push({ url: 'pages/customConfig' }) - }) - Button($r("app.string.customRefreshAnim")) - .margin({ top: 10 }) - .onClick(() => { - router.push({ url: 'pages/customRefreshAnim' }) - }) - Button($r("app.string.fullScreen")) - .margin({ top: 10 }) - .onClick(() => { - router.push({ url: 'pages/fullScreen' }) - }) - Button($r("app.string.tabsTestPage")) - .margin({ top: 10 }) - .onClick(() => { - router.push({ url: 'pages/tabsTestPage' }) - }) - Button($r("app.string.lazyForEachGuide")) - .margin({ top: 10 }) - .onClick(() => { - router.push({ url: 'pages/lazyForEachGuide' }) + router.push({ url: 'pages/demoV2' }) }) } .width('100%') diff --git a/entry/src/main/ets/pages/interface.test.ets b/entry/src/main/ets/pages/interface.test.ets index 9fe72357c968ac902ae94441b061b548e2afd810..0f9deb6c74aeea5d289fc1bb52ce1180a6066d17 100644 --- a/entry/src/main/ets/pages/interface.test.ets +++ b/entry/src/main/ets/pages/interface.test.ets @@ -107,9 +107,9 @@ class MyDataSource extends BasicDataSource { } @Entry -@ComponentV2 +@Component struct MyComponent { - @Local data: MyDataSource = new MyDataSource(); + @State data: MyDataSource = new MyDataSource(); // 需绑定列表或宫格组件 private scroller: Scroller = new Scroller(); private timer: null | number = null; @@ -128,6 +128,8 @@ struct MyComponent { Column() { Column() { PullToRefresh({ + // 必传项,列表组件所绑定的数据 + data: $data, // 必传项,需绑定传入主体布局内的列表或宫格组件 scroller: this.scroller, // 必传项,自定义主体布局,内部有列表或宫格组件 diff --git a/entry/src/main/ets/pages/interface.testV2.ets b/entry/src/main/ets/pages/interface.testV2.ets new file mode 100644 index 0000000000000000000000000000000000000000..723eaa2e112ac9cfe4db85305cdc5570ea5d79b4 --- /dev/null +++ b/entry/src/main/ets/pages/interface.testV2.ets @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import router from '@ohos.router'; +import promptAction from '@ohos.promptAction'; +import mediaquery from '@ohos.mediaquery'; +import { PullToRefreshV2 } from '@ohos/pulltorefresh/index' + +class BasicDataSource implements IDataSource { + private listeners: DataChangeListener[] = new Array(); + + public totalCount(): number { + return 0; + } + + public getData(index: number): Object { + return index; + } + + // 为LazyForEach组件向其数据源处添加listener监听 + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + console.info('add listener'); + this.listeners.push(listener); + } + } + + // 为对应的LazyForEach组件在数据源处去除listener监听 + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + console.info('remove listener'); + this.listeners.splice(pos, 1); + } + } + + // 通知LazyForEach组件需要重载所有子组件 + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded(); + }) + } + + // 通知LazyForEach组件需要在index对应索引处添加子组件 + notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index); + }) + } + + // 通知LazyForEach组件需要在index对应索引处添加子组件 + notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index); + }) + } + + // 通知LazyForEach组件需要在index对应索引处删除该子组件 + notifyDataDelete(index: number): void { + this.listeners.forEach(listener => { + listener.onDataDelete(index); + }) + } + + notifyDataMove(from: number, to: number): void { + this.listeners.forEach(listener => { + listener.onDataMove(from, to); + }) + } +} + +class MyDataSource extends BasicDataSource { + private dataArray: string[] = []; + + public totalCount(): number { + return this.dataArray.length; + } + + public getData(index: number): Object { + return this.dataArray[index]; + } + + public addData(index: number, data: string): void { + this.dataArray.splice(index, 0, data); + this.notifyDataAdd(index); + } + + public pushData(data: string): void { + this.dataArray.push(data); + this.notifyDataAdd(this.dataArray.length - 1); + } + + public clear(): void { + this.dataArray = []; + } +} + +@Entry +@ComponentV2 +struct MyComponent { + @Local data: MyDataSource = new MyDataSource(); + // 需绑定列表或宫格组件 + private scroller: Scroller = new Scroller(); + private timer: null | number = null; + + private getResourceString(res:Resource){ + return getContext().resourceManager.getStringSync(res.id) + } + + aboutToAppear() { + for (let i = 1; i <= 20; i++) { + this.data.pushData(`Hello ${i}`); + } + } + + build() { + Column() { + Column() { + PullToRefreshV2({ + // 必传项,需绑定传入主体布局内的列表或宫格组件 + scroller: this.scroller, + // 必传项,自定义主体布局,内部有列表或宫格组件 + customList: () => { + // 一个用@Builder修饰过的UI方法 + this.getListView(); + }, + // 可选项,下拉刷新回调 + onRefresh: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + this.timer = setTimeout(() => { + resolve(this.getResourceString($r("app.string.RefreshSuccessful"))); + console.log(' 刷新成功'); + this.data.addData(0, 'ADD HEAD 0'); + }, 2000); + }); + }, + // 可选项,上拉加载更多回调 + onLoadMore: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + this.timer = setTimeout(() => { + resolve(''); + console.log('上拉加载完成'); + this.data.pushData(`Hello ${this.data.totalCount()}`); + }, 2000); + }); + }, + customLoad: null, + customRefresh: null, + }) + } + + Column() { + //添加this.data.totalCount接口的性能测试按钮 + Button('this.data.totalCount') + .width('90%') + .margin({ top: 20 }) + .onClick(() => { + try { + const BASE_COUNT = 2000 // 循环次数:测试普通接口性能 + let startTime = new Date().getTime() + for (let index = 0; index < BASE_COUNT; index++) { + this.data.totalCount() + } + let endTime = new Date().getTime(); + let averageTime = ((endTime - startTime) * 1000) / 2000; + console.info(`${startTime},${endTime}`) + //循环2000次之后的平均时间 + promptAction.showToast({ message: "averageTime: " + averageTime + "us", duration: 2000 }); + } catch (e) { + promptAction.showToast({ message: JSON.stringify(e) }); + } + + }) + } + + Column() { + //添加this.data.getData接口的性能测试按钮 + Button('this.data.getData') + .width('90%') + .margin({ top: 20 }) + .onClick(() => { + try { + const BASE_COUNT = 2000 // 循环次数:测试普通接口性能 + let startTime = new Date().getTime() + for (let index = 0; index < BASE_COUNT; index++) { + this.data.getData(2) + } + let endTime = new Date().getTime(); + let averageTime = ((endTime - startTime) * 1000) / 2000; + console.info(`${startTime},${endTime}`) + //循环2000次之后的平均时间 + promptAction.showToast({ message: "averageTime: " + averageTime + "us", duration: 2000 }); + } catch (e) { + promptAction.showToast({ message: JSON.stringify(e) }); + } + + }) + } + + Column() { + //添加this.data.addData接口的性能测试按钮 + Button('this.data.addData') + .width('90%') + .margin({ top: 20 }) + .onClick(() => { + try { + const BASE_COUNT = 2000 // 循环次数:测试普通接口性能 + let startTime = new Date().getTime() + for (let index = 0; index < BASE_COUNT; index++) { + this.data.addData(0, 'ADD HEAD 0') + } + let endTime = new Date().getTime(); + let averageTime = ((endTime - startTime) * 1000) / 2000; + console.info(`${startTime},${endTime}`) + //循环2000次之后的平均时间 + promptAction.showToast({ message: "averageTime: " + averageTime + "us", duration: 2000 }); + } catch (e) { + promptAction.showToast({ message: JSON.stringify(e) }); + } + + }) + } + + Column() { + //添加this.data.pushData接口的性能测试按钮 + Button('this.data.pushData') + .width('90%') + .margin({ top: 20 }) + .onClick(() => { + try { + const BASE_COUNT = 2000 // 循环次数:测试普通接口性能 + let startTime = new Date().getTime() + for (let index = 0; index < BASE_COUNT; index++) { + this.data.pushData('Hello') + } + let endTime = new Date().getTime(); + let averageTime = ((endTime - startTime) * 1000) / 2000; + console.info(`${startTime},${endTime}`) + //循环2000次之后的平均时间 + promptAction.showToast({ message: "averageTime: " + averageTime + "us", duration: 2000 }); + } catch (e) { + promptAction.showToast({ message: JSON.stringify(e) }); + } + + }) + } + }.height('50%') + + } + + @Builder + private getListView() { + List({ space: 3, scroller: this.scroller }) { + LazyForEach(this.data, (item: string, index?: number) => { + ListItem() { + Row() { + Text(item).fontSize(50)//组件挂载显示触发 + .onAppear(() => { + if (index) { + console.log(" onAppear: index=" + index + ' content= ' + this.data.getData(index)); + } + })//组件卸载载显示触发 + .onDisAppear(() => { + if (index) { + console.log(" onDisAppear: index=" + index + ' content= ' + this.data.getData(index)); + } + }) + }.margin({ left: 10, right: 10 }) + } + }, (item: string) => item); + }.cachedCount(5) + .backgroundColor('#eeeeee') + .divider({ strokeWidth: 1, color: 0x222222 }) + .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果 + } + + aboutToDisappear() { + clearTimeout(this.timer); + this.data.clear(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/lazyForEachGuide.ets b/entry/src/main/ets/pages/lazyForEachGuide.ets index e679a4f0f836e240dc132f4acfff41a940613d08..8de1dda9ab6d349d7310f0f2fc1cad140810ad5a 100644 --- a/entry/src/main/ets/pages/lazyForEachGuide.ets +++ b/entry/src/main/ets/pages/lazyForEachGuide.ets @@ -104,9 +104,9 @@ class MyDataSource extends BasicDataSource { } @Entry -@ComponentV2 +@Component struct MyComponent { - @Local data: MyDataSource = new MyDataSource(); + @State data: MyDataSource = new MyDataSource(); // 需绑定列表或宫格组件 private scroller: Scroller = new Scroller(); private timer: null | number = null; @@ -124,6 +124,8 @@ struct MyComponent { build() { Column() { PullToRefresh({ + // 必传项,列表组件所绑定的数据 + data: $data, // 必传项,需绑定传入主体布局内的列表或宫格组件 scroller: this.scroller, // 必传项,自定义主体布局,内部有列表或宫格组件 diff --git a/entry/src/main/ets/pages/lazyForEachGuideV2.ets b/entry/src/main/ets/pages/lazyForEachGuideV2.ets new file mode 100644 index 0000000000000000000000000000000000000000..527c65f03e7a6fc18bd0627bb806ffbd05c77286 --- /dev/null +++ b/entry/src/main/ets/pages/lazyForEachGuideV2.ets @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PullToRefreshV2 } from '@ohos/pulltorefresh' + +class BasicDataSource implements IDataSource { + private listeners: DataChangeListener[] = new Array(); + + public totalCount(): number { + return 0; + } + + public getData(index: number): Object { + return index; + } + + // 为LazyForEach组件向其数据源处添加listener监听 + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + console.info('add listener'); + this.listeners.push(listener); + } + } + + // 为对应的LazyForEach组件在数据源处去除listener监听 + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + console.info('remove listener'); + this.listeners.splice(pos, 1); + } + } + + // 通知LazyForEach组件需要重载所有子组件 + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded(); + }) + } + + // 通知LazyForEach组件需要在index对应索引处添加子组件 + notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index); + }) + } + + // 通知LazyForEach组件需要在index对应索引处添加子组件 + notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index); + }) + } + + // 通知LazyForEach组件需要在index对应索引处删除该子组件 + notifyDataDelete(index: number): void { + this.listeners.forEach(listener => { + listener.onDataDelete(index); + }) + } + + notifyDataMove(from: number, to: number): void { + this.listeners.forEach(listener => { + listener.onDataMove(from, to); + }) + } +} + +class MyDataSource extends BasicDataSource { + private dataArray: string[] = []; + + public totalCount(): number { + return this.dataArray.length; + } + + public getData(index: number): Object { + return this.dataArray[index]; + } + + public addData(index: number, data: string): void { + this.dataArray.splice(index, 0, data); + this.notifyDataAdd(index); + } + + public pushData(data: string): void { + this.dataArray.push(data); + this.notifyDataAdd(this.dataArray.length - 1); + } + + public clear(): void { + this.dataArray = []; + } +} + +@Entry +@ComponentV2 +struct MyComponent { + @Local data: MyDataSource = new MyDataSource(); + // 需绑定列表或宫格组件 + private scroller: Scroller = new Scroller(); + private timer: null | number = null; + + private getResourceString(res: Resource) { + return getContext().resourceManager.getStringSync(res.id) + } + + aboutToAppear() { + for (let i = 1; i <= 20; i++) { + this.data.pushData(`Hello ${i}`); + } + } + + build() { + Column() { + PullToRefreshV2({ + // 必传项,需绑定传入主体布局内的列表或宫格组件 + scroller: this.scroller, + // 必传项,自定义主体布局,内部有列表或宫格组件 + customList: () => { + // 一个用@Builder修饰过的UI方法 + this.getListView(); + }, + // 可选项,下拉刷新回调 + onRefresh: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + this.timer = setTimeout(() => { + resolve(this.getResourceString($r("app.string.RefreshSuccessful"))); + console.log(' 刷新成功'); + this.data.addData(0, 'ADD HEAD 0'); + }, 2000); + }); + }, + // 可选项,上拉加载更多回调 + onLoadMore: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + this.timer = setTimeout(() => { + resolve(''); + console.log('上拉加载完成'); + for (let index = 0; index < 30; index++) { + this.data.pushData(`Hello ${this.data.totalCount()}`); + } + }, 2000); + }); + }, + customLoad: null, + customRefresh: null, + }) + } + } + + @Builder + private getListView() { + List({ space: 3, scroller: this.scroller }) { + LazyForEach(this.data, (item: string, index?: number) => { + ListItem() { + Row() { + Text(item).fontSize(50)//组件挂载显示触发 + .onAppear(() => { + if (index) { + console.log(" onAppear: index=" + index + ' content= ' + this.data.getData(index)); + } + })//组件卸载载显示触发 + .onDisAppear(() => { + if (index) { + console.log(" onDisAppear: index=" + index + ' content= ' + this.data.getData(index)); + } + }) + }.margin({ left: 10, right: 10 }) + } + }, (item: string) => item); + }.cachedCount(5) + .backgroundColor('#eeeeee') + .divider({ strokeWidth: 1, color: 0x222222 }) + .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果 + } + + aboutToDisappear() { + clearTimeout(this.timer); + this.data.clear(); + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/quickStart.ets b/entry/src/main/ets/pages/quickStart.ets index eb35e5377653f67e7d9b6a7bf6e882c1cfbce29b..8d6a82caf5fabc994101436c593e231592b20e5d 100644 --- a/entry/src/main/ets/pages/quickStart.ets +++ b/entry/src/main/ets/pages/quickStart.ets @@ -15,14 +15,14 @@ import { PullToRefresh } from '@ohos/pulltorefresh' @Entry -@ComponentV2 +@Component struct Index { private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; private dataStrings: Array = [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), $r("app.string.Settings"), $r("app.string.LogOut")]; - @Local data: Array = this.dataStrings; + @State data: Array = this.dataStrings; // 需绑定列表或宫格组件 private scroller: Scroller = new Scroller(); @@ -33,8 +33,8 @@ struct Index { build() { Column() { PullToRefresh({ - // 可选项,列表组件所绑定的数据 - data: this.data, + // 必传项,列表组件所绑定的数据 + data: $data, // 必传项,需绑定传入主体布局内的列表或宫格组件 scroller: this.scroller, // 必传项,自定义主体布局,内部有列表或宫格组件 diff --git a/entry/src/main/ets/pages/quickStartV2.ets b/entry/src/main/ets/pages/quickStartV2.ets new file mode 100644 index 0000000000000000000000000000000000000000..865588328ef653f5eb63f97570117e913c43f751 --- /dev/null +++ b/entry/src/main/ets/pages/quickStartV2.ets @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PullToRefreshV2 } from '@ohos/pulltorefresh' + +@Entry +@ComponentV2 +struct Index { + private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; + private dataStrings: Array = + [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), + $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), + $r("app.string.Settings"), $r("app.string.LogOut")]; + @Local data: Array = this.dataStrings; + // 需绑定列表或宫格组件 + private scroller: Scroller = new Scroller(); + + private getResourceString(res: Resource) { + return getContext().resourceManager.getStringSync(res.id) + } + + build() { + Column() { + PullToRefreshV2({ + // 可选项,列表组件所绑定的数据 + data: this.data, + // 必传项,需绑定传入主体布局内的列表或宫格组件 + scroller: this.scroller, + // 必传项,自定义主体布局,内部有列表或宫格组件 + customList: () => { + // 一个用@Builder修饰过的UI方法 + this.getListView(); + }, + // 可选项,下拉刷新回调 + onRefresh: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(this.getResourceString($r("app.string.RefreshSuccessful"))); + this.data = [...this.dataNumbers]; + }, 2000); + }); + }, + // 可选项,上拉加载更多回调 + onLoadMore: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(''); + this.data.push(`${this.getResourceString($r("app.string.AddedEntry"))} ${this.data.length}`); + }, 2000); + }); + }, + customLoad: null, + customRefresh: null, + }) + } + } + + @Builder + private getListView() { + List({ space: 20, scroller: this.scroller }) { + ForEach(this.data, (item: string) => { + ListItem() { + Text(item) + .width('100%') + .height(150) + .fontSize(20) + .textAlign(TextAlign.Center) + .backgroundColor('#95efd2') + } + }) + } + .backgroundColor('#eeeeee') + .divider({ strokeWidth: 1, color: 0x222222 }) + .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果 + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/tabsTestPage.ets b/entry/src/main/ets/pages/tabsTestPage.ets index 993290783a3f04e11630fc377c73dc1e12c0aadf..8cf47626573efd5853611b7d8b6b0b65bce62c26 100644 --- a/entry/src/main/ets/pages/tabsTestPage.ets +++ b/entry/src/main/ets/pages/tabsTestPage.ets @@ -15,11 +15,11 @@ import { PullToRefresh } from '@ohos/pulltorefresh' @Entry -@ComponentV2 +@Component struct TabsExample { - @Local fontColor: string = '#182431' - @Local selectedFontColor: string = '#007DFF' - @Local currentIndex: number = 0 + @State fontColor: string = '#182431' + @State selectedFontColor: string = '#007DFF' + @State currentIndex: number = 0 private controller: TabsController = new TabsController() @Builder @@ -73,14 +73,14 @@ struct TabsExample { } } -@ComponentV2 +@Component struct PullToRefreshDemo { private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; private dataStrings: Array = [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), $r("app.string.Settings"), $r("app.string.LogOut")]; - @Local data: Array = this.dataStrings; + @State data: Array = this.dataStrings; // 需绑定列表或宫格组件 private scroller: Scroller = new Scroller(); @@ -91,8 +91,8 @@ struct PullToRefreshDemo { build() { Column() { PullToRefresh({ - // 可选项,列表组件所绑定的数据 - data: this.data, + // 必传项,列表组件所绑定的数据 + data: $data, // 必传项,需绑定传入主体布局内的列表或宫格组件 scroller: this.scroller, // 必传项,自定义主体布局,内部有列表或宫格组件 diff --git a/entry/src/main/ets/pages/tabsTestPageV2.ets b/entry/src/main/ets/pages/tabsTestPageV2.ets new file mode 100644 index 0000000000000000000000000000000000000000..a183216939adb0e74dc09104121788e2f63477a4 --- /dev/null +++ b/entry/src/main/ets/pages/tabsTestPageV2.ets @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PullToRefreshV2 } from '@ohos/pulltorefresh' + +@Entry +@ComponentV2 +struct TabsExample { + @Local fontColor: string = '#182431' + @Local selectedFontColor: string = '#007DFF' + @Local currentIndex: number = 0 + private controller: TabsController = new TabsController() + + @Builder + TabBuilder(index: number, name: string) { + Column() { + Text(name) + .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor) + .fontSize(16) + .fontWeight(this.currentIndex === index ? 500 : 400) + .lineHeight(22) + .margin({ top: 17, bottom: 7 }) + Divider() + .strokeWidth(2) + .color('#007DFF') + .opacity(this.currentIndex === index ? 1 : 0) + }.width('100%') + } + + build() { + Column() { + Tabs({ barPosition: BarPosition.Start, controller: this.controller }) { + TabContent() { + // Column().width('100%').height('100%').backgroundColor('#00CB87') + PullToRefreshDemo() + }.tabBar(this.TabBuilder(0, 'green')) + + TabContent() { + Column().width('100%').height('100%').backgroundColor('#007DFF') + }.tabBar(this.TabBuilder(1, 'blue')) + + TabContent() { + Column().width('100%').height('100%').backgroundColor('#FFBF00') + }.tabBar(this.TabBuilder(2, 'yellow')) + + TabContent() { + Column().width('100%').height('100%').backgroundColor('#E67C92') + }.tabBar(this.TabBuilder(3, 'pink')) + } + .vertical(false) + .barMode(BarMode.Fixed) + .barWidth(360) + .barHeight(56) + .animationDuration(400) + .onChange((index: number) => { + this.currentIndex = index + }) + .width('100%') + .margin({ top: 52 }) + .backgroundColor('#F1F3F5') + }.width('100%') + } +} + +@ComponentV2 +struct PullToRefreshDemo { + private dataNumbers: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; + private dataStrings: Array = + [$r("app.string.MyComments"), $r("app.string.RelatedToMe"), $r("app.string.PersonalCenter1"), + $r("app.string.PersonalCenter2"), $r("app.string.PersonalCenter3"), $r("app.string.MyReleases"), + $r("app.string.Settings"), $r("app.string.LogOut")]; + @Local data: Array = this.dataStrings; + // 需绑定列表或宫格组件 + private scroller: Scroller = new Scroller(); + + private getResourceString(res: Resource) { + return getContext().resourceManager.getStringSync(res.id) + } + + build() { + Column() { + PullToRefreshV2({ + // 可选项,列表组件所绑定的数据 + data: this.data, + // 必传项,需绑定传入主体布局内的列表或宫格组件 + scroller: this.scroller, + // 必传项,自定义主体布局,内部有列表或宫格组件 + customList: () => { + // 一个用@Builder修饰过的UI方法 + this.getListView(); + }, + // 可选项,下拉刷新回调 + onRefresh: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(this.getResourceString($r("app.string.RefreshSuccessful"))); + this.data = [...this.dataNumbers]; + }, 2000); + }); + }, + // 可选项,上拉加载更多回调 + onLoadMore: () => { + return new Promise((resolve, reject) => { + // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据 + setTimeout(() => { + resolve(''); + this.data.push(`${this.getResourceString($r("app.string.AddedEntry"))} ${this.data.length}`) + }, 2000); + }); + }, + customLoad: null, + customRefresh: null, + }) + } + } + + @Builder + private getListView() { + List({ space: 20, scroller: this.scroller }) { + ForEach(this.data, (item: string) => { + ListItem() { + Text(item) + .width('100%') + .height(150) + .fontSize(20) + .textAlign(TextAlign.Center) + .backgroundColor('#95efd2') + } + }) + } + .backgroundColor('#eeeeee') + .divider({ strokeWidth: 1, color: 0x222222 }) + .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果 + } +} diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 3c5b296f7bd23363f2b31b51af53d8f43b3cf92c..6044f0db56c5f8fa8a9314bd3eece9c4a11ad85c 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -7,6 +7,15 @@ "pages/tabsTestPage", "pages/lazyForEachGuide", "pages/fullScreen", - "pages/interface.test" + "pages/interface.test", + "pages/quickStartV2", + "pages/customConfigV2", + "pages/customRefreshAnimV2", + "pages/tabsTestPageV2", + "pages/lazyForEachGuideV2", + "pages/fullScreenV2", + "pages/interface.testV2", + "pages/demoV1", + "pages/demoV2" ] -} +} \ No newline at end of file diff --git a/library/index.ets b/library/index.ets index fd3cacc9739579cf62ffe3fcceecabb7fba3ad2a..2260f1fe0c2b13d83358873c28625737392df9db 100644 --- a/library/index.ets +++ b/library/index.ets @@ -15,3 +15,5 @@ export { PullToRefreshConfigurator } from './src/main/ets/components/PullToRefresh/PullToRefreshConfigurator' export { PullToRefresh } from './src/main/ets/components/PullToRefresh/PullToRefresh' + +export { PullToRefreshV2 } from './src/main/ets/components/PullToRefresh/PullToRefreshV2' diff --git a/library/oh-package.json5 b/library/oh-package.json5 index eac17b890eb77240a3b6dcd7b9cfd229186af619..b7d754db5997fdca808f7b8c3915f4ba07c77727 100644 --- a/library/oh-package.json5 +++ b/library/oh-package.json5 @@ -14,7 +14,7 @@ "main": "index.ets", "repository": "https://gitee.com/openharmony-sig/PullToRefresh", "type": "module", - "version": "2.1.1-rc.0", + "version": "2.1.1-rc.1", "dependencies": {}, "tags": [ "UI" diff --git a/library/src/main/ets/components/PullToRefresh/PullToRefresh.ets b/library/src/main/ets/components/PullToRefresh/PullToRefresh.ets index da6648d5b3963ab855f977926143048dded74e80..1e0d03e6313dca84388d6a2f29f68959c14bb4d1 100644 --- a/library/src/main/ets/components/PullToRefresh/PullToRefresh.ets +++ b/library/src/main/ets/components/PullToRefresh/PullToRefresh.ets @@ -23,22 +23,22 @@ const IS_PULL_UP_1 = 41; const IS_PULL_UP_2 = 42; const IS_LOADING = 5; -@ComponentV2 +@Component export struct PullToRefresh { - @Param data?: Object[] | undefined = undefined; - @Param scroller: Scroller = new Scroller(); + @Link data: Object[]; + scroller: Scroller = new Scroller(); @BuilderParam customList?: () => void; - @Param refreshConfigurator?: PullToRefreshConfigurator = new PullToRefreshConfigurator(); - @Param mWidth?: Length = '100%'; - @Param mHeight?: Length = '100%'; - @Param onRefresh?: () => Promise = () => { + refreshConfigurator?: PullToRefreshConfigurator; + mWidth?: Length = '100%'; + mHeight?: Length = '100%'; + onRefresh?: () => Promise = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('刷新失败'); }, 1000); }); }; - @Param onLoadMore?: () => Promise = () => { + onLoadMore?: () => Promise = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(''); @@ -48,21 +48,21 @@ export struct PullToRefresh { // 自定义下拉动画 @BuilderParam customRefresh?: (() => void) | null; //开启自定义下拉动画 - @Param onAnimPullDown?: (value?: number, width?: number, height?: number) => void | undefined = undefined; - @Param onAnimRefreshing?: (value?: number, width?: number, height?: number) => void | undefined = undefined; + onAnimPullDown?: (value?: number, width?: number, height?: number) => void; + onAnimRefreshing?: (value?: number, width?: number, height?: number) => void; // 自定义上拉动画 @BuilderParam customLoad?: (() => void) | null; - @Param onAnimPullUp?: (value?: number, width?: number, height?: number) => void | undefined = undefined; - @Param onAnimLoading?: (value?: number, width?: number, height?: number) => void | undefined = undefined; + onAnimPullUp?: (value?: number, width?: number, height?: number) => void; + onAnimLoading?: (value?: number, width?: number, height?: number) => void; //-----------------------------以下为组件内自用属性-----------------------------// - @Local private mHeightNumber?: number = 0; - @Local private trYTop?: number = 0; - @Local private trYBottom?: number = 0; - @Local private state?: number = IS_FREE; - @Local private refreshText?: string = ''; - @Local private loadText?: ResourceStr = ''; - @Local private angle1?: number | string = 0; - @Local private angle2?: number | string = 0; + @State private mHeightNumber?: number = 0; + @State private trYTop?: number = 0; + @State private trYBottom?: number = 0; + @State private state?: number = IS_FREE; + @State private refreshText?: string = ''; + @State private loadText?: ResourceStr = ''; + @State private angle1?: number | string = 0; + @State private angle2?: number | string = 0; private mWidthNumber?: number = 0; private touchYOld?: number = 0; private touchYNew?: number = 0; @@ -82,13 +82,14 @@ export struct PullToRefresh { private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }) aboutToAppear() { - // if (!this.refreshConfigurator) { - // this.refreshConfigurator = new PullToRefreshConfigurator(); - // } + if (!this.refreshConfigurator) { + this.refreshConfigurator = new PullToRefreshConfigurator(); + } } private initCanvas(): void { if (this.refreshRingOx == 0) { + if (this.canvasRefresh !== undefined && this.refreshConfigurator !== undefined) { this.canvasRefresh.strokeStyle = this.refreshConfigurator.getRefreshColor(); this.canvasRefresh.fillStyle = this.refreshConfigurator.getRefreshColor(); diff --git a/library/src/main/ets/components/PullToRefresh/PullToRefreshV2.ets b/library/src/main/ets/components/PullToRefresh/PullToRefreshV2.ets new file mode 100644 index 0000000000000000000000000000000000000000..afe968d6bba510aef5ae0b80b4b7b64fde46e7db --- /dev/null +++ b/library/src/main/ets/components/PullToRefresh/PullToRefreshV2.ets @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PullToRefreshConfigurator } from './PullToRefreshConfigurator' + +const IS_FREE = 0; +const IS_PULL_DOWN_1 = 11; +const IS_PULL_DOWN_2 = 12; +const IS_REFRESHING = 2; +const IS_REFRESHED = 3; +const IS_PULL_UP_1 = 41; +const IS_PULL_UP_2 = 42; +const IS_LOADING = 5; + +@ComponentV2 +export struct PullToRefreshV2 { + @Param data?: Object[] | undefined = undefined; + @Param scroller: Scroller = new Scroller(); + @BuilderParam customList?: () => void; + @Param refreshConfigurator?: PullToRefreshConfigurator = new PullToRefreshConfigurator(); + @Param mWidth?: Length = '100%'; + @Param mHeight?: Length = '100%'; + @Param onRefresh?: () => Promise = () => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve('刷新失败'); + }, 1000); + }); + }; + @Param onLoadMore?: () => Promise = () => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(''); + }, 1000); + }); + }; + // 自定义下拉动画 + @BuilderParam customRefresh?: (() => void) | null; + //开启自定义下拉动画 + @Param onAnimPullDown?: (value?: number, width?: number, height?: number) => void | undefined = undefined; + @Param onAnimRefreshing?: (value?: number, width?: number, height?: number) => void | undefined = undefined; + // 自定义上拉动画 + @BuilderParam customLoad?: (() => void) | null; + @Param onAnimPullUp?: (value?: number, width?: number, height?: number) => void | undefined = undefined; + @Param onAnimLoading?: (value?: number, width?: number, height?: number) => void | undefined = undefined; + //-----------------------------以下为组件内自用属性-----------------------------// + @Local private mHeightNumber?: number = 0; + @Local private trYTop?: number = 0; + @Local private trYBottom?: number = 0; + @Local private state?: number = IS_FREE; + @Local private refreshText?: string = ''; + @Local private loadText?: ResourceStr = ''; + @Local private angle1?: number | string = 0; + @Local private angle2?: number | string = 0; + private mWidthNumber?: number = 0; + private touchYOld?: number = 0; + private touchYNew?: number = 0; + private listOffsetOld?: number = 0; + private listOffsetNew?: number = 0; + private canvasSetting?: RenderingContextSettings = new RenderingContextSettings(true); + private canvasRefresh?: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.canvasSetting); + private value?: number = 0; + private timer?: number; + private refreshRingOx?: number = 0; + private refreshRingOy?: number = 0; + private refreshRingRadius?: number = 0; + private refreshPoint1x?: number = 0; + private refreshPoint1y?: number = 0; + private refreshPoint2x?: number = 0; + private refreshPoint2y?: number = 0; + private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }) + + aboutToAppear() { + // if (!this.refreshConfigurator) { + // this.refreshConfigurator = new PullToRefreshConfigurator(); + // } + } + + private initCanvas(): void { + if (this.refreshRingOx == 0) { + if (this.canvasRefresh !== undefined && this.refreshConfigurator !== undefined) { + this.canvasRefresh.strokeStyle = this.refreshConfigurator.getRefreshColor(); + this.canvasRefresh.fillStyle = this.refreshConfigurator.getRefreshColor(); + this.canvasRefresh.lineWidth = this.refreshConfigurator.getRefreshHeight() / 60 + 1; + } + if (this.refreshConfigurator !== undefined) { + this.refreshRingOx = this.refreshConfigurator.getRefreshWidth() / 2; // 圆心x坐标 + this.refreshRingOy = this.refreshConfigurator.getRefreshHeight() / 2; // 圆心y坐标 + this.refreshRingRadius = this.refreshConfigurator.getRefreshHeight() / 4; // 半径 + this.refreshPoint1x = this.refreshRingOx + this.refreshRingRadius * Math.cos(Math.PI * 150 / 180); + this.refreshPoint1y = this.refreshRingOy + this.refreshRingRadius * Math.sin(Math.PI * 150 / 180); + this.refreshPoint2x = this.refreshRingOx + this.refreshRingRadius * Math.cos(Math.PI * -30 / 180); + this.refreshPoint2y = this.refreshRingOy + this.refreshRingRadius * Math.sin(Math.PI * -30 / 180); + } + } + } + + build() { + Column() { + // 下拉刷新动画部分 + Stack() { + this.headerUI() + } + .width('100%') + .height(this.trYTop !== undefined ? this.trYTop : 0) + .backgroundColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshBackgroundColor() : 0) + + // 主体列表部分 + Column() { + if (this.customList !== undefined) { + this.customList() + } + } + .width('100%') + .height((this.mHeightNumber !== undefined ? this.mHeightNumber : 0) - (this.trYTop !== undefined ? this.trYTop : 0) + (this.trYBottom !== undefined ? this.trYBottom : 0)) + + // 上拉加载动画部分 + Stack() { + this.footerUI() + } + .width('100%') + .height(this.trYBottom !== undefined ? -this.trYBottom : 0) + .backgroundColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadBackgroundColor() : 0) + } + .width(this.mWidth === undefined ? '100%' : this.mWidth) + .height(this.mHeight === undefined ? '100%' : this.mHeight) + .onAreaChange((oldValue: Area, newValue: Area) => { + this.mWidthNumber = Math.round(newValue.width as number); + this.mHeightNumber = Math.round(newValue.height as number); + }) + .parallelGesture( + PanGesture(this.panOption) + .onActionStart((event?: GestureEvent) => { + if (event !== undefined) { + this.touchYOld = event.offsetY; + } + }) + .onActionUpdate((event?: GestureEvent) => { + if (event !== undefined) { + this.onActionUpdate(event); + } + }) + .onActionEnd(() => { + this.onActionEnd(); + }) + ) + } + + @Builder + private headerUI() { + if (this.customRefresh !== undefined && this.customRefresh !== null) { + Column() { + this.customRefresh() + } + .width('100%') + .height('100%') + .visibility((this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2 || this.state == IS_REFRESHING) ? Visibility.Visible : Visibility.Hidden) + } else { + Stack() { + Text(this.refreshText) + .textAlign(TextAlign.Center) + .fontColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshTextColor() : 0) + .fontSize(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshTextSize() : 0) + Stack() { + Canvas(this.canvasRefresh) + .width('100%') + .height('100%') + .onReady(() => { + this.initCanvas(); + }) + .visibility(this.state == IS_PULL_DOWN_2 ? Visibility.Visible : Visibility.Hidden) + LoadingProgress() + .width(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshHeight() : 0) + .height(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshHeight() : 0) + .color(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshColor() : 0) + .visibility(this.state == IS_REFRESHING ? Visibility.Visible : Visibility.Hidden) + } + .width(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshWidth() : 0) + .height(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshHeight() : 0) + } + .width('100%') + .height('100%') + } + } + + @Builder + private footerUI() { + if (this.customLoad !== undefined && this.customLoad !== null) { + Column() { + this.customLoad() + } + .width('100%') + .height('100%') + .visibility((this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2 || this.state == IS_LOADING ) ? Visibility.Visible : Visibility.Hidden) + } else { + Row() { + Stack() { + Image($r('app.media.icon_up')) + .width('100%') + .height('100%') + .objectFit(ImageFit.Contain) + .visibility(this.state == IS_PULL_UP_2 ? Visibility.Visible : Visibility.Hidden) + .rotate({ + z: 1, + angle: this.angle1 !== undefined ? this.angle1 : 0 + }) + Image($r('app.media.icon_load')) + .width('100%') + .height('100%') + .objectFit(ImageFit.Contain) + .visibility(this.state == IS_LOADING ? Visibility.Visible : Visibility.Hidden) + .rotate({ + z: 1, + angle: this.angle2 !== undefined ? this.angle2 : 0 + }) + } + .width(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadImgHeight() : 0) + .height(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadImgHeight() : 0) + + Text(this.loadText) + .constraintSize({maxWidth: this.mWidth}) + .height('100%') + .textAlign(TextAlign.Center) + .margin({ left: 8 }) + .fontColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextColor() : 0) + .fontSize(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getLoadTextSize() : 0) + } + .height('100%') + } + } + + private onActionUpdate(event: GestureEvent): void { + if (this.state !== undefined && this.refreshConfigurator !== undefined && this.touchYOld !== undefined) { + if (this.state == IS_FREE || + this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2 || + this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) { + let maxTranslate = this.refreshConfigurator.getMaxTranslate() + let loadImgHeight = this.refreshConfigurator.getLoadImgHeight() + let refreshHeight = this.refreshConfigurator.getRefreshHeight() + if (!this.scroller.currentOffset()) { + return; + } + this.touchYNew = event.offsetY; + + // 当前手势是否下拉 + let distanceY = this.touchYNew - this.touchYOld; + let isPullAction = distanceY > 0; + + //兼容页面滑动到顶部时,this.scroller.currentOffset().yOffset返回0.000000....的浮点数的情况 + let yOffset: number = this.scroller.currentOffset().yOffset; + let isTop: boolean = yOffset == 0 ? true : false; + if (yOffset > -0.0001 && yOffset < 0.001) { + isTop = true; + } + + if ((this.state == IS_FREE && isTop && isPullAction) || // 处于自由状态且列表处于顶部位置 并且 当前手势是下拉手势 + this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2) { // 处于下拉状态中 + if (this.refreshConfigurator.getHasRefresh()) { + if (this.touchYOld !== undefined && refreshHeight !== undefined) { + // 获取最新位移距离 + let trY = this.touchYNew - this.touchYOld; + + //防止下拉回滑时list组件底层跟着滑动 + if(trY < 0) { + this.scroller.scrollTo({xOffset: 0, yOffset: 0}) + } + + // 计算当前需要位移的总距离 + this.trYTop = this.getTranslateYOfRefresh(trY); + if (this.trYTop < refreshHeight) { + this.state = IS_PULL_DOWN_1; + } else { + this.state = IS_PULL_DOWN_2; + } + // 如果没有自定义刷新动画,就执行内置动画下拉时的逻辑 + if (!this.customRefresh && maxTranslate !== undefined) { + this.drawRefreshView(this.trYTop / maxTranslate); + } + // 如果有下拉中动画回调,就执行下拉中动画回调 + if (this.onAnimPullDown && maxTranslate !== undefined) { + this.onAnimPullDown(this.trYTop / maxTranslate, this.mWidthNumber, this.trYTop); + } + } + } + } else if (this.refreshConfigurator.getHasLoadMore()) { + this.listOffsetNew = this.scroller.currentOffset().yOffset; + // 列表处于底部位置且上滑时,2.已上滑时 + try { + if (this.touchYOld !== undefined) { + if ((this.state == IS_FREE && this.listOffsetOld == this.listOffsetNew && this.listOffsetOld != 0 && this.touchYNew < this.touchYOld && this.scroller.isAtEnd()) || + this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) { + // 获取最新的位移距离 + let trY = this.touchYNew - this.touchYOld; + // 计算当前需要位移的总距离 + this.trYBottom = this.getTranslateYOfLoadMore(trY); + if (loadImgHeight !== undefined && this.trYBottom > -loadImgHeight) { + this.state = IS_PULL_UP_1; + } else { + this.state = IS_PULL_UP_2; + } + // 如果没有自定义加载更多动画,就执行内置动画上拉时的逻辑 + if (!this.customLoad && maxTranslate !== undefined) { + this.drawLoadView(true, -this.trYBottom / maxTranslate); + } + // 如果有上拉中动画回调,就执行上拉中动画回调 + if (this.onAnimPullUp) { + if (this.trYBottom !== undefined && maxTranslate !== undefined) { + this.onAnimPullUp(-this.trYBottom / maxTranslate, this.mWidthNumber, -this.trYBottom); + } + } + } + } + } catch (error) { + if (this.touchYOld !== undefined) { + if ((this.state == IS_FREE && this.listOffsetOld == this.listOffsetNew && this.listOffsetOld != 0 && this.touchYNew < this.touchYOld) || + this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) { + if (this.touchYNew !== undefined && this.touchYOld !== undefined && loadImgHeight !== undefined) { + // 获取最新的位移距离 + let trY = this.touchYNew - this.touchYOld; + // 计算当前需要位移的总距离 + this.trYBottom = this.getTranslateYOfLoadMore(trY); + if (this.trYBottom > -loadImgHeight) { + this.state = IS_PULL_UP_1; + } else { + this.state = IS_PULL_UP_2; + } + // 如果没有自定义加载更多动画,就执行内置动画上拉时的逻辑 + if (!this.customLoad && maxTranslate !== undefined) { + this.drawLoadView(true, -this.trYBottom / maxTranslate); + } + // 如果有上拉中动画回调,就执行上拉中动画回调 + if (this.onAnimPullUp) { + if (this.trYBottom !== undefined && maxTranslate !== undefined) { + this.onAnimPullUp(-this.trYBottom / maxTranslate, this.mWidthNumber, -this.trYBottom); + } + } + } + } + } + } + this.listOffsetOld = this.listOffsetNew; + } + this.touchYOld = this.touchYNew; + } + } + } + + private onActionEnd(): void { + if (this.refreshConfigurator !== undefined) { + let maxTranslate = this.refreshConfigurator.getMaxTranslate() + let refreshAnimDuration = this.refreshConfigurator.getRefreshAnimDuration(); + if (this.refreshConfigurator.getListIsPlacement()) { + if (this.state !== undefined) { + if (this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2) { + // 让列表归位到顶部 + this.scroller.scrollEdge(Edge.Top); + // 让列表归位到底部 + } else if (this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) { + this.scroller.scrollEdge(Edge.Bottom); + } + } + } + if (this.trYTop !== undefined) { + if (this.trYTop > 0) { // 下拉结束 + if (this.state !== undefined && maxTranslate !== undefined) { + if (this.state == IS_FREE || this.state == IS_PULL_DOWN_1 || this.state == IS_PULL_DOWN_2) { + if (this.trYTop / maxTranslate < 0.75) { + this.closeRefresh(); + } else { + this.state = IS_REFRESHING; + this.trYTop = maxTranslate * 0.75; + clearInterval(this.timer); + this.timer = setInterval(() => { + if (this.value !== undefined) { + if (this.value >= 1) { + this.value -= 1; + } else { + if (refreshAnimDuration !== undefined && refreshAnimDuration !== 0) { + this.value += 10 / refreshAnimDuration; + } + } + // 保留3位小数 + this.value = Math.round(this.value * 1000) / 1000; + } + // 刷新中动画采用系统组件,因此不用自己去执行动画 + // 如果有刷新中动画回调,就执行刷新中动画回调 + if (this.onAnimRefreshing) { + this.onAnimRefreshing(this.value, this.mWidthNumber, this.trYTop); + } + }, 10); + if (this.onRefresh !== undefined) { + this.onRefresh().then((refreshText) => { + if (refreshText.length == 0) { + this.closeRefresh(); + } else { + this.state = IS_REFRESHED; + if (!this.customRefresh) { + this.refreshText = refreshText; + } + setTimeout(() => { + this.closeRefresh(); + }, this.refreshConfigurator?.getRefreshCompleteTextHoldTime() ?? 1000); + } + }); + } + } + } + } + } else if (this.trYBottom !== undefined && this.trYBottom < 0) { // 上拉结束 + if (this.state !== undefined && maxTranslate !== undefined) { + if (this.state == IS_FREE || this.state == IS_PULL_UP_1 || this.state == IS_PULL_UP_2) { + if (-this.trYBottom / maxTranslate < 0.75) { + this.closeLoad(); + } else { + this.state = IS_LOADING; + this.trYBottom = -maxTranslate * 0.75; + clearInterval(this.timer); + this.timer = setInterval(() => { + if (this.value !== undefined) { + if (this.value >= 1) { + this.value -= 1; + } else { + this.value += 0.01; + } + // 保留2位小数 + this.value = Math.round(this.value * 100) / 100; + // 如果没有自定义加载中动画,就执行内置加载中动画 + if (!this.customLoad) { + this.drawLoadView(false, this.value); + } + } + // 如果有加载中动画回调,就执行加载中动画回调 + if (this.onAnimLoading) { + if (this.trYBottom !== undefined) { + this.onAnimLoading(this.value, this.mWidthNumber, -this.trYBottom); + } + } + }, 10); + if (this.onLoadMore !== undefined) { + this.onLoadMore().then((loadText) => { + this.closeLoad(); + }); + } + } + } + } + } else { + this.state = IS_FREE; + } + } + } + } + + private getTranslateYOfRefresh(newTranslateY: number): number { + if (this.refreshConfigurator !== undefined) { + let maxTranslateY = this.refreshConfigurator.getMaxTranslate(); + let sensitivity = this.refreshConfigurator.getSensitivity(); + if (maxTranslateY !== undefined && sensitivity !== undefined && this.trYTop !== undefined) { + // 阻尼值计算 + if (this.trYTop / maxTranslateY < 0.2) { + newTranslateY = newTranslateY * 1 * sensitivity; + } else if (this.trYTop / maxTranslateY < 0.4) { + newTranslateY = newTranslateY * 0.8 * sensitivity; + } else if (this.trYTop / maxTranslateY < 0.6) { + newTranslateY = newTranslateY * 0.6 * sensitivity; + } else if (this.trYTop / maxTranslateY < 0.8) { + newTranslateY = newTranslateY * 0.4 * sensitivity; + } else { + newTranslateY = newTranslateY * 0.2 * sensitivity; + } + // 下拉值计算 + if (this.trYTop + newTranslateY > maxTranslateY) { + return maxTranslateY; + } else if (this.trYTop + newTranslateY < 0) { + return 0; + } else { + return this.trYTop + newTranslateY; + } + } + } + return 0; + } + + private getTranslateYOfLoadMore(newTranslateY: number): number { + if (this.refreshConfigurator !== undefined) { + let maxTranslateY = this.refreshConfigurator.getMaxTranslate(); + let sensitivity = this.refreshConfigurator.getSensitivity(); + if (maxTranslateY !== undefined && sensitivity !== undefined && this.trYBottom !== undefined) { + // 阻尼值计算 + if (this.trYBottom / maxTranslateY > -0.2) { + newTranslateY = newTranslateY * 1 * sensitivity; + } else if (this.trYBottom / maxTranslateY > -0.4) { + newTranslateY = newTranslateY * 0.8 * sensitivity; + } else if (this.trYBottom / maxTranslateY > -0.6) { + newTranslateY = newTranslateY * 0.6 * sensitivity; + } else if (this.trYBottom / maxTranslateY > -0.8) { + newTranslateY = newTranslateY * 0.4 * sensitivity; + } else { + newTranslateY = newTranslateY * 0.2 * sensitivity; + } + // 下拉值计算 + if (this.trYBottom + newTranslateY < -maxTranslateY) { + return -maxTranslateY; + } else if (this.trYBottom + newTranslateY > 0) { + return 0; + } else { + return this.trYBottom + newTranslateY; + } + } + } + return 0; + } + + private drawRefreshView(value: number): void { + if (this.refreshConfigurator !== undefined && this.trYTop !== undefined) { + let refreshHeight = this.refreshConfigurator.getRefreshHeight() + if (refreshHeight !== undefined && this.trYTop >= refreshHeight) { + if (this.canvasRefresh !== undefined) { + let refreshWidth = this.refreshConfigurator.getRefreshWidth() + if (refreshWidth !== undefined) { + this.canvasRefresh.clearRect(0, 0, refreshWidth, refreshHeight); + } + // 绘制圆环 + this.canvasRefresh.beginPath(); + if (this.refreshRingOx !== undefined && this.refreshRingOy !== undefined && this.refreshRingRadius !== undefined) { + this.canvasRefresh.arc(this.refreshRingOx, this.refreshRingOy, this.refreshRingRadius, 0, Math.PI * 2); + } + this.canvasRefresh.stroke(); + // 绘制卫星 + value = value > 0.75 ? 0.75 : value; + this.canvasRefresh.beginPath(); + if (this.refreshPoint2x !== undefined && this.refreshPoint1x !== undefined + && this.refreshPoint2y !== undefined && this.refreshPoint1y !== undefined) { + this.canvasRefresh.arc( + value * (this.refreshPoint2x - this.refreshPoint1x) + this.refreshPoint1x, + value * (this.refreshPoint2y - this.refreshPoint1y) + this.refreshPoint1y, + refreshHeight / 20 + 1, 0, Math.PI * 2); + } + this.canvasRefresh.fill(); + } + } + } + } + + private drawLoadView(isPullUp: boolean, value: number): void { + if (isPullUp) { + if (this.refreshConfigurator !== undefined) { + let loadImgHeight = this.refreshConfigurator.getLoadImgHeight() + if (loadImgHeight !== undefined && this.trYBottom !== undefined) { + if (this.trYBottom <= -loadImgHeight) { + if (value < 0.75) { + this.angle1 = 0; + if (this.refreshConfigurator !== undefined) { + this.loadText = this.refreshConfigurator.getLoadTextPullUp1(); + } + } else { + this.angle1 = 180; + if (this.refreshConfigurator !== undefined) { + this.loadText = this.refreshConfigurator.getLoadTextPullUp2(); + } + } + } else { + this.loadText = ''; + } + } + } + } else { + this.angle2 = value * 360; + if (this.refreshConfigurator !== undefined) { + this.loadText = this.refreshConfigurator.getLoadTextLoading(); + } + } + } + + public closeRefresh(): void { + clearInterval(this.timer); + if (this.refreshConfigurator !== undefined) { + animateTo({ duration: this.refreshConfigurator.getAnimDuration() }, () => { + this.trYTop = 0; + }); + } + if (this.refreshConfigurator !== undefined) { + setTimeout(() => { + this.state = IS_FREE; + this.refreshText = ''; + }, this.refreshConfigurator.getAnimDuration()); + } + } + + public closeLoad(): void { + clearInterval(this.timer); + if (this.refreshConfigurator !== undefined) { + animateTo({ duration: this.refreshConfigurator.getAnimDuration() }, () => { + this.trYBottom = 0; + }); + } + this.state = IS_FREE; + this.loadText = ''; + } +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 index 037c84d11aff21cd0ee71b1f3541d7486b9f55aa..dcb6ec392c26e5185c188d8c416f1f4605cbc81b 100644 --- a/oh-package.json5 +++ b/oh-package.json5 @@ -7,6 +7,6 @@ "name": "pulltorefresh", "description": "example description", "repository": {}, - "version": "2.1.1-rc.0", + "version": "2.1.1-rc.1", "dependencies": {}, } \ No newline at end of file