HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

关于 uni-push 小米厂商推送“个人订阅”迁移至“公信消息”的适配通知

unipush 小米

关于push,近期小米推送将上线订阅消息功能,原私信通道下的个人订阅类目将统一调整至公信消息。仅接入小米个人订阅消息的 App 开发者需关注,未使用无需处理。

1、订阅类消息仅支持使用 8 个专属订阅类目,规则详见小米推送2026年消息分类新规:https://dev.mi.com/xiaomihyperos/documentation/detail?pId=2321

2、2026年8月1日起原有私信渠道下的【个人订阅】通道将关闭申请入口,于2026年12月31日正式关停使用。

3、2026年8月1日之前已申请接入过【个人订阅】分类的应用,需要在2026年12月31日前完成订阅消息功能接入和订阅授权弹窗适配。具体接入流程请参考:https://dev.mi.com/xiaomihyperos/documentation/detail?pId=2320

4、2026年8月1日之后新申请订阅分类的需要按照新规接入发送

对接个推侧需要进行以下适配:
1、服务端:接入订阅类消息后推送提醒方式与私信消息相同,新订阅消息的channel_id与原有【个人订阅】分类的channel_id不同,在调用个推服务推送API下发订阅类消息时,必须同时设置小米消息分类和私信模板相关参数,接口设置参照文档【小米】私信模板id:https://docs.getui.com/getui/server/rest_v2/third_party/#doc-title-15
2、客户端:

  • Android小米厂商SDK 需要升级到 com.getui.opt:xmp:3.3.7 版本
  • 客户端需完成订阅消息功能接入,同步适配订阅授权弹窗,统一采用系统原生 API 调用实现,完整接入规范可参考小米推送订阅消息官方文档:https://dev.mi.com/xiaomihyperos/documentation/detail?pId=2320

以上请尽快完成适配,根据应用业务场景,发送不同类别的消息。如有疑问,可以添加微信客服群进行咨询,加群方式参考文档:https://uniapp.dcloud.net.cn/unipush-v2.html#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

继续阅读 »

关于push,近期小米推送将上线订阅消息功能,原私信通道下的个人订阅类目将统一调整至公信消息。仅接入小米个人订阅消息的 App 开发者需关注,未使用无需处理。

1、订阅类消息仅支持使用 8 个专属订阅类目,规则详见小米推送2026年消息分类新规:https://dev.mi.com/xiaomihyperos/documentation/detail?pId=2321

2、2026年8月1日起原有私信渠道下的【个人订阅】通道将关闭申请入口,于2026年12月31日正式关停使用。

3、2026年8月1日之前已申请接入过【个人订阅】分类的应用,需要在2026年12月31日前完成订阅消息功能接入和订阅授权弹窗适配。具体接入流程请参考:https://dev.mi.com/xiaomihyperos/documentation/detail?pId=2320

4、2026年8月1日之后新申请订阅分类的需要按照新规接入发送

对接个推侧需要进行以下适配:
1、服务端:接入订阅类消息后推送提醒方式与私信消息相同,新订阅消息的channel_id与原有【个人订阅】分类的channel_id不同,在调用个推服务推送API下发订阅类消息时,必须同时设置小米消息分类和私信模板相关参数,接口设置参照文档【小米】私信模板id:https://docs.getui.com/getui/server/rest_v2/third_party/#doc-title-15
2、客户端:

  • Android小米厂商SDK 需要升级到 com.getui.opt:xmp:3.3.7 版本
  • 客户端需完成订阅消息功能接入,同步适配订阅授权弹窗,统一采用系统原生 API 调用实现,完整接入规范可参考小米推送订阅消息官方文档:https://dev.mi.com/xiaomihyperos/documentation/detail?pId=2320

以上请尽快完成适配,根据应用业务场景,发送不同类别的消息。如有疑问,可以添加微信客服群进行咨询,加群方式参考文档:https://uniapp.dcloud.net.cn/unipush-v2.html#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

收起阅读 »

怎么用 BeeWare(Briefcase)把 Python 应用打包成 iOS App 并上架

iOS

'''BeeWare 走的是和 Flet 不一样的路子:它的 Toga 用的是各平台的原生控件,iOS 上就是真的 UIKit,不套 Flutter 或者 WebView。打包工具叫 Briefcase,briefcase create iOS 会生成一个 Xcode 工程,briefcase build iOS 编译,briefcase package iOS 出包。

因为最终还是 Xcode 在干活,这几条命令都得在 macOS 上跑。Windows 上 Briefcase 只能处理 Windows/Android 那些目标。这一步没有变通。

我的分工是:Python 代码在 Windows 上写,iOS 出包丢给 macOS 机器;证书、描述文件、IPA 上传用开心上架(AppUploader)在 Windows 上处理。

先说结论:哪一步真绕不开 Mac

环节 我实际怎么做的
注册 Bundle ID、签发证书、生成描述文件 Windows 上做完
写 Python 代码 Windows
briefcase create / build / package iOS 要 macOS + Xcode
提交 App Store Windows 上传
装到 iPhone 测试 扫码注册 UDID,再扫码装

说明一下:我用的签证书工具本身在 macOS 上也能用(网页版不挑系统,桌面客户端有 macOS 版)。表里"不需要 Mac"是指这一步不再需要 Xcode、钥匙串访问或 Transporter。

第一步:在 pyproject.toml 里定好 bundle

Briefcase 的配置全在 pyproject.toml 里。iOS 的包标识由 bundle 加 app 名拼出来:

[tool.briefcase]  
project_name = "My App"  
bundle = "com.yourcompany"  
version = "1.0.0"  

[tool.briefcase.app.myapp]  
formal_name = "My App"

这样最终的 Bundle Identifier 就是 com.yourcompany.myapp。拼出来的这个值要和 Apple 注册的 Bundle ID、描述文件绑定的 App ID 完全一致——注意是拼出来的结果,不是 bundle 字段本身,这点第一次配的时候容易搞错。

第二步:在 Windows 上签出证书和描述文件

注册 Bundle ID:开心上架的「Bundle ID」页面点「新建 Bundle ID」,Identifier 填拼出来的那个完整标识。创建后改不了,所以先把 bundle 和 app 名定下来再注册。

签发证书:「证书」页面点「新建证书」,上架选发布签名证书,真机调试选开发签名证书。绑定上面的 Bundle ID,填名称邮箱,设一个 .p12 保护密码——导入 Mac 钥匙串时要输,服务器不保存它,忘了只能吊销重签。勾上「使用开心上架服务同步证书」的话,出包用的那台 Mac 直接登录下载就行。
签发证书

生成描述文件:「描述文件」页面点「新建描述文件」,上架选 App Store 类型,内测选 Ad Hoc 并勾上测试设备,关联上面的 Bundle ID 和证书,提交后下载 .mobileprovision。

私钥和 CSR 都在你本机生成后封进 .p12,不用在 Mac 上走「钥匙串访问 → 从证书颁发机构请求证书」那一套——对只写 Python、平时压根不碰 Xcode 的人来说,这一步省下来挺值的。

第三步:在 macOS 上导入签名材料

security import dist.p12 -k ~/Library/Keychains/login.keychain-db -P "你的p12密码" -T /usr/bin/codesign  

mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles  
cp AppStore.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/

第四步:生成工程、编译、出包

在 macOS 上依次跑:

briefcase create iOS  
briefcase build iOS  
briefcase package iOS

briefcase create iOS 会在 build/ 下生成一个 Xcode 工程。package 这一步会做签名,Briefcase 会让你选用哪个签名身份(identity),也可以用参数直接指定,具体参数名以 briefcase package iOS --help 输出的为准——Briefcase 各版本的命令行选项变动比较频繁。

签名这块有个实操建议:别让 Xcode 自动管理签名。生成的工程里如果开着 Automatically manage signing,Xcode 会跑去 Apple 那边替你申请新证书,而同类型证书一共就 3 个名额,几轮下来会占满。用 Xcode 打开生成的工程,把它关掉,手动选第二步那套证书和描述文件,再回来跑 briefcase package iOS。

改了 pyproject.toml 里跟原生工程相关的配置之后,记得重新 briefcase create iOS(或者带 -r 更新),不然生成的工程里还是旧配置。

第五步:把 IPA 传上去

拿到包之后就跟 Mac 无关了。我一般先在开心上架的「文件&提交」页面把 IPA 打开看一眼签名信息——Bundle ID 是不是拼出来的那个、描述文件类型对不对、证书有没有过期,确认了再点「提交 App Store」。

命令行版本,在 Windows 上直接跑:

appuploader-cli upload -f MyApp.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop

-p 是 Apple 生成的 App 专用密码(xxxx-xxxx-xxxx-xxxx),不是 Apple ID 登录密码。也可以用 API 密钥:

appuploader-cli upload -f MyApp.ipa --api-key <KeyID> --api-issuer <IssuerID> --private-key Auth_Key.p8

密钥方式只收 .ipa,且 App 要先在 App Store Connect 里建好。

第六步:发内测包

「设备」页面点「注册设备」,二维码发出去让人扫上报 UDID(一年 100 台,免费账号 3 台)。回描述文件那边点「重建」,生成一份包含这些设备的 Ad Hoc 描述文件,拷到 Mac 上替换掉旧的。

重新 package 一次,然后在「文件&提交」页面生成二维码让人扫码装。

写在最后

BeeWare 这条路对纯 Python 背景的人最友好的地方,是你几乎不用学 Objective-C 或 Swift。但签名这一环是躲不掉的 Apple 规则,和你用什么语言无关——把证书和描述文件在 Windows 上准备好,Mac 那边就只剩三条 briefcase 命令。'''

继续阅读 »

'''BeeWare 走的是和 Flet 不一样的路子:它的 Toga 用的是各平台的原生控件,iOS 上就是真的 UIKit,不套 Flutter 或者 WebView。打包工具叫 Briefcase,briefcase create iOS 会生成一个 Xcode 工程,briefcase build iOS 编译,briefcase package iOS 出包。

因为最终还是 Xcode 在干活,这几条命令都得在 macOS 上跑。Windows 上 Briefcase 只能处理 Windows/Android 那些目标。这一步没有变通。

我的分工是:Python 代码在 Windows 上写,iOS 出包丢给 macOS 机器;证书、描述文件、IPA 上传用开心上架(AppUploader)在 Windows 上处理。

先说结论:哪一步真绕不开 Mac

环节 我实际怎么做的
注册 Bundle ID、签发证书、生成描述文件 Windows 上做完
写 Python 代码 Windows
briefcase create / build / package iOS 要 macOS + Xcode
提交 App Store Windows 上传
装到 iPhone 测试 扫码注册 UDID,再扫码装

说明一下:我用的签证书工具本身在 macOS 上也能用(网页版不挑系统,桌面客户端有 macOS 版)。表里"不需要 Mac"是指这一步不再需要 Xcode、钥匙串访问或 Transporter。

第一步:在 pyproject.toml 里定好 bundle

Briefcase 的配置全在 pyproject.toml 里。iOS 的包标识由 bundle 加 app 名拼出来:

[tool.briefcase]  
project_name = "My App"  
bundle = "com.yourcompany"  
version = "1.0.0"  

[tool.briefcase.app.myapp]  
formal_name = "My App"

这样最终的 Bundle Identifier 就是 com.yourcompany.myapp。拼出来的这个值要和 Apple 注册的 Bundle ID、描述文件绑定的 App ID 完全一致——注意是拼出来的结果,不是 bundle 字段本身,这点第一次配的时候容易搞错。

第二步:在 Windows 上签出证书和描述文件

注册 Bundle ID:开心上架的「Bundle ID」页面点「新建 Bundle ID」,Identifier 填拼出来的那个完整标识。创建后改不了,所以先把 bundle 和 app 名定下来再注册。

签发证书:「证书」页面点「新建证书」,上架选发布签名证书,真机调试选开发签名证书。绑定上面的 Bundle ID,填名称邮箱,设一个 .p12 保护密码——导入 Mac 钥匙串时要输,服务器不保存它,忘了只能吊销重签。勾上「使用开心上架服务同步证书」的话,出包用的那台 Mac 直接登录下载就行。
签发证书

生成描述文件:「描述文件」页面点「新建描述文件」,上架选 App Store 类型,内测选 Ad Hoc 并勾上测试设备,关联上面的 Bundle ID 和证书,提交后下载 .mobileprovision。

私钥和 CSR 都在你本机生成后封进 .p12,不用在 Mac 上走「钥匙串访问 → 从证书颁发机构请求证书」那一套——对只写 Python、平时压根不碰 Xcode 的人来说,这一步省下来挺值的。

第三步:在 macOS 上导入签名材料

security import dist.p12 -k ~/Library/Keychains/login.keychain-db -P "你的p12密码" -T /usr/bin/codesign  

mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles  
cp AppStore.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/

第四步:生成工程、编译、出包

在 macOS 上依次跑:

briefcase create iOS  
briefcase build iOS  
briefcase package iOS

briefcase create iOS 会在 build/ 下生成一个 Xcode 工程。package 这一步会做签名,Briefcase 会让你选用哪个签名身份(identity),也可以用参数直接指定,具体参数名以 briefcase package iOS --help 输出的为准——Briefcase 各版本的命令行选项变动比较频繁。

签名这块有个实操建议:别让 Xcode 自动管理签名。生成的工程里如果开着 Automatically manage signing,Xcode 会跑去 Apple 那边替你申请新证书,而同类型证书一共就 3 个名额,几轮下来会占满。用 Xcode 打开生成的工程,把它关掉,手动选第二步那套证书和描述文件,再回来跑 briefcase package iOS。

改了 pyproject.toml 里跟原生工程相关的配置之后,记得重新 briefcase create iOS(或者带 -r 更新),不然生成的工程里还是旧配置。

第五步:把 IPA 传上去

拿到包之后就跟 Mac 无关了。我一般先在开心上架的「文件&提交」页面把 IPA 打开看一眼签名信息——Bundle ID 是不是拼出来的那个、描述文件类型对不对、证书有没有过期,确认了再点「提交 App Store」。

命令行版本,在 Windows 上直接跑:

appuploader-cli upload -f MyApp.ipa -u dev@example.com -p abcd-efgh-ijkl-mnop

-p 是 Apple 生成的 App 专用密码(xxxx-xxxx-xxxx-xxxx),不是 Apple ID 登录密码。也可以用 API 密钥:

appuploader-cli upload -f MyApp.ipa --api-key <KeyID> --api-issuer <IssuerID> --private-key Auth_Key.p8

密钥方式只收 .ipa,且 App 要先在 App Store Connect 里建好。

第六步:发内测包

「设备」页面点「注册设备」,二维码发出去让人扫上报 UDID(一年 100 台,免费账号 3 台)。回描述文件那边点「重建」,生成一份包含这些设备的 Ad Hoc 描述文件,拷到 Mac 上替换掉旧的。

重新 package 一次,然后在「文件&提交」页面生成二维码让人扫码装。

写在最后

BeeWare 这条路对纯 Python 背景的人最友好的地方,是你几乎不用学 Objective-C 或 Swift。但签名这一环是躲不掉的 Apple 规则,和你用什么语言无关——把证书和描述文件在 Windows 上准备好,Mac 那边就只剩三条 briefcase 命令。'''

收起阅读 »

如何让Canvas绘制内容能超出父组件的限制

canvas

问题现象
如何让Canvas绘制内容能超出父组件的限制,“按下”的响应区域不超出父组件范围?组件中.clip属性能实现此效果吗?

背景知识
HarmonyOS组件中.clip属性:是否对子组件超出当前组件范围外的区域进行裁剪。参考链接:clip。
HarmonyOS组件中.responseRegion属性可以实现组件的响应区域范围的变化,响应区域范围可以超出或者小于组件的布局范围。参考链接:自定义控制的多层级手势事件。
Canvas:Canvas组件提供画布,用于自定义绘制图形。
CanvasRenderingContext2D:使用CanvasRenderingContext2D在Canvas画布组件上进行绘制,绘制对象可以是矩形、文本、图片等。
解决方案
在HarmonyOS中,无法通过.clip属性实现所需效果。当.clip属性设置为true时,子组件超出当前组件范围的区域将不会响应绑定的手势事件;当设置为undefined时,系统将不再对超出部分进行裁剪,但这并不意味着可以控制内容超出组件本身的绘制范围。可以通过以下方案实现让Canvas绘制内容能超出父组件限制的功能,步骤如下:

使用Column父组件包含子组件Canvas,Column父组件宽高为(150,150)区域,子组件Canvas宽高为(300,300)。
对Canvas组件设置.responseRegion属性触摸热区为.responseRegion({ x: 75, y: 0, width: 150, height: 150 })。实现功能:蓝色区域(150150)可响应Canvas绘制内容时“按下(down)”的触摸操作,红色区域(300300去除蓝色区域的范围)不可响应“按下(down)”的触摸操作,红色区域只可响应从蓝色区域“按下(down)”后的“移动(move)”触摸操作。
说明
当组件Canvas绑定了.responseRegion(Rect),所有落在Rect区域范围的触摸事件和手势可被组件Canvas对应的回调响应。

@Entry
@Component
struct DrawBankCom {
paintSize: number = 5; // 当前画笔大小
private settings: RenderingContextSettings = new RenderingContextSettings(true);
canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
tempPath: Path2D = new Path2D();
@State @Watch('onChange') pathArray: Array<Path2D> = []; // 所有画图路径信息
isUpdate: boolean = true;
removeArray: Array<Path2D> = []; // 回退的路径集合
@State text: string = '';
@State eventType: string = '';

build() {
Column() {
if (this.isUpdate) {
Row() {
Column() {
Canvas(this.canvasContext)
// Canvas(子组件)宽高设置为300300
.width(300)
.height(300)
.onReady(() => {
this.canvasContext.lineWidth = this.paintSize;
this.canvasContext.stroke(this.tempPath);
for (let index = 0; index < this.pathArray.length; index++) {
this.canvasContext.stroke(this.pathArray[index]);
}
})
.onTouch((event?: TouchEvent) => {
if (event) {
if (event.type === TouchType.Down) {
this.eventType = 'Down';
this.canvasContext.lineWidth = this.paintSize;
this.canvasContext.beginPath();
this.tempPath = new Path2D();
this.pathArray.push(this.tempPath);
this.tempPath.moveTo(event.touches[0].x, event.touches[0].y);
this.canvasContext.moveTo(event.touches[0].x, event.touches[0].y);
}
if (event.type === TouchType.Up) {
this.eventType = 'Up';
}
if (event.type === TouchType.Move) {
this.eventType = 'Move';
this.tempPath.lineTo(event.touches[0].x, event.touches[0].y);
this.canvasContext.stroke(this.tempPath);
this.canvasContext.lineTo(event.touches[0].x, event.touches[0].y);
this.canvasContext.stroke();
}
this.text = 'TouchType:' + this.eventType + '\n touch point and touch element:\nx: ' +
event.touches[0].x + '\n' + 'y: ' + event.touches[0].y + '\nwidth:' +
event.target.area.width + '\nheight:' + event.target.area.height + '\pathArray size:' +
this.pathArray.length;
}
})
.renderFit(RenderFit.TOP_RIGHT)
// 设置触摸热区,蓝色区域可响应“down”操作,红色不可响应“down”操作,只能响应“move”操作
.responseRegion({
x: 75,
y: 0,
width: 150,
height: 150
});
}
// Column(父组件)宽高设置为150
150
.width(150)
.height(150)
.backgroundColor('#ffbdf3e9');
};
}

  Column() {  
    Row() {  
      Button('回退').onClick(() => {  
        this.pathArray.pop();  
      });  
    };  

    Text(this.text);  
  };  
}  
.width(300)  
.height(300)  
.clip(false)  
.borderRadius(10)  
.backgroundColor('#ff0000');  

}

onChange() {
this.canvasContext.reset();
this.canvasContext.lineWidth = this.paintSize;
for (let index = 0; index < this.pathArray.length; index++) {
this.canvasContext.stroke(this.pathArray[index]);
}
}
}
具体表现为从蓝色区域开始画线,可以延伸到红色区域,但是不可从红色区域开始画线。

总结
使用.responseRegion属性可以实现组件的响应区域范围的变化,能实现让Canvas绘制内容能超出父组件的限制。
https://sites.google.com/view/1svbrd24/home
https://sites.google.com/view/2jlhml67/home
https://sites.google.com/view/0ojkvo59/home
https://sites.google.com/view/5erbeg51/home
https://sites.google.com/view/7txhqp00/home
https://sites.google.com/view/9ahuri30/home
https://sites.google.com/view/9brhff50/home
https://sites.google.com/view/7usmzs19/home
https://sites.google.com/view/7fkyls54/home
https://sites.google.com/view/4yfngr91/home
https://sites.google.com/view/7bibkd36/home
https://sites.google.com/view/7rgsbk35/home
https://sites.google.com/view/5tjymn45/home
https://sites.google.com/view/0qxruy91/home
https://sites.google.com/view/6xlnnt32/home
https://sites.google.com/view/9ekbqp32/home
https://sites.google.com/view/5vwcmf35/home
https://sites.google.com/view/2xhdlr45/home
https://sites.google.com/view/0giwkx12/home
https://sites.google.com/view/9cqmye26/home
https://sites.google.com/view/0rxdat90/home
https://sites.google.com/view/3yywdt98/home
https://sites.google.com/view/1lbijc54/home
https://sites.google.com/view/4ilgby72/home
https://sites.google.com/view/3lexut78/home
https://sites.google.com/view/1dsgmu66/home
https://sites.google.com/view/7lsxyd26/home
https://sites.google.com/view/2ulade56/home
https://sites.google.com/view/1tipkq60/home
https://sites.google.com/view/4jhjbn51/home
https://sites.google.com/view/2bpvrp99/home
https://sites.google.com/view/7hyotm19/home
https://sites.google.com/view/1sxxqc05/home
https://sites.google.com/view/7bzjsk57/home
https://sites.google.com/view/0rvozy96/home
https://sites.google.com/view/4lqeqc80/home
https://sites.google.com/view/5xnhok81/home
https://sites.google.com/view/9piiqm40/home
https://sites.google.com/view/2opvsv00/home
https://sites.google.com/view/5sdsim60/home
https://sites.google.com/view/6qrjip83/home
https://sites.google.com/view/8wzpjp01/home
https://sites.google.com/view/2rbzfb85/home
https://sites.google.com/view/8yjxua98/home
https://sites.google.com/view/5asfix82/home
https://sites.google.com/view/5myuib41/home
https://sites.google.com/view/9arbno01/home
https://sites.google.com/view/9fqrsq43/home
https://sites.google.com/view/2lgtzy82/home
https://sites.google.com/view/4detht79/home
https://sites.google.com/view/9yezyo00/home
https://sites.google.com/view/0nofnm30/home
https://sites.google.com/view/2vfhym56/home
https://sites.google.com/view/1rivml82/home
https://sites.google.com/view/7ohgjy23/home
https://sites.google.com/view/1gbrst25/home
https://sites.google.com/view/8zevsj79/home
https://sites.google.com/view/1tfuhl96/home
https://sites.google.com/view/8fmixj07/home
https://sites.google.com/view/3yprml67/home
https://sites.google.com/view/4vzytf83/home
https://sites.google.com/view/0zydqk84/home
https://sites.google.com/view/8onjpb17/home
https://sites.google.com/view/5qbsvc57/home
https://sites.google.com/view/2uqmyg64/home
https://sites.google.com/view/6hnwoa03/home
https://sites.google.com/view/2bdovq40/home
https://sites.google.com/view/3ykzjl65/home
https://sites.google.com/view/3ypjlx51/home
https://sites.google.com/view/2vecse29/home
https://sites.google.com/view/8wjmoz24/home
https://sites.google.com/view/6anaaz10/home
https://sites.google.com/view/6vztlx87/home
https://sites.google.com/view/9hxqwy10/home
https://sites.google.com/view/6emmnf53/home
https://sites.google.com/view/9qgmon96/home
https://sites.google.com/view/8vqblh76/home
https://sites.google.com/view/5repzi61/home
https://sites.google.com/view/6iqxbj96/home
https://sites.google.com/view/4fiyto32/home
https://sites.google.com/view/1uimwi96/home
https://sites.google.com/view/5ibqaj17/home
https://sites.google.com/view/9nqdyq84/home
https://sites.google.com/view/4lwyyd65/home
https://sites.google.com/view/3gawuk38/home
https://sites.google.com/view/5lgzag04/home
https://sites.google.com/view/1imonc99/home
https://sites.google.com/view/9xyukm01/home
https://sites.google.com/view/6nalbh77/home
https://sites.google.com/view/2ypecr06/home
https://sites.google.com/view/5zdody20/home
https://sites.google.com/view/6wkgom88/home
https://sites.google.com/view/8zwqqi53/home
https://sites.google.com/view/0qyluw74/home
https://sites.google.com/view/5aphjd68/home
https://sites.google.com/view/2sacyn38/home
https://sites.google.com/view/8dbjrm35/home
https://sites.google.com/view/6unuwk46/home
https://sites.google.com/view/5hrlie99/home
https://sites.google.com/view/6mxmyz56/home
https://sites.google.com/view/9gdzzy11/home
https://sites.google.com/view/3wkfon30/home
https://sites.google.com/view/7jesbl97/home
https://sites.google.com/view/7xllml61/home
https://sites.google.com/view/3mpkjx30/home
https://sites.google.com/view/6idizy90/home
https://sites.google.com/view/1czsip30/home
https://sites.google.com/view/3ezdwf97/home
https://sites.google.com/view/1ydubh84/home
https://sites.google.com/view/4cvedy61/home
https://sites.google.com/view/7yyumz73/home
https://sites.google.com/view/5pkquw00/home
https://sites.google.com/view/7dceex86/home
https://sites.google.com/view/3gnvyg34/home
https://sites.google.com/view/2ehwht60/home
https://sites.google.com/view/3ocyay63/home
https://sites.google.com/view/6uizgx22/home
https://sites.google.com/view/3cgblb20/home
https://sites.google.com/view/3guucg59/home
https://sites.google.com/view/9cdybx22/home
https://sites.google.com/view/7giaxy95/home
https://sites.google.com/view/3qkkfi06/home
https://sites.google.com/view/9goqyg50/home
https://sites.google.com/view/6sfntm95/home
https://sites.google.com/view/8hekup65/home
https://sites.google.com/view/8dypsx49/home
https://sites.google.com/view/1emhrm38/home
https://sites.google.com/view/1yyqhu29/home
https://sites.google.com/view/5heoek19/home
https://sites.google.com/view/3tvkkw85/home
https://sites.google.com/view/4dqdwv87/home
https://sites.google.com/view/2tsotl60/home
https://sites.google.com/view/7mpwib24/home
https://sites.google.com/view/9dlwvj94/home
https://sites.google.com/view/3hggdc60/home
https://sites.google.com/view/3vhyat21/home
https://sites.google.com/view/7vyebf30/home
https://sites.google.com/view/4wnnbn37/home
https://sites.google.com/view/6mjlkw77/home
https://sites.google.com/view/0jcihr79/home
https://sites.google.com/view/6lpxba81/home
https://sites.google.com/view/7nmcwv30/home
https://sites.google.com/view/3ztaut28/home
https://sites.google.com/view/1ecdeq22/home
https://sites.google.com/view/0dgpba93/home
https://sites.google.com/view/1aqqqo25/home
https://sites.google.com/view/1raxlw38/home
https://sites.google.com/view/3snsha82/home
https://sites.google.com/view/5uwlrm29/home
https://sites.google.com/view/4feaqq70/home
https://sites.google.com/view/3nlhqj52/home
https://sites.google.com/view/1pidkm14/home
https://sites.google.com/view/2wqdnc45/home
https://sites.google.com/view/1iwyhj71/home
https://sites.google.com/view/8evxkf88/home
https://sites.google.com/view/5zbhhn62/home
https://sites.google.com/view/9hjcnp07/home
https://sites.google.com/view/3mxnek99/home
https://sites.google.com/view/9bmsbt04/home
https://sites.google.com/view/2lezmt81/home
https://sites.google.com/view/6qmqey84/home
https://coub.com/view/scsevfdzaa
https://coub.com/view/ghy3aenpkf
https://coub.com/view/xfyiqequqh
https://coub.com/view/45ky2yyzja
https://coub.com/view/v7lv7a0sy1
https://coub.com/view/e09y796sai
https://coub.com/view/z9dgue55fb
https://coub.com/view/4v9yryaq7b
https://coub.com/view/rjakrzmprm
https://coub.com/view/443k6aufbo
https://coub.com/view/adxz7xc4zj
https://coub.com/view/gontyw4afu
https://coub.com/view/67wn0hy74w
https://coub.com/view/p7i0qsp0th
https://coub.com/view/bu18agb0cw
https://coub.com/view/u453dsg9rs
https://coub.com/view/0xh6401od2
https://coub.com/view/s8ivp1bnms
https://coub.com/view/q7d0nkso0h
https://coub.com/view/gadau0rb64
https://coub.com/view/79ww1tgrud
https://coub.com/view/grqy2h2n44
https://coub.com/view/dvjar8c2ng
https://coub.com/view/365566dfec
https://coub.com/view/szouxmw105
https://coub.com/view/yucl2gqgiq
https://coub.com/view/mcb2rze9ox
https://coub.com/view/0j5cdu3ezv
https://coub.com/view/023m21y9ex
https://coub.com/view/0tvarwqbh6
https://coub.com/view/ks6hpuqzv7
https://coub.com/view/390avxe2u9
https://coub.com/view/vqb725jiar
https://coub.com/view/9ekoylsaw1
https://coub.com/view/n5wdecqayc
https://coub.com/view/lu3z4p5rah
https://coub.com/view/6lu4dpcva8
https://coub.com/view/l3dai5ah2r
https://coub.com/view/6rged9l4g5
https://coub.com/view/u4gupyey94
https://coub.com/view/9oyqag6yr3
https://coub.com/view/3b6ywmk5lt
https://coub.com/view/t5mm2naqc1

继续阅读 »

问题现象
如何让Canvas绘制内容能超出父组件的限制,“按下”的响应区域不超出父组件范围?组件中.clip属性能实现此效果吗?

背景知识
HarmonyOS组件中.clip属性:是否对子组件超出当前组件范围外的区域进行裁剪。参考链接:clip。
HarmonyOS组件中.responseRegion属性可以实现组件的响应区域范围的变化,响应区域范围可以超出或者小于组件的布局范围。参考链接:自定义控制的多层级手势事件。
Canvas:Canvas组件提供画布,用于自定义绘制图形。
CanvasRenderingContext2D:使用CanvasRenderingContext2D在Canvas画布组件上进行绘制,绘制对象可以是矩形、文本、图片等。
解决方案
在HarmonyOS中,无法通过.clip属性实现所需效果。当.clip属性设置为true时,子组件超出当前组件范围的区域将不会响应绑定的手势事件;当设置为undefined时,系统将不再对超出部分进行裁剪,但这并不意味着可以控制内容超出组件本身的绘制范围。可以通过以下方案实现让Canvas绘制内容能超出父组件限制的功能,步骤如下:

使用Column父组件包含子组件Canvas,Column父组件宽高为(150,150)区域,子组件Canvas宽高为(300,300)。
对Canvas组件设置.responseRegion属性触摸热区为.responseRegion({ x: 75, y: 0, width: 150, height: 150 })。实现功能:蓝色区域(150150)可响应Canvas绘制内容时“按下(down)”的触摸操作,红色区域(300300去除蓝色区域的范围)不可响应“按下(down)”的触摸操作,红色区域只可响应从蓝色区域“按下(down)”后的“移动(move)”触摸操作。
说明
当组件Canvas绑定了.responseRegion(Rect),所有落在Rect区域范围的触摸事件和手势可被组件Canvas对应的回调响应。

@Entry
@Component
struct DrawBankCom {
paintSize: number = 5; // 当前画笔大小
private settings: RenderingContextSettings = new RenderingContextSettings(true);
canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
tempPath: Path2D = new Path2D();
@State @Watch('onChange') pathArray: Array<Path2D> = []; // 所有画图路径信息
isUpdate: boolean = true;
removeArray: Array<Path2D> = []; // 回退的路径集合
@State text: string = '';
@State eventType: string = '';

build() {
Column() {
if (this.isUpdate) {
Row() {
Column() {
Canvas(this.canvasContext)
// Canvas(子组件)宽高设置为300300
.width(300)
.height(300)
.onReady(() => {
this.canvasContext.lineWidth = this.paintSize;
this.canvasContext.stroke(this.tempPath);
for (let index = 0; index < this.pathArray.length; index++) {
this.canvasContext.stroke(this.pathArray[index]);
}
})
.onTouch((event?: TouchEvent) => {
if (event) {
if (event.type === TouchType.Down) {
this.eventType = 'Down';
this.canvasContext.lineWidth = this.paintSize;
this.canvasContext.beginPath();
this.tempPath = new Path2D();
this.pathArray.push(this.tempPath);
this.tempPath.moveTo(event.touches[0].x, event.touches[0].y);
this.canvasContext.moveTo(event.touches[0].x, event.touches[0].y);
}
if (event.type === TouchType.Up) {
this.eventType = 'Up';
}
if (event.type === TouchType.Move) {
this.eventType = 'Move';
this.tempPath.lineTo(event.touches[0].x, event.touches[0].y);
this.canvasContext.stroke(this.tempPath);
this.canvasContext.lineTo(event.touches[0].x, event.touches[0].y);
this.canvasContext.stroke();
}
this.text = 'TouchType:' + this.eventType + '\n touch point and touch element:\nx: ' +
event.touches[0].x + '\n' + 'y: ' + event.touches[0].y + '\nwidth:' +
event.target.area.width + '\nheight:' + event.target.area.height + '\pathArray size:' +
this.pathArray.length;
}
})
.renderFit(RenderFit.TOP_RIGHT)
// 设置触摸热区,蓝色区域可响应“down”操作,红色不可响应“down”操作,只能响应“move”操作
.responseRegion({
x: 75,
y: 0,
width: 150,
height: 150
});
}
// Column(父组件)宽高设置为150
150
.width(150)
.height(150)
.backgroundColor('#ffbdf3e9');
};
}

  Column() {  
    Row() {  
      Button('回退').onClick(() => {  
        this.pathArray.pop();  
      });  
    };  

    Text(this.text);  
  };  
}  
.width(300)  
.height(300)  
.clip(false)  
.borderRadius(10)  
.backgroundColor('#ff0000');  

}

onChange() {
this.canvasContext.reset();
this.canvasContext.lineWidth = this.paintSize;
for (let index = 0; index < this.pathArray.length; index++) {
this.canvasContext.stroke(this.pathArray[index]);
}
}
}
具体表现为从蓝色区域开始画线,可以延伸到红色区域,但是不可从红色区域开始画线。

总结
使用.responseRegion属性可以实现组件的响应区域范围的变化,能实现让Canvas绘制内容能超出父组件的限制。
https://sites.google.com/view/1svbrd24/home
https://sites.google.com/view/2jlhml67/home
https://sites.google.com/view/0ojkvo59/home
https://sites.google.com/view/5erbeg51/home
https://sites.google.com/view/7txhqp00/home
https://sites.google.com/view/9ahuri30/home
https://sites.google.com/view/9brhff50/home
https://sites.google.com/view/7usmzs19/home
https://sites.google.com/view/7fkyls54/home
https://sites.google.com/view/4yfngr91/home
https://sites.google.com/view/7bibkd36/home
https://sites.google.com/view/7rgsbk35/home
https://sites.google.com/view/5tjymn45/home
https://sites.google.com/view/0qxruy91/home
https://sites.google.com/view/6xlnnt32/home
https://sites.google.com/view/9ekbqp32/home
https://sites.google.com/view/5vwcmf35/home
https://sites.google.com/view/2xhdlr45/home
https://sites.google.com/view/0giwkx12/home
https://sites.google.com/view/9cqmye26/home
https://sites.google.com/view/0rxdat90/home
https://sites.google.com/view/3yywdt98/home
https://sites.google.com/view/1lbijc54/home
https://sites.google.com/view/4ilgby72/home
https://sites.google.com/view/3lexut78/home
https://sites.google.com/view/1dsgmu66/home
https://sites.google.com/view/7lsxyd26/home
https://sites.google.com/view/2ulade56/home
https://sites.google.com/view/1tipkq60/home
https://sites.google.com/view/4jhjbn51/home
https://sites.google.com/view/2bpvrp99/home
https://sites.google.com/view/7hyotm19/home
https://sites.google.com/view/1sxxqc05/home
https://sites.google.com/view/7bzjsk57/home
https://sites.google.com/view/0rvozy96/home
https://sites.google.com/view/4lqeqc80/home
https://sites.google.com/view/5xnhok81/home
https://sites.google.com/view/9piiqm40/home
https://sites.google.com/view/2opvsv00/home
https://sites.google.com/view/5sdsim60/home
https://sites.google.com/view/6qrjip83/home
https://sites.google.com/view/8wzpjp01/home
https://sites.google.com/view/2rbzfb85/home
https://sites.google.com/view/8yjxua98/home
https://sites.google.com/view/5asfix82/home
https://sites.google.com/view/5myuib41/home
https://sites.google.com/view/9arbno01/home
https://sites.google.com/view/9fqrsq43/home
https://sites.google.com/view/2lgtzy82/home
https://sites.google.com/view/4detht79/home
https://sites.google.com/view/9yezyo00/home
https://sites.google.com/view/0nofnm30/home
https://sites.google.com/view/2vfhym56/home
https://sites.google.com/view/1rivml82/home
https://sites.google.com/view/7ohgjy23/home
https://sites.google.com/view/1gbrst25/home
https://sites.google.com/view/8zevsj79/home
https://sites.google.com/view/1tfuhl96/home
https://sites.google.com/view/8fmixj07/home
https://sites.google.com/view/3yprml67/home
https://sites.google.com/view/4vzytf83/home
https://sites.google.com/view/0zydqk84/home
https://sites.google.com/view/8onjpb17/home
https://sites.google.com/view/5qbsvc57/home
https://sites.google.com/view/2uqmyg64/home
https://sites.google.com/view/6hnwoa03/home
https://sites.google.com/view/2bdovq40/home
https://sites.google.com/view/3ykzjl65/home
https://sites.google.com/view/3ypjlx51/home
https://sites.google.com/view/2vecse29/home
https://sites.google.com/view/8wjmoz24/home
https://sites.google.com/view/6anaaz10/home
https://sites.google.com/view/6vztlx87/home
https://sites.google.com/view/9hxqwy10/home
https://sites.google.com/view/6emmnf53/home
https://sites.google.com/view/9qgmon96/home
https://sites.google.com/view/8vqblh76/home
https://sites.google.com/view/5repzi61/home
https://sites.google.com/view/6iqxbj96/home
https://sites.google.com/view/4fiyto32/home
https://sites.google.com/view/1uimwi96/home
https://sites.google.com/view/5ibqaj17/home
https://sites.google.com/view/9nqdyq84/home
https://sites.google.com/view/4lwyyd65/home
https://sites.google.com/view/3gawuk38/home
https://sites.google.com/view/5lgzag04/home
https://sites.google.com/view/1imonc99/home
https://sites.google.com/view/9xyukm01/home
https://sites.google.com/view/6nalbh77/home
https://sites.google.com/view/2ypecr06/home
https://sites.google.com/view/5zdody20/home
https://sites.google.com/view/6wkgom88/home
https://sites.google.com/view/8zwqqi53/home
https://sites.google.com/view/0qyluw74/home
https://sites.google.com/view/5aphjd68/home
https://sites.google.com/view/2sacyn38/home
https://sites.google.com/view/8dbjrm35/home
https://sites.google.com/view/6unuwk46/home
https://sites.google.com/view/5hrlie99/home
https://sites.google.com/view/6mxmyz56/home
https://sites.google.com/view/9gdzzy11/home
https://sites.google.com/view/3wkfon30/home
https://sites.google.com/view/7jesbl97/home
https://sites.google.com/view/7xllml61/home
https://sites.google.com/view/3mpkjx30/home
https://sites.google.com/view/6idizy90/home
https://sites.google.com/view/1czsip30/home
https://sites.google.com/view/3ezdwf97/home
https://sites.google.com/view/1ydubh84/home
https://sites.google.com/view/4cvedy61/home
https://sites.google.com/view/7yyumz73/home
https://sites.google.com/view/5pkquw00/home
https://sites.google.com/view/7dceex86/home
https://sites.google.com/view/3gnvyg34/home
https://sites.google.com/view/2ehwht60/home
https://sites.google.com/view/3ocyay63/home
https://sites.google.com/view/6uizgx22/home
https://sites.google.com/view/3cgblb20/home
https://sites.google.com/view/3guucg59/home
https://sites.google.com/view/9cdybx22/home
https://sites.google.com/view/7giaxy95/home
https://sites.google.com/view/3qkkfi06/home
https://sites.google.com/view/9goqyg50/home
https://sites.google.com/view/6sfntm95/home
https://sites.google.com/view/8hekup65/home
https://sites.google.com/view/8dypsx49/home
https://sites.google.com/view/1emhrm38/home
https://sites.google.com/view/1yyqhu29/home
https://sites.google.com/view/5heoek19/home
https://sites.google.com/view/3tvkkw85/home
https://sites.google.com/view/4dqdwv87/home
https://sites.google.com/view/2tsotl60/home
https://sites.google.com/view/7mpwib24/home
https://sites.google.com/view/9dlwvj94/home
https://sites.google.com/view/3hggdc60/home
https://sites.google.com/view/3vhyat21/home
https://sites.google.com/view/7vyebf30/home
https://sites.google.com/view/4wnnbn37/home
https://sites.google.com/view/6mjlkw77/home
https://sites.google.com/view/0jcihr79/home
https://sites.google.com/view/6lpxba81/home
https://sites.google.com/view/7nmcwv30/home
https://sites.google.com/view/3ztaut28/home
https://sites.google.com/view/1ecdeq22/home
https://sites.google.com/view/0dgpba93/home
https://sites.google.com/view/1aqqqo25/home
https://sites.google.com/view/1raxlw38/home
https://sites.google.com/view/3snsha82/home
https://sites.google.com/view/5uwlrm29/home
https://sites.google.com/view/4feaqq70/home
https://sites.google.com/view/3nlhqj52/home
https://sites.google.com/view/1pidkm14/home
https://sites.google.com/view/2wqdnc45/home
https://sites.google.com/view/1iwyhj71/home
https://sites.google.com/view/8evxkf88/home
https://sites.google.com/view/5zbhhn62/home
https://sites.google.com/view/9hjcnp07/home
https://sites.google.com/view/3mxnek99/home
https://sites.google.com/view/9bmsbt04/home
https://sites.google.com/view/2lezmt81/home
https://sites.google.com/view/6qmqey84/home
https://coub.com/view/scsevfdzaa
https://coub.com/view/ghy3aenpkf
https://coub.com/view/xfyiqequqh
https://coub.com/view/45ky2yyzja
https://coub.com/view/v7lv7a0sy1
https://coub.com/view/e09y796sai
https://coub.com/view/z9dgue55fb
https://coub.com/view/4v9yryaq7b
https://coub.com/view/rjakrzmprm
https://coub.com/view/443k6aufbo
https://coub.com/view/adxz7xc4zj
https://coub.com/view/gontyw4afu
https://coub.com/view/67wn0hy74w
https://coub.com/view/p7i0qsp0th
https://coub.com/view/bu18agb0cw
https://coub.com/view/u453dsg9rs
https://coub.com/view/0xh6401od2
https://coub.com/view/s8ivp1bnms
https://coub.com/view/q7d0nkso0h
https://coub.com/view/gadau0rb64
https://coub.com/view/79ww1tgrud
https://coub.com/view/grqy2h2n44
https://coub.com/view/dvjar8c2ng
https://coub.com/view/365566dfec
https://coub.com/view/szouxmw105
https://coub.com/view/yucl2gqgiq
https://coub.com/view/mcb2rze9ox
https://coub.com/view/0j5cdu3ezv
https://coub.com/view/023m21y9ex
https://coub.com/view/0tvarwqbh6
https://coub.com/view/ks6hpuqzv7
https://coub.com/view/390avxe2u9
https://coub.com/view/vqb725jiar
https://coub.com/view/9ekoylsaw1
https://coub.com/view/n5wdecqayc
https://coub.com/view/lu3z4p5rah
https://coub.com/view/6lu4dpcva8
https://coub.com/view/l3dai5ah2r
https://coub.com/view/6rged9l4g5
https://coub.com/view/u4gupyey94
https://coub.com/view/9oyqag6yr3
https://coub.com/view/3b6ywmk5lt
https://coub.com/view/t5mm2naqc1

收起阅读 »

JavaScript `this` 指南

JavaScript this

JavaScript this 指南

一、核心心法

this 不是在函数定义时确定的,而是在函数被调用时确定的。
判断 this,只需要看一件事:这个函数是怎么被调用的(调用点)。

唯一的例外是箭头函数——它根本没有自己的 this。


二、五种绑定规则

规则 1:默认绑定(独立函数调用)

function foo() {  
  console.log(this);  
}  

foo();  // 非严格模式: globalThis (浏览器 window / Node global)  
        // 严格模式:   undefined
'use strict';  
function foo() { console.log(this); }  
foo();  // undefined

顶层 this 的值取决于环境:

环境 顶层 this
浏览器 <script> window
ES Module (.mjs / type="module") undefined
CommonJS 模块 (Node .js) module.exports(即 {})
函数内(非严格) globalThis
函数内(严格) undefined
class 内部 永远严格模式

规则 2:隐式绑定(方法调用)

看调用时最后一个点前面是谁。

const obj = {  
  name: 'obj',  
  foo() { console.log(this.name); }  
};  

obj.foo();  // 'obj'

多层嵌套只看最后一层:

const a = { name: 'a', b: { name: 'b', foo() { console.log(this.name); } } };  
a.b.foo();  // 'b'  ← 不是 'a'

方括号访问同理:

a.b['foo']();  // 'b'

规则 3:显式绑定(call / apply / bind)

function greet(greeting, punct) {  
  console.log(greeting + ', ' + this.name + punct);  
}  
const me = { name: 'Alice' };  

greet.call(me, 'Hi', '!');       // 逐个传参  
greet.apply(me, ['Hi', '!']);    // 数组传参 (Apply → Array)  
const bound = greet.bind(me, 'Hi');  // 返回新函数 + 支持柯里化  
bound('!');

bind 的三个特性:

  1. 返回一个新函数,不立即执行
  2. 支持参数预置(柯里化)
  3. 硬绑定:绑定后无法再被 call/apply 改变
const hard = greet.bind(me);  
hard.call({ name: 'Bob' }, 'Yo', '!');  // 依然是 Alice

传 null / undefined 的坑:

function foo() { console.log(this); }  
foo.call(null);  // 非严格: globalThis(被替换了!)  严格: null

安全做法——DMZ 空对象:

const DMZ = Object.create(null);   // 比 {} 更干净,无原型  
foo.apply(DMZ, [1, 2]);

原始值会被装箱(仅非严格模式):

function f() { console.log(typeof this); }  
f.call(1);              // 'object'  (Number 包装对象)  
// 严格模式下: 'number'

规则 4:new 绑定

new Foo() 做了四件事:

  1. 创建一个新的空对象
  2. 将该对象的 [[Prototype]] 指向 Foo.prototype
  3. 以该对象为 this 执行 Foo
  4. 如果 Foo 没有返回一个对象,则返回这个新对象
function Person(name) {  
  this.name = name;  
  // return { name: 'hacked' };  // 若返回对象,则 new 的结果是它  
  // return 42;                  // 返回原始值 → 被忽略,仍返回 this  
}  
const p = new Person('Alice');  // p.name === 'Alice'

规则 5:箭头函数(词法绑定)

箭头函数没有自己的 this,它继承定义时所在外层作用域的 this,且永久不可改变。

const obj = {  
  name: 'obj',  
  regular() {  
    const arrow = () => console.log(this.name);  
    arrow();  // 'obj'  ← 继承 regular 的 this  
  },  
  arrowMethod: () => console.log(this?.name)  // ⚠️ 继承的是模块/全局的 this  
};  

obj.regular();      // 'obj'  
obj.arrowMethod();  // undefined  ← 经典陷阱!

箭头函数无法被改变:

const arrow = () => console.log(this);  
arrow.call({ a: 1 });   // call 无效  
arrow.bind({ a: 1 })(); // bind 无效  
new arrow();            // TypeError: arrow is not a constructor

箭头函数也没有 arguments、super、new.target。


三、优先级

new 绑定  >  显式绑定(bind/call/apply)  >  隐式绑定(obj.fn)  >  默认绑定

箭头函数不在此列——它压根不参与,直接看词法作用域。

验证 new > bind:

function Foo(a) { this.a = a; }  
const obj = {};  
const B = Foo.bind(obj);  

B(2);              // obj.a === 2      (bind 生效)  
const b = new B(3);// b.a === 3, obj.a 仍是 2   ← new 赢了

这正是 bind polyfill 里必须判断 new.target 的原因。


四、隐式丢失:最常见的 Bug 来源

1. 赋值给变量

const obj = { name: 'obj', foo() { console.log(this?.name); } };  
const fn = obj.foo;   // 只拿到了函数引用,丢掉了 obj  
fn();                 // undefined

2. 作为回调传递

setTimeout(obj.foo, 100);        // 丢失  
[1].forEach(obj.foo);            // 丢失  
btn.addEventListener('click', obj.foo);  // this 变成 btn

3. 解构

const { foo } = obj;  
foo();  // 丢失

这就是 React 中 const { setState } = this 会出问题、
以及 Vue3 用 ref 而非 this 的部分原因。

4. 间接引用

const o1 = { name: 'o1', foo() { console.log(this.name); } };  
const o2 = { name: 'o2' };  

o2.foo = o1.foo;  
o2.foo();          // 'o2'  (正常隐式绑定)  

(o2.foo = o1.foo)();  // 赋值表达式返回函数本身 → 默认绑定 → undefined/报错

5. 解决方案汇总

// ① bind  
setTimeout(obj.foo.bind(obj), 100);  

// ② 箭头函数包裹(推荐,可读性好)  
setTimeout(() => obj.foo(), 100);  

// ③ 数组方法的 thisArg 参数  
[1, 2].forEach(obj.foo, obj);  

// ④ class field 箭头函数(见下文)

支持 thisArg 的数组方法:
forEach map filter some every find findIndex findLast findLastIndex flatMap

不支持的: reduce reduceRight sort(它们的第二参数另有用途)


五、Class 中的 this

类体内永远是严格模式

class Counter {  
  count = 0;  

  inc() { this.count++; }               // 原型方法 → this 会丢失  
  dec = () => { this.count--; };        // 类字段箭头函数 → this 永久绑定实例  
}  

const c = new Counter();  
const { inc, dec } = c;  
dec();   // ✅ 正常  
inc();   // ❌ TypeError: Cannot read properties of undefined

两者的本质区别:

原型方法 inc() 类字段 dec = () => {}
存放位置 Counter.prototype 每个实例上
内存 共享一份 每个实例一份(内存开销大)
this 调用时决定 定义时锁死为实例
可被 call 改变 是 否
可被子类 super 调用 是 否

推荐做法: 默认用原型方法,只在需要作为回调传递时用箭头字段,或在构造器中 bind:

class Counter {  
  constructor() {  
    this.inc = this.inc.bind(this);  // 等价效果  
  }  
}

静态方法中的 this

class A {  
  static create() { return new this(); }  // this === 类本身,支持子类继承  
}  
class B extends A {}  
B.create();  // 返回 B 的实例,而非 A

继承与 super

class Base {  
  constructor() { this.type = 'base'; }  
  hello() { console.log('base hello'); }  
}  
class Child extends Base {  
  constructor() {  
    // console.log(this);  // ❌ ReferenceError: 必须先调用 super()  
    super();               // 派生类中,super() 之前 this 处于 TDZ  
    console.log(this.type);// 'base'  
  }  
  hello() {  
    super.hello();         // super 方法调用时,this 仍是当前实例  
  }  
}

私有字段的坑

class A {  
  #secret = 1;  
  get() { return this.#secret; }  
}  
const a = new A();  
a.get.call({});  // TypeError: Cannot read private member #secret  
                 // 私有字段是"品牌检查",this 必须是真实实例

六、各类场景速查

DOM 事件

btn.addEventListener('click', function () {  
  console.log(this);  // === event.currentTarget (btn)  
});  

btn.addEventListener('click', () => {  
  console.log(this);  // 外层作用域的 this,不是 btn  
});

handleEvent 特殊接口(传对象而非函数):

const handler = {  
  count: 0,  
  handleEvent(e) { console.log(this.count++, e.type); }  // this === handler  
};  
btn.addEventListener('click', handler);

内联 onclick="..." 属性中的 this 也是元素本身。

定时器

setTimeout(function () {  
  console.log(this);  
  // 浏览器非严格: window  
  // Node:         Timeout 对象  
}, 0);

getter / setter

const obj = {  
  _v: 1,  
  get v() { return this._v; }   // this 是访问该属性的对象  
};

Proxy 中的 this 泄漏

const target = { greet() { return this; } };  
const proxy = new Proxy(target, {});  
proxy.greet() === proxy;  // true —— this 是 proxy,不是 target  
// 若 target 用了私有字段或内部槽(如 Map/Set),会报错

标签模板 / 生成器 / async

async function f() { return this; }   // 与普通函数规则一致  
function* g() { yield this; }         // 同上

七、手写实现(面试高频)

Function.prototype.myCall = function (context, ...args) {  
  if (typeof this !== 'function') throw new TypeError('not a function');  
  context = context ?? globalThis;  
  if (Object(context) !== context) context = Object(context);  // 原始值装箱  

  const key = Symbol('fn');       // Symbol 避免属性名冲突  
  context[key] = this;  
  try {  
    return context[key](...args);  
  } finally {  
    delete context[key];  
  }  
};  

Function.prototype.myApply = function (context, args = []) {  
  return this.myCall(context, ...args);  
};  

Function.prototype.myBind = function (context, ...preArgs) {  
  const fn = this;  
  if (typeof fn !== 'function') throw new TypeError('not a function');  

  function bound(...args) {  
    // 关键:new 调用时忽略 context,使用新建的 this  
    return fn.apply(  
      new.target ? this : context,  
      [...preArgs, ...args]  
    );  
  }  
  // 维持原型链,使 instanceof 正确  
  bound.prototype = Object.create(fn.prototype || null);  
  return bound;  
};
// 手写 new  
function myNew(Ctor, ...args) {  
  const obj = Object.create(Ctor.prototype);  
  const ret = Ctor.apply(obj, args);  
  return (ret !== null && (typeof ret === 'object' || typeof ret === 'function'))  
    ? ret  
    : obj;  
}

八、综合练习(先自己作答)

var name = 'global';   // 注意:必须是 var,let/const 不挂载到 window  

const obj = {  
  name: 'obj',  
  f1() { console.log(this.name); },  
  f2: () => console.log(this?.name),  
  f3() {  
    return function () { console.log(this?.name); };  
  },  
  f4() {  
    return () => console.log(this.name);  
  }  
};  

obj.f1();          // ?  
obj.f2();          // ?  
obj.f3()();        // ?  
obj.f4()();        // ?  
const g = obj.f1;  
g();               // ?  
obj.f1.call({ name: 'x' });  // ?  
new obj.f1();      // ?

答案

obj.f1()          → 'obj'        隐式绑定  
obj.f2()          → 'global'/undefined  箭头函数继承顶层 this  
obj.f3()()        → 'global'/undefined  返回的函数独立调用,默认绑定  
obj.f4()()        → 'obj'        箭头函数继承 f4 的 this  
g()               → 'global'/undefined  隐式丢失  
obj.f1.call({...})→ 'x'          显式绑定  
new obj.f1()      → undefined    new 绑定,新对象上没有 name

(浏览器非严格模式下顶层 this 是 window,故为 'global';ESM/严格模式下为 undefined 或报错)


九、终极判断流程图

函数是箭头函数?  
├─ 是 → this = 定义时外层作用域的 this(向上查找,不可改变)  
└─ 否 ↓  
   用 new 调用? ──→ 是 → this = 新创建的对象  
   ↓ 否  
   用 call/apply/bind 调用? ──→ 是 → this = 指定的对象  
   ↓ 否                              (非严格下 null/undefined 会替换为 globalThis)  
   通过 obj.fn() 调用? ──→ 是 → this = obj(只看最后一个点)  
   ↓ 否  
   严格模式? ──→ 是 → this = undefined  
              └→ 否 → this = globalThis

十、最佳实践

  1. 优先用箭头函数写回调,避免 this 丢失
  2. 对象方法用简写语法 foo() {},不要用箭头函数(除非确实想要外层 this)
  3. class 中默认用原型方法,需要传递为回调时才用箭头字段或构造器 bind
  4. 全项目开启严格模式 / 使用 ESM,让 this 错误尽早暴露为 undefined 报错
  5. TypeScript 用户:开启 noImplicitThis,并善用显式 this 参数:
    function handler(this: HTMLButtonElement, e: MouseEvent) { /* ... */ }  
  6. 不确定时,打印 console.log(this) —— 比推理快十倍
继续阅读 »

JavaScript this 指南

一、核心心法

this 不是在函数定义时确定的,而是在函数被调用时确定的。
判断 this,只需要看一件事:这个函数是怎么被调用的(调用点)。

唯一的例外是箭头函数——它根本没有自己的 this。


二、五种绑定规则

规则 1:默认绑定(独立函数调用)

function foo() {  
  console.log(this);  
}  

foo();  // 非严格模式: globalThis (浏览器 window / Node global)  
        // 严格模式:   undefined
'use strict';  
function foo() { console.log(this); }  
foo();  // undefined

顶层 this 的值取决于环境:

环境 顶层 this
浏览器 <script> window
ES Module (.mjs / type="module") undefined
CommonJS 模块 (Node .js) module.exports(即 {})
函数内(非严格) globalThis
函数内(严格) undefined
class 内部 永远严格模式

规则 2:隐式绑定(方法调用)

看调用时最后一个点前面是谁。

const obj = {  
  name: 'obj',  
  foo() { console.log(this.name); }  
};  

obj.foo();  // 'obj'

多层嵌套只看最后一层:

const a = { name: 'a', b: { name: 'b', foo() { console.log(this.name); } } };  
a.b.foo();  // 'b'  ← 不是 'a'

方括号访问同理:

a.b['foo']();  // 'b'

规则 3:显式绑定(call / apply / bind)

function greet(greeting, punct) {  
  console.log(greeting + ', ' + this.name + punct);  
}  
const me = { name: 'Alice' };  

greet.call(me, 'Hi', '!');       // 逐个传参  
greet.apply(me, ['Hi', '!']);    // 数组传参 (Apply → Array)  
const bound = greet.bind(me, 'Hi');  // 返回新函数 + 支持柯里化  
bound('!');

bind 的三个特性:

  1. 返回一个新函数,不立即执行
  2. 支持参数预置(柯里化)
  3. 硬绑定:绑定后无法再被 call/apply 改变
const hard = greet.bind(me);  
hard.call({ name: 'Bob' }, 'Yo', '!');  // 依然是 Alice

传 null / undefined 的坑:

function foo() { console.log(this); }  
foo.call(null);  // 非严格: globalThis(被替换了!)  严格: null

安全做法——DMZ 空对象:

const DMZ = Object.create(null);   // 比 {} 更干净,无原型  
foo.apply(DMZ, [1, 2]);

原始值会被装箱(仅非严格模式):

function f() { console.log(typeof this); }  
f.call(1);              // 'object'  (Number 包装对象)  
// 严格模式下: 'number'

规则 4:new 绑定

new Foo() 做了四件事:

  1. 创建一个新的空对象
  2. 将该对象的 [[Prototype]] 指向 Foo.prototype
  3. 以该对象为 this 执行 Foo
  4. 如果 Foo 没有返回一个对象,则返回这个新对象
function Person(name) {  
  this.name = name;  
  // return { name: 'hacked' };  // 若返回对象,则 new 的结果是它  
  // return 42;                  // 返回原始值 → 被忽略,仍返回 this  
}  
const p = new Person('Alice');  // p.name === 'Alice'

规则 5:箭头函数(词法绑定)

箭头函数没有自己的 this,它继承定义时所在外层作用域的 this,且永久不可改变。

const obj = {  
  name: 'obj',  
  regular() {  
    const arrow = () => console.log(this.name);  
    arrow();  // 'obj'  ← 继承 regular 的 this  
  },  
  arrowMethod: () => console.log(this?.name)  // ⚠️ 继承的是模块/全局的 this  
};  

obj.regular();      // 'obj'  
obj.arrowMethod();  // undefined  ← 经典陷阱!

箭头函数无法被改变:

const arrow = () => console.log(this);  
arrow.call({ a: 1 });   // call 无效  
arrow.bind({ a: 1 })(); // bind 无效  
new arrow();            // TypeError: arrow is not a constructor

箭头函数也没有 arguments、super、new.target。


三、优先级

new 绑定  >  显式绑定(bind/call/apply)  >  隐式绑定(obj.fn)  >  默认绑定

箭头函数不在此列——它压根不参与,直接看词法作用域。

验证 new > bind:

function Foo(a) { this.a = a; }  
const obj = {};  
const B = Foo.bind(obj);  

B(2);              // obj.a === 2      (bind 生效)  
const b = new B(3);// b.a === 3, obj.a 仍是 2   ← new 赢了

这正是 bind polyfill 里必须判断 new.target 的原因。


四、隐式丢失:最常见的 Bug 来源

1. 赋值给变量

const obj = { name: 'obj', foo() { console.log(this?.name); } };  
const fn = obj.foo;   // 只拿到了函数引用,丢掉了 obj  
fn();                 // undefined

2. 作为回调传递

setTimeout(obj.foo, 100);        // 丢失  
[1].forEach(obj.foo);            // 丢失  
btn.addEventListener('click', obj.foo);  // this 变成 btn

3. 解构

const { foo } = obj;  
foo();  // 丢失

这就是 React 中 const { setState } = this 会出问题、
以及 Vue3 用 ref 而非 this 的部分原因。

4. 间接引用

const o1 = { name: 'o1', foo() { console.log(this.name); } };  
const o2 = { name: 'o2' };  

o2.foo = o1.foo;  
o2.foo();          // 'o2'  (正常隐式绑定)  

(o2.foo = o1.foo)();  // 赋值表达式返回函数本身 → 默认绑定 → undefined/报错

5. 解决方案汇总

// ① bind  
setTimeout(obj.foo.bind(obj), 100);  

// ② 箭头函数包裹(推荐,可读性好)  
setTimeout(() => obj.foo(), 100);  

// ③ 数组方法的 thisArg 参数  
[1, 2].forEach(obj.foo, obj);  

// ④ class field 箭头函数(见下文)

支持 thisArg 的数组方法:
forEach map filter some every find findIndex findLast findLastIndex flatMap

不支持的: reduce reduceRight sort(它们的第二参数另有用途)


五、Class 中的 this

类体内永远是严格模式

class Counter {  
  count = 0;  

  inc() { this.count++; }               // 原型方法 → this 会丢失  
  dec = () => { this.count--; };        // 类字段箭头函数 → this 永久绑定实例  
}  

const c = new Counter();  
const { inc, dec } = c;  
dec();   // ✅ 正常  
inc();   // ❌ TypeError: Cannot read properties of undefined

两者的本质区别:

原型方法 inc() 类字段 dec = () => {}
存放位置 Counter.prototype 每个实例上
内存 共享一份 每个实例一份(内存开销大)
this 调用时决定 定义时锁死为实例
可被 call 改变 是 否
可被子类 super 调用 是 否

推荐做法: 默认用原型方法,只在需要作为回调传递时用箭头字段,或在构造器中 bind:

class Counter {  
  constructor() {  
    this.inc = this.inc.bind(this);  // 等价效果  
  }  
}

静态方法中的 this

class A {  
  static create() { return new this(); }  // this === 类本身,支持子类继承  
}  
class B extends A {}  
B.create();  // 返回 B 的实例,而非 A

继承与 super

class Base {  
  constructor() { this.type = 'base'; }  
  hello() { console.log('base hello'); }  
}  
class Child extends Base {  
  constructor() {  
    // console.log(this);  // ❌ ReferenceError: 必须先调用 super()  
    super();               // 派生类中,super() 之前 this 处于 TDZ  
    console.log(this.type);// 'base'  
  }  
  hello() {  
    super.hello();         // super 方法调用时,this 仍是当前实例  
  }  
}

私有字段的坑

class A {  
  #secret = 1;  
  get() { return this.#secret; }  
}  
const a = new A();  
a.get.call({});  // TypeError: Cannot read private member #secret  
                 // 私有字段是"品牌检查",this 必须是真实实例

六、各类场景速查

DOM 事件

btn.addEventListener('click', function () {  
  console.log(this);  // === event.currentTarget (btn)  
});  

btn.addEventListener('click', () => {  
  console.log(this);  // 外层作用域的 this,不是 btn  
});

handleEvent 特殊接口(传对象而非函数):

const handler = {  
  count: 0,  
  handleEvent(e) { console.log(this.count++, e.type); }  // this === handler  
};  
btn.addEventListener('click', handler);

内联 onclick="..." 属性中的 this 也是元素本身。

定时器

setTimeout(function () {  
  console.log(this);  
  // 浏览器非严格: window  
  // Node:         Timeout 对象  
}, 0);

getter / setter

const obj = {  
  _v: 1,  
  get v() { return this._v; }   // this 是访问该属性的对象  
};

Proxy 中的 this 泄漏

const target = { greet() { return this; } };  
const proxy = new Proxy(target, {});  
proxy.greet() === proxy;  // true —— this 是 proxy,不是 target  
// 若 target 用了私有字段或内部槽(如 Map/Set),会报错

标签模板 / 生成器 / async

async function f() { return this; }   // 与普通函数规则一致  
function* g() { yield this; }         // 同上

七、手写实现(面试高频)

Function.prototype.myCall = function (context, ...args) {  
  if (typeof this !== 'function') throw new TypeError('not a function');  
  context = context ?? globalThis;  
  if (Object(context) !== context) context = Object(context);  // 原始值装箱  

  const key = Symbol('fn');       // Symbol 避免属性名冲突  
  context[key] = this;  
  try {  
    return context[key](...args);  
  } finally {  
    delete context[key];  
  }  
};  

Function.prototype.myApply = function (context, args = []) {  
  return this.myCall(context, ...args);  
};  

Function.prototype.myBind = function (context, ...preArgs) {  
  const fn = this;  
  if (typeof fn !== 'function') throw new TypeError('not a function');  

  function bound(...args) {  
    // 关键:new 调用时忽略 context,使用新建的 this  
    return fn.apply(  
      new.target ? this : context,  
      [...preArgs, ...args]  
    );  
  }  
  // 维持原型链,使 instanceof 正确  
  bound.prototype = Object.create(fn.prototype || null);  
  return bound;  
};
// 手写 new  
function myNew(Ctor, ...args) {  
  const obj = Object.create(Ctor.prototype);  
  const ret = Ctor.apply(obj, args);  
  return (ret !== null && (typeof ret === 'object' || typeof ret === 'function'))  
    ? ret  
    : obj;  
}

八、综合练习(先自己作答)

var name = 'global';   // 注意:必须是 var,let/const 不挂载到 window  

const obj = {  
  name: 'obj',  
  f1() { console.log(this.name); },  
  f2: () => console.log(this?.name),  
  f3() {  
    return function () { console.log(this?.name); };  
  },  
  f4() {  
    return () => console.log(this.name);  
  }  
};  

obj.f1();          // ?  
obj.f2();          // ?  
obj.f3()();        // ?  
obj.f4()();        // ?  
const g = obj.f1;  
g();               // ?  
obj.f1.call({ name: 'x' });  // ?  
new obj.f1();      // ?

答案

obj.f1()          → 'obj'        隐式绑定  
obj.f2()          → 'global'/undefined  箭头函数继承顶层 this  
obj.f3()()        → 'global'/undefined  返回的函数独立调用,默认绑定  
obj.f4()()        → 'obj'        箭头函数继承 f4 的 this  
g()               → 'global'/undefined  隐式丢失  
obj.f1.call({...})→ 'x'          显式绑定  
new obj.f1()      → undefined    new 绑定,新对象上没有 name

(浏览器非严格模式下顶层 this 是 window,故为 'global';ESM/严格模式下为 undefined 或报错)


九、终极判断流程图

函数是箭头函数?  
├─ 是 → this = 定义时外层作用域的 this(向上查找,不可改变)  
└─ 否 ↓  
   用 new 调用? ──→ 是 → this = 新创建的对象  
   ↓ 否  
   用 call/apply/bind 调用? ──→ 是 → this = 指定的对象  
   ↓ 否                              (非严格下 null/undefined 会替换为 globalThis)  
   通过 obj.fn() 调用? ──→ 是 → this = obj(只看最后一个点)  
   ↓ 否  
   严格模式? ──→ 是 → this = undefined  
              └→ 否 → this = globalThis

十、最佳实践

  1. 优先用箭头函数写回调,避免 this 丢失
  2. 对象方法用简写语法 foo() {},不要用箭头函数(除非确实想要外层 this)
  3. class 中默认用原型方法,需要传递为回调时才用箭头字段或构造器 bind
  4. 全项目开启严格模式 / 使用 ESM,让 this 错误尽早暴露为 undefined 报错
  5. TypeScript 用户:开启 noImplicitThis,并善用显式 this 参数:
    function handler(this: HTMLButtonElement, e: MouseEvent) { /* ... */ }  
  6. 不确定时,打印 console.log(this) —— 比推理快十倍
收起阅读 »

使用animateTo循环播放动画时刷新状态变量无法刷新UI

问题现象
在使用animateTo方法实现循环动画的过程中,如果希望在每次动画播放时通过更新状态变量来刷新UI,可能会遇到UI无法动态响应更新的问题。例如,即使在事件中配置状态变量changeNum每次播放增加1,并设置动画循环播放3次,实际的UI显示仅更新一次,而onFinish的回调中changeNum仅记录1。

@Entry
@Component
struct AnimateToExample {
@State widthSize: number = 250;
@State heightSize: number = 100;
uiContext: UIContext | undefined = undefined;
@State changeNum: number = 0;
aboutToAppear() {
this.uiContext = this.getUIContext();
if (!this.uiContext) {
console.warn('no uiContext');
return;
}
}
playLrc() {
this.uiContext?.animateTo({
duration: 2000,
curve: Curve.EaseOut,
iterations: 3,
playMode: PlayMode.Normal,
onFinish: () => {
console.info('play end');
}
}, () => {
this.widthSize = 300;
this.heightSize = 60;
this.changeNum++;
});
}
build() {
Column() {
Button(this.changeNum.toString())
.width(this.widthSize)
.height(this.heightSize)
.margin(30)
.onClick(() => {
this.playLrc();
});
}.width('100%').margin({ top: 5 });
}
}
问题效果预览:

点击放大

背景知识
animateTo:提供animateTo接口来指定由于闭包代码导致的状态变化插入过渡动效。
关键帧动画 (keyframeAnimateTo):在UIContext中提供keyframeAnimateTo接口来指定若干个关键帧状态,实现分段的动画。
解决方案
显示动画的参数iterations表示动画的执行次数,并不代表闭包函数里面的逻辑执行次数,所以问题代码中iterations:3并不会使得修改宽高动画执行3次,需要使用关键帧动画。
使用关键帧动画,可以分段执行动画逻辑。在动画结束后在onFinish回调中递归执行动画,从而实现预期效果。
@Entry
@Component
struct AnimateToExample {
@State widthSize: number = 250;
@State heightSize: number = 100;
uiContext: UIContext | undefined = undefined;
@State changeNum: number = 0;

aboutToAppear() {
this.uiContext = this.getUIContext();
};

playLrc() {
// 使用关键帧动画
this.uiContext?.keyframeAnimateTo({
iterations: 1,
onFinish: () => {
if (this.changeNum % 3 !== 0) {
this.playLrc();
}
}
}, [
{
// 第一段关键帧动画
duration: 800,
event: () => {
this.widthSize = 300;
this.heightSize = 60;
this.changeNum++;
}
},
// 第二段关键帧动画
{
duration: 500,
event: () => {
this.widthSize = 250;
this.heightSize = 100;
}
}
]);
}

build() {
Column() {
Button(this.changeNum.toString())
.width(this.widthSize)
.height(this.heightSize)
.margin(30)
.onClick(() => {
this.playLrc();
});
}.width('100%').margin({ top: 5 });
}
}
https://sites.google.com/view/6hnwoa03/home
https://sites.google.com/view/2bdovq40/home
https://sites.google.com/view/3ykzjl65/home
https://sites.google.com/view/3ypjlx51/home
https://sites.google.com/view/2vecse29/home
https://sites.google.com/view/8wjmoz24/home
https://sites.google.com/view/6anaaz10/home
https://sites.google.com/view/6vztlx87/home
https://sites.google.com/view/9hxqwy10/home
https://sites.google.com/view/6emmnf53/home
https://sites.google.com/view/9qgmon96/home
https://sites.google.com/view/8vqblh76/home
https://sites.google.com/view/5repzi61/home
https://sites.google.com/view/6iqxbj96/home
https://sites.google.com/view/4fiyto32/home
https://sites.google.com/view/1uimwi96/home
https://sites.google.com/view/5ibqaj17/home
https://sites.google.com/view/9nqdyq84/home
https://sites.google.com/view/4lwyyd65/home
https://sites.google.com/view/3gawuk38/home
https://sites.google.com/view/5lgzag04/home
https://sites.google.com/view/1imonc99/home
https://sites.google.com/view/9xyukm01/home
https://sites.google.com/view/6nalbh77/home
https://sites.google.com/view/2ypecr06/home
https://sites.google.com/view/5zdody20/home
https://sites.google.com/view/6wkgom88/home
https://sites.google.com/view/8zwqqi53/home
https://sites.google.com/view/0qyluw74/home
https://sites.google.com/view/5aphjd68/home
https://sites.google.com/view/2sacyn38/home
https://sites.google.com/view/8dbjrm35/home
https://sites.google.com/view/6unuwk46/home
https://sites.google.com/view/5hrlie99/home
https://sites.google.com/view/6mxmyz56/home
https://sites.google.com/view/9gdzzy11/home
https://sites.google.com/view/3wkfon30/home
https://sites.google.com/view/7jesbl97/home
https://sites.google.com/view/7xllml61/home
https://sites.google.com/view/3mpkjx30/home
https://sites.google.com/view/6idizy90/home
https://sites.google.com/view/1czsip30/home
https://sites.google.com/view/3ezdwf97/home
https://sites.google.com/view/1ydubh84/home
https://sites.google.com/view/4cvedy61/home
https://sites.google.com/view/7yyumz73/home
https://sites.google.com/view/5pkquw00/home
https://sites.google.com/view/7dceex86/home
https://sites.google.com/view/3gnvyg34/home
https://sites.google.com/view/2ehwht60/home
https://sites.google.com/view/3ocyay63/home
https://sites.google.com/view/6uizgx22/home
https://sites.google.com/view/3cgblb20/home
https://sites.google.com/view/3guucg59/home
https://sites.google.com/view/9cdybx22/home
https://sites.google.com/view/7giaxy95/home
https://sites.google.com/view/3qkkfi06/home
https://sites.google.com/view/9goqyg50/home
https://sites.google.com/view/6sfntm95/home
https://sites.google.com/view/8hekup65/home
https://sites.google.com/view/8dypsx49/home
https://sites.google.com/view/1emhrm38/home
https://sites.google.com/view/1yyqhu29/home
https://sites.google.com/view/5heoek19/home
https://sites.google.com/view/3tvkkw85/home
https://sites.google.com/view/4dqdwv87/home
https://sites.google.com/view/2tsotl60/home
https://sites.google.com/view/7mpwib24/home
https://sites.google.com/view/9dlwvj94/home
https://sites.google.com/view/3hggdc60/home
https://sites.google.com/view/3vhyat21/home
https://sites.google.com/view/7vyebf30/home
https://sites.google.com/view/4wnnbn37/home
https://sites.google.com/view/6mjlkw77/home
https://sites.google.com/view/0jcihr79/home
https://sites.google.com/view/6lpxba81/home
https://sites.google.com/view/7nmcwv30/home
https://sites.google.com/view/3ztaut28/home
https://sites.google.com/view/1ecdeq22/home
https://sites.google.com/view/0dgpba93/home
https://sites.google.com/view/1aqqqo25/home
https://sites.google.com/view/1raxlw38/home
https://sites.google.com/view/3snsha82/home
https://sites.google.com/view/5uwlrm29/home
https://sites.google.com/view/4feaqq70/home
https://sites.google.com/view/3nlhqj52/home
https://sites.google.com/view/1pidkm14/home
https://sites.google.com/view/2wqdnc45/home
https://sites.google.com/view/1iwyhj71/home
https://sites.google.com/view/8evxkf88/home
https://sites.google.com/view/5zbhhn62/home
https://sites.google.com/view/9hjcnp07/home
https://sites.google.com/view/3mxnek99/home
https://sites.google.com/view/9bmsbt04/home
https://sites.google.com/view/2lezmt81/home
https://sites.google.com/view/6qmqey84/home
https://coub.com/view/kvpoylkwik
https://coub.com/view/i77t3xznqp
https://coub.com/view/f7trec03b8
https://coub.com/view/3n90r2j0rd
https://coub.com/view/y57xd610co
https://coub.com/view/eaephw8q6a
https://coub.com/view/hrrjoj4h9l
https://coub.com/view/wsjrcxld3n
https://coub.com/view/npkzo9bhsy
https://coub.com/view/e684ftu7dh
https://coub.com/view/3o0ro925t2
https://coub.com/view/fnl2q6kiht
https://coub.com/view/n99qs782yl
https://coub.com/view/1ondlagd1i
https://coub.com/view/9z6hsd1v17
https://coub.com/view/10d32yhsna
https://coub.com/view/ouwb3ocnhh
https://coub.com/view/xdc8nn4avc
https://coub.com/view/09w1hw0sip
https://coub.com/view/zlwigv6w8f
https://coub.com/view/j3r71lim3x
https://coub.com/view/2430o5fdrc
https://coub.com/view/ujctsdcdwy
https://coub.com/view/0buxtgmwxv
https://coub.com/view/377gnonlc2
https://coub.com/view/2nxu03hy9c
https://coub.com/view/qc7d9vj1v0
https://coub.com/view/3mqfz4b3fw
https://coub.com/view/g7ijtiaan7
https://coub.com/view/5inj7wcs41
https://coub.com/view/re695cu3l0
https://coub.com/view/qk2gxc95lg
https://coub.com/view/1g97y8qqtc
https://coub.com/view/wbze3qybuc
https://coub.com/view/ht8lrsgyup
https://coub.com/view/uwy6yesicp
https://coub.com/view/8xz2oua5mz
https://coub.com/view/va9tyn2k4l
https://coub.com/view/5kamzbr4mi
https://coub.com/view/9slmznc3ol
https://coub.com/view/dnhe4x7otv
https://coub.com/view/libk78392s
https://coub.com/view/2splvx7ais
https://coub.com/view/0fqk03fwlz
https://coub.com/view/u3but76ukt
https://coub.com/view/2xrg8kdx5o
https://coub.com/view/iqeb7cwj8k
https://coub.com/view/lx86s7cfpr
https://coub.com/view/qin8evf0sk
https://coub.com/view/ul1dmz1db2

继续阅读 »

问题现象
在使用animateTo方法实现循环动画的过程中,如果希望在每次动画播放时通过更新状态变量来刷新UI,可能会遇到UI无法动态响应更新的问题。例如,即使在事件中配置状态变量changeNum每次播放增加1,并设置动画循环播放3次,实际的UI显示仅更新一次,而onFinish的回调中changeNum仅记录1。

@Entry
@Component
struct AnimateToExample {
@State widthSize: number = 250;
@State heightSize: number = 100;
uiContext: UIContext | undefined = undefined;
@State changeNum: number = 0;
aboutToAppear() {
this.uiContext = this.getUIContext();
if (!this.uiContext) {
console.warn('no uiContext');
return;
}
}
playLrc() {
this.uiContext?.animateTo({
duration: 2000,
curve: Curve.EaseOut,
iterations: 3,
playMode: PlayMode.Normal,
onFinish: () => {
console.info('play end');
}
}, () => {
this.widthSize = 300;
this.heightSize = 60;
this.changeNum++;
});
}
build() {
Column() {
Button(this.changeNum.toString())
.width(this.widthSize)
.height(this.heightSize)
.margin(30)
.onClick(() => {
this.playLrc();
});
}.width('100%').margin({ top: 5 });
}
}
问题效果预览:

点击放大

背景知识
animateTo:提供animateTo接口来指定由于闭包代码导致的状态变化插入过渡动效。
关键帧动画 (keyframeAnimateTo):在UIContext中提供keyframeAnimateTo接口来指定若干个关键帧状态,实现分段的动画。
解决方案
显示动画的参数iterations表示动画的执行次数,并不代表闭包函数里面的逻辑执行次数,所以问题代码中iterations:3并不会使得修改宽高动画执行3次,需要使用关键帧动画。
使用关键帧动画,可以分段执行动画逻辑。在动画结束后在onFinish回调中递归执行动画,从而实现预期效果。
@Entry
@Component
struct AnimateToExample {
@State widthSize: number = 250;
@State heightSize: number = 100;
uiContext: UIContext | undefined = undefined;
@State changeNum: number = 0;

aboutToAppear() {
this.uiContext = this.getUIContext();
};

playLrc() {
// 使用关键帧动画
this.uiContext?.keyframeAnimateTo({
iterations: 1,
onFinish: () => {
if (this.changeNum % 3 !== 0) {
this.playLrc();
}
}
}, [
{
// 第一段关键帧动画
duration: 800,
event: () => {
this.widthSize = 300;
this.heightSize = 60;
this.changeNum++;
}
},
// 第二段关键帧动画
{
duration: 500,
event: () => {
this.widthSize = 250;
this.heightSize = 100;
}
}
]);
}

build() {
Column() {
Button(this.changeNum.toString())
.width(this.widthSize)
.height(this.heightSize)
.margin(30)
.onClick(() => {
this.playLrc();
});
}.width('100%').margin({ top: 5 });
}
}
https://sites.google.com/view/6hnwoa03/home
https://sites.google.com/view/2bdovq40/home
https://sites.google.com/view/3ykzjl65/home
https://sites.google.com/view/3ypjlx51/home
https://sites.google.com/view/2vecse29/home
https://sites.google.com/view/8wjmoz24/home
https://sites.google.com/view/6anaaz10/home
https://sites.google.com/view/6vztlx87/home
https://sites.google.com/view/9hxqwy10/home
https://sites.google.com/view/6emmnf53/home
https://sites.google.com/view/9qgmon96/home
https://sites.google.com/view/8vqblh76/home
https://sites.google.com/view/5repzi61/home
https://sites.google.com/view/6iqxbj96/home
https://sites.google.com/view/4fiyto32/home
https://sites.google.com/view/1uimwi96/home
https://sites.google.com/view/5ibqaj17/home
https://sites.google.com/view/9nqdyq84/home
https://sites.google.com/view/4lwyyd65/home
https://sites.google.com/view/3gawuk38/home
https://sites.google.com/view/5lgzag04/home
https://sites.google.com/view/1imonc99/home
https://sites.google.com/view/9xyukm01/home
https://sites.google.com/view/6nalbh77/home
https://sites.google.com/view/2ypecr06/home
https://sites.google.com/view/5zdody20/home
https://sites.google.com/view/6wkgom88/home
https://sites.google.com/view/8zwqqi53/home
https://sites.google.com/view/0qyluw74/home
https://sites.google.com/view/5aphjd68/home
https://sites.google.com/view/2sacyn38/home
https://sites.google.com/view/8dbjrm35/home
https://sites.google.com/view/6unuwk46/home
https://sites.google.com/view/5hrlie99/home
https://sites.google.com/view/6mxmyz56/home
https://sites.google.com/view/9gdzzy11/home
https://sites.google.com/view/3wkfon30/home
https://sites.google.com/view/7jesbl97/home
https://sites.google.com/view/7xllml61/home
https://sites.google.com/view/3mpkjx30/home
https://sites.google.com/view/6idizy90/home
https://sites.google.com/view/1czsip30/home
https://sites.google.com/view/3ezdwf97/home
https://sites.google.com/view/1ydubh84/home
https://sites.google.com/view/4cvedy61/home
https://sites.google.com/view/7yyumz73/home
https://sites.google.com/view/5pkquw00/home
https://sites.google.com/view/7dceex86/home
https://sites.google.com/view/3gnvyg34/home
https://sites.google.com/view/2ehwht60/home
https://sites.google.com/view/3ocyay63/home
https://sites.google.com/view/6uizgx22/home
https://sites.google.com/view/3cgblb20/home
https://sites.google.com/view/3guucg59/home
https://sites.google.com/view/9cdybx22/home
https://sites.google.com/view/7giaxy95/home
https://sites.google.com/view/3qkkfi06/home
https://sites.google.com/view/9goqyg50/home
https://sites.google.com/view/6sfntm95/home
https://sites.google.com/view/8hekup65/home
https://sites.google.com/view/8dypsx49/home
https://sites.google.com/view/1emhrm38/home
https://sites.google.com/view/1yyqhu29/home
https://sites.google.com/view/5heoek19/home
https://sites.google.com/view/3tvkkw85/home
https://sites.google.com/view/4dqdwv87/home
https://sites.google.com/view/2tsotl60/home
https://sites.google.com/view/7mpwib24/home
https://sites.google.com/view/9dlwvj94/home
https://sites.google.com/view/3hggdc60/home
https://sites.google.com/view/3vhyat21/home
https://sites.google.com/view/7vyebf30/home
https://sites.google.com/view/4wnnbn37/home
https://sites.google.com/view/6mjlkw77/home
https://sites.google.com/view/0jcihr79/home
https://sites.google.com/view/6lpxba81/home
https://sites.google.com/view/7nmcwv30/home
https://sites.google.com/view/3ztaut28/home
https://sites.google.com/view/1ecdeq22/home
https://sites.google.com/view/0dgpba93/home
https://sites.google.com/view/1aqqqo25/home
https://sites.google.com/view/1raxlw38/home
https://sites.google.com/view/3snsha82/home
https://sites.google.com/view/5uwlrm29/home
https://sites.google.com/view/4feaqq70/home
https://sites.google.com/view/3nlhqj52/home
https://sites.google.com/view/1pidkm14/home
https://sites.google.com/view/2wqdnc45/home
https://sites.google.com/view/1iwyhj71/home
https://sites.google.com/view/8evxkf88/home
https://sites.google.com/view/5zbhhn62/home
https://sites.google.com/view/9hjcnp07/home
https://sites.google.com/view/3mxnek99/home
https://sites.google.com/view/9bmsbt04/home
https://sites.google.com/view/2lezmt81/home
https://sites.google.com/view/6qmqey84/home
https://coub.com/view/kvpoylkwik
https://coub.com/view/i77t3xznqp
https://coub.com/view/f7trec03b8
https://coub.com/view/3n90r2j0rd
https://coub.com/view/y57xd610co
https://coub.com/view/eaephw8q6a
https://coub.com/view/hrrjoj4h9l
https://coub.com/view/wsjrcxld3n
https://coub.com/view/npkzo9bhsy
https://coub.com/view/e684ftu7dh
https://coub.com/view/3o0ro925t2
https://coub.com/view/fnl2q6kiht
https://coub.com/view/n99qs782yl
https://coub.com/view/1ondlagd1i
https://coub.com/view/9z6hsd1v17
https://coub.com/view/10d32yhsna
https://coub.com/view/ouwb3ocnhh
https://coub.com/view/xdc8nn4avc
https://coub.com/view/09w1hw0sip
https://coub.com/view/zlwigv6w8f
https://coub.com/view/j3r71lim3x
https://coub.com/view/2430o5fdrc
https://coub.com/view/ujctsdcdwy
https://coub.com/view/0buxtgmwxv
https://coub.com/view/377gnonlc2
https://coub.com/view/2nxu03hy9c
https://coub.com/view/qc7d9vj1v0
https://coub.com/view/3mqfz4b3fw
https://coub.com/view/g7ijtiaan7
https://coub.com/view/5inj7wcs41
https://coub.com/view/re695cu3l0
https://coub.com/view/qk2gxc95lg
https://coub.com/view/1g97y8qqtc
https://coub.com/view/wbze3qybuc
https://coub.com/view/ht8lrsgyup
https://coub.com/view/uwy6yesicp
https://coub.com/view/8xz2oua5mz
https://coub.com/view/va9tyn2k4l
https://coub.com/view/5kamzbr4mi
https://coub.com/view/9slmznc3ol
https://coub.com/view/dnhe4x7otv
https://coub.com/view/libk78392s
https://coub.com/view/2splvx7ais
https://coub.com/view/0fqk03fwlz
https://coub.com/view/u3but76ukt
https://coub.com/view/2xrg8kdx5o
https://coub.com/view/iqeb7cwj8k
https://coub.com/view/lx86s7cfpr
https://coub.com/view/qin8evf0sk
https://coub.com/view/ul1dmz1db2

收起阅读 »

如何实现一个自定义高度的底部弹窗

问题现象
需要实现的底部弹窗可以设置一个最大高度。同时,实现的底部弹窗需要从下往上出现。

如果弹窗内部视图的高度超过了这个最大高度,弹窗内部的视图就以这个自定义最大高度进行滚动展示;
如果弹窗内部视图的高度没有超过这个最大高度,那么弹窗内部视图就完全展示;
背景知识
组件的constraintSize属性用于设置约束尺寸,组件布局时,进行尺寸范围限制。

对于组件的显示方式,转场主要通过transition属性配置转场参数,在组件插入和删除时显示过渡动效,主要用于容器组件中的子组件插入和删除时,提升用户体验。

解决方案
首先,通过使用容器组件的constraintSize属性对视图进行高度限制,constraintSize的优先级高于Width和Height,constraintSize里面的maxHeight小于Height时,就会滚动展示。接着,实现弹窗从底部向上显示,通过设置组件transition属性的转场参数。

示例代码如下:

@CustomDialog
struct CustomDialogExample {
// 最大可滚动区域高度
maxScrollHeight: number = 70;
// 视图是否显示,用于底部弹窗出现与消失时的动效处理
@Link showFlag: Visibility;
controller: CustomDialogController;
scroller: Scroller = new Scroller;
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

build() {
Column({ space: 5 }) {
RelativeContainer() {
Text('标题')
.fontSize(17)
.fontColor(Color.Black)
.fontWeight(FontWeight.Bold)
.alignRules({
middle: { anchor: 'container', align: HorizontalAlign.Center },
center: { anchor: 'container', align: VerticalAlign.Center }
});
}
.width('100%')
.height(48);

  Scroll(this.scroller) {  
    Column() {  
      ForEach(this.arr, (item: number) => {  
        Text(item.toString())  
          .width('90%')  
          .height(50)  
          .backgroundColor(0xFFFFFF)  
          .borderColor(Color.Black)  
          .fontSize(16)  
          .textAlign(TextAlign.Center);  
        Divider()  
          .vertical(false)  
          .strokeWidth(0.5)  
          .width('60%');  
      }, (item: string) => item);  
    }.width('100%');  
  }  
  .edgeEffect(EdgeEffect.Spring)  
  .scrollSnap({  
    snapAlign: ScrollSnapAlign.START,  
    snapPagination: 400,  
    enableSnapToStart: true,  
    enableSnapToEnd: true  
  })  
  // 使用constraintSize方法可以设置约束尺寸,组件布局时,进行尺寸范围限制  
  .constraintSize({ maxHeight: this.maxScrollHeight + '%' });  

}  
.justifyContent(FlexAlign.Center)  
.backgroundColor('#f5f5f5')  
.borderRadius({  
  topLeft: '16',  
  topRight: '16'  
})  
.visibility(this.showFlag)  
.transition(TransitionEffect.OPACITY.animation({ duration: 200 })  // 弹窗出现与消失的动效  
  .combine(TransitionEffect.translate({ y: 100 })),  
  (transitionIn: boolean) => {  
    if (!transitionIn) {  
      this.controller.close();  
    }  
  }  
);  

}
}

@Entry
@Component
struct CustomDialogDemo {
// 最大可滚动区域高度
@State maxScrollHeight: number = 70;
@State settingDialogShowFlag: Visibility = Visibility.Visible;
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample({
maxScrollHeight: this.maxScrollHeight,
showFlag: this.settingDialogShowFlag
}),
alignment: DialogAlignment.Bottom,
width: '100%',
// customStyle需要设置为true,否则底部弹窗出现的动效会有问题
customStyle: true,
autoCancel: true,
// 修改点击弹窗外部区域和返回操作时弹窗消失的方式,这里的处理会有一个动效。否则弹窗会以默认的方式消失
onWillDismiss: () => {
this.settingDialogShowFlag = Visibility.Hidden;
}
});

build() {
Column() {
Row({ space: 5 }) {
Row() {
Button('减10')
.onClick(() => {
this.maxScrollHeight -= 10;
});
};

    Row() {  
      Text(this.maxScrollHeight + '%')  
        .width('40%')  
        .height(45)  
        .textAlign(TextAlign.Center)  
        .backgroundColor('#f5f5f5');  
    };  

    Row() {  
      Button('加10')  
        .onClick(() => {  
          this.maxScrollHeight += 10;  
        });  
    };  
  };  

  Button('打开底部弹窗')  
    .onClick(() => {  
      this.settingDialogShowFlag = Visibility.Visible;  
      this.dialogController.open();  
    });  
}.justifyContent(FlexAlign.Center).width('100%').height('100%');  

}
}

https://sites.google.com/view/8ojcyz21/home
https://sites.google.com/view/8votdo34/home
https://sites.google.com/view/2glnsx28/home
https://sites.google.com/view/5cljni07/home
https://sites.google.com/view/8prkhh07/home
https://sites.google.com/view/7khplo30/home
https://sites.google.com/view/1iwwoj29/home
https://sites.google.com/view/5lokbt20/home
https://sites.google.com/view/8ucjap35/home
https://sites.google.com/view/3vmwqk28/home
https://sites.google.com/view/4hqmfe46/home
https://sites.google.com/view/8jjhsd05/home
https://sites.google.com/view/2ajqbl31/home
https://sites.google.com/view/8eroul90/home
https://sites.google.com/view/8whpgb50/home
https://sites.google.com/view/7fhwgy79/home
https://sites.google.com/view/8otxzd32/home
https://sites.google.com/view/0skklh79/home
https://sites.google.com/view/7epixg32/home
https://sites.google.com/view/2fdymh76/home
https://sites.google.com/view/1qzbbd10/home
https://sites.google.com/view/3betru91/home
https://sites.google.com/view/0fjymy02/home
https://sites.google.com/view/1tuyht61/home
https://sites.google.com/view/3lqsci93/home
https://sites.google.com/view/4moosi57/home
https://sites.google.com/view/0ebmiw84/home
https://sites.google.com/view/4usaol83/home
https://sites.google.com/view/0hknci80/home
https://sites.google.com/view/7ldcgt68/home
https://sites.google.com/view/8emzek60/home
https://sites.google.com/view/9oaldi75/home
https://sites.google.com/view/1hujlx46/home
https://sites.google.com/view/9krrdx96/home
https://sites.google.com/view/0rdwwl74/home
https://sites.google.com/view/4oalem69/home
https://sites.google.com/view/7tpgsy97/home
https://sites.google.com/view/4sjvkh25/home
https://sites.google.com/view/6ixmrs26/home
https://sites.google.com/view/7jjcws23/home
https://sites.google.com/view/4entvi31/home
https://sites.google.com/view/0hqtsy80/home
https://sites.google.com/view/2vthve29/home
https://sites.google.com/view/2wjwhq06/home
https://sites.google.com/view/7lnlrg38/home
https://sites.google.com/view/3jwoff92/home
https://sites.google.com/view/9exfcx28/home
https://sites.google.com/view/6ayynm56/home
https://sites.google.com/view/2mkert04/home
https://sites.google.com/view/9bfpha34/home
https://sites.google.com/view/7kwinp20/home
https://sites.google.com/view/0kzpsl33/home
https://sites.google.com/view/7vqzug72/home
https://sites.google.com/view/8qybcb91/home
https://sites.google.com/view/1manil34/home
https://sites.google.com/view/9zlzlw68/home
https://sites.google.com/view/7ytpgf83/home
https://sites.google.com/view/0figtg65/home
https://sites.google.com/view/3nphmi02/home
https://sites.google.com/view/0qkeih69/home
https://sites.google.com/view/1qqdjc96/home
https://sites.google.com/view/2ocmzl47/home
https://sites.google.com/view/0rszvm31/home
https://sites.google.com/view/8twpcf39/home
https://sites.google.com/view/1tmhcz01/home
https://sites.google.com/view/1fsimv87/home
https://sites.google.com/view/0krela31/home
https://sites.google.com/view/8jhshd10/home
https://sites.google.com/view/5tseou06/home
https://sites.google.com/view/2msyts76/home
https://sites.google.com/view/6dxuri73/home
https://sites.google.com/view/1pmboa56/home
https://sites.google.com/view/9iueuv34/home
https://sites.google.com/view/8pubxw30/home
https://sites.google.com/view/1tlyco56/home
https://sites.google.com/view/8srvbu36/home
https://sites.google.com/view/4gsnto37/home
https://sites.google.com/view/6akswp56/home
https://sites.google.com/view/2inxol47/home
https://sites.google.com/view/3oojsn55/home
https://sites.google.com/view/5mdqwb17/home
https://sites.google.com/view/7xuice12/home
https://sites.google.com/view/0qzfci43/home
https://sites.google.com/view/6ohonc26/home
https://sites.google.com/view/4tbmuv32/home
https://sites.google.com/view/1rbxss83/home
https://sites.google.com/view/2niebx01/home
https://sites.google.com/view/3qaeqy35/home
https://sites.google.com/view/0cbewx04/home
https://sites.google.com/view/3csyuh21/home
https://sites.google.com/view/8wylyx13/home
https://sites.google.com/view/1kpxse38/home
https://sites.google.com/view/8ovanb09/home
https://sites.google.com/view/6rmoih50/home
https://sites.google.com/view/5cfhmg25/home
https://sites.google.com/view/4ivxrt19/home
https://sites.google.com/view/4ltzqb02/home
https://sites.google.com/view/3vdlsy06/home
https://sites.google.com/view/5vwlbj78/home
https://sites.google.com/view/4naisw60/home

继续阅读 »

问题现象
需要实现的底部弹窗可以设置一个最大高度。同时,实现的底部弹窗需要从下往上出现。

如果弹窗内部视图的高度超过了这个最大高度,弹窗内部的视图就以这个自定义最大高度进行滚动展示;
如果弹窗内部视图的高度没有超过这个最大高度,那么弹窗内部视图就完全展示;
背景知识
组件的constraintSize属性用于设置约束尺寸,组件布局时,进行尺寸范围限制。

对于组件的显示方式,转场主要通过transition属性配置转场参数,在组件插入和删除时显示过渡动效,主要用于容器组件中的子组件插入和删除时,提升用户体验。

解决方案
首先,通过使用容器组件的constraintSize属性对视图进行高度限制,constraintSize的优先级高于Width和Height,constraintSize里面的maxHeight小于Height时,就会滚动展示。接着,实现弹窗从底部向上显示,通过设置组件transition属性的转场参数。

示例代码如下:

@CustomDialog
struct CustomDialogExample {
// 最大可滚动区域高度
maxScrollHeight: number = 70;
// 视图是否显示,用于底部弹窗出现与消失时的动效处理
@Link showFlag: Visibility;
controller: CustomDialogController;
scroller: Scroller = new Scroller;
private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

build() {
Column({ space: 5 }) {
RelativeContainer() {
Text('标题')
.fontSize(17)
.fontColor(Color.Black)
.fontWeight(FontWeight.Bold)
.alignRules({
middle: { anchor: 'container', align: HorizontalAlign.Center },
center: { anchor: 'container', align: VerticalAlign.Center }
});
}
.width('100%')
.height(48);

  Scroll(this.scroller) {  
    Column() {  
      ForEach(this.arr, (item: number) => {  
        Text(item.toString())  
          .width('90%')  
          .height(50)  
          .backgroundColor(0xFFFFFF)  
          .borderColor(Color.Black)  
          .fontSize(16)  
          .textAlign(TextAlign.Center);  
        Divider()  
          .vertical(false)  
          .strokeWidth(0.5)  
          .width('60%');  
      }, (item: string) => item);  
    }.width('100%');  
  }  
  .edgeEffect(EdgeEffect.Spring)  
  .scrollSnap({  
    snapAlign: ScrollSnapAlign.START,  
    snapPagination: 400,  
    enableSnapToStart: true,  
    enableSnapToEnd: true  
  })  
  // 使用constraintSize方法可以设置约束尺寸,组件布局时,进行尺寸范围限制  
  .constraintSize({ maxHeight: this.maxScrollHeight + '%' });  

}  
.justifyContent(FlexAlign.Center)  
.backgroundColor('#f5f5f5')  
.borderRadius({  
  topLeft: '16',  
  topRight: '16'  
})  
.visibility(this.showFlag)  
.transition(TransitionEffect.OPACITY.animation({ duration: 200 })  // 弹窗出现与消失的动效  
  .combine(TransitionEffect.translate({ y: 100 })),  
  (transitionIn: boolean) => {  
    if (!transitionIn) {  
      this.controller.close();  
    }  
  }  
);  

}
}

@Entry
@Component
struct CustomDialogDemo {
// 最大可滚动区域高度
@State maxScrollHeight: number = 70;
@State settingDialogShowFlag: Visibility = Visibility.Visible;
dialogController: CustomDialogController = new CustomDialogController({
builder: CustomDialogExample({
maxScrollHeight: this.maxScrollHeight,
showFlag: this.settingDialogShowFlag
}),
alignment: DialogAlignment.Bottom,
width: '100%',
// customStyle需要设置为true,否则底部弹窗出现的动效会有问题
customStyle: true,
autoCancel: true,
// 修改点击弹窗外部区域和返回操作时弹窗消失的方式,这里的处理会有一个动效。否则弹窗会以默认的方式消失
onWillDismiss: () => {
this.settingDialogShowFlag = Visibility.Hidden;
}
});

build() {
Column() {
Row({ space: 5 }) {
Row() {
Button('减10')
.onClick(() => {
this.maxScrollHeight -= 10;
});
};

    Row() {  
      Text(this.maxScrollHeight + '%')  
        .width('40%')  
        .height(45)  
        .textAlign(TextAlign.Center)  
        .backgroundColor('#f5f5f5');  
    };  

    Row() {  
      Button('加10')  
        .onClick(() => {  
          this.maxScrollHeight += 10;  
        });  
    };  
  };  

  Button('打开底部弹窗')  
    .onClick(() => {  
      this.settingDialogShowFlag = Visibility.Visible;  
      this.dialogController.open();  
    });  
}.justifyContent(FlexAlign.Center).width('100%').height('100%');  

}
}

https://sites.google.com/view/8ojcyz21/home
https://sites.google.com/view/8votdo34/home
https://sites.google.com/view/2glnsx28/home
https://sites.google.com/view/5cljni07/home
https://sites.google.com/view/8prkhh07/home
https://sites.google.com/view/7khplo30/home
https://sites.google.com/view/1iwwoj29/home
https://sites.google.com/view/5lokbt20/home
https://sites.google.com/view/8ucjap35/home
https://sites.google.com/view/3vmwqk28/home
https://sites.google.com/view/4hqmfe46/home
https://sites.google.com/view/8jjhsd05/home
https://sites.google.com/view/2ajqbl31/home
https://sites.google.com/view/8eroul90/home
https://sites.google.com/view/8whpgb50/home
https://sites.google.com/view/7fhwgy79/home
https://sites.google.com/view/8otxzd32/home
https://sites.google.com/view/0skklh79/home
https://sites.google.com/view/7epixg32/home
https://sites.google.com/view/2fdymh76/home
https://sites.google.com/view/1qzbbd10/home
https://sites.google.com/view/3betru91/home
https://sites.google.com/view/0fjymy02/home
https://sites.google.com/view/1tuyht61/home
https://sites.google.com/view/3lqsci93/home
https://sites.google.com/view/4moosi57/home
https://sites.google.com/view/0ebmiw84/home
https://sites.google.com/view/4usaol83/home
https://sites.google.com/view/0hknci80/home
https://sites.google.com/view/7ldcgt68/home
https://sites.google.com/view/8emzek60/home
https://sites.google.com/view/9oaldi75/home
https://sites.google.com/view/1hujlx46/home
https://sites.google.com/view/9krrdx96/home
https://sites.google.com/view/0rdwwl74/home
https://sites.google.com/view/4oalem69/home
https://sites.google.com/view/7tpgsy97/home
https://sites.google.com/view/4sjvkh25/home
https://sites.google.com/view/6ixmrs26/home
https://sites.google.com/view/7jjcws23/home
https://sites.google.com/view/4entvi31/home
https://sites.google.com/view/0hqtsy80/home
https://sites.google.com/view/2vthve29/home
https://sites.google.com/view/2wjwhq06/home
https://sites.google.com/view/7lnlrg38/home
https://sites.google.com/view/3jwoff92/home
https://sites.google.com/view/9exfcx28/home
https://sites.google.com/view/6ayynm56/home
https://sites.google.com/view/2mkert04/home
https://sites.google.com/view/9bfpha34/home
https://sites.google.com/view/7kwinp20/home
https://sites.google.com/view/0kzpsl33/home
https://sites.google.com/view/7vqzug72/home
https://sites.google.com/view/8qybcb91/home
https://sites.google.com/view/1manil34/home
https://sites.google.com/view/9zlzlw68/home
https://sites.google.com/view/7ytpgf83/home
https://sites.google.com/view/0figtg65/home
https://sites.google.com/view/3nphmi02/home
https://sites.google.com/view/0qkeih69/home
https://sites.google.com/view/1qqdjc96/home
https://sites.google.com/view/2ocmzl47/home
https://sites.google.com/view/0rszvm31/home
https://sites.google.com/view/8twpcf39/home
https://sites.google.com/view/1tmhcz01/home
https://sites.google.com/view/1fsimv87/home
https://sites.google.com/view/0krela31/home
https://sites.google.com/view/8jhshd10/home
https://sites.google.com/view/5tseou06/home
https://sites.google.com/view/2msyts76/home
https://sites.google.com/view/6dxuri73/home
https://sites.google.com/view/1pmboa56/home
https://sites.google.com/view/9iueuv34/home
https://sites.google.com/view/8pubxw30/home
https://sites.google.com/view/1tlyco56/home
https://sites.google.com/view/8srvbu36/home
https://sites.google.com/view/4gsnto37/home
https://sites.google.com/view/6akswp56/home
https://sites.google.com/view/2inxol47/home
https://sites.google.com/view/3oojsn55/home
https://sites.google.com/view/5mdqwb17/home
https://sites.google.com/view/7xuice12/home
https://sites.google.com/view/0qzfci43/home
https://sites.google.com/view/6ohonc26/home
https://sites.google.com/view/4tbmuv32/home
https://sites.google.com/view/1rbxss83/home
https://sites.google.com/view/2niebx01/home
https://sites.google.com/view/3qaeqy35/home
https://sites.google.com/view/0cbewx04/home
https://sites.google.com/view/3csyuh21/home
https://sites.google.com/view/8wylyx13/home
https://sites.google.com/view/1kpxse38/home
https://sites.google.com/view/8ovanb09/home
https://sites.google.com/view/6rmoih50/home
https://sites.google.com/view/5cfhmg25/home
https://sites.google.com/view/4ivxrt19/home
https://sites.google.com/view/4ltzqb02/home
https://sites.google.com/view/3vdlsy06/home
https://sites.google.com/view/5vwlbj78/home
https://sites.google.com/view/4naisw60/home

收起阅读 »

如何解决打断无限循环动画后,动画失效问题

问题现象
第一次触发无限循环动画后,快速连续多次点击打断该无限循环动画,再次点击就无法看到动画了。

背景知识
animateTo:指定由于闭包代码导致的状态变化插入过渡动效。接口参数有两个,分别是value和event,其中value指定AnimateParam对象(包括时长、Curve等)event为动画的闭包函数,闭包内变量改变产生的属性动画将遵循相同的动画参数。

问题定位
对动画的执行机制进行排查,确认在动画打断过程中是否存在多次叠加的情况,从而导致动画未能正常显示。

分析结论
动画并未真正消失,而是在每次打断过程中被持续叠加,由于叠加的动画实例过多,彼此之间相互覆盖或干扰,导致在视觉上表现不明显,从而给人以动画消失的错觉。

修改结论
首先,应设置一个duration为0的动画,用于确保在每次动画被中断时,能够清除前一次的动画实例,避免动画叠加;该动画的属性值需设置为一个与上一次动画最终状态不同的指定值,以确保状态的更新和正确清除。随后,再创建一个用于实现所需初始动画效果的动画实例,以确保动画表现符合预期。

if (this.isRecording) {
// 设置一个duration为0的动画停掉上一次动画
this.getUIContext().animateTo({ duration: 0, iterations: 1, playMode: PlayMode.Normal }, () => {
// 这里的opacityValue不可以和上一次设置的终止0.1相同
this.opacityValue = 0.2;
});
this.isRecording = false;
// 再创建需要的动画
this.getUIContext().animateTo({ duration: 0, iterations: 1, playMode: PlayMode.Normal }, () => {
this.opacityValue = 1;
});
} else {
this.isRecording = true;
this.getUIContext().animateTo({ duration: 1500, iterations: -1, }, () => {
this.opacityValue = 0.1;
});
}
完整示例参考如下:

@Entry
@Component
struct Page {
@State opacityValue: number = 1;
@State isRecording: boolean = false;

build() {
Row() {
Column() {
Text(this.isRecording ? 'Hello World' : 'Welcome')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.opacity(this.opacityValue)
.textAlign(TextAlign.Center)
.fontColor('#000')
.onClick(() => {
if (this.isRecording) {
// 设置一个duration为0的动画停掉上一次动画
this.getUIContext().animateTo({ duration: 0, iterations: 1, playMode: PlayMode.Normal }, () => {
// 这里的opacityValue不可以和上一次设置的终止0.1相同
this.opacityValue = 0.2;
});
this.isRecording = false;
// 再创建需要的动画
this.getUIContext().animateTo({ duration: 0, iterations: 1, playMode: PlayMode.Normal }, () => {
this.opacityValue = 1;
});
} else {
this.isRecording = true;
this.getUIContext().animateTo({ duration: 1500, iterations: -1, }, () => {
this.opacityValue = 0.1;
});
}
});
}
.width('100%');
}
.height('100%');
}
}
https://sites.google.com/view/0kqtje63/home
https://sites.google.com/view/2yuici15/home
https://sites.google.com/view/2xujlh37/home
https://sites.google.com/view/3ahalq09/home
https://sites.google.com/view/3tqjcf61/home
https://sites.google.com/view/6jajrr88/home
https://sites.google.com/view/9ghecz74/home
https://sites.google.com/view/8qlhiy40/home
https://sites.google.com/view/6sknwb20/home
https://sites.google.com/view/2bwlws24/home
https://sites.google.com/view/2blczd15/home
https://sites.google.com/view/1lmiwz57/home
https://sites.google.com/view/7htwsc76/home
https://sites.google.com/view/3chktr81/home
https://sites.google.com/view/1rejbt07/home
https://sites.google.com/view/6zfyyu19/home
https://sites.google.com/view/9pcdic70/home
https://sites.google.com/view/0wcdbm94/home
https://sites.google.com/view/2yddpg19/home
https://sites.google.com/view/7sevuo10/home
https://sites.google.com/view/6incag10/home
https://sites.google.com/view/4bbfak54/home
https://sites.google.com/view/1wioqp25/home
https://sites.google.com/view/8vhssf95/home
https://sites.google.com/view/9eekvo81/home
https://sites.google.com/view/8qprfl23/home
https://sites.google.com/view/4wvrav40/home
https://sites.google.com/view/8legom73/home
https://sites.google.com/view/2kwmgy35/home
https://sites.google.com/view/0nkzdh51/home
https://sites.google.com/view/2kucvr93/home
https://sites.google.com/view/3csonu11/home
https://sites.google.com/view/3cajfi21/home
https://sites.google.com/view/2ysddp77/home
https://sites.google.com/view/5cqzyg62/home
https://sites.google.com/view/6waqdl39/home
https://sites.google.com/view/9qwaeq72/home
https://sites.google.com/view/1yknjw85/home
https://sites.google.com/view/6vaarx10/home
https://sites.google.com/view/7ltamt18/home
https://sites.google.com/view/3nrfrt86/home
https://sites.google.com/view/8rzfds48/home
https://sites.google.com/view/2vrecv27/home
https://sites.google.com/view/8pgryd86/home
https://sites.google.com/view/9ukcxw90/home
https://sites.google.com/view/6szkbf52/home
https://sites.google.com/view/1mvila90/home
https://sites.google.com/view/9eytcv61/home
https://sites.google.com/view/4cnnur37/home
https://sites.google.com/view/5uvpgz70/home
https://sites.google.com/view/9wnykm74/home
https://sites.google.com/view/1maebc71/home
https://sites.google.com/view/7bjvba88/home
https://sites.google.com/view/6yxdgq20/home
https://sites.google.com/view/6oplhm39/home
https://sites.google.com/view/0gslqp42/home
https://sites.google.com/view/2fobzs04/home
https://sites.google.com/view/5geezf47/home
https://sites.google.com/view/6damyk90/home
https://sites.google.com/view/8txifb96/home
https://sites.google.com/view/4uycua91/home
https://sites.google.com/view/4qahte14/home
https://sites.google.com/view/3hmwmu69/home
https://sites.google.com/view/2dqgbe78/home
https://sites.google.com/view/4ogacg47/home
https://sites.google.com/view/0zvppg85/home
https://sites.google.com/view/8bsjqs81/home
https://sites.google.com/view/0yhduz67/home
https://sites.google.com/view/0nvpde36/home
https://sites.google.com/view/4svrfc10/in%C3%ADcio
https://sites.google.com/view/4gjwxj46/in%C3%ADcio
https://sites.google.com/view/0xejpo69/in%C3%ADcio
https://sites.google.com/view/4eizzb39/in%C3%ADcio
https://sites.google.com/view/8cglkg37/in%C3%ADcio
https://sites.google.com/view/2poltf08/in%C3%ADcio
https://sites.google.com/view/6ggdzs74/in%C3%ADcio
https://sites.google.com/view/7rpmpu57/in%C3%ADcio
https://sites.google.com/view/6gkcjo69/in%C3%ADcio
https://sites.google.com/view/5jgxxq89/in%C3%ADcio

继续阅读 »

问题现象
第一次触发无限循环动画后,快速连续多次点击打断该无限循环动画,再次点击就无法看到动画了。

背景知识
animateTo:指定由于闭包代码导致的状态变化插入过渡动效。接口参数有两个,分别是value和event,其中value指定AnimateParam对象(包括时长、Curve等)event为动画的闭包函数,闭包内变量改变产生的属性动画将遵循相同的动画参数。

问题定位
对动画的执行机制进行排查,确认在动画打断过程中是否存在多次叠加的情况,从而导致动画未能正常显示。

分析结论
动画并未真正消失,而是在每次打断过程中被持续叠加,由于叠加的动画实例过多,彼此之间相互覆盖或干扰,导致在视觉上表现不明显,从而给人以动画消失的错觉。

修改结论
首先,应设置一个duration为0的动画,用于确保在每次动画被中断时,能够清除前一次的动画实例,避免动画叠加;该动画的属性值需设置为一个与上一次动画最终状态不同的指定值,以确保状态的更新和正确清除。随后,再创建一个用于实现所需初始动画效果的动画实例,以确保动画表现符合预期。

if (this.isRecording) {
// 设置一个duration为0的动画停掉上一次动画
this.getUIContext().animateTo({ duration: 0, iterations: 1, playMode: PlayMode.Normal }, () => {
// 这里的opacityValue不可以和上一次设置的终止0.1相同
this.opacityValue = 0.2;
});
this.isRecording = false;
// 再创建需要的动画
this.getUIContext().animateTo({ duration: 0, iterations: 1, playMode: PlayMode.Normal }, () => {
this.opacityValue = 1;
});
} else {
this.isRecording = true;
this.getUIContext().animateTo({ duration: 1500, iterations: -1, }, () => {
this.opacityValue = 0.1;
});
}
完整示例参考如下:

@Entry
@Component
struct Page {
@State opacityValue: number = 1;
@State isRecording: boolean = false;

build() {
Row() {
Column() {
Text(this.isRecording ? 'Hello World' : 'Welcome')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.opacity(this.opacityValue)
.textAlign(TextAlign.Center)
.fontColor('#000')
.onClick(() => {
if (this.isRecording) {
// 设置一个duration为0的动画停掉上一次动画
this.getUIContext().animateTo({ duration: 0, iterations: 1, playMode: PlayMode.Normal }, () => {
// 这里的opacityValue不可以和上一次设置的终止0.1相同
this.opacityValue = 0.2;
});
this.isRecording = false;
// 再创建需要的动画
this.getUIContext().animateTo({ duration: 0, iterations: 1, playMode: PlayMode.Normal }, () => {
this.opacityValue = 1;
});
} else {
this.isRecording = true;
this.getUIContext().animateTo({ duration: 1500, iterations: -1, }, () => {
this.opacityValue = 0.1;
});
}
});
}
.width('100%');
}
.height('100%');
}
}
https://sites.google.com/view/0kqtje63/home
https://sites.google.com/view/2yuici15/home
https://sites.google.com/view/2xujlh37/home
https://sites.google.com/view/3ahalq09/home
https://sites.google.com/view/3tqjcf61/home
https://sites.google.com/view/6jajrr88/home
https://sites.google.com/view/9ghecz74/home
https://sites.google.com/view/8qlhiy40/home
https://sites.google.com/view/6sknwb20/home
https://sites.google.com/view/2bwlws24/home
https://sites.google.com/view/2blczd15/home
https://sites.google.com/view/1lmiwz57/home
https://sites.google.com/view/7htwsc76/home
https://sites.google.com/view/3chktr81/home
https://sites.google.com/view/1rejbt07/home
https://sites.google.com/view/6zfyyu19/home
https://sites.google.com/view/9pcdic70/home
https://sites.google.com/view/0wcdbm94/home
https://sites.google.com/view/2yddpg19/home
https://sites.google.com/view/7sevuo10/home
https://sites.google.com/view/6incag10/home
https://sites.google.com/view/4bbfak54/home
https://sites.google.com/view/1wioqp25/home
https://sites.google.com/view/8vhssf95/home
https://sites.google.com/view/9eekvo81/home
https://sites.google.com/view/8qprfl23/home
https://sites.google.com/view/4wvrav40/home
https://sites.google.com/view/8legom73/home
https://sites.google.com/view/2kwmgy35/home
https://sites.google.com/view/0nkzdh51/home
https://sites.google.com/view/2kucvr93/home
https://sites.google.com/view/3csonu11/home
https://sites.google.com/view/3cajfi21/home
https://sites.google.com/view/2ysddp77/home
https://sites.google.com/view/5cqzyg62/home
https://sites.google.com/view/6waqdl39/home
https://sites.google.com/view/9qwaeq72/home
https://sites.google.com/view/1yknjw85/home
https://sites.google.com/view/6vaarx10/home
https://sites.google.com/view/7ltamt18/home
https://sites.google.com/view/3nrfrt86/home
https://sites.google.com/view/8rzfds48/home
https://sites.google.com/view/2vrecv27/home
https://sites.google.com/view/8pgryd86/home
https://sites.google.com/view/9ukcxw90/home
https://sites.google.com/view/6szkbf52/home
https://sites.google.com/view/1mvila90/home
https://sites.google.com/view/9eytcv61/home
https://sites.google.com/view/4cnnur37/home
https://sites.google.com/view/5uvpgz70/home
https://sites.google.com/view/9wnykm74/home
https://sites.google.com/view/1maebc71/home
https://sites.google.com/view/7bjvba88/home
https://sites.google.com/view/6yxdgq20/home
https://sites.google.com/view/6oplhm39/home
https://sites.google.com/view/0gslqp42/home
https://sites.google.com/view/2fobzs04/home
https://sites.google.com/view/5geezf47/home
https://sites.google.com/view/6damyk90/home
https://sites.google.com/view/8txifb96/home
https://sites.google.com/view/4uycua91/home
https://sites.google.com/view/4qahte14/home
https://sites.google.com/view/3hmwmu69/home
https://sites.google.com/view/2dqgbe78/home
https://sites.google.com/view/4ogacg47/home
https://sites.google.com/view/0zvppg85/home
https://sites.google.com/view/8bsjqs81/home
https://sites.google.com/view/0yhduz67/home
https://sites.google.com/view/0nvpde36/home
https://sites.google.com/view/4svrfc10/in%C3%ADcio
https://sites.google.com/view/4gjwxj46/in%C3%ADcio
https://sites.google.com/view/0xejpo69/in%C3%ADcio
https://sites.google.com/view/4eizzb39/in%C3%ADcio
https://sites.google.com/view/8cglkg37/in%C3%ADcio
https://sites.google.com/view/2poltf08/in%C3%ADcio
https://sites.google.com/view/6ggdzs74/in%C3%ADcio
https://sites.google.com/view/7rpmpu57/in%C3%ADcio
https://sites.google.com/view/6gkcjo69/in%C3%ADcio
https://sites.google.com/view/5jgxxq89/in%C3%ADcio

收起阅读 »

如何解决拖拽功能和长按功能的冲突问题

问题现象
在List组件中,单个ListItem在同时设置拖拽功能与长按功能时,实际运行之后会产生冲突,如何解决单个ListItem拖拽功能和长按功能的冲突问题?

效果预览
点击放大

背景知识
支持统一拖拽提供了一种通过鼠标或手势触屏传递数据的机制,即从一个组件位置拖出数据并将其拖入到另一个组件位置,以触发响应。
长按手势通过长按屏幕触发长按手势事件。
由于拖拽事件和长按手势都需要通过长按来触发,因此这种组合手势类型会出现冲突,例如List组件中每个ListItem都设置单独的长按事件时,拖拽功能和长按手势功能就会出现冲突。
Grid网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。
滑动手势PanGesture,能够提供自定义拖拽事件的功能。
解决方案
由于在ListItem中设置单独的长按事件会与拖拽事件产生冲突,因此建议放弃List本身的拖拽事件,使用滑动手势PanGesture,自行实现拖拽过程的逻辑,然后再通过组合手势的功能,去实现其他的长按事件。
import curves from '@ohos.curves';

@Entry
@Component
struct Page {
// 元素数组
@State numbers: number[] = [];
// 多列
private str: string = '';
row: number = 4;
// 元素数组中最后一个元素的索引
@State lastIndex: number = 0;
@State dragItem: number = -1;
@State scaleItem: number = -1;
item: number = -1;
private dragRefOffsetX: number = 0;
private dragRefOffsetY: number = 0;
@State offsetX: number = 0;
@State offsetY: number = 0;
private FIX_VP_X: number = 108;
private FIX_VP_Y: number = 120;

aboutToAppear() {
for (let i = 1; i <= 36; i++) {
this.numbers.push(i);
}
this.lastIndex = this.numbers.length - 1;
// 多列
for (let i = 0; i < this.row; i++) {
this.str = this.str + '1fr ';
}
}

itemMove(index: number, newIndex: number): void {
console.info('index:' + index + ' newIndex:' + newIndex);
if (!this.isDraggable(newIndex)) {
return;
}
let tmp = this.numbers.splice(index, 1);
this.numbers.splice(newIndex, 0, tmp[0]);
}

// 向下滑
down(index: number): void {
if (!this.isDraggable(index + this.row)) {
return;
}
this.offsetY -= this.FIX_VP_Y;
this.dragRefOffsetY += this.FIX_VP_Y;
// 多列
this.itemMove(index, index + this.row);
}

// 向下滑(右下角为空)
down2(index: number): void {
if (!this.isDraggable(index + 3)) {
return;
}
this.offsetY -= this.FIX_VP_Y;
this.dragRefOffsetY += this.FIX_VP_Y;
this.itemMove(index, index + 3);
}

// 向上滑
up(index: number): void {
if (!this.isDraggable(index - this.row)) {
return;
}
this.offsetY += this.FIX_VP_Y;
this.dragRefOffsetY -= this.FIX_VP_Y;
this.itemMove(index, index - this.row);
}

// 向左滑
left(index: number): void {
if (!this.isDraggable(index - 1)) {
return;
}
this.offsetX += this.FIX_VP_X;
this.dragRefOffsetX -= this.FIX_VP_X;
this.itemMove(index, index - 1);
}

// 向右滑
right(index: number): void {
if (!this.isDraggable(index + 1)) {
return;
}
this.offsetX -= this.FIX_VP_X;
this.dragRefOffsetX += this.FIX_VP_X;
this.itemMove(index, index + 1);
}

// 向右下滑
lowerRight(index: number): void {
if (!this.isDraggable(index + this.row + 1)) {
return;
}
this.offsetX -= this.FIX_VP_X;
this.dragRefOffsetX += this.FIX_VP_X;
this.offsetY -= this.FIX_VP_Y;
this.dragRefOffsetY += this.FIX_VP_Y;
this.itemMove(index, index + this.row + 1);
}

// 向右上滑
upperRight(index: number): void {
if (!this.isDraggable(index - (this.row - 1))) {
return;
}
this.offsetX -= this.FIX_VP_X;
this.dragRefOffsetX += this.FIX_VP_X;
this.offsetY += this.FIX_VP_Y;
this.dragRefOffsetY -= this.FIX_VP_Y;
this.itemMove(index, index - (this.row - 1));
}

// 向左下滑
lowerLeft(index: number): void {
if (!this.isDraggable(index + (this.row - 1))) {
return;
}
this.offsetX += this.FIX_VP_X;
this.dragRefOffsetX -= this.FIX_VP_X;
this.offsetY -= this.FIX_VP_Y;
this.dragRefOffsetY += this.FIX_VP_Y;
this.itemMove(index, index + (this.row - 1));
}

// 向左上滑
upperLeft(index: number): void {
if (!this.isDraggable(index - (this.row + 1))) {
return;
}
this.offsetX += this.FIX_VP_X;
this.dragRefOffsetX -= this.FIX_VP_X;
this.offsetY += this.FIX_VP_Y;
this.dragRefOffsetY -= this.FIX_VP_Y;
this.itemMove(index, index - (this.row + 1));
}

// 通过元素的索引,控制对应元素是否能移动排序
isDraggable(index: number): boolean {
console.info(index: ${index});
return index > -1; // 恒成立,所有元素均可移动排序
}

build() {
Column() {
Grid() {
ForEach(this.numbers, (item: number) => {
GridItem() {
Text(item + '')
.fontSize(16)
.width('100%')
.textAlign(TextAlign.Center)
.height(100)
.borderRadius(10)
.backgroundColor(0xFFFFFF)
.shadow(this.scaleItem == item ? {
radius: 70,
color: '#15000000',
offsetX: 0,
offsetY: 0
} :
{
radius: 0,
color: '#15000000',
offsetX: 0,
offsetY: 0
})
.animation({ curve: Curve.Sharp, duration: 300 });
}
// 添加震动
.onTouch(() => {
})
.onAreaChange((oldVal, newVal) => {
// 多列
this.FIX_VP_X = Math.round(newVal.width as number);
this.FIX_VP_Y = Math.round(newVal.height as number);
console.info(oldVal:${JSON.stringify(oldVal)});
})
// 指定固定GridItem不响应事件
.hitTestBehavior(this.isDraggable(this.numbers.indexOf(item)) ? HitTestMode.Default : HitTestMode.None)
.scale({ x: this.scaleItem == item ? 1.05 : 1, y: this.scaleItem == item ? 1.05 : 1 })
.zIndex(this.dragItem == item ? 1 : 0)
.translate(this.dragItem == item ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
.padding(10)
.gesture(
GestureGroup(GestureMode.Sequence,
LongPressGesture({ repeat: true, duration: 50 })
.onAction((event?: GestureEvent) => {
console.info(event: ${event});
this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
this.scaleItem = item;
});
})
.onActionEnd(() => {
this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
this.scaleItem = -1;
});
}),
PanGesture({ fingers: 1, direction: null, distance: 0 })
.onActionStart(() => {
this.dragItem = item;
this.dragRefOffsetX = 0;
this.dragRefOffsetY = 0;
})
.onActionUpdate((event: GestureEvent) => {
this.offsetY = event.offsetY - this.dragRefOffsetY;
this.offsetX = event.offsetX - this.dragRefOffsetX;
console.info(移动过程中event.offsetY:${event.offsetY},
this.dragRefOffsetY: ${this.dragRefOffsetY}, this.offsetY: ${this.offsetY});
console.info(移动过程中event.offsetX:${event.offsetX},
this.dragRefOffsetX: ${this.dragRefOffsetX}, this.offsetX: ${this.offsetX});
this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
let index = this.numbers.indexOf(this.dragItem);
console.info('index= ', index);
if (this.offsetY >= this.FIX_VP_Y / 2 &&
(this.offsetX <= this.FIX_VP_X / 2 && this.offsetX >= -this.FIX_VP_X / 2)
&& (index + this.row <= this.lastIndex)) {
// 向下滑
this.down(index);
} else if (this.offsetY <= -this.FIX_VP_Y / 2 &&
(this.offsetX <= this.FIX_VP_X / 2 && this.offsetX >= -this.FIX_VP_X / 2)
&& index - this.row >= 0) {
// 向上滑
this.up(index);
} else if (this.offsetX >= this.FIX_VP_X / 2 &&
(this.offsetY <= this.FIX_VP_Y / 2 && this.offsetY >= -this.FIX_VP_Y / 2)
&& !(((index - (this.row - 1)) % this.row == 0) || index == this.lastIndex)) {
// 向右滑
this.right(index);
} else if (this.offsetX <= -this.FIX_VP_X / 2 &&
(this.offsetY <= this.FIX_VP_Y / 2 && this.offsetY >= -this.FIX_VP_Y / 2)
&& !(index % this.row == 0)) {
// 向左滑
this.left(index);
} else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2
&& ((index + this.row + 1 <= this.lastIndex && !((index - (this.row - 1)) % this.row == 0)) ||
!((index - (this.row - 1)) % this.row == 0))) {
// 向右下滑
this.lowerRight(index);
} else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2
&& !((index - this.row < 0) || ((index - (this.row - 1)) % this.row == 0))) {
// 向右上滑
this.upperRight(index);
} else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2
&& (!(index % this.row == 0) && (index + (this.row - 1) <= this.lastIndex))) {
// 向左下滑
this.lowerLeft(index);
} else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2
&& !((index <= this.row - 1) || (index % this.row == 0))) {
// 向左上滑
this.upperLeft(index);
} else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2
&& (index == this.lastIndex)) {
// 向右下滑(右下角为空)
this.down2(index);
}
});
})
.onActionEnd(() => {
this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
this.dragItem = -1;
});
this.getUIContext().animateTo({
curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
}, () => {
this.scaleItem = -1;
});
})
)
.onCancel(() => {
this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
this.dragItem = -1;
});
this.getUIContext().animateTo({
curve: curves.interpolatingSpring(14, 1, 170, 17)
}, () => {
this.scaleItem = -1;
});
})
);
}, (item: number) => item.toString());
}
.width('90%')
.editMode(true)
.scrollBar(BarState.Off)
// 多列
.columnsTemplate(this.str);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.width('100%').height('100%').backgroundColor('#f1f3f5').padding({ top: 5 });
}
}
https://sites.google.com/view/8ojcyz21/home
https://sites.google.com/view/8votdo34/home
https://sites.google.com/view/2glnsx28/home
https://sites.google.com/view/5cljni07/home
https://sites.google.com/view/8prkhh07/home
https://sites.google.com/view/7khplo30/home
https://sites.google.com/view/1iwwoj29/home
https://sites.google.com/view/5lokbt20/home
https://sites.google.com/view/8ucjap35/home
https://sites.google.com/view/3vmwqk28/home
https://sites.google.com/view/4hqmfe46/home
https://sites.google.com/view/8jjhsd05/home
https://sites.google.com/view/2ajqbl31/home
https://sites.google.com/view/8eroul90/home
https://sites.google.com/view/8whpgb50/home
https://sites.google.com/view/7fhwgy79/home
https://sites.google.com/view/8otxzd32/home
https://sites.google.com/view/0skklh79/home
https://sites.google.com/view/7epixg32/home
https://sites.google.com/view/2fdymh76/home
https://sites.google.com/view/1qzbbd10/home
https://sites.google.com/view/3betru91/home
https://sites.google.com/view/0fjymy02/home
https://sites.google.com/view/1tuyht61/home
https://sites.google.com/view/3lqsci93/home
https://sites.google.com/view/4moosi57/home
https://sites.google.com/view/0ebmiw84/home
https://sites.google.com/view/4usaol83/home
https://sites.google.com/view/0hknci80/home
https://sites.google.com/view/7ldcgt68/home
https://sites.google.com/view/8emzek60/home
https://sites.google.com/view/9oaldi75/home
https://sites.google.com/view/1hujlx46/home
https://sites.google.com/view/9krrdx96/home
https://sites.google.com/view/0rdwwl74/home
https://sites.google.com/view/4oalem69/home
https://sites.google.com/view/7tpgsy97/home
https://sites.google.com/view/4sjvkh25/home
https://sites.google.com/view/6ixmrs26/home
https://sites.google.com/view/7jjcws23/home
https://sites.google.com/view/4entvi31/home
https://sites.google.com/view/0hqtsy80/home
https://sites.google.com/view/2vthve29/home
https://sites.google.com/view/2wjwhq06/home
https://sites.google.com/view/7lnlrg38/home
https://sites.google.com/view/3jwoff92/home
https://sites.google.com/view/9exfcx28/home
https://sites.google.com/view/6ayynm56/home
https://sites.google.com/view/2mkert04/home
https://sites.google.com/view/9bfpha34/home
https://sites.google.com/view/7kwinp20/home
https://sites.google.com/view/0kzpsl33/home
https://sites.google.com/view/7vqzug72/home
https://sites.google.com/view/8qybcb91/home
https://sites.google.com/view/1manil34/home
https://sites.google.com/view/9zlzlw68/home
https://sites.google.com/view/7ytpgf83/home
https://sites.google.com/view/0figtg65/home
https://sites.google.com/view/3nphmi02/home
https://sites.google.com/view/0qkeih69/home
https://sites.google.com/view/1qqdjc96/home
https://sites.google.com/view/2ocmzl47/home
https://sites.google.com/view/0rszvm31/home
https://sites.google.com/view/8twpcf39/home
https://sites.google.com/view/1tmhcz01/home
https://sites.google.com/view/1fsimv87/home
https://sites.google.com/view/0krela31/home
https://sites.google.com/view/8jhshd10/home
https://sites.google.com/view/5tseou06/home
https://sites.google.com/view/2msyts76/home
https://sites.google.com/view/6dxuri73/home
https://sites.google.com/view/1pmboa56/home
https://sites.google.com/view/9iueuv34/home
https://sites.google.com/view/8pubxw30/home
https://sites.google.com/view/1tlyco56/home
https://sites.google.com/view/8srvbu36/home
https://sites.google.com/view/4gsnto37/home
https://sites.google.com/view/6akswp56/home
https://sites.google.com/view/2inxol47/home
https://sites.google.com/view/3oojsn55/home
https://sites.google.com/view/5mdqwb17/home
https://sites.google.com/view/7xuice12/home
https://sites.google.com/view/0qzfci43/home
https://sites.google.com/view/6ohonc26/home
https://sites.google.com/view/4tbmuv32/home
https://sites.google.com/view/1rbxss83/home
https://sites.google.com/view/2niebx01/home
https://sites.google.com/view/3qaeqy35/home
https://sites.google.com/view/0cbewx04/home
https://sites.google.com/view/3csyuh21/home
https://sites.google.com/view/8wylyx13/home
https://sites.google.com/view/1kpxse38/home
https://sites.google.com/view/8ovanb09/home
https://sites.google.com/view/6rmoih50/home
https://sites.google.com/view/5cfhmg25/home
https://sites.google.com/view/4ivxrt19/home
https://sites.google.com/view/4ltzqb02/home
https://sites.google.com/view/3vdlsy06/home
https://sites.google.com/view/5vwlbj78/home
https://sites.google.com/view/4naisw60/home

继续阅读 »

问题现象
在List组件中,单个ListItem在同时设置拖拽功能与长按功能时,实际运行之后会产生冲突,如何解决单个ListItem拖拽功能和长按功能的冲突问题?

效果预览
点击放大

背景知识
支持统一拖拽提供了一种通过鼠标或手势触屏传递数据的机制,即从一个组件位置拖出数据并将其拖入到另一个组件位置,以触发响应。
长按手势通过长按屏幕触发长按手势事件。
由于拖拽事件和长按手势都需要通过长按来触发,因此这种组合手势类型会出现冲突,例如List组件中每个ListItem都设置单独的长按事件时,拖拽功能和长按手势功能就会出现冲突。
Grid网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。
滑动手势PanGesture,能够提供自定义拖拽事件的功能。
解决方案
由于在ListItem中设置单独的长按事件会与拖拽事件产生冲突,因此建议放弃List本身的拖拽事件,使用滑动手势PanGesture,自行实现拖拽过程的逻辑,然后再通过组合手势的功能,去实现其他的长按事件。
import curves from '@ohos.curves';

@Entry
@Component
struct Page {
// 元素数组
@State numbers: number[] = [];
// 多列
private str: string = '';
row: number = 4;
// 元素数组中最后一个元素的索引
@State lastIndex: number = 0;
@State dragItem: number = -1;
@State scaleItem: number = -1;
item: number = -1;
private dragRefOffsetX: number = 0;
private dragRefOffsetY: number = 0;
@State offsetX: number = 0;
@State offsetY: number = 0;
private FIX_VP_X: number = 108;
private FIX_VP_Y: number = 120;

aboutToAppear() {
for (let i = 1; i <= 36; i++) {
this.numbers.push(i);
}
this.lastIndex = this.numbers.length - 1;
// 多列
for (let i = 0; i < this.row; i++) {
this.str = this.str + '1fr ';
}
}

itemMove(index: number, newIndex: number): void {
console.info('index:' + index + ' newIndex:' + newIndex);
if (!this.isDraggable(newIndex)) {
return;
}
let tmp = this.numbers.splice(index, 1);
this.numbers.splice(newIndex, 0, tmp[0]);
}

// 向下滑
down(index: number): void {
if (!this.isDraggable(index + this.row)) {
return;
}
this.offsetY -= this.FIX_VP_Y;
this.dragRefOffsetY += this.FIX_VP_Y;
// 多列
this.itemMove(index, index + this.row);
}

// 向下滑(右下角为空)
down2(index: number): void {
if (!this.isDraggable(index + 3)) {
return;
}
this.offsetY -= this.FIX_VP_Y;
this.dragRefOffsetY += this.FIX_VP_Y;
this.itemMove(index, index + 3);
}

// 向上滑
up(index: number): void {
if (!this.isDraggable(index - this.row)) {
return;
}
this.offsetY += this.FIX_VP_Y;
this.dragRefOffsetY -= this.FIX_VP_Y;
this.itemMove(index, index - this.row);
}

// 向左滑
left(index: number): void {
if (!this.isDraggable(index - 1)) {
return;
}
this.offsetX += this.FIX_VP_X;
this.dragRefOffsetX -= this.FIX_VP_X;
this.itemMove(index, index - 1);
}

// 向右滑
right(index: number): void {
if (!this.isDraggable(index + 1)) {
return;
}
this.offsetX -= this.FIX_VP_X;
this.dragRefOffsetX += this.FIX_VP_X;
this.itemMove(index, index + 1);
}

// 向右下滑
lowerRight(index: number): void {
if (!this.isDraggable(index + this.row + 1)) {
return;
}
this.offsetX -= this.FIX_VP_X;
this.dragRefOffsetX += this.FIX_VP_X;
this.offsetY -= this.FIX_VP_Y;
this.dragRefOffsetY += this.FIX_VP_Y;
this.itemMove(index, index + this.row + 1);
}

// 向右上滑
upperRight(index: number): void {
if (!this.isDraggable(index - (this.row - 1))) {
return;
}
this.offsetX -= this.FIX_VP_X;
this.dragRefOffsetX += this.FIX_VP_X;
this.offsetY += this.FIX_VP_Y;
this.dragRefOffsetY -= this.FIX_VP_Y;
this.itemMove(index, index - (this.row - 1));
}

// 向左下滑
lowerLeft(index: number): void {
if (!this.isDraggable(index + (this.row - 1))) {
return;
}
this.offsetX += this.FIX_VP_X;
this.dragRefOffsetX -= this.FIX_VP_X;
this.offsetY -= this.FIX_VP_Y;
this.dragRefOffsetY += this.FIX_VP_Y;
this.itemMove(index, index + (this.row - 1));
}

// 向左上滑
upperLeft(index: number): void {
if (!this.isDraggable(index - (this.row + 1))) {
return;
}
this.offsetX += this.FIX_VP_X;
this.dragRefOffsetX -= this.FIX_VP_X;
this.offsetY += this.FIX_VP_Y;
this.dragRefOffsetY -= this.FIX_VP_Y;
this.itemMove(index, index - (this.row + 1));
}

// 通过元素的索引,控制对应元素是否能移动排序
isDraggable(index: number): boolean {
console.info(index: ${index});
return index > -1; // 恒成立,所有元素均可移动排序
}

build() {
Column() {
Grid() {
ForEach(this.numbers, (item: number) => {
GridItem() {
Text(item + '')
.fontSize(16)
.width('100%')
.textAlign(TextAlign.Center)
.height(100)
.borderRadius(10)
.backgroundColor(0xFFFFFF)
.shadow(this.scaleItem == item ? {
radius: 70,
color: '#15000000',
offsetX: 0,
offsetY: 0
} :
{
radius: 0,
color: '#15000000',
offsetX: 0,
offsetY: 0
})
.animation({ curve: Curve.Sharp, duration: 300 });
}
// 添加震动
.onTouch(() => {
})
.onAreaChange((oldVal, newVal) => {
// 多列
this.FIX_VP_X = Math.round(newVal.width as number);
this.FIX_VP_Y = Math.round(newVal.height as number);
console.info(oldVal:${JSON.stringify(oldVal)});
})
// 指定固定GridItem不响应事件
.hitTestBehavior(this.isDraggable(this.numbers.indexOf(item)) ? HitTestMode.Default : HitTestMode.None)
.scale({ x: this.scaleItem == item ? 1.05 : 1, y: this.scaleItem == item ? 1.05 : 1 })
.zIndex(this.dragItem == item ? 1 : 0)
.translate(this.dragItem == item ? { x: this.offsetX, y: this.offsetY } : { x: 0, y: 0 })
.padding(10)
.gesture(
GestureGroup(GestureMode.Sequence,
LongPressGesture({ repeat: true, duration: 50 })
.onAction((event?: GestureEvent) => {
console.info(event: ${event});
this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
this.scaleItem = item;
});
})
.onActionEnd(() => {
this.getUIContext().animateTo({ curve: Curve.Friction, duration: 300 }, () => {
this.scaleItem = -1;
});
}),
PanGesture({ fingers: 1, direction: null, distance: 0 })
.onActionStart(() => {
this.dragItem = item;
this.dragRefOffsetX = 0;
this.dragRefOffsetY = 0;
})
.onActionUpdate((event: GestureEvent) => {
this.offsetY = event.offsetY - this.dragRefOffsetY;
this.offsetX = event.offsetX - this.dragRefOffsetX;
console.info(移动过程中event.offsetY:${event.offsetY},
this.dragRefOffsetY: ${this.dragRefOffsetY}, this.offsetY: ${this.offsetY});
console.info(移动过程中event.offsetX:${event.offsetX},
this.dragRefOffsetX: ${this.dragRefOffsetX}, this.offsetX: ${this.offsetX});
this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
let index = this.numbers.indexOf(this.dragItem);
console.info('index= ', index);
if (this.offsetY >= this.FIX_VP_Y / 2 &&
(this.offsetX <= this.FIX_VP_X / 2 && this.offsetX >= -this.FIX_VP_X / 2)
&& (index + this.row <= this.lastIndex)) {
// 向下滑
this.down(index);
} else if (this.offsetY <= -this.FIX_VP_Y / 2 &&
(this.offsetX <= this.FIX_VP_X / 2 && this.offsetX >= -this.FIX_VP_X / 2)
&& index - this.row >= 0) {
// 向上滑
this.up(index);
} else if (this.offsetX >= this.FIX_VP_X / 2 &&
(this.offsetY <= this.FIX_VP_Y / 2 && this.offsetY >= -this.FIX_VP_Y / 2)
&& !(((index - (this.row - 1)) % this.row == 0) || index == this.lastIndex)) {
// 向右滑
this.right(index);
} else if (this.offsetX <= -this.FIX_VP_X / 2 &&
(this.offsetY <= this.FIX_VP_Y / 2 && this.offsetY >= -this.FIX_VP_Y / 2)
&& !(index % this.row == 0)) {
// 向左滑
this.left(index);
} else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2
&& ((index + this.row + 1 <= this.lastIndex && !((index - (this.row - 1)) % this.row == 0)) ||
!((index - (this.row - 1)) % this.row == 0))) {
// 向右下滑
this.lowerRight(index);
} else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2
&& !((index - this.row < 0) || ((index - (this.row - 1)) % this.row == 0))) {
// 向右上滑
this.upperRight(index);
} else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2
&& (!(index % this.row == 0) && (index + (this.row - 1) <= this.lastIndex))) {
// 向左下滑
this.lowerLeft(index);
} else if (this.offsetX <= -this.FIX_VP_X / 2 && this.offsetY <= -this.FIX_VP_Y / 2
&& !((index <= this.row - 1) || (index % this.row == 0))) {
// 向左上滑
this.upperLeft(index);
} else if (this.offsetX >= this.FIX_VP_X / 2 && this.offsetY >= this.FIX_VP_Y / 2
&& (index == this.lastIndex)) {
// 向右下滑(右下角为空)
this.down2(index);
}
});
})
.onActionEnd(() => {
this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
this.dragItem = -1;
});
this.getUIContext().animateTo({
curve: curves.interpolatingSpring(14, 1, 170, 17), delay: 150
}, () => {
this.scaleItem = -1;
});
})
)
.onCancel(() => {
this.getUIContext().animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
this.dragItem = -1;
});
this.getUIContext().animateTo({
curve: curves.interpolatingSpring(14, 1, 170, 17)
}, () => {
this.scaleItem = -1;
});
})
);
}, (item: number) => item.toString());
}
.width('90%')
.editMode(true)
.scrollBar(BarState.Off)
// 多列
.columnsTemplate(this.str);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.width('100%').height('100%').backgroundColor('#f1f3f5').padding({ top: 5 });
}
}
https://sites.google.com/view/8ojcyz21/home
https://sites.google.com/view/8votdo34/home
https://sites.google.com/view/2glnsx28/home
https://sites.google.com/view/5cljni07/home
https://sites.google.com/view/8prkhh07/home
https://sites.google.com/view/7khplo30/home
https://sites.google.com/view/1iwwoj29/home
https://sites.google.com/view/5lokbt20/home
https://sites.google.com/view/8ucjap35/home
https://sites.google.com/view/3vmwqk28/home
https://sites.google.com/view/4hqmfe46/home
https://sites.google.com/view/8jjhsd05/home
https://sites.google.com/view/2ajqbl31/home
https://sites.google.com/view/8eroul90/home
https://sites.google.com/view/8whpgb50/home
https://sites.google.com/view/7fhwgy79/home
https://sites.google.com/view/8otxzd32/home
https://sites.google.com/view/0skklh79/home
https://sites.google.com/view/7epixg32/home
https://sites.google.com/view/2fdymh76/home
https://sites.google.com/view/1qzbbd10/home
https://sites.google.com/view/3betru91/home
https://sites.google.com/view/0fjymy02/home
https://sites.google.com/view/1tuyht61/home
https://sites.google.com/view/3lqsci93/home
https://sites.google.com/view/4moosi57/home
https://sites.google.com/view/0ebmiw84/home
https://sites.google.com/view/4usaol83/home
https://sites.google.com/view/0hknci80/home
https://sites.google.com/view/7ldcgt68/home
https://sites.google.com/view/8emzek60/home
https://sites.google.com/view/9oaldi75/home
https://sites.google.com/view/1hujlx46/home
https://sites.google.com/view/9krrdx96/home
https://sites.google.com/view/0rdwwl74/home
https://sites.google.com/view/4oalem69/home
https://sites.google.com/view/7tpgsy97/home
https://sites.google.com/view/4sjvkh25/home
https://sites.google.com/view/6ixmrs26/home
https://sites.google.com/view/7jjcws23/home
https://sites.google.com/view/4entvi31/home
https://sites.google.com/view/0hqtsy80/home
https://sites.google.com/view/2vthve29/home
https://sites.google.com/view/2wjwhq06/home
https://sites.google.com/view/7lnlrg38/home
https://sites.google.com/view/3jwoff92/home
https://sites.google.com/view/9exfcx28/home
https://sites.google.com/view/6ayynm56/home
https://sites.google.com/view/2mkert04/home
https://sites.google.com/view/9bfpha34/home
https://sites.google.com/view/7kwinp20/home
https://sites.google.com/view/0kzpsl33/home
https://sites.google.com/view/7vqzug72/home
https://sites.google.com/view/8qybcb91/home
https://sites.google.com/view/1manil34/home
https://sites.google.com/view/9zlzlw68/home
https://sites.google.com/view/7ytpgf83/home
https://sites.google.com/view/0figtg65/home
https://sites.google.com/view/3nphmi02/home
https://sites.google.com/view/0qkeih69/home
https://sites.google.com/view/1qqdjc96/home
https://sites.google.com/view/2ocmzl47/home
https://sites.google.com/view/0rszvm31/home
https://sites.google.com/view/8twpcf39/home
https://sites.google.com/view/1tmhcz01/home
https://sites.google.com/view/1fsimv87/home
https://sites.google.com/view/0krela31/home
https://sites.google.com/view/8jhshd10/home
https://sites.google.com/view/5tseou06/home
https://sites.google.com/view/2msyts76/home
https://sites.google.com/view/6dxuri73/home
https://sites.google.com/view/1pmboa56/home
https://sites.google.com/view/9iueuv34/home
https://sites.google.com/view/8pubxw30/home
https://sites.google.com/view/1tlyco56/home
https://sites.google.com/view/8srvbu36/home
https://sites.google.com/view/4gsnto37/home
https://sites.google.com/view/6akswp56/home
https://sites.google.com/view/2inxol47/home
https://sites.google.com/view/3oojsn55/home
https://sites.google.com/view/5mdqwb17/home
https://sites.google.com/view/7xuice12/home
https://sites.google.com/view/0qzfci43/home
https://sites.google.com/view/6ohonc26/home
https://sites.google.com/view/4tbmuv32/home
https://sites.google.com/view/1rbxss83/home
https://sites.google.com/view/2niebx01/home
https://sites.google.com/view/3qaeqy35/home
https://sites.google.com/view/0cbewx04/home
https://sites.google.com/view/3csyuh21/home
https://sites.google.com/view/8wylyx13/home
https://sites.google.com/view/1kpxse38/home
https://sites.google.com/view/8ovanb09/home
https://sites.google.com/view/6rmoih50/home
https://sites.google.com/view/5cfhmg25/home
https://sites.google.com/view/4ivxrt19/home
https://sites.google.com/view/4ltzqb02/home
https://sites.google.com/view/3vdlsy06/home
https://sites.google.com/view/5vwlbj78/home
https://sites.google.com/view/4naisw60/home

收起阅读 »

Tab组件内Navigation跳转,TabBar导航栏隐藏失败如何解决

tabbar

问题现象
在“我的”页面点击“跳转设置页”按钮跳转到设置页面,底部的TabBar导航栏仍然存在。

点击放大点击放大

主页面有“首页”、“我的”两个Tab页面,部分代码如下:

@Entry
@Component
struct TabsNavPage {
build() {
Tabs() {
TabContent() {
MinePage()
}
.tabBar(BottomTabBarStyle.of($r('sys.media.ohos_app_icon'), '我的'))

  TabContent() {  
    Text('首页')  
  }  
  .tabBar(BottomTabBarStyle.of($r('sys.media.ohos_app_icon'), '首页'))  
}  
.barPosition(BarPosition.End)  

}
}

@Component
struct MinePage {
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();

@Builder
PagesMap(name: string) {
if (name === 'Settings') {
Settings();
}
}

build() {
Navigation(this.pathStack) {
Row(){
Column() {
Button('跳转设置页')
.onClick(() => {
this.pathStack.pushPathByName('Settings', null);
})
}
}
}
.hideTitleBar(true)
.navDestination(this.PagesMap)
}
}

@Component
struct Settings {
@Consume('pathStack') pathStack: NavPathStack = new NavPathStack();

build() {
NavDestination() {
Button('返回')
.onClick(() => {
this.pathStack.pop();
});
}.title('设置')
}
}
效果预览
点击放大

背景知识
Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(NavDestination的子组件),首页和非首页通过路由进行切换。
@Provide装饰器和@Consume装饰器:应用于与后代组件的双向数据同步、状态数据在多个层级之间传递的场景。
解决方案
问题现象中是用Tab组件包裹Navigation组件,跳转Navigation子组件并不会影响到外层的Tab组件。

将Navigation组件作为根容器包括Tab组件,为保证子组件都共享一个NavPathStack实例,可以使用@Provide和@Consume装饰器将导航控制器对象NavPathStack传递给Tabs内的子组件使用。

@Entry
@Component
struct TabsNavPage {
// 使用@Provide将路由栈对象传递给TabContent内的组件
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();

@Builder
PagesMap(name: string) {
if (name === 'Settings') {
Settings();
}
}

build() {
// 使用Navigation包裹Tabs,Tabs子页使用同一个路由栈对象
Navigation(this.pathStack) {
Tabs() {
TabContent() {
MinePage();
}
.tabBar(BottomTabBarStyle.of($r('sys.media.ohos_app_icon'), '我的'))

    TabContent() {  
      Text('首页').fontColor('40fp');  
    }  
    .tabBar(BottomTabBarStyle.of($r('sys.media.ohos_app_icon'), '首页'))  
  }  
  .barPosition(BarPosition.End);  
}  
.hideTitleBar(true)  
.navDestination(this.PagesMap)  

}
}

@Component
struct MinePage {
// 获取Navigation的路由栈对象
@Consume('pathStack') pathStack: NavPathStack;

build() {
Column() {
Button('跳转设置页')
.onClick(() => {
this.pathStack.pushPathByName('Settings', null);
});
};
}
}

@Component
struct Settings {
@Consume('pathStack') pathStack: NavPathStack = new NavPathStack();

build() {
NavDestination() {
Button('返回')
.onClick(() => {
this.pathStack.pop();
});
}.title('设置')
}
}
https://coub.com/view/5kamzbr4mi
https://coub.com/view/9slmznc3ol
https://coub.com/view/dnhe4x7otv
https://coub.com/view/libk78392s
https://coub.com/view/2splvx7ais
https://coub.com/view/0fqk03fwlz
https://coub.com/view/u3but76ukt
https://coub.com/view/2xrg8kdx5o
https://coub.com/view/iqeb7cwj8k
https://coub.com/view/lx86s7cfpr
https://coub.com/view/qin8evf0sk
https://coub.com/view/ul1dmz1db2
https://coub.com/view/1n6f59dch3
https://coub.com/view/mz789w4pbg
https://coub.com/view/trb9jcog9p
https://coub.com/view/j3i3lac4yx
https://coub.com/view/84l11twj2s
https://coub.com/view/ut50q4erj3
https://coub.com/view/w6uuiu7ru3
https://coub.com/view/iun2hgfzqm
https://coub.com/view/hc6d3b3sts
https://coub.com/view/7l7wd758hs
https://coub.com/view/sshi8p319r
https://coub.com/view/2y21fnu5t4
https://coub.com/view/3dpvlq1hxa
https://coub.com/view/7tefrrju9a
https://coub.com/view/kr0zw5ow09
https://coub.com/view/vze4xn6lxk
https://coub.com/view/gp0hoyjbev
https://coub.com/view/8ukozyn2pf
https://coub.com/view/gne8uf8fun
https://coub.com/view/o1ljlnaqrl
https://coub.com/view/6pe00kuw3p
https://coub.com/view/3ys8ww2kf5
https://coub.com/view/ky54919n96
https://coub.com/view/ovvfpcdgiy
https://coub.com/view/h1g7qx1ibk
https://coub.com/view/4ytxeco099
https://coub.com/view/z8grd6tisg
https://coub.com/view/ihcl3n786e
https://coub.com/view/e48vma3hes
https://coub.com/view/rgk8us00m7
https://coub.com/view/mxmu9agwxl
https://coub.com/view/jjq7sf2887
https://coub.com/view/rtlotmcb2o
https://coub.com/view/2xzrkf1j13
https://coub.com/view/datluk4gwo
https://coub.com/view/5ld5j0uc5o
https://coub.com/view/cp081k64fp
https://coub.com/view/0sgmugss1z
https://coub.com/view/ynwmj064s7
https://coub.com/view/y8atenamwg
https://coub.com/view/acqfefno2e
https://coub.com/view/hmwvsetfho
https://coub.com/view/n59qhb108b
https://coub.com/view/946i56xqgw
https://coub.com/view/fu355xmxbd
https://coub.com/view/9gfbdnyv3z
https://coub.com/view/i16nn052at
https://coub.com/view/s2o2prg0rl
https://coub.com/view/f7m17v6t3x
https://coub.com/view/6bmqboq0w9
https://coub.com/view/tvj9e2712i
https://coub.com/view/b5kbn4qpgx
https://coub.com/view/hh77rcnz58
https://coub.com/view/8w0hoxvhxu
https://coub.com/view/ogtrlf718w
https://coub.com/view/dcy0w8eeq1
https://coub.com/view/foo2zaj4nz
https://coub.com/view/ffai2oxjgj
https://coub.com/view/h7llkwvo4t
https://coub.com/view/fxf6iote20
https://coub.com/view/zr4atw33g7
https://coub.com/view/w6ksjzfgri
https://coub.com/view/6wlivajplu
https://coub.com/view/m3p9mpc87g
https://coub.com/view/1svk68qjxi
https://coub.com/view/jn78pmgtsy
https://coub.com/view/ru4kxd02ca
https://coub.com/view/u2qm3zht04
https://coub.com/view/07vrswbr9o
https://coub.com/view/z53dkteu04
https://coub.com/view/r3kczagbat
https://coub.com/view/ebkii1w2uy
https://coub.com/view/gu2ovmapto
https://coub.com/view/6s1y6nkktx
https://coub.com/view/2mzsrq6bnh
https://coub.com/view/bmh3bh21dl
https://coub.com/view/fdiekfi3y1
https://coub.com/view/ex4dtaj1ri
https://coub.com/view/tsgmmorhu1
https://coub.com/view/gh01h6433n
https://coub.com/view/3zfrusqatl
https://coub.com/view/u27l9exjjr
https://coub.com/view/wx8rybzzdd
https://coub.com/view/u13mvzb6i9
https://coub.com/view/lw5mv3n3vp
https://coub.com/view/tn7jo1khbm
https://coub.com/view/hr9h8qk489
https://coub.com/view/dfx72ezl4k
https://coub.com/view/xfnivvh59q
https://coub.com/view/6dmijjf9eu
https://coub.com/view/fi22iqa3iv
https://coub.com/view/57fq1sjhpa
https://coub.com/view/9xii5apv7o
https://coub.com/view/e5fhxwb6re
https://coub.com/view/wuc96hdc6z
https://coub.com/view/bvp1embwd5
https://coub.com/view/u1lurshbkm
https://coub.com/view/nih2qy00t8
https://coub.com/view/mrx826u951
https://coub.com/view/kes50gj32a
https://coub.com/view/fcv3394eq2
https://coub.com/view/4qc0x8z64l
https://coub.com/view/n2k5itdn4w
https://coub.com/view/t988vol3yp
https://coub.com/view/synur4gqha
https://coub.com/view/bd6fqj7ej9
https://coub.com/view/crwhjs6qgk
https://coub.com/view/vde66uoo7x
https://coub.com/view/68md6egw0p
https://coub.com/view/2fgvpgk4ir
https://coub.com/view/4ygf27wtro
https://coub.com/view/0qdbv1ptnp
https://coub.com/view/b5ii094vls
https://coub.com/view/o8fq8rif3n
https://coub.com/view/3odrc01a85
https://coub.com/view/8k08cynikg

继续阅读 »

问题现象
在“我的”页面点击“跳转设置页”按钮跳转到设置页面,底部的TabBar导航栏仍然存在。

点击放大点击放大

主页面有“首页”、“我的”两个Tab页面,部分代码如下:

@Entry
@Component
struct TabsNavPage {
build() {
Tabs() {
TabContent() {
MinePage()
}
.tabBar(BottomTabBarStyle.of($r('sys.media.ohos_app_icon'), '我的'))

  TabContent() {  
    Text('首页')  
  }  
  .tabBar(BottomTabBarStyle.of($r('sys.media.ohos_app_icon'), '首页'))  
}  
.barPosition(BarPosition.End)  

}
}

@Component
struct MinePage {
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();

@Builder
PagesMap(name: string) {
if (name === 'Settings') {
Settings();
}
}

build() {
Navigation(this.pathStack) {
Row(){
Column() {
Button('跳转设置页')
.onClick(() => {
this.pathStack.pushPathByName('Settings', null);
})
}
}
}
.hideTitleBar(true)
.navDestination(this.PagesMap)
}
}

@Component
struct Settings {
@Consume('pathStack') pathStack: NavPathStack = new NavPathStack();

build() {
NavDestination() {
Button('返回')
.onClick(() => {
this.pathStack.pop();
});
}.title('设置')
}
}
效果预览
点击放大

背景知识
Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(NavDestination的子组件),首页和非首页通过路由进行切换。
@Provide装饰器和@Consume装饰器:应用于与后代组件的双向数据同步、状态数据在多个层级之间传递的场景。
解决方案
问题现象中是用Tab组件包裹Navigation组件,跳转Navigation子组件并不会影响到外层的Tab组件。

将Navigation组件作为根容器包括Tab组件,为保证子组件都共享一个NavPathStack实例,可以使用@Provide和@Consume装饰器将导航控制器对象NavPathStack传递给Tabs内的子组件使用。

@Entry
@Component
struct TabsNavPage {
// 使用@Provide将路由栈对象传递给TabContent内的组件
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();

@Builder
PagesMap(name: string) {
if (name === 'Settings') {
Settings();
}
}

build() {
// 使用Navigation包裹Tabs,Tabs子页使用同一个路由栈对象
Navigation(this.pathStack) {
Tabs() {
TabContent() {
MinePage();
}
.tabBar(BottomTabBarStyle.of($r('sys.media.ohos_app_icon'), '我的'))

    TabContent() {  
      Text('首页').fontColor('40fp');  
    }  
    .tabBar(BottomTabBarStyle.of($r('sys.media.ohos_app_icon'), '首页'))  
  }  
  .barPosition(BarPosition.End);  
}  
.hideTitleBar(true)  
.navDestination(this.PagesMap)  

}
}

@Component
struct MinePage {
// 获取Navigation的路由栈对象
@Consume('pathStack') pathStack: NavPathStack;

build() {
Column() {
Button('跳转设置页')
.onClick(() => {
this.pathStack.pushPathByName('Settings', null);
});
};
}
}

@Component
struct Settings {
@Consume('pathStack') pathStack: NavPathStack = new NavPathStack();

build() {
NavDestination() {
Button('返回')
.onClick(() => {
this.pathStack.pop();
});
}.title('设置')
}
}
https://coub.com/view/5kamzbr4mi
https://coub.com/view/9slmznc3ol
https://coub.com/view/dnhe4x7otv
https://coub.com/view/libk78392s
https://coub.com/view/2splvx7ais
https://coub.com/view/0fqk03fwlz
https://coub.com/view/u3but76ukt
https://coub.com/view/2xrg8kdx5o
https://coub.com/view/iqeb7cwj8k
https://coub.com/view/lx86s7cfpr
https://coub.com/view/qin8evf0sk
https://coub.com/view/ul1dmz1db2
https://coub.com/view/1n6f59dch3
https://coub.com/view/mz789w4pbg
https://coub.com/view/trb9jcog9p
https://coub.com/view/j3i3lac4yx
https://coub.com/view/84l11twj2s
https://coub.com/view/ut50q4erj3
https://coub.com/view/w6uuiu7ru3
https://coub.com/view/iun2hgfzqm
https://coub.com/view/hc6d3b3sts
https://coub.com/view/7l7wd758hs
https://coub.com/view/sshi8p319r
https://coub.com/view/2y21fnu5t4
https://coub.com/view/3dpvlq1hxa
https://coub.com/view/7tefrrju9a
https://coub.com/view/kr0zw5ow09
https://coub.com/view/vze4xn6lxk
https://coub.com/view/gp0hoyjbev
https://coub.com/view/8ukozyn2pf
https://coub.com/view/gne8uf8fun
https://coub.com/view/o1ljlnaqrl
https://coub.com/view/6pe00kuw3p
https://coub.com/view/3ys8ww2kf5
https://coub.com/view/ky54919n96
https://coub.com/view/ovvfpcdgiy
https://coub.com/view/h1g7qx1ibk
https://coub.com/view/4ytxeco099
https://coub.com/view/z8grd6tisg
https://coub.com/view/ihcl3n786e
https://coub.com/view/e48vma3hes
https://coub.com/view/rgk8us00m7
https://coub.com/view/mxmu9agwxl
https://coub.com/view/jjq7sf2887
https://coub.com/view/rtlotmcb2o
https://coub.com/view/2xzrkf1j13
https://coub.com/view/datluk4gwo
https://coub.com/view/5ld5j0uc5o
https://coub.com/view/cp081k64fp
https://coub.com/view/0sgmugss1z
https://coub.com/view/ynwmj064s7
https://coub.com/view/y8atenamwg
https://coub.com/view/acqfefno2e
https://coub.com/view/hmwvsetfho
https://coub.com/view/n59qhb108b
https://coub.com/view/946i56xqgw
https://coub.com/view/fu355xmxbd
https://coub.com/view/9gfbdnyv3z
https://coub.com/view/i16nn052at
https://coub.com/view/s2o2prg0rl
https://coub.com/view/f7m17v6t3x
https://coub.com/view/6bmqboq0w9
https://coub.com/view/tvj9e2712i
https://coub.com/view/b5kbn4qpgx
https://coub.com/view/hh77rcnz58
https://coub.com/view/8w0hoxvhxu
https://coub.com/view/ogtrlf718w
https://coub.com/view/dcy0w8eeq1
https://coub.com/view/foo2zaj4nz
https://coub.com/view/ffai2oxjgj
https://coub.com/view/h7llkwvo4t
https://coub.com/view/fxf6iote20
https://coub.com/view/zr4atw33g7
https://coub.com/view/w6ksjzfgri
https://coub.com/view/6wlivajplu
https://coub.com/view/m3p9mpc87g
https://coub.com/view/1svk68qjxi
https://coub.com/view/jn78pmgtsy
https://coub.com/view/ru4kxd02ca
https://coub.com/view/u2qm3zht04
https://coub.com/view/07vrswbr9o
https://coub.com/view/z53dkteu04
https://coub.com/view/r3kczagbat
https://coub.com/view/ebkii1w2uy
https://coub.com/view/gu2ovmapto
https://coub.com/view/6s1y6nkktx
https://coub.com/view/2mzsrq6bnh
https://coub.com/view/bmh3bh21dl
https://coub.com/view/fdiekfi3y1
https://coub.com/view/ex4dtaj1ri
https://coub.com/view/tsgmmorhu1
https://coub.com/view/gh01h6433n
https://coub.com/view/3zfrusqatl
https://coub.com/view/u27l9exjjr
https://coub.com/view/wx8rybzzdd
https://coub.com/view/u13mvzb6i9
https://coub.com/view/lw5mv3n3vp
https://coub.com/view/tn7jo1khbm
https://coub.com/view/hr9h8qk489
https://coub.com/view/dfx72ezl4k
https://coub.com/view/xfnivvh59q
https://coub.com/view/6dmijjf9eu
https://coub.com/view/fi22iqa3iv
https://coub.com/view/57fq1sjhpa
https://coub.com/view/9xii5apv7o
https://coub.com/view/e5fhxwb6re
https://coub.com/view/wuc96hdc6z
https://coub.com/view/bvp1embwd5
https://coub.com/view/u1lurshbkm
https://coub.com/view/nih2qy00t8
https://coub.com/view/mrx826u951
https://coub.com/view/kes50gj32a
https://coub.com/view/fcv3394eq2
https://coub.com/view/4qc0x8z64l
https://coub.com/view/n2k5itdn4w
https://coub.com/view/t988vol3yp
https://coub.com/view/synur4gqha
https://coub.com/view/bd6fqj7ej9
https://coub.com/view/crwhjs6qgk
https://coub.com/view/vde66uoo7x
https://coub.com/view/68md6egw0p
https://coub.com/view/2fgvpgk4ir
https://coub.com/view/4ygf27wtro
https://coub.com/view/0qdbv1ptnp
https://coub.com/view/b5ii094vls
https://coub.com/view/o8fq8rif3n
https://coub.com/view/3odrc01a85
https://coub.com/view/8k08cynikg

收起阅读 »

Android离线打包,plus.runtime.install导致应用闪退

logcat日志如下:

Unresolved exception class when finding catch block: net.lingala.zip4j.exception.ZipException
2026-08-28 15:40:18.591 29792-30261 AndroidRuntime com.gdxinyue.newusmp
E FATAL EXCEPTION: Thread-10
Process: com.gdxinyue.newusmp, PID: 29792
java.lang.NullPointerException: Attempt to read from null array
at io.dcloud.feature.pdr.RuntimeFeatureImpl$a.run

参考博客:https://blog.csdn.net/qq_32058147/article/details/155034674

在Android Stadio主项目的 build.gradle > dependencies中增加以下依赖:

dependencies {
implementation 'net.lingala.zip4j:zip4j:2.11.5'
}

继续阅读 »

logcat日志如下:

Unresolved exception class when finding catch block: net.lingala.zip4j.exception.ZipException
2026-08-28 15:40:18.591 29792-30261 AndroidRuntime com.gdxinyue.newusmp
E FATAL EXCEPTION: Thread-10
Process: com.gdxinyue.newusmp, PID: 29792
java.lang.NullPointerException: Attempt to read from null array
at io.dcloud.feature.pdr.RuntimeFeatureImpl$a.run

参考博客:https://blog.csdn.net/qq_32058147/article/details/155034674

在Android Stadio主项目的 build.gradle > dependencies中增加以下依赖:

dependencies {
implementation 'net.lingala.zip4j:zip4j:2.11.5'
}

收起阅读 »

uni-starter模板代码更正

创建uni-app项目时选择uni-starter模板,我发现其中pages/list/search/search.vue中的“搜索联想”部分代码<uni-list-item>的:title="item.name"可能写错了,应该改为:title="item.title",具体代码如下:
<!-- 搜索联想 -->
<view class="search-associative" v-if="associativeShow">
<uni-list>
<!-- :title="item.name"可能写错了,文章表没有name字段,associativeList只是获取_id、title字段! -->
<!-- <uni-list-item v-for="(item,index) in associativeList" :key="item._id" :ellipsis="1" :title="item.name" @click="associativeClick(item)" show-extra-icon
clickable :extra-icon="{size:18,color:iconColor,type:'search'}" >
</uni-list-item> -->

            <!-- 正确改为:title="item.title",在搜索框输入文章记录的标题子串时,会显示篇文章的标题 -->  
            <uni-list-item  v-for="(item,index) in associativeList" :key="item._id" :ellipsis="1" :title="item.title" @click="associativeClick(item)" show-extra-icon  
             clickable :extra-icon="{size:18,color:iconColor,type:'search'}" >  
            </uni-list-item>  
        </uni-list>  
    </view>  

这个搜索联想的功能应该是在搜索框输入文章记录标题的子串时,提示有没有这篇文章,若存在,则显示所有标题带有此子串的文章标题列表,这样就可以直接点击找到此文章,因为模板的代码写错了"item.name",导致完全没有提示,希望可以改正过来。

继续阅读 »

创建uni-app项目时选择uni-starter模板,我发现其中pages/list/search/search.vue中的“搜索联想”部分代码<uni-list-item>的:title="item.name"可能写错了,应该改为:title="item.title",具体代码如下:
<!-- 搜索联想 -->
<view class="search-associative" v-if="associativeShow">
<uni-list>
<!-- :title="item.name"可能写错了,文章表没有name字段,associativeList只是获取_id、title字段! -->
<!-- <uni-list-item v-for="(item,index) in associativeList" :key="item._id" :ellipsis="1" :title="item.name" @click="associativeClick(item)" show-extra-icon
clickable :extra-icon="{size:18,color:iconColor,type:'search'}" >
</uni-list-item> -->

            <!-- 正确改为:title="item.title",在搜索框输入文章记录的标题子串时,会显示篇文章的标题 -->  
            <uni-list-item  v-for="(item,index) in associativeList" :key="item._id" :ellipsis="1" :title="item.title" @click="associativeClick(item)" show-extra-icon  
             clickable :extra-icon="{size:18,color:iconColor,type:'search'}" >  
            </uni-list-item>  
        </uni-list>  
    </view>  

这个搜索联想的功能应该是在搜索框输入文章记录标题的子串时,提示有没有这篇文章,若存在,则显示所有标题带有此子串的文章标题列表,这样就可以直接点击找到此文章,因为模板的代码写错了"item.name",导致完全没有提示,希望可以改正过来。

收起阅读 »

组件实现随手拖动松手后贴边效果

问题现象
如何实现手指长按组件时,组件能够跟随手指拖动,手指抬起后,组件贴边停靠的效果。

背景知识
onTouch是用于处理触摸事件的组件属性,支持TouchType.Down(按下)、TouchType.Move(移动)、TouchType.Up(抬起),用于多模交互场景(如拖拽、点击、滑动等)。
animateTo是用于实现动画效果的核心函数,支持平滑过渡、弹性动画、曲线控制等。
解决方案
为实现上述功能,可通过如下步骤实现:

通过组件的触摸事件onTouch,其中使用TouchType.Down记录手指按下时的初始坐标,使用TouchType.Move获取手指移动时的位置信息,通过获取的位置信息修改组件的position属性实现元素的拖拽功能:
// 指针按下事件:记录初始坐标
case TouchType.Down: {
this.windowStartX = event.touches[0].windowX;
this.windowStartY = event.touches[0].windowY;
break;
}
case TouchType.Move: {
const windowX: number = event.touches[0].windowX;
const windowY: number = event.touches[0].windowY;
// 左边距吸附时:更新左边缘位置
if (this.edge.left !== undefined) {
this.edge.left = this.edge.left as number + (windowX - this.windowStartX);
}
// 右边距吸附时:更新右边缘位置
else {
this.edge.right = this.edge.right as number - (windowX - this.windowStartX);
}
// 更新顶部位置
this.edge.top = this.edge.top as number + (windowY - this.windowStartY);
// 更新起始坐标用于下次计算
this.windowStartX = windowX;
this.windowStartY = windowY;
break;
}
再通过TouchType.Up(手指抬起),对比手指抬起时组件所处位置,与容器组件的中心位置,判断出需要向哪边进行贴边操作,或与容器元素宽度进行对比,判断元素是否超出容器,选择将组件position属性的left或right设置为undefined并将其对立方向设置为0,且位置的调整设置于animateTo中,实现将元素从容器内部或容器外部缓慢调整至容器边缘,从而实现组件拖拽后的贴边效果:
case TouchType.Up: {
// 计算悬浮窗中心点在父组件中的水平坐标
let centerX: number;
if (this.edge.left !== undefined) {
centerX = this.edge.left as number + this.floatWindowWidth / 2;
} else {
centerX = this.containerWidth - (this.edge.right as number) - this.floatWindowWidth / 2;
}
// 使用阻尼动画实现吸附效果
this.uiContext.animateTo({ curve: curves.springMotion() }, () => {
// 获取屏幕宽度
let width = this.uiContext.px2vp(display.getDefaultDisplaySync().width);
// 启用吸附功能时
if (this.openAdsorb) {
// 根据中心点位置决定吸附方向
if (centerX > (this.containerWidth / 2)) {
// 右侧吸附
this.edge.right = this.pagePadding;
this.edge.left = undefined;
} else {
// 左侧吸附
this.edge.right = undefined;
this.edge.left = this.pagePadding;
}
} else {
// 检查是否超出左侧屏幕
let overflowLeft = this.edge.right! > width - this.floatWindowWidth;
// 检查是否超出右侧屏幕
let overflowRight = this.edge.right! < 0;
// 处理超出屏幕边界的情况
if (overflowLeft) { // 超出左边屏幕
this.edge.left = undefined;
this.edge.right = width - this.floatWindowWidth;
} else if (overflowRight) { // 超出右边屏幕
this.edge.right = this.pagePadding;
this.edge.left = undefined;
}
}
})
break;
}
完整实现如下:

import { curves, display } from '@kit.ArkUI';

@Component
export struct FloatWindowView {
@State edge: Edges = { top: 200, left: 0 };
@Link containerWidth: number;
@Link containerHeight: number;
private windowStartX: number = 0;
private windowStartY: number = 0;
// 是否启用吸附功能
openAdsorb: boolean = true;
@Prop pagePadding: number = 0; // 页面内容内边距,用于悬浮窗位置计算
@State floatWindowWidth: number = 100; // 悬浮窗宽度
@State floatWindowHeight: number = 50; // 悬浮窗高度
uiContext: UIContext = this.getUIContext();

// 触摸回调,实现悬浮窗跟手拖拽和贴边吸附动画
onTouchEvent(event: TouchEvent): void {
switch (event.type) {
// 指针按下事件:记录初始坐标
case TouchType.Down: {
this.windowStartX = event.touches[0].windowX;
this.windowStartY = event.touches[0].windowY;
break;
}
case TouchType.Move: {
const windowX: number = event.touches[0].windowX;
const windowY: number = event.touches[0].windowY;
// 左边距吸附时:更新左边缘位置
if (this.edge.left !== undefined) {
this.edge.left = this.edge.left as number + (windowX - this.windowStartX);
}
// 右边距吸附时:更新右边缘位置
else {
this.edge.right = this.edge.right as number - (windowX - this.windowStartX);
}
// 更新顶部位置
this.edge.top = this.edge.top as number + (windowY - this.windowStartY);
// 更新起始坐标用于下次计算
this.windowStartX = windowX;
this.windowStartY = windowY;
break;
}
// 指针抬起事件:实现吸附动画和边界限制
case TouchType.Up: {
// 计算悬浮窗中心点在父组件中的水平坐标
let centerX: number;
if (this.edge.left !== undefined) {
centerX = this.edge.left as number + this.floatWindowWidth / 2;
} else {
centerX = this.containerWidth - (this.edge.right as number) - this.floatWindowWidth / 2;
}
// 使用阻尼动画实现吸附效果
this.uiContext.animateTo({ curve: curves.springMotion() }, () => {
// 获取屏幕宽度
let width = this.uiContext.px2vp(display.getDefaultDisplaySync().width);
// 启用吸附功能时
if (this.openAdsorb) {
// 根据中心点位置决定吸附方向
if (centerX > (this.containerWidth / 2)) {
// 右侧吸附
this.edge.right = this.pagePadding;
this.edge.left = undefined;
} else {
// 左侧吸附
this.edge.right = undefined;
this.edge.left = this.pagePadding;
}
} else {
// 检查是否超出左侧屏幕
let overflowLeft = this.edge.right! > width - this.floatWindowWidth;
// 检查是否超出右侧屏幕
let overflowRight = this.edge.right! < 0;
// 处理超出屏幕边界的情况
if (overflowLeft) { // 超出左边屏幕
this.edge.left = undefined;
this.edge.right = width - this.floatWindowWidth;
} else if (overflowRight) { // 超出右边屏幕
this.edge.right = this.pagePadding;
this.edge.left = undefined;
}
}
})
break;
}
default: {
break;
}
}
}

build() {
Column() {
}
.clip(true)
.backgroundColor('#0A59F7')
.width(this.floatWindowWidth)
.height(this.floatWindowHeight)
.position(this.edge)
.onTouch((event: TouchEvent) => {
this.onTouchEvent(event);
})
}
}

@Entry
@Component
struct Index {
// 父组件宽度
@State containerWidth: number = 0;
// 父组件高度
@State containerHeight: number = 0;

build() {
Stack() {
FloatWindowView({
containerWidth: this.containerWidth, // 传递父容器宽度
containerHeight: this.containerHeight, // 传递父容器高度
})
.width('100%')
}
// 设置外层Stack容器的尺寸
.height('100%')
.width('100%')

.onAreaChange((oldValue: Area, newValue: Area) => {  
  // 记录父组件的宽高  
  if (oldValue.width !== newValue.width) {  
    this.containerWidth = newValue.width as number;  
  }  

  if (oldValue.height !== newValue.height) {  
    this.containerHeight = newValue.height as number;  
  }  
})  

}
}
https://sites.google.com/view/8ojcyz21/home
https://sites.google.com/view/8votdo34/home
https://sites.google.com/view/2glnsx28/home
https://sites.google.com/view/5cljni07/home
https://sites.google.com/view/8prkhh07/home
https://sites.google.com/view/7khplo30/home
https://sites.google.com/view/1iwwoj29/home
https://sites.google.com/view/5lokbt20/home
https://sites.google.com/view/8ucjap35/home
https://sites.google.com/view/3vmwqk28/home
https://sites.google.com/view/4hqmfe46/home
https://sites.google.com/view/8jjhsd05/home
https://sites.google.com/view/2ajqbl31/home
https://sites.google.com/view/8eroul90/home
https://sites.google.com/view/8whpgb50/home
https://sites.google.com/view/7fhwgy79/home
https://sites.google.com/view/8otxzd32/home
https://sites.google.com/view/0skklh79/home
https://sites.google.com/view/7epixg32/home
https://sites.google.com/view/2fdymh76/home
https://sites.google.com/view/1qzbbd10/home
https://sites.google.com/view/3betru91/home
https://sites.google.com/view/0fjymy02/home
https://sites.google.com/view/1tuyht61/home
https://sites.google.com/view/3lqsci93/home
https://sites.google.com/view/4moosi57/home
https://sites.google.com/view/0ebmiw84/home
https://sites.google.com/view/4usaol83/home
https://sites.google.com/view/0hknci80/home
https://sites.google.com/view/7ldcgt68/home
https://sites.google.com/view/8emzek60/home
https://sites.google.com/view/9oaldi75/home
https://sites.google.com/view/1hujlx46/home
https://sites.google.com/view/9krrdx96/home
https://sites.google.com/view/0rdwwl74/home
https://sites.google.com/view/4oalem69/home
https://sites.google.com/view/7tpgsy97/home
https://sites.google.com/view/4sjvkh25/home
https://sites.google.com/view/6ixmrs26/home
https://sites.google.com/view/7jjcws23/home
https://sites.google.com/view/4entvi31/home
https://sites.google.com/view/0hqtsy80/home
https://sites.google.com/view/2vthve29/home
https://sites.google.com/view/2wjwhq06/home
https://sites.google.com/view/7lnlrg38/home
https://sites.google.com/view/3jwoff92/home
https://sites.google.com/view/9exfcx28/home
https://sites.google.com/view/6ayynm56/home
https://sites.google.com/view/2mkert04/home
https://sites.google.com/view/9bfpha34/home
https://sites.google.com/view/7kwinp20/home
https://sites.google.com/view/0kzpsl33/home
https://sites.google.com/view/7vqzug72/home
https://sites.google.com/view/8qybcb91/home
https://sites.google.com/view/1manil34/home
https://sites.google.com/view/9zlzlw68/home
https://sites.google.com/view/7ytpgf83/home
https://sites.google.com/view/0figtg65/home
https://sites.google.com/view/3nphmi02/home
https://sites.google.com/view/0qkeih69/home
https://sites.google.com/view/1qqdjc96/home
https://sites.google.com/view/2ocmzl47/home
https://sites.google.com/view/0rszvm31/home
https://sites.google.com/view/8twpcf39/home
https://sites.google.com/view/1tmhcz01/home
https://sites.google.com/view/1fsimv87/home
https://sites.google.com/view/0krela31/home
https://sites.google.com/view/8jhshd10/home
https://sites.google.com/view/5tseou06/home
https://sites.google.com/view/2msyts76/home
https://sites.google.com/view/6dxuri73/home
https://sites.google.com/view/1pmboa56/home
https://sites.google.com/view/9iueuv34/home
https://sites.google.com/view/8pubxw30/home
https://sites.google.com/view/1tlyco56/home
https://sites.google.com/view/8srvbu36/home
https://sites.google.com/view/4gsnto37/home
https://sites.google.com/view/6akswp56/home
https://sites.google.com/view/2inxol47/home
https://sites.google.com/view/3oojsn55/home
https://sites.google.com/view/5mdqwb17/home
https://sites.google.com/view/7xuice12/home
https://sites.google.com/view/0qzfci43/home
https://sites.google.com/view/6ohonc26/home
https://sites.google.com/view/4tbmuv32/home
https://sites.google.com/view/1rbxss83/home
https://sites.google.com/view/2niebx01/home
https://sites.google.com/view/3qaeqy35/home
https://sites.google.com/view/0cbewx04/home
https://sites.google.com/view/3csyuh21/home
https://sites.google.com/view/8wylyx13/home
https://sites.google.com/view/1kpxse38/home
https://sites.google.com/view/8ovanb09/home
https://sites.google.com/view/6rmoih50/home
https://sites.google.com/view/5cfhmg25/home
https://sites.google.com/view/4ivxrt19/home
https://sites.google.com/view/4ltzqb02/home
https://sites.google.com/view/3vdlsy06/home
https://sites.google.com/view/5vwlbj78/home
https://sites.google.com/view/4naisw60/home

继续阅读 »

问题现象
如何实现手指长按组件时,组件能够跟随手指拖动,手指抬起后,组件贴边停靠的效果。

背景知识
onTouch是用于处理触摸事件的组件属性,支持TouchType.Down(按下)、TouchType.Move(移动)、TouchType.Up(抬起),用于多模交互场景(如拖拽、点击、滑动等)。
animateTo是用于实现动画效果的核心函数,支持平滑过渡、弹性动画、曲线控制等。
解决方案
为实现上述功能,可通过如下步骤实现:

通过组件的触摸事件onTouch,其中使用TouchType.Down记录手指按下时的初始坐标,使用TouchType.Move获取手指移动时的位置信息,通过获取的位置信息修改组件的position属性实现元素的拖拽功能:
// 指针按下事件:记录初始坐标
case TouchType.Down: {
this.windowStartX = event.touches[0].windowX;
this.windowStartY = event.touches[0].windowY;
break;
}
case TouchType.Move: {
const windowX: number = event.touches[0].windowX;
const windowY: number = event.touches[0].windowY;
// 左边距吸附时:更新左边缘位置
if (this.edge.left !== undefined) {
this.edge.left = this.edge.left as number + (windowX - this.windowStartX);
}
// 右边距吸附时:更新右边缘位置
else {
this.edge.right = this.edge.right as number - (windowX - this.windowStartX);
}
// 更新顶部位置
this.edge.top = this.edge.top as number + (windowY - this.windowStartY);
// 更新起始坐标用于下次计算
this.windowStartX = windowX;
this.windowStartY = windowY;
break;
}
再通过TouchType.Up(手指抬起),对比手指抬起时组件所处位置,与容器组件的中心位置,判断出需要向哪边进行贴边操作,或与容器元素宽度进行对比,判断元素是否超出容器,选择将组件position属性的left或right设置为undefined并将其对立方向设置为0,且位置的调整设置于animateTo中,实现将元素从容器内部或容器外部缓慢调整至容器边缘,从而实现组件拖拽后的贴边效果:
case TouchType.Up: {
// 计算悬浮窗中心点在父组件中的水平坐标
let centerX: number;
if (this.edge.left !== undefined) {
centerX = this.edge.left as number + this.floatWindowWidth / 2;
} else {
centerX = this.containerWidth - (this.edge.right as number) - this.floatWindowWidth / 2;
}
// 使用阻尼动画实现吸附效果
this.uiContext.animateTo({ curve: curves.springMotion() }, () => {
// 获取屏幕宽度
let width = this.uiContext.px2vp(display.getDefaultDisplaySync().width);
// 启用吸附功能时
if (this.openAdsorb) {
// 根据中心点位置决定吸附方向
if (centerX > (this.containerWidth / 2)) {
// 右侧吸附
this.edge.right = this.pagePadding;
this.edge.left = undefined;
} else {
// 左侧吸附
this.edge.right = undefined;
this.edge.left = this.pagePadding;
}
} else {
// 检查是否超出左侧屏幕
let overflowLeft = this.edge.right! > width - this.floatWindowWidth;
// 检查是否超出右侧屏幕
let overflowRight = this.edge.right! < 0;
// 处理超出屏幕边界的情况
if (overflowLeft) { // 超出左边屏幕
this.edge.left = undefined;
this.edge.right = width - this.floatWindowWidth;
} else if (overflowRight) { // 超出右边屏幕
this.edge.right = this.pagePadding;
this.edge.left = undefined;
}
}
})
break;
}
完整实现如下:

import { curves, display } from '@kit.ArkUI';

@Component
export struct FloatWindowView {
@State edge: Edges = { top: 200, left: 0 };
@Link containerWidth: number;
@Link containerHeight: number;
private windowStartX: number = 0;
private windowStartY: number = 0;
// 是否启用吸附功能
openAdsorb: boolean = true;
@Prop pagePadding: number = 0; // 页面内容内边距,用于悬浮窗位置计算
@State floatWindowWidth: number = 100; // 悬浮窗宽度
@State floatWindowHeight: number = 50; // 悬浮窗高度
uiContext: UIContext = this.getUIContext();

// 触摸回调,实现悬浮窗跟手拖拽和贴边吸附动画
onTouchEvent(event: TouchEvent): void {
switch (event.type) {
// 指针按下事件:记录初始坐标
case TouchType.Down: {
this.windowStartX = event.touches[0].windowX;
this.windowStartY = event.touches[0].windowY;
break;
}
case TouchType.Move: {
const windowX: number = event.touches[0].windowX;
const windowY: number = event.touches[0].windowY;
// 左边距吸附时:更新左边缘位置
if (this.edge.left !== undefined) {
this.edge.left = this.edge.left as number + (windowX - this.windowStartX);
}
// 右边距吸附时:更新右边缘位置
else {
this.edge.right = this.edge.right as number - (windowX - this.windowStartX);
}
// 更新顶部位置
this.edge.top = this.edge.top as number + (windowY - this.windowStartY);
// 更新起始坐标用于下次计算
this.windowStartX = windowX;
this.windowStartY = windowY;
break;
}
// 指针抬起事件:实现吸附动画和边界限制
case TouchType.Up: {
// 计算悬浮窗中心点在父组件中的水平坐标
let centerX: number;
if (this.edge.left !== undefined) {
centerX = this.edge.left as number + this.floatWindowWidth / 2;
} else {
centerX = this.containerWidth - (this.edge.right as number) - this.floatWindowWidth / 2;
}
// 使用阻尼动画实现吸附效果
this.uiContext.animateTo({ curve: curves.springMotion() }, () => {
// 获取屏幕宽度
let width = this.uiContext.px2vp(display.getDefaultDisplaySync().width);
// 启用吸附功能时
if (this.openAdsorb) {
// 根据中心点位置决定吸附方向
if (centerX > (this.containerWidth / 2)) {
// 右侧吸附
this.edge.right = this.pagePadding;
this.edge.left = undefined;
} else {
// 左侧吸附
this.edge.right = undefined;
this.edge.left = this.pagePadding;
}
} else {
// 检查是否超出左侧屏幕
let overflowLeft = this.edge.right! > width - this.floatWindowWidth;
// 检查是否超出右侧屏幕
let overflowRight = this.edge.right! < 0;
// 处理超出屏幕边界的情况
if (overflowLeft) { // 超出左边屏幕
this.edge.left = undefined;
this.edge.right = width - this.floatWindowWidth;
} else if (overflowRight) { // 超出右边屏幕
this.edge.right = this.pagePadding;
this.edge.left = undefined;
}
}
})
break;
}
default: {
break;
}
}
}

build() {
Column() {
}
.clip(true)
.backgroundColor('#0A59F7')
.width(this.floatWindowWidth)
.height(this.floatWindowHeight)
.position(this.edge)
.onTouch((event: TouchEvent) => {
this.onTouchEvent(event);
})
}
}

@Entry
@Component
struct Index {
// 父组件宽度
@State containerWidth: number = 0;
// 父组件高度
@State containerHeight: number = 0;

build() {
Stack() {
FloatWindowView({
containerWidth: this.containerWidth, // 传递父容器宽度
containerHeight: this.containerHeight, // 传递父容器高度
})
.width('100%')
}
// 设置外层Stack容器的尺寸
.height('100%')
.width('100%')

.onAreaChange((oldValue: Area, newValue: Area) => {  
  // 记录父组件的宽高  
  if (oldValue.width !== newValue.width) {  
    this.containerWidth = newValue.width as number;  
  }  

  if (oldValue.height !== newValue.height) {  
    this.containerHeight = newValue.height as number;  
  }  
})  

}
}
https://sites.google.com/view/8ojcyz21/home
https://sites.google.com/view/8votdo34/home
https://sites.google.com/view/2glnsx28/home
https://sites.google.com/view/5cljni07/home
https://sites.google.com/view/8prkhh07/home
https://sites.google.com/view/7khplo30/home
https://sites.google.com/view/1iwwoj29/home
https://sites.google.com/view/5lokbt20/home
https://sites.google.com/view/8ucjap35/home
https://sites.google.com/view/3vmwqk28/home
https://sites.google.com/view/4hqmfe46/home
https://sites.google.com/view/8jjhsd05/home
https://sites.google.com/view/2ajqbl31/home
https://sites.google.com/view/8eroul90/home
https://sites.google.com/view/8whpgb50/home
https://sites.google.com/view/7fhwgy79/home
https://sites.google.com/view/8otxzd32/home
https://sites.google.com/view/0skklh79/home
https://sites.google.com/view/7epixg32/home
https://sites.google.com/view/2fdymh76/home
https://sites.google.com/view/1qzbbd10/home
https://sites.google.com/view/3betru91/home
https://sites.google.com/view/0fjymy02/home
https://sites.google.com/view/1tuyht61/home
https://sites.google.com/view/3lqsci93/home
https://sites.google.com/view/4moosi57/home
https://sites.google.com/view/0ebmiw84/home
https://sites.google.com/view/4usaol83/home
https://sites.google.com/view/0hknci80/home
https://sites.google.com/view/7ldcgt68/home
https://sites.google.com/view/8emzek60/home
https://sites.google.com/view/9oaldi75/home
https://sites.google.com/view/1hujlx46/home
https://sites.google.com/view/9krrdx96/home
https://sites.google.com/view/0rdwwl74/home
https://sites.google.com/view/4oalem69/home
https://sites.google.com/view/7tpgsy97/home
https://sites.google.com/view/4sjvkh25/home
https://sites.google.com/view/6ixmrs26/home
https://sites.google.com/view/7jjcws23/home
https://sites.google.com/view/4entvi31/home
https://sites.google.com/view/0hqtsy80/home
https://sites.google.com/view/2vthve29/home
https://sites.google.com/view/2wjwhq06/home
https://sites.google.com/view/7lnlrg38/home
https://sites.google.com/view/3jwoff92/home
https://sites.google.com/view/9exfcx28/home
https://sites.google.com/view/6ayynm56/home
https://sites.google.com/view/2mkert04/home
https://sites.google.com/view/9bfpha34/home
https://sites.google.com/view/7kwinp20/home
https://sites.google.com/view/0kzpsl33/home
https://sites.google.com/view/7vqzug72/home
https://sites.google.com/view/8qybcb91/home
https://sites.google.com/view/1manil34/home
https://sites.google.com/view/9zlzlw68/home
https://sites.google.com/view/7ytpgf83/home
https://sites.google.com/view/0figtg65/home
https://sites.google.com/view/3nphmi02/home
https://sites.google.com/view/0qkeih69/home
https://sites.google.com/view/1qqdjc96/home
https://sites.google.com/view/2ocmzl47/home
https://sites.google.com/view/0rszvm31/home
https://sites.google.com/view/8twpcf39/home
https://sites.google.com/view/1tmhcz01/home
https://sites.google.com/view/1fsimv87/home
https://sites.google.com/view/0krela31/home
https://sites.google.com/view/8jhshd10/home
https://sites.google.com/view/5tseou06/home
https://sites.google.com/view/2msyts76/home
https://sites.google.com/view/6dxuri73/home
https://sites.google.com/view/1pmboa56/home
https://sites.google.com/view/9iueuv34/home
https://sites.google.com/view/8pubxw30/home
https://sites.google.com/view/1tlyco56/home
https://sites.google.com/view/8srvbu36/home
https://sites.google.com/view/4gsnto37/home
https://sites.google.com/view/6akswp56/home
https://sites.google.com/view/2inxol47/home
https://sites.google.com/view/3oojsn55/home
https://sites.google.com/view/5mdqwb17/home
https://sites.google.com/view/7xuice12/home
https://sites.google.com/view/0qzfci43/home
https://sites.google.com/view/6ohonc26/home
https://sites.google.com/view/4tbmuv32/home
https://sites.google.com/view/1rbxss83/home
https://sites.google.com/view/2niebx01/home
https://sites.google.com/view/3qaeqy35/home
https://sites.google.com/view/0cbewx04/home
https://sites.google.com/view/3csyuh21/home
https://sites.google.com/view/8wylyx13/home
https://sites.google.com/view/1kpxse38/home
https://sites.google.com/view/8ovanb09/home
https://sites.google.com/view/6rmoih50/home
https://sites.google.com/view/5cfhmg25/home
https://sites.google.com/view/4ivxrt19/home
https://sites.google.com/view/4ltzqb02/home
https://sites.google.com/view/3vdlsy06/home
https://sites.google.com/view/5vwlbj78/home
https://sites.google.com/view/4naisw60/home

收起阅读 »