知识付费直播系统-Uniapp源码
仓库地址 https://gitee.com/tuzhi-open/tuzhi-mobile-open
凸知(Tuzhi) 是一套完整的知识付费解决方案,支持私有化部署,覆盖课程售卖、直播授课、圈子社区、考试测评、分销营销、实物商品等完整业务场景,提供管理后台、移动端(H5 / 微信小程序)、PC 学员端等多端入口,帮助个人 IP、教培机构与品牌商家搭建自有卖课平台。
凸知移动端 是一款基于 uni-app(Vue 2)+ TDesign 开发的知识付费移动端前端,支持 H5 / 微信小程序 多端编译(不支持 App 打包)。覆盖课程点播、直播、专栏、圈子社区、订单支付(前端交互)、优惠券、余额红包、考试答题等完整学习场景。
系统演示:
| 端 | 演示地址 | 账号 | 密码 |
|---|---|---|---|
| 后台 | https://tuzhi.mutouweb.com/admin.php | admin | 123456 |
| 移动端(用户) | https://tuzhi.mutouweb.com | 13222222222 | 123456 |
| PC 端(用户) | https://tuzhi.mutouweb.com/pc/#/ | 13222222222 | 123456 |
一、功能特性
- 终端覆盖:电脑端 / 微信公众号(H5)/ 微信小程序多端学习
- 课程内容:图文 / 视频 / 音频 / 专栏售卖,支持试看试听、课件资料与内容保护
- 互动教学:练习 / 考试测评 / 题库 / 试卷库 / 证书 / 表单 / 圈子社区
- 直播能力:直播授课 / 伪直播 / 回放 / 直播带货 / 送礼物 / 发红包
- 营销增长:分销推广 / 优惠券 / 每日签到 / 积分商城 / 付费会员卡 / 组合商品 / 卡密兑换
- 实物商品:商品管理 / 多规格 SKU / 限购设置 / 售后退换
- 交易支付:购物车 / 订单管理 / 余额支付 / 微信支付 / 支付宝支付
- 用户数据:用户管理 / 订阅记录 / 学习分析 / 交易分析 / 订单管理
- 系统安全:页面 DIY 装修 / 云存储 / 短信与消息推送 / 管理员权限 / 版权保护(跑马灯 / 防录屏)
界面预览:
二、系统架构
凸知是一套完整的知识付费系统,由四个部分组成:
| 部分 | 技术栈 | 说明 | 源码开放 |
|---|---|---|---|
| 后端服务 | ThinkPHP 5.0 + FastAdmin + MySQL | 接口服务,承载支付、直播、分销等全部业务逻辑 | 购买授权 |
| 移动端(本仓库) | uni-app(Vue 2)+ TDesign | H5 / 微信小程序学员端(不支持 App 打包) | 开源 |
| 管理后台 | Vue 3 + Arco Design | 管理员 / 讲师运营后台 | 购买授权 |
| PC 学员端 | Vue 3 + Arco Design | 电脑端学员学习页面 | 购买授权 |
凸知终端技术结构:
整体调用链路:移动端 / PC 学员端 / 管理后台 → 后端接口服务({code, msg, data} 协议)→ MySQL 数据库。
本仓库仅开源移动端前端源码;如需后端服务或管理后台等其余端的源码,需购买商业授权获取,详见「十一、使用许可」与授权价格。
三、技术栈
| 分类 | 技术 | 说明 |
|---|---|---|
| 跨端框架 | uni-app(Vue 2.x) | vue-cli 工程,一套代码编译 H5 / 微信小程序等多端(不支持 App 打包) |
| 开发语言 | JavaScript(Vue 2 语法)+ Sass | 页面按 xxx.vue + js.vue + css.css 三文件拆分,样式用 CSS 规则 |
| UI 组件库 | TDesign(@tdesign/uniapp) |
全局主题样式,小程序端内置构建适配补丁(scripts/) |
uView(本地 components/uview-ui) |
列表 / 弹窗 / 表单等通用组件 | |
自研 components/tz/ |
音频 / 视频 / 媒体编辑 / 上传 / 文件预览等 45+ 业务组件 | |
| qiun-data-charts | 图表可视化 | |
| 状态管理 | Vuex 3 | 全局登录态 / 用户信息等 |
| 国际化 | vue-i18n 8 | 内置 zh-CN / US 多语言 |
| 网络请求 | flyio + 自封装 $api |
$api('module.action') 点分路由,统一鉴权 / 错误处理 / 401 跳转 |
| 音视频播放 | 阿里云播放器 Aliplayer(H5,本地化资源)、hls.js / flv.js | 直播与点播,支持倍速 / 弹幕 / 水印等组件 |
| 微信能力 | jweixin-module | 微信登录 / 分享 JS-SDK 封装 |
| 构建工具 | @vue/cli 5 + webpack | 由 @dcloudio/vue-cli-plugin-uni 驱动多端编译 |
| 测试 | Jest | 多端单元测试(npm run test:*) |
四、快速开始
环境要求:Node.js >= 14(建议 16/18)、npm
# ① 安装依赖
npm install
# ② H5 开发(默认不启用 Mock,对接真实后端;如需 Mock 见「Mock 数据」)
npm run dev:h5
浏览器访问 http://localhost:8080 即可看到完整页面效果。
微信小程序编译:
# ③ 小程序开发模式(监听编译,产物在 dist/dev/mp-weixin)
npm run dev:mp-weixin
# ④ 小程序生产构建(内置 TDesign 适配补丁,产物在 dist/build/mp-weixin)
npm run build:mp-weixin
用微信开发者工具「导入项目」选择对应产物目录(开发选 dist/dev/mp-weixin,生产选 dist/build/mp-weixin),并在 src/manifest.json 中替换为自己的 appid。
对接真实后端:
- 修改
src/siteinfo.js中的siteroot为你的后端地址; - 在
src/common/request/api_list.js中核对接口地址与{code, msg, data}协议; - 微信小程序构建需在
src/manifest.json中替换为自己的 appid。
五、构建
| 目标 | 命令 |
|---|---|
| H5 | npm run build:h5 |
| 微信小程序 | npm run build:mp-weixin |
小程序构建内置 TDesign 适配补丁(
scripts/),构建成功后产物在dist/build/下。⚠️ 注意:当前版本不支持 App 打包,仅支持 H5 与微信小程序两个平台。
六、Mock 数据
- H5 开发模式(
NODE_ENV=development)默认关闭,全部走真实后端接口; - 开启方式:将
src/static/config.js的enableMock改为true(显式设置优先级最高),或通过uni.setStorageSync('enableMock', true)/ 环境变量MOCK_ENABLE切换; - 覆盖 200+ 接口,fixture 位于
src/mock/data/,按module/action路由镜像; - 未覆盖的接口自动透传真实网络并输出提示;
- Mock 数据由仓库内 mock-crawler 工具采集真实后端响应生成,发布前已做敏感字段脱敏(token/openid/手机号等)。
七、目录结构
src/
├── pages/ 页面(index 主包 + public/app/user/order/course 五个分包,约 100+ 页面)
│ ├── index/ 首页(主包唯一页面,tabBar 入口)
│ ├── launch/ 启动页(加载与参数透传跳转,未在 pages.json 注册)
│ ├── public/ 公共分包:登录 / 注册 / 找回密码 / 协议 / 搜索 / 消息 / 结果页等
│ ├── app/ 业务功能分包:活动 / 圈子 / 考试 / 优惠券 / 积分 / 签到 / 证书 / 分销 / 直播带货等
│ ├── user/ 用户分包:个人中心 / 资产 / 余额 / 地址 / 收藏 / 学习记录 / 订阅 / 红包等
│ ├── order/ 订单分包:订单列表 / 详情 / 提交 / 评价 / 售后 / 物流等
│ ├── course/ 课程分包:课程详情 / 分类 / 专栏 / 小组 / 直播等
│ └── template/ 页面模板(新建页面的三文件拆分参考,未在 pages.json 注册)
├── components/ 组件(tz/ 自研组件库、uview-ui、qiun-data-charts、视频播放器等)
├── common/ 公共层
│ ├── request/ 接口封装($api 点分路由、统一鉴权/错误处理)
│ ├── utils/ 工具(路由映射、支付、直播、OSS 签名等)
│ ├── order/ 订单状态处理
│ └── wechat/ 微信登录/分享 SDK 封装
├── mock/ Mock 数据(H5 开发模式默认关闭,开启方式见「六、Mock 数据」)
├── static/ 静态资源
├── pages.json 页面路由与分包配置
└── manifest.json 应用配置(appid 等)
八、开发约定
- 页面采用
xxx.vue + js.vue + css.css三文件拆分(<script src="./js.vue">+@import "./css.css"),逻辑与样式分离; - 接口调用统一走
this.$api('module.action', data),登录态、401 跳转、错误提示由拦截器统一处理; - 业务组件优先复用
components/tz/自研组件库; - 提交信息遵循 Conventional Commits(
feat:/fix:/chore:)。
九、相关链接
| 名称 | 链接 |
|---|---|
| 使用文档 | https://wood-soft.feishu.cn/wiki/KKLkwOPBHiButJkTluTcoXKenjh |
| 功能列表 | https://wood-soft.feishu.cn/wiki/YGFYw0k8PiJDWxketgHcBNKNnxe |
| 授权价格 | https://www.tuzhi.ltd/index/pricing/version.html |
| 功能演示 | https://www.tuzhi.ltd/index/demo/index.html |
十、软件安全
安全问题请通过 Issue(需注明"安全")或邮件私下报告给项目维护团队,请勿在公开渠道描述可利用的漏洞细节。我们将在确认后尽快修复并同步发布。
十一、使用许可
- 本软件遵循 凸知开源协议 V1.0(非商业用途免费,商业用途需购买商业授权),详见 LICENSE;
- 第三方组件许可声明见 THIRD-PARTY-NOTICES.md(发布前请核对);
仓库地址 https://gitee.com/tuzhi-open/tuzhi-mobile-open
凸知(Tuzhi) 是一套完整的知识付费解决方案,支持私有化部署,覆盖课程售卖、直播授课、圈子社区、考试测评、分销营销、实物商品等完整业务场景,提供管理后台、移动端(H5 / 微信小程序)、PC 学员端等多端入口,帮助个人 IP、教培机构与品牌商家搭建自有卖课平台。
凸知移动端 是一款基于 uni-app(Vue 2)+ TDesign 开发的知识付费移动端前端,支持 H5 / 微信小程序 多端编译(不支持 App 打包)。覆盖课程点播、直播、专栏、圈子社区、订单支付(前端交互)、优惠券、余额红包、考试答题等完整学习场景。
系统演示:
| 端 | 演示地址 | 账号 | 密码 |
|---|---|---|---|
| 后台 | https://tuzhi.mutouweb.com/admin.php | admin | 123456 |
| 移动端(用户) | https://tuzhi.mutouweb.com | 13222222222 | 123456 |
| PC 端(用户) | https://tuzhi.mutouweb.com/pc/#/ | 13222222222 | 123456 |
一、功能特性
- 终端覆盖:电脑端 / 微信公众号(H5)/ 微信小程序多端学习
- 课程内容:图文 / 视频 / 音频 / 专栏售卖,支持试看试听、课件资料与内容保护
- 互动教学:练习 / 考试测评 / 题库 / 试卷库 / 证书 / 表单 / 圈子社区
- 直播能力:直播授课 / 伪直播 / 回放 / 直播带货 / 送礼物 / 发红包
- 营销增长:分销推广 / 优惠券 / 每日签到 / 积分商城 / 付费会员卡 / 组合商品 / 卡密兑换
- 实物商品:商品管理 / 多规格 SKU / 限购设置 / 售后退换
- 交易支付:购物车 / 订单管理 / 余额支付 / 微信支付 / 支付宝支付
- 用户数据:用户管理 / 订阅记录 / 学习分析 / 交易分析 / 订单管理
- 系统安全:页面 DIY 装修 / 云存储 / 短信与消息推送 / 管理员权限 / 版权保护(跑马灯 / 防录屏)
界面预览:
二、系统架构
凸知是一套完整的知识付费系统,由四个部分组成:
| 部分 | 技术栈 | 说明 | 源码开放 |
|---|---|---|---|
| 后端服务 | ThinkPHP 5.0 + FastAdmin + MySQL | 接口服务,承载支付、直播、分销等全部业务逻辑 | 购买授权 |
| 移动端(本仓库) | uni-app(Vue 2)+ TDesign | H5 / 微信小程序学员端(不支持 App 打包) | 开源 |
| 管理后台 | Vue 3 + Arco Design | 管理员 / 讲师运营后台 | 购买授权 |
| PC 学员端 | Vue 3 + Arco Design | 电脑端学员学习页面 | 购买授权 |
凸知终端技术结构:
整体调用链路:移动端 / PC 学员端 / 管理后台 → 后端接口服务({code, msg, data} 协议)→ MySQL 数据库。
本仓库仅开源移动端前端源码;如需后端服务或管理后台等其余端的源码,需购买商业授权获取,详见「十一、使用许可」与授权价格。
三、技术栈
| 分类 | 技术 | 说明 |
|---|---|---|
| 跨端框架 | uni-app(Vue 2.x) | vue-cli 工程,一套代码编译 H5 / 微信小程序等多端(不支持 App 打包) |
| 开发语言 | JavaScript(Vue 2 语法)+ Sass | 页面按 xxx.vue + js.vue + css.css 三文件拆分,样式用 CSS 规则 |
| UI 组件库 | TDesign(@tdesign/uniapp) |
全局主题样式,小程序端内置构建适配补丁(scripts/) |
uView(本地 components/uview-ui) |
列表 / 弹窗 / 表单等通用组件 | |
自研 components/tz/ |
音频 / 视频 / 媒体编辑 / 上传 / 文件预览等 45+ 业务组件 | |
| qiun-data-charts | 图表可视化 | |
| 状态管理 | Vuex 3 | 全局登录态 / 用户信息等 |
| 国际化 | vue-i18n 8 | 内置 zh-CN / US 多语言 |
| 网络请求 | flyio + 自封装 $api |
$api('module.action') 点分路由,统一鉴权 / 错误处理 / 401 跳转 |
| 音视频播放 | 阿里云播放器 Aliplayer(H5,本地化资源)、hls.js / flv.js | 直播与点播,支持倍速 / 弹幕 / 水印等组件 |
| 微信能力 | jweixin-module | 微信登录 / 分享 JS-SDK 封装 |
| 构建工具 | @vue/cli 5 + webpack | 由 @dcloudio/vue-cli-plugin-uni 驱动多端编译 |
| 测试 | Jest | 多端单元测试(npm run test:*) |
四、快速开始
环境要求:Node.js >= 14(建议 16/18)、npm
# ① 安装依赖
npm install
# ② H5 开发(默认不启用 Mock,对接真实后端;如需 Mock 见「Mock 数据」)
npm run dev:h5
浏览器访问 http://localhost:8080 即可看到完整页面效果。
微信小程序编译:
# ③ 小程序开发模式(监听编译,产物在 dist/dev/mp-weixin)
npm run dev:mp-weixin
# ④ 小程序生产构建(内置 TDesign 适配补丁,产物在 dist/build/mp-weixin)
npm run build:mp-weixin
用微信开发者工具「导入项目」选择对应产物目录(开发选 dist/dev/mp-weixin,生产选 dist/build/mp-weixin),并在 src/manifest.json 中替换为自己的 appid。
对接真实后端:
- 修改
src/siteinfo.js中的siteroot为你的后端地址; - 在
src/common/request/api_list.js中核对接口地址与{code, msg, data}协议; - 微信小程序构建需在
src/manifest.json中替换为自己的 appid。
五、构建
| 目标 | 命令 |
|---|---|
| H5 | npm run build:h5 |
| 微信小程序 | npm run build:mp-weixin |
小程序构建内置 TDesign 适配补丁(
scripts/),构建成功后产物在dist/build/下。⚠️ 注意:当前版本不支持 App 打包,仅支持 H5 与微信小程序两个平台。
六、Mock 数据
- H5 开发模式(
NODE_ENV=development)默认关闭,全部走真实后端接口; - 开启方式:将
src/static/config.js的enableMock改为true(显式设置优先级最高),或通过uni.setStorageSync('enableMock', true)/ 环境变量MOCK_ENABLE切换; - 覆盖 200+ 接口,fixture 位于
src/mock/data/,按module/action路由镜像; - 未覆盖的接口自动透传真实网络并输出提示;
- Mock 数据由仓库内 mock-crawler 工具采集真实后端响应生成,发布前已做敏感字段脱敏(token/openid/手机号等)。
七、目录结构
src/
├── pages/ 页面(index 主包 + public/app/user/order/course 五个分包,约 100+ 页面)
│ ├── index/ 首页(主包唯一页面,tabBar 入口)
│ ├── launch/ 启动页(加载与参数透传跳转,未在 pages.json 注册)
│ ├── public/ 公共分包:登录 / 注册 / 找回密码 / 协议 / 搜索 / 消息 / 结果页等
│ ├── app/ 业务功能分包:活动 / 圈子 / 考试 / 优惠券 / 积分 / 签到 / 证书 / 分销 / 直播带货等
│ ├── user/ 用户分包:个人中心 / 资产 / 余额 / 地址 / 收藏 / 学习记录 / 订阅 / 红包等
│ ├── order/ 订单分包:订单列表 / 详情 / 提交 / 评价 / 售后 / 物流等
│ ├── course/ 课程分包:课程详情 / 分类 / 专栏 / 小组 / 直播等
│ └── template/ 页面模板(新建页面的三文件拆分参考,未在 pages.json 注册)
├── components/ 组件(tz/ 自研组件库、uview-ui、qiun-data-charts、视频播放器等)
├── common/ 公共层
│ ├── request/ 接口封装($api 点分路由、统一鉴权/错误处理)
│ ├── utils/ 工具(路由映射、支付、直播、OSS 签名等)
│ ├── order/ 订单状态处理
│ └── wechat/ 微信登录/分享 SDK 封装
├── mock/ Mock 数据(H5 开发模式默认关闭,开启方式见「六、Mock 数据」)
├── static/ 静态资源
├── pages.json 页面路由与分包配置
└── manifest.json 应用配置(appid 等)
八、开发约定
- 页面采用
xxx.vue + js.vue + css.css三文件拆分(<script src="./js.vue">+@import "./css.css"),逻辑与样式分离; - 接口调用统一走
this.$api('module.action', data),登录态、401 跳转、错误提示由拦截器统一处理; - 业务组件优先复用
components/tz/自研组件库; - 提交信息遵循 Conventional Commits(
feat:/fix:/chore:)。
九、相关链接
| 名称 | 链接 |
|---|---|
| 使用文档 | https://wood-soft.feishu.cn/wiki/KKLkwOPBHiButJkTluTcoXKenjh |
| 功能列表 | https://wood-soft.feishu.cn/wiki/YGFYw0k8PiJDWxketgHcBNKNnxe |
| 授权价格 | https://www.tuzhi.ltd/index/pricing/version.html |
| 功能演示 | https://www.tuzhi.ltd/index/demo/index.html |
十、软件安全
安全问题请通过 Issue(需注明"安全")或邮件私下报告给项目维护团队,请勿在公开渠道描述可利用的漏洞细节。我们将在确认后尽快修复并同步发布。
十一、使用许可
- 本软件遵循 凸知开源协议 V1.0(非商业用途免费,商业用途需购买商业授权),详见 LICENSE;
- 第三方组件许可声明见 THIRD-PARTY-NOTICES.md(发布前请核对);
【项目转让/可二开】已上线人才×项目撮合平台|完整前后端+管理后台,可直接部署运营
【项目转让/可二开】已上线人才×项目撮合平台|完整前后端+管理后台,可直接部署运营
正文:
手上有一套已经开发完成并上线的人才/项目双边撮合平台,目前准备整体转让,也接受基于现有系统做二次开发。
当前业务场景是:项目方发布需求 → 程序员/服务方入驻 → 平台审核 → 人工匹配撮合 → 跟进项目。
不是单独卖一个前端模板,项目包含完整的:
Web 前端
后端服务
管理后台
数据库结构
用户/需求等核心业务模块
部署配置
已上线域名及现有项目资产(具体交接范围可谈)
系统目前做的是程序员项目撮合,但底层属于双边供需平台,因此也可以继续二开成:
兼职招聘 / 灵活用工 / 自由职业 / 企业服务 / 本地服务 / 技术外包 / 供需撮合等平台。
为什么转让
个人精力和后续发展方向调整,不准备继续长期运营,所以考虑把完整项目交给真正有需求的人继续做。
适合谁
正准备开发人才、兼职、灵工、接单、撮合类产品的人;
想直接拿现成系统二开,不想从0开发的人;
有获客/运营能力,但缺技术产品的团队;
软件公司/工作室,可作为现成项目底座继续开发。
合作方式
A:整套项目独家转让
完成交易后按照约定交接源码、数据库、后台及部署资料等,不是单纯多份源码授权。
B:基于现有系统二开
如果你正好有类似项目需求,但不想直接买整套,也可以把需求发给我,可以在现有系统基础上修改。
有兴趣可以私聊我,先看线上 Demo 和后台,再决定是否继续谈
【项目转让/可二开】已上线人才×项目撮合平台|完整前后端+管理后台,可直接部署运营
正文:
手上有一套已经开发完成并上线的人才/项目双边撮合平台,目前准备整体转让,也接受基于现有系统做二次开发。
当前业务场景是:项目方发布需求 → 程序员/服务方入驻 → 平台审核 → 人工匹配撮合 → 跟进项目。
不是单独卖一个前端模板,项目包含完整的:
Web 前端
后端服务
管理后台
数据库结构
用户/需求等核心业务模块
部署配置
已上线域名及现有项目资产(具体交接范围可谈)
系统目前做的是程序员项目撮合,但底层属于双边供需平台,因此也可以继续二开成:
兼职招聘 / 灵活用工 / 自由职业 / 企业服务 / 本地服务 / 技术外包 / 供需撮合等平台。
为什么转让
个人精力和后续发展方向调整,不准备继续长期运营,所以考虑把完整项目交给真正有需求的人继续做。
适合谁
正准备开发人才、兼职、灵工、接单、撮合类产品的人;
想直接拿现成系统二开,不想从0开发的人;
有获客/运营能力,但缺技术产品的团队;
软件公司/工作室,可作为现成项目底座继续开发。
合作方式
A:整套项目独家转让
完成交易后按照约定交接源码、数据库、后台及部署资料等,不是单纯多份源码授权。
B:基于现有系统二开
如果你正好有类似项目需求,但不想直接买整套,也可以把需求发给我,可以在现有系统基础上修改。
有兴趣可以私聊我,先看线上 Demo 和后台,再决定是否继续谈
收起阅读 »#需求# uniapp亟待优化
-
2020年chromium 83之后不再兼容4.4,app端js engine一直停在基于83的版本。uniapp应该提供“现代”的js引擎,让开发者能按需选择
-
更灵活的针对vue3 patch的方式,让开发者能按需使用,或者定期跟进vue 3 大版本
-
vite vue3分包下打包到app,依然还是将js合到一个大的app-service.js中,分包的css也不支持手动配置优化。应该能按照vite的打包配置,拆分(js/css)到各个chunk,应用启动时只加载首页资源,同时可以利用类似link prefetch/module preload的特性,让开发者能按需预载其它页面需要的(静态)资源。以达到最高的加载效率
-
uniapp目前还在用着老旧的vite/sass版本,但在cli项目中手动更新至vite 7/sass 1.100 其实是能正常运行的,只是组件库基本都因为过时方式导致过多sass警告信息
。。。
-
2020年chromium 83之后不再兼容4.4,app端js engine一直停在基于83的版本。uniapp应该提供“现代”的js引擎,让开发者能按需选择
-
更灵活的针对vue3 patch的方式,让开发者能按需使用,或者定期跟进vue 3 大版本
-
vite vue3分包下打包到app,依然还是将js合到一个大的app-service.js中,分包的css也不支持手动配置优化。应该能按照vite的打包配置,拆分(js/css)到各个chunk,应用启动时只加载首页资源,同时可以利用类似link prefetch/module preload的特性,让开发者能按需预载其它页面需要的(静态)资源。以达到最高的加载效率
-
uniapp目前还在用着老旧的vite/sass版本,但在cli项目中手动更新至vite 7/sass 1.100 其实是能正常运行的,只是组件库基本都因为过时方式导致过多sass警告信息
。。。
收起阅读 »如何实现上下切换的页面间跳转动画
使用pageTransition函数实现页面转场效果。通过PageTransitionEnter和PageTransitionExit指定页面进入和退出的动画效果。将slide属性设置为SlideEffect.Bottom,页面入场时从下方滑入,出场时滑出到下方,从而实现上下切换效果。参考代码如下:
// Index.ets
@Entry
@Component
struct PageTransition1 {
pageInfos: NavPathStack = new NavPathStack();
build() {
Stack({ alignContent: Alignment.Bottom }) {
Navigation(this.pageInfos) {
Image($r('app.media.ic_banner01')).width('100%').height(200) // The image is stored in the media folder
}
}.height('100%').width('100%')
}
pageTransition() {
PageTransitionEnter({ duration: 500, curve: Curve.Linear }).slide(SlideEffect.Bottom)
PageTransitionExit({ duration: 500, curve: Curve.Ease }).slide(SlideEffect.Bottom)
}
}
// Page1.ets
@Entry
@Component
struct PageTransition2 {
pageInfos: NavPathStack = new NavPathStack();
build() {
Stack({ alignContent: Alignment.Bottom }) {
Navigation(this.pageInfos) {
Image($r('app.media.ic_banner02')).width('100%').height(200) // The image is stored in the media folder
}
}.height('100%').width('100%')
}
pageTransition() {
PageTransitionEnter({ duration: 500, curve: Curve.Linear }).slide(SlideEffect.Bottom)
PageTransitionExit({ duration: 500, curve: Curve.Ease }).slide(SlideEffect.Bottom)
}
}
https://coub.com/stories/5089937-coventry
https://coub.com/stories/5089936-city
https://coub.com/stories/5089935-derby
https://coub.com/stories/5089934-chester
https://coub.com/stories/5089932-uclan
https://coub.com/stories/5089930-cardiff
https://coub.com/stories/5089928-uwic
https://coub.com/stories/5089925-certificate-uc
https://coub.com/stories/5089923-degree-cantab
https://coub.com/stories/5089921-graduation-ucb
https://coub.com/stories/5089918-brunel
https://coub.com/stories/5089916-bristol
https://coub.com/stories/5089914-brighton
https://coub.com/stories/5089911-bradford
https://coub.com/stories/5089909-bpp
https://coub.com/stories/5089906-bu
https://coub.com/stories/5089904-bolton
https://coub.com/stories/5089903-ucb
https://coub.com/stories/5089902-bcu
https://coub.com/stories/5089901-uob
https://coub.com/stories/5089900-bbk
https://coub.com/stories/5089899-bedfordhire
https://coub.com/stories/5089898-1-1bath
https://coub.com/stories/5089897-bangor
https://coub.com/stories/5089896-aston
https://coub.com/stories/5089895-copyual
https://coub.com/stories/5089894-aub
https://coub.com/stories/5089893-aber
https://coub.com/stories/5089892-abertay
https://coub.com/stories/5089890-au
https://coub.com/stories/5089888-langara
https://coub.com/stories/5089885-uofs
https://coub.com/stories/5089883-uofr
https://coub.com/stories/5089881-mcgill
https://coub.com/stories/5089878-concordia
https://coub.com/stories/5089876-mcmaste
https://coub.com/stories/5089874-yu
https://coub.com/stories/5089871-wlu
https://coub.com/stories/5089868-windsor
https://coub.com/stories/5089866-certificate-uwo
https://coub.com/stories/5089864-degree-waterloo
https://coub.com/stories/5089861-graduation-uoft
https://coub.com/stories/5089859-cbu
https://coub.com/stories/5089857-uoit
https://coub.com/stories/5089855-uottawa
https://coub.com/stories/5089852-uofg
https://coub.com/stories/5089850-carleton
https://coub.com/stories/5089847-brock
https://coub.com/stories/5089846-msvu
https://coub.com/stories/5089845-dalhousie
https://coub.com/stories/5089835
https://coub.com/stories/5089838-degree-mun
https://coub.com/stories/5089837-graduation-tru
https://coub.com/stories/5089836-ubc
https://coub.com/stories/5089835
https://coub.com/stories/5089834-bu
https://coub.com/stories/5089833-vancouver
https://coub.com/stories/5089832-ufv
https://coub.com/stories/5089831-unbc
https://coub.com/stories/5089829-ubc
https://coub.com/stories/5089828-sfu
https://coub.com/stories/5089827-kpu
https://coub.com/stories/5089826-macewan
https://coub.com/stories/5089825-uofl
https://coub.com/stories/5089824-certificate-uc
https://coub.com/stories/5089823-degree-ua
https://coub.com/stories/5089822-graduation-ua
https://coub.com/stories/5089821-asu
https://coub.com/stories/5089820-uh
https://coub.com/stories/5089819-ucsd
https://coub.com/stories/5089818-stanford
https://coub.com/stories/5089817-fsu
https://coub.com/stories/5089816-ohio
https://coub.com/stories/5089815-ku
https://coub.com/stories/5089814-missouri
https://coub.com/stories/5089813-iowa
https://coub.com/stories/5089812-twincities
https://coub.com/stories/5089811-mississippi
https://coub.com/stories/5089810-um
https://coub.com/stories/5089808-temple
https://coub.com/stories/5089809-gwu
https://coub.com/stories/5089807-ucsb
https://coub.com/stories/5089806-uci
https://coub.com/stories/5089805-brandeis
https://coub.com/stories/5089804-wfu
https://coub.com/stories/5089803-tufts
https://coub.com/stories/5089802-uva
https://coub.com/stories/5089801-ucb
https://coub.com/stories/5089800-vandy
https://coub.com/stories/5089799-emory
https://coub.com/stories/5089798-rice
https://coub.com/stories/5089797-duke
https://coub.com/stories/5089796-princeton
https://coub.com/stories/5089795-pace
https://coub.com/stories/5089794-utk
https://coub.com/stories/5089793-mit
https://coub.com/stories/5089792-msu
https://coub.com/stories/5089791-psu
https://coub.com/stories/5089790-usc
https://coub.com/stories/5089789-seattle
https://coub.com/stories/5089788-nyu
https://coub.com/stories/5089787-wsu
https://coub.com/stories/5089786-ucla
https://coub.com/stories/5089785-osu
https://coub.com/stories/5089784-uts
https://coub.com/stories/5089783-unsw
https://coub.com/stories/5089782-uow
https://coub.com/stories/5089781-usyd
https://coub.com/stories/5089780-uon
https://coub.com/stories/5089779-une
https://coub.com/stories/5089778-uws
https://coub.com/stories/5089777-scu
https://coub.com/stories/5089776-monash
https://coub.com/stories/5089775-sut
https://coub.com/stories/5089774-latrobe
https://coub.com/stories/5089773-unimelb
https://coub.com/stories/5089772-rmit
https://coub.com/stories/5089771-deakin
https://coub.com/stories/5089770-swinburne
https://coub.com/stories/5089769-utas
https://coub.com/stories/5089768-victoria
https://coub.com/stories/5089767-adelaide
https://coub.com/stories/5089766-unisa
https://coub.com/stories/5089765-uc
https://coub.com/stories/5089764-acu
https://coub.com/stories/5089763-anu
https://coub.com/stories/5089762-uc
https://coub.com/stories/5089761-cdu
https://coub.com/stories/5089760-ecu
https://coub.com/stories/5089759-murdoch
https://coub.com/stories/5089758-uwa
https://coub.com/stories/5089757-curtin
https://coub.com/stories/5089756-utas
https://coub.com/stories/5089755-usq
https://coub.com/stories/5089754-bond
https://coub.com/stories/5089753-jcu
https://coub.com/stories/5089752-qut
https://coub.com/stories/5089751-cqu
https://coub.com/stories/5089750-jcu
https://coub.com/stories/5089749-griffith
https://coub.com/stories/5089748-uq
使用pageTransition函数实现页面转场效果。通过PageTransitionEnter和PageTransitionExit指定页面进入和退出的动画效果。将slide属性设置为SlideEffect.Bottom,页面入场时从下方滑入,出场时滑出到下方,从而实现上下切换效果。参考代码如下:
// Index.ets
@Entry
@Component
struct PageTransition1 {
pageInfos: NavPathStack = new NavPathStack();
build() {
Stack({ alignContent: Alignment.Bottom }) {
Navigation(this.pageInfos) {
Image($r('app.media.ic_banner01')).width('100%').height(200) // The image is stored in the media folder
}
}.height('100%').width('100%')
}
pageTransition() {
PageTransitionEnter({ duration: 500, curve: Curve.Linear }).slide(SlideEffect.Bottom)
PageTransitionExit({ duration: 500, curve: Curve.Ease }).slide(SlideEffect.Bottom)
}
}
// Page1.ets
@Entry
@Component
struct PageTransition2 {
pageInfos: NavPathStack = new NavPathStack();
build() {
Stack({ alignContent: Alignment.Bottom }) {
Navigation(this.pageInfos) {
Image($r('app.media.ic_banner02')).width('100%').height(200) // The image is stored in the media folder
}
}.height('100%').width('100%')
}
pageTransition() {
PageTransitionEnter({ duration: 500, curve: Curve.Linear }).slide(SlideEffect.Bottom)
PageTransitionExit({ duration: 500, curve: Curve.Ease }).slide(SlideEffect.Bottom)
}
}
https://coub.com/stories/5089937-coventry
https://coub.com/stories/5089936-city
https://coub.com/stories/5089935-derby
https://coub.com/stories/5089934-chester
https://coub.com/stories/5089932-uclan
https://coub.com/stories/5089930-cardiff
https://coub.com/stories/5089928-uwic
https://coub.com/stories/5089925-certificate-uc
https://coub.com/stories/5089923-degree-cantab
https://coub.com/stories/5089921-graduation-ucb
https://coub.com/stories/5089918-brunel
https://coub.com/stories/5089916-bristol
https://coub.com/stories/5089914-brighton
https://coub.com/stories/5089911-bradford
https://coub.com/stories/5089909-bpp
https://coub.com/stories/5089906-bu
https://coub.com/stories/5089904-bolton
https://coub.com/stories/5089903-ucb
https://coub.com/stories/5089902-bcu
https://coub.com/stories/5089901-uob
https://coub.com/stories/5089900-bbk
https://coub.com/stories/5089899-bedfordhire
https://coub.com/stories/5089898-1-1bath
https://coub.com/stories/5089897-bangor
https://coub.com/stories/5089896-aston
https://coub.com/stories/5089895-copyual
https://coub.com/stories/5089894-aub
https://coub.com/stories/5089893-aber
https://coub.com/stories/5089892-abertay
https://coub.com/stories/5089890-au
https://coub.com/stories/5089888-langara
https://coub.com/stories/5089885-uofs
https://coub.com/stories/5089883-uofr
https://coub.com/stories/5089881-mcgill
https://coub.com/stories/5089878-concordia
https://coub.com/stories/5089876-mcmaste
https://coub.com/stories/5089874-yu
https://coub.com/stories/5089871-wlu
https://coub.com/stories/5089868-windsor
https://coub.com/stories/5089866-certificate-uwo
https://coub.com/stories/5089864-degree-waterloo
https://coub.com/stories/5089861-graduation-uoft
https://coub.com/stories/5089859-cbu
https://coub.com/stories/5089857-uoit
https://coub.com/stories/5089855-uottawa
https://coub.com/stories/5089852-uofg
https://coub.com/stories/5089850-carleton
https://coub.com/stories/5089847-brock
https://coub.com/stories/5089846-msvu
https://coub.com/stories/5089845-dalhousie
https://coub.com/stories/5089835
https://coub.com/stories/5089838-degree-mun
https://coub.com/stories/5089837-graduation-tru
https://coub.com/stories/5089836-ubc
https://coub.com/stories/5089835
https://coub.com/stories/5089834-bu
https://coub.com/stories/5089833-vancouver
https://coub.com/stories/5089832-ufv
https://coub.com/stories/5089831-unbc
https://coub.com/stories/5089829-ubc
https://coub.com/stories/5089828-sfu
https://coub.com/stories/5089827-kpu
https://coub.com/stories/5089826-macewan
https://coub.com/stories/5089825-uofl
https://coub.com/stories/5089824-certificate-uc
https://coub.com/stories/5089823-degree-ua
https://coub.com/stories/5089822-graduation-ua
https://coub.com/stories/5089821-asu
https://coub.com/stories/5089820-uh
https://coub.com/stories/5089819-ucsd
https://coub.com/stories/5089818-stanford
https://coub.com/stories/5089817-fsu
https://coub.com/stories/5089816-ohio
https://coub.com/stories/5089815-ku
https://coub.com/stories/5089814-missouri
https://coub.com/stories/5089813-iowa
https://coub.com/stories/5089812-twincities
https://coub.com/stories/5089811-mississippi
https://coub.com/stories/5089810-um
https://coub.com/stories/5089808-temple
https://coub.com/stories/5089809-gwu
https://coub.com/stories/5089807-ucsb
https://coub.com/stories/5089806-uci
https://coub.com/stories/5089805-brandeis
https://coub.com/stories/5089804-wfu
https://coub.com/stories/5089803-tufts
https://coub.com/stories/5089802-uva
https://coub.com/stories/5089801-ucb
https://coub.com/stories/5089800-vandy
https://coub.com/stories/5089799-emory
https://coub.com/stories/5089798-rice
https://coub.com/stories/5089797-duke
https://coub.com/stories/5089796-princeton
https://coub.com/stories/5089795-pace
https://coub.com/stories/5089794-utk
https://coub.com/stories/5089793-mit
https://coub.com/stories/5089792-msu
https://coub.com/stories/5089791-psu
https://coub.com/stories/5089790-usc
https://coub.com/stories/5089789-seattle
https://coub.com/stories/5089788-nyu
https://coub.com/stories/5089787-wsu
https://coub.com/stories/5089786-ucla
https://coub.com/stories/5089785-osu
https://coub.com/stories/5089784-uts
https://coub.com/stories/5089783-unsw
https://coub.com/stories/5089782-uow
https://coub.com/stories/5089781-usyd
https://coub.com/stories/5089780-uon
https://coub.com/stories/5089779-une
https://coub.com/stories/5089778-uws
https://coub.com/stories/5089777-scu
https://coub.com/stories/5089776-monash
https://coub.com/stories/5089775-sut
https://coub.com/stories/5089774-latrobe
https://coub.com/stories/5089773-unimelb
https://coub.com/stories/5089772-rmit
https://coub.com/stories/5089771-deakin
https://coub.com/stories/5089770-swinburne
https://coub.com/stories/5089769-utas
https://coub.com/stories/5089768-victoria
https://coub.com/stories/5089767-adelaide
https://coub.com/stories/5089766-unisa
https://coub.com/stories/5089765-uc
https://coub.com/stories/5089764-acu
https://coub.com/stories/5089763-anu
https://coub.com/stories/5089762-uc
https://coub.com/stories/5089761-cdu
https://coub.com/stories/5089760-ecu
https://coub.com/stories/5089759-murdoch
https://coub.com/stories/5089758-uwa
https://coub.com/stories/5089757-curtin
https://coub.com/stories/5089756-utas
https://coub.com/stories/5089755-usq
https://coub.com/stories/5089754-bond
https://coub.com/stories/5089753-jcu
https://coub.com/stories/5089752-qut
https://coub.com/stories/5089751-cqu
https://coub.com/stories/5089750-jcu
https://coub.com/stories/5089749-griffith
https://coub.com/stories/5089748-uq
开发自定义键盘,HarmonyOS中有没有长按按键子项生成气泡效果的API
问题现象
按键子项生成气泡效果用什么组件实现好?
效果预览
点击放大
背景知识
使用自定义键盘实现官网示例代码进行自定义键盘调试,然后用LongPressGesture增加长按手势事件,最后使用Popup控制为组件绑定Popup气泡,使用Popup气泡,如果按键处于边缘位置,组件会自动偏移,完整的显示在屏幕中间。
解决方案
自定义键盘没有直接长按生成气泡的接口,需要自定义长按逻辑,手指在长按小键盘组件时,使用Popup生成一个气泡,在气泡生成之后,使用onTouch获取手指位置并且根据气泡组件的位置和横向大小,计算当前手指划过的距离从而标记手指当前选择的内容。
代码实现思路如下:
创建变量控制气泡显隐样式以及气泡在边缘的位置:
@State customPopup: boolean = false;
@State indexNum: number = 0;
// 手指位置1、2、3、4(长按气泡中显示的内容数量)四个参数,分别代指长按后出现的四个位置,参数值与实际业务有关。
@State fingerRect: string = '1';
// 组件宽度
@State widthRect: number = 0;
// 键盘宽度
@State inputWidthRect: number = 0;
// 判断是否在偏右侧位置
@State isRight: boolean = false;
增加LongPressGesture长按控制手势,在onAction事件中增加逻辑,让气泡仅显示在指定按键上方;长按手势默认最短长按时间为500毫秒,可配置duration参数控制最短长按时长:
.gesture(
// 绑定可以重复触发的LongPressGesture
LongPressGesture({ repeat: true })
.onAction(() => {
for (let i = 0; i < EnglishKeyboardData[0].length; i++) {
if (item.text === EnglishKeyboardData[0][i].text) {
this.indexNum = i;
break;
}
}
this.customPopup = true;
})
.onActionEnd(() => {
this.customPopup = !this.customPopup;
})
)
增加bindPopup气泡控制,设置placement: Placement.TopRight,让气泡显示在按钮上方,通过this.popupBuilder(item)给自定义气泡组件传参然后自定义气泡内容:
.bindPopup(this.customPopup && EnglishKeyboardData[0].indexOf(item) === this.indexNum, {
// CustomPopupOptions类型气泡的内容
builder: this.popupBuilder(item),
placement: Placement.TopLeft,
targetSpace: '15vp',
enableArrow: false,
onStateChange: (e) => {
if (!e.isVisible) {
this.customPopup = false;
}
}
})
@Builder
popupBuilder(item: Menu) {
Row() {
ForEach([1, 2, 3, 4], (num: number, index: number) => {
if (this.fingerRect === index + 1 + '') {
Row() {
Text(item.text + '' + num)
.fontColor(Color.White);
}
.globalFancy()
.backgroundColor(Color.Blue)
.justifyContent(FlexAlign.Center);
} else {
Row() {
Text(item.text + '' + num);
}
.globalFancy()
.justifyContent(FlexAlign.Center);
}
}, (num: number) => JSON.stringify(num));
}
.height(50)
.width(150)
.padding(5)
.justifyContent(FlexAlign.SpaceAround)
.onAreaChange((oldVal, newVal) => {
// 如果此时键盘在偏右侧位置,需要重新计算位置
this.isRight = this.inputWidthRect <= Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}
使用onAreaChange监听新生成的气泡组件的宽度,再使用onTouch事件获取当前手指的坐标,然后根据手指的坐标和气泡组件的宽度,获取当前手指在气泡组件中的位置:
.onTouch((event) => {
// 获取当前手指的坐标(相对于父容器,与组件rect的坐标系一致)
const fingerX = event.touches[0].x;
// 靠近右边屏幕时,需要特殊处理。
this.fingerRect =
this.isRight ? Math.ceil(((fingerX + event.touches[0].displayX) - 230) / this.widthRect) + '' :
Math.ceil(fingerX / this.widthRect) + '';
this.fingerRect =
(Number(this.fingerRect) <= 1) ? '1' : Number(this.fingerRect) >= 4 ? '4' : this.fingerRect;
})
.onAreaChange((oldVal, newVal) => {
this.widthRect = Number(newVal.width);
});
参考官网自定义键盘实现,将demo中的NumberKeyboard文件替换为以下示例代码:
// 该场景代码基于自定义键盘实现官网示例代码https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-custom-keyboard#section1680905412256进行调试;
// 修改demo中NumberKeyboard文件如下
@Component
export struct NumberKeyboard {
@State customPopup: boolean = false;
@State indexNum: number = 0;
// 手指位置1、2、3、4(长按气泡中显示的内容数量)四个参数,分别代指长按后出现的四个位置,参数值与实际业务有关。
@State fingerRect: string = '1';
// 组件宽度
@State widthRect: number = 0;
// 键盘宽度
@State inputWidthRect: number = 0;
// 判断是否在偏右侧位置
@State isRight: boolean = false;
build() {
Row() {
Scroll() {
Column() {
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[0], (item: Menu) => {
EnglishButton({ item: item })
.gesture(
// 绑定可以重复触发的LongPressGesture
LongPressGesture({ repeat: true })
.onAction(() => {
for (let i = 0; i < EnglishKeyboardData[0].length; i++) {
if (item.text === EnglishKeyboardData[0][i].text) {
this.indexNum = i;
break;
}
}
this.customPopup = true;
})
.onActionEnd(() => {
this.customPopup = !this.customPopup;
})
)
.bindPopup(this.customPopup && EnglishKeyboardData[0].indexOf(item) === this.indexNum, {
// CustomPopupOptions类型气泡的内容
builder: this.popupBuilder(item),
placement: Placement.TopLeft,
targetSpace: '15vp',
enableArrow: false,
onStateChange: (e) => {
if (!e.isVisible) {
this.customPopup = false;
}
}
})
.onTouch((event) => {
// 获取当前手指的坐标(相对于父容器,与组件rect的坐标系一致)
const fingerX = event.touches[0].x;
// 靠近右边屏幕时,需要特殊处理。
this.fingerRect =
this.isRight ? Math.ceil(((fingerX + event.touches[0].displayX) - 230) / this.widthRect) + '' :
Math.ceil(fingerX / this.widthRect) + '';
this.fingerRect =
(Number(this.fingerRect) <= 1) ? '1' : Number(this.fingerRect) >= 4 ? '4' : this.fingerRect;
})
.onAreaChange((oldVal, newVal) => {
this.widthRect = Number(newVal.width);
});
}, (item: Menu) => JSON.stringify(item));
};
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[1], (item: Menu) => {
EnglishButton({ item: item });
}, (item: Menu) => JSON.stringify(item));
}
.padding({
left: 18,
right: 18,
top: 11
});
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[2], (item: Menu) => {
EnglishButton({ item: item });
}, (item: Menu) => JSON.stringify(item));
}
.padding({ top: 11 });
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[3], (item: Menu) => {
EnglishButton({ item: item });
}, (item: Menu) => JSON.stringify(item));
}
.padding({ top: 11 });
}
.onAreaChange((oldVal, newVal) => {
// 键盘宽度
this.inputWidthRect = Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off);
};
}
@Builder
popupBuilder(item: Menu) {
Row() {
ForEach([1, 2, 3, 4], (num: number, index: number) => {
if (this.fingerRect === index + 1 + '') {
Row() {
Text(item.text + '' + num)
.fontColor(Color.White);
}
.globalFancy()
.backgroundColor(Color.Blue)
.justifyContent(FlexAlign.Center);
} else {
Row() {
Text(item.text + '' + num);
}
.globalFancy()
.justifyContent(FlexAlign.Center);
}
}, (num: number) => JSON.stringify(num));
}
.height(50)
.width(150)
.padding(5)
.justifyContent(FlexAlign.SpaceAround)
.onAreaChange((oldVal, newVal) => {
// 如果此时键盘在偏右侧位置,需要重新计算位置
this.isRight = this.inputWidthRect <= Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}
}
@Styles
function globalFancy() {
.borderRadius(10)
.width('25%')
.height('100%');
}
// [Start EnglishButton_start]
@Component
struct EnglishButton {
@Consume inputText: string;
// [StartExclude EnglishButton_start]
@Prop item: Menu;
@Consume keyboardController: KeyboardController;
getEnglishText(item: Menu): string | Resource {
if (this.keyboardController.isUpperCase && item.upperText) {
return item.upperText;
} else {
return item.text;
}
}
// [EndExclude EnglishButton_start]
build() {
Button({ type: ButtonType.Normal }) {
if (typeof this.getEnglishText(this.item) === 'string' ||
(this.getEnglishText(this.item) as Resource).type !== Constants.RESOURCE_TYPE_MEDIA) {
Text(this.getEnglishText(this.item));
} else {
Image(this.getEnglishText(this.item))
.width(Constants.KEYBOARD_BUTTON_FONTSIZE_18)
.height(Constants.KEYBOARD_BUTTON_FONTSIZE_18);
}
}
.fontColor(Color.Black)
.backgroundColor(this.item.backgroundColor)
.borderRadius(Constants.KEYBOARD_BUTTON_RADIUS)
.fontSize(Constants.KEYBOARD_BUTTON_FONTSIZE_18)
.padding(0)
.width(this.item.width)
.height(this.item.height)
.onClick(() => {
this.inputText = this.keyboardController.onInput(this.getEnglishText(this.item));
});
}
}
// [End EnglishButton_start]
https://coub.com/view/4b8l5y
https://coub.com/view/4b8l5w
https://coub.com/view/4b8l5v
https://coub.com/view/4b8l5t
https://coub.com/view/4b8l5r
https://coub.com/view/4b8l5p
https://coub.com/view/4b8l5l
https://coub.com/view/4b8l5h
https://coub.com/view/4b8l5e
https://coub.com/view/4b8l5c
https://coub.com/view/4b8l5b
https://coub.com/view/4b8l5a
https://coub.com/view/4b8l58
https://coub.com/view/4b8l57
https://coub.com/view/4b8l54
https://coub.com/view/4b8l51
https://coub.com/view/4b8l50
https://coub.com/view/4b8l4z
https://coub.com/view/4b8l4w
https://coub.com/view/4b8l4t
https://coub.com/view/4b8l4r
https://coub.com/view/4b8l4o
https://coub.com/view/4b8l4l
https://coub.com/view/4b8l4j
https://coub.com/view/4b8l4f
https://coub.com/view/4b8l4d
https://coub.com/view/4b8l4b
https://coub.com/view/4b8l47
https://coub.com/view/4b8l46
https://coub.com/view/4b8l41
https://coub.com/view/4b8l3y
https://coub.com/view/4b8l3v
https://coub.com/view/4b8l3s
https://coub.com/view/4b8l3r
https://coub.com/view/4b8l3q
https://coub.com/view/4b8l3h
https://coub.com/view/4b8l3f
https://coub.com/view/4b8l3c
https://coub.com/view/4b8l39
https://coub.com/view/4b8l36
https://coub.com/view/4b8l30
https://coub.com/view/4b8l2y
https://coub.com/view/4b8l2v
https://coub.com/view/4b8l2t
https://coub.com/view/4b8l2q
https://coub.com/view/4b8l2n
https://coub.com/view/4b8l2j
https://coub.com/view/4b8l2f
https://coub.com/view/4b8l2c
https://coub.com/view/4b8l2b
https://coub.com/view/4b8l28
https://coub.com/view/4b8l27
https://coub.com/view/4b8l23
https://coub.com/view/4b8l20
https://coub.com/view/4b8l1v
https://coub.com/view/4b8l16
https://coub.com/view/4b8l12
https://coub.com/view/4b8l0w
https://coub.com/view/4b8l0o
https://coub.com/view/4b8l0j
https://coub.com/view/4b8l0g
https://coub.com/view/4b8l0f
https://coub.com/view/4b8l0d
https://coub.com/view/4b8l08
https://coub.com/view/4b8l06
https://coub.com/view/4b8l04
https://coub.com/view/4b8l01
https://coub.com/view/4b8kzx
https://coub.com/view/4b8kzv
https://coub.com/view/4b8kzr
https://coub.com/view/4b8kzl
https://coub.com/view/4b8kzi
https://coub.com/view/4b8kzg
https://coub.com/view/4b8kze
https://coub.com/view/4b8kzc
https://coub.com/view/4b8kpg
https://coub.com/view/4b8kpf
https://coub.com/view/4b8kpe
https://coub.com/view/4b8kpc
https://coub.com/view/4b8kpa
https://coub.com/view/4b8kp7
https://coub.com/view/4b8kp5
https://coub.com/view/4b8kp1
https://coub.com/view/4b8kox
https://coub.com/view/4b8kov
https://coub.com/view/4b8kot
https://coub.com/view/4b8kor
https://coub.com/view/4b8kop
https://coub.com/view/4b8kol
https://coub.com/view/4b8koj
https://coub.com/view/4b8kog
https://coub.com/view/4b8koe
https://coub.com/view/4b8koc
https://coub.com/view/4b8knx
https://coub.com/view/4b8knw
https://coub.com/view/4b8knv
https://coub.com/view/4b8knu
https://coub.com/view/4b8knt
https://coub.com/view/4b8kns
https://coub.com/view/4b8knq
https://coub.com/view/4b8knp
https://coub.com/view/4b8kno
https://coub.com/view/4b8knn
https://coub.com/view/4b8knm
https://coub.com/view/4b8knl
https://coub.com/view/4b8knk
https://coub.com/view/4b8knj
https://coub.com/view/4b8kni
https://coub.com/view/4b8knd
https://coub.com/view/4b8knb
https://coub.com/view/4b8kna
https://coub.com/view/4b8kn9
https://coub.com/view/4b8kn8
问题现象
按键子项生成气泡效果用什么组件实现好?
效果预览
点击放大
背景知识
使用自定义键盘实现官网示例代码进行自定义键盘调试,然后用LongPressGesture增加长按手势事件,最后使用Popup控制为组件绑定Popup气泡,使用Popup气泡,如果按键处于边缘位置,组件会自动偏移,完整的显示在屏幕中间。
解决方案
自定义键盘没有直接长按生成气泡的接口,需要自定义长按逻辑,手指在长按小键盘组件时,使用Popup生成一个气泡,在气泡生成之后,使用onTouch获取手指位置并且根据气泡组件的位置和横向大小,计算当前手指划过的距离从而标记手指当前选择的内容。
代码实现思路如下:
创建变量控制气泡显隐样式以及气泡在边缘的位置:
@State customPopup: boolean = false;
@State indexNum: number = 0;
// 手指位置1、2、3、4(长按气泡中显示的内容数量)四个参数,分别代指长按后出现的四个位置,参数值与实际业务有关。
@State fingerRect: string = '1';
// 组件宽度
@State widthRect: number = 0;
// 键盘宽度
@State inputWidthRect: number = 0;
// 判断是否在偏右侧位置
@State isRight: boolean = false;
增加LongPressGesture长按控制手势,在onAction事件中增加逻辑,让气泡仅显示在指定按键上方;长按手势默认最短长按时间为500毫秒,可配置duration参数控制最短长按时长:
.gesture(
// 绑定可以重复触发的LongPressGesture
LongPressGesture({ repeat: true })
.onAction(() => {
for (let i = 0; i < EnglishKeyboardData[0].length; i++) {
if (item.text === EnglishKeyboardData[0][i].text) {
this.indexNum = i;
break;
}
}
this.customPopup = true;
})
.onActionEnd(() => {
this.customPopup = !this.customPopup;
})
)
增加bindPopup气泡控制,设置placement: Placement.TopRight,让气泡显示在按钮上方,通过this.popupBuilder(item)给自定义气泡组件传参然后自定义气泡内容:
.bindPopup(this.customPopup && EnglishKeyboardData[0].indexOf(item) === this.indexNum, {
// CustomPopupOptions类型气泡的内容
builder: this.popupBuilder(item),
placement: Placement.TopLeft,
targetSpace: '15vp',
enableArrow: false,
onStateChange: (e) => {
if (!e.isVisible) {
this.customPopup = false;
}
}
})
@Builder
popupBuilder(item: Menu) {
Row() {
ForEach([1, 2, 3, 4], (num: number, index: number) => {
if (this.fingerRect === index + 1 + '') {
Row() {
Text(item.text + '' + num)
.fontColor(Color.White);
}
.globalFancy()
.backgroundColor(Color.Blue)
.justifyContent(FlexAlign.Center);
} else {
Row() {
Text(item.text + '' + num);
}
.globalFancy()
.justifyContent(FlexAlign.Center);
}
}, (num: number) => JSON.stringify(num));
}
.height(50)
.width(150)
.padding(5)
.justifyContent(FlexAlign.SpaceAround)
.onAreaChange((oldVal, newVal) => {
// 如果此时键盘在偏右侧位置,需要重新计算位置
this.isRight = this.inputWidthRect <= Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}
使用onAreaChange监听新生成的气泡组件的宽度,再使用onTouch事件获取当前手指的坐标,然后根据手指的坐标和气泡组件的宽度,获取当前手指在气泡组件中的位置:
.onTouch((event) => {
// 获取当前手指的坐标(相对于父容器,与组件rect的坐标系一致)
const fingerX = event.touches[0].x;
// 靠近右边屏幕时,需要特殊处理。
this.fingerRect =
this.isRight ? Math.ceil(((fingerX + event.touches[0].displayX) - 230) / this.widthRect) + '' :
Math.ceil(fingerX / this.widthRect) + '';
this.fingerRect =
(Number(this.fingerRect) <= 1) ? '1' : Number(this.fingerRect) >= 4 ? '4' : this.fingerRect;
})
.onAreaChange((oldVal, newVal) => {
this.widthRect = Number(newVal.width);
});
参考官网自定义键盘实现,将demo中的NumberKeyboard文件替换为以下示例代码:
// 该场景代码基于自定义键盘实现官网示例代码https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-custom-keyboard#section1680905412256进行调试;
// 修改demo中NumberKeyboard文件如下
@Component
export struct NumberKeyboard {
@State customPopup: boolean = false;
@State indexNum: number = 0;
// 手指位置1、2、3、4(长按气泡中显示的内容数量)四个参数,分别代指长按后出现的四个位置,参数值与实际业务有关。
@State fingerRect: string = '1';
// 组件宽度
@State widthRect: number = 0;
// 键盘宽度
@State inputWidthRect: number = 0;
// 判断是否在偏右侧位置
@State isRight: boolean = false;
build() {
Row() {
Scroll() {
Column() {
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[0], (item: Menu) => {
EnglishButton({ item: item })
.gesture(
// 绑定可以重复触发的LongPressGesture
LongPressGesture({ repeat: true })
.onAction(() => {
for (let i = 0; i < EnglishKeyboardData[0].length; i++) {
if (item.text === EnglishKeyboardData[0][i].text) {
this.indexNum = i;
break;
}
}
this.customPopup = true;
})
.onActionEnd(() => {
this.customPopup = !this.customPopup;
})
)
.bindPopup(this.customPopup && EnglishKeyboardData[0].indexOf(item) === this.indexNum, {
// CustomPopupOptions类型气泡的内容
builder: this.popupBuilder(item),
placement: Placement.TopLeft,
targetSpace: '15vp',
enableArrow: false,
onStateChange: (e) => {
if (!e.isVisible) {
this.customPopup = false;
}
}
})
.onTouch((event) => {
// 获取当前手指的坐标(相对于父容器,与组件rect的坐标系一致)
const fingerX = event.touches[0].x;
// 靠近右边屏幕时,需要特殊处理。
this.fingerRect =
this.isRight ? Math.ceil(((fingerX + event.touches[0].displayX) - 230) / this.widthRect) + '' :
Math.ceil(fingerX / this.widthRect) + '';
this.fingerRect =
(Number(this.fingerRect) <= 1) ? '1' : Number(this.fingerRect) >= 4 ? '4' : this.fingerRect;
})
.onAreaChange((oldVal, newVal) => {
this.widthRect = Number(newVal.width);
});
}, (item: Menu) => JSON.stringify(item));
};
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[1], (item: Menu) => {
EnglishButton({ item: item });
}, (item: Menu) => JSON.stringify(item));
}
.padding({
left: 18,
right: 18,
top: 11
});
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[2], (item: Menu) => {
EnglishButton({ item: item });
}, (item: Menu) => JSON.stringify(item));
}
.padding({ top: 11 });
Row({ space: Constants.ENGLISH_KEYBOARD_BUTTON_SPACE }) {
ForEach(EnglishKeyboardData[3], (item: Menu) => {
EnglishButton({ item: item });
}, (item: Menu) => JSON.stringify(item));
}
.padding({ top: 11 });
}
.onAreaChange((oldVal, newVal) => {
// 键盘宽度
this.inputWidthRect = Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off);
};
}
@Builder
popupBuilder(item: Menu) {
Row() {
ForEach([1, 2, 3, 4], (num: number, index: number) => {
if (this.fingerRect === index + 1 + '') {
Row() {
Text(item.text + '' + num)
.fontColor(Color.White);
}
.globalFancy()
.backgroundColor(Color.Blue)
.justifyContent(FlexAlign.Center);
} else {
Row() {
Text(item.text + '' + num);
}
.globalFancy()
.justifyContent(FlexAlign.Center);
}
}, (num: number) => JSON.stringify(num));
}
.height(50)
.width(150)
.padding(5)
.justifyContent(FlexAlign.SpaceAround)
.onAreaChange((oldVal, newVal) => {
// 如果此时键盘在偏右侧位置,需要重新计算位置
this.isRight = this.inputWidthRect <= Math.ceil(Number(newVal.globalPosition.x) + Number(newVal.width));
});
}
}
@Styles
function globalFancy() {
.borderRadius(10)
.width('25%')
.height('100%');
}
// [Start EnglishButton_start]
@Component
struct EnglishButton {
@Consume inputText: string;
// [StartExclude EnglishButton_start]
@Prop item: Menu;
@Consume keyboardController: KeyboardController;
getEnglishText(item: Menu): string | Resource {
if (this.keyboardController.isUpperCase && item.upperText) {
return item.upperText;
} else {
return item.text;
}
}
// [EndExclude EnglishButton_start]
build() {
Button({ type: ButtonType.Normal }) {
if (typeof this.getEnglishText(this.item) === 'string' ||
(this.getEnglishText(this.item) as Resource).type !== Constants.RESOURCE_TYPE_MEDIA) {
Text(this.getEnglishText(this.item));
} else {
Image(this.getEnglishText(this.item))
.width(Constants.KEYBOARD_BUTTON_FONTSIZE_18)
.height(Constants.KEYBOARD_BUTTON_FONTSIZE_18);
}
}
.fontColor(Color.Black)
.backgroundColor(this.item.backgroundColor)
.borderRadius(Constants.KEYBOARD_BUTTON_RADIUS)
.fontSize(Constants.KEYBOARD_BUTTON_FONTSIZE_18)
.padding(0)
.width(this.item.width)
.height(this.item.height)
.onClick(() => {
this.inputText = this.keyboardController.onInput(this.getEnglishText(this.item));
});
}
}
// [End EnglishButton_start]
https://coub.com/view/4b8l5y
https://coub.com/view/4b8l5w
https://coub.com/view/4b8l5v
https://coub.com/view/4b8l5t
https://coub.com/view/4b8l5r
https://coub.com/view/4b8l5p
https://coub.com/view/4b8l5l
https://coub.com/view/4b8l5h
https://coub.com/view/4b8l5e
https://coub.com/view/4b8l5c
https://coub.com/view/4b8l5b
https://coub.com/view/4b8l5a
https://coub.com/view/4b8l58
https://coub.com/view/4b8l57
https://coub.com/view/4b8l54
https://coub.com/view/4b8l51
https://coub.com/view/4b8l50
https://coub.com/view/4b8l4z
https://coub.com/view/4b8l4w
https://coub.com/view/4b8l4t
https://coub.com/view/4b8l4r
https://coub.com/view/4b8l4o
https://coub.com/view/4b8l4l
https://coub.com/view/4b8l4j
https://coub.com/view/4b8l4f
https://coub.com/view/4b8l4d
https://coub.com/view/4b8l4b
https://coub.com/view/4b8l47
https://coub.com/view/4b8l46
https://coub.com/view/4b8l41
https://coub.com/view/4b8l3y
https://coub.com/view/4b8l3v
https://coub.com/view/4b8l3s
https://coub.com/view/4b8l3r
https://coub.com/view/4b8l3q
https://coub.com/view/4b8l3h
https://coub.com/view/4b8l3f
https://coub.com/view/4b8l3c
https://coub.com/view/4b8l39
https://coub.com/view/4b8l36
https://coub.com/view/4b8l30
https://coub.com/view/4b8l2y
https://coub.com/view/4b8l2v
https://coub.com/view/4b8l2t
https://coub.com/view/4b8l2q
https://coub.com/view/4b8l2n
https://coub.com/view/4b8l2j
https://coub.com/view/4b8l2f
https://coub.com/view/4b8l2c
https://coub.com/view/4b8l2b
https://coub.com/view/4b8l28
https://coub.com/view/4b8l27
https://coub.com/view/4b8l23
https://coub.com/view/4b8l20
https://coub.com/view/4b8l1v
https://coub.com/view/4b8l16
https://coub.com/view/4b8l12
https://coub.com/view/4b8l0w
https://coub.com/view/4b8l0o
https://coub.com/view/4b8l0j
https://coub.com/view/4b8l0g
https://coub.com/view/4b8l0f
https://coub.com/view/4b8l0d
https://coub.com/view/4b8l08
https://coub.com/view/4b8l06
https://coub.com/view/4b8l04
https://coub.com/view/4b8l01
https://coub.com/view/4b8kzx
https://coub.com/view/4b8kzv
https://coub.com/view/4b8kzr
https://coub.com/view/4b8kzl
https://coub.com/view/4b8kzi
https://coub.com/view/4b8kzg
https://coub.com/view/4b8kze
https://coub.com/view/4b8kzc
https://coub.com/view/4b8kpg
https://coub.com/view/4b8kpf
https://coub.com/view/4b8kpe
https://coub.com/view/4b8kpc
https://coub.com/view/4b8kpa
https://coub.com/view/4b8kp7
https://coub.com/view/4b8kp5
https://coub.com/view/4b8kp1
https://coub.com/view/4b8kox
https://coub.com/view/4b8kov
https://coub.com/view/4b8kot
https://coub.com/view/4b8kor
https://coub.com/view/4b8kop
https://coub.com/view/4b8kol
https://coub.com/view/4b8koj
https://coub.com/view/4b8kog
https://coub.com/view/4b8koe
https://coub.com/view/4b8koc
https://coub.com/view/4b8knx
https://coub.com/view/4b8knw
https://coub.com/view/4b8knv
https://coub.com/view/4b8knu
https://coub.com/view/4b8knt
https://coub.com/view/4b8kns
https://coub.com/view/4b8knq
https://coub.com/view/4b8knp
https://coub.com/view/4b8kno
https://coub.com/view/4b8knn
https://coub.com/view/4b8knm
https://coub.com/view/4b8knl
https://coub.com/view/4b8knk
https://coub.com/view/4b8knj
https://coub.com/view/4b8kni
https://coub.com/view/4b8knd
https://coub.com/view/4b8knb
https://coub.com/view/4b8kna
https://coub.com/view/4b8kn9
https://coub.com/view/4b8kn8
如何获取ArkTS状态管理框架代理前的原始对象
使用getTarget接口获取状态管理框架代理前的原始对象。
参考示例如下:
import { UIUtils } from '@kit.ArkUI';
@Observed
class UserInfo {
name: string = 'Tom';
}
@Entry
@Component
struct GetTargetDemo {
@State info: UserInfo = new UserInfo();
build() {
Column() {
Text(info.name: ${this.info.name})
Button('Change the properties of the proxy object')
.onClick(() => {
this.info.name = 'Alice'; // The Text component can refresh
})
Button('更改原始对象的属性')
.onClick(() => {
let rawInfo: UserInfo = UIUtils.getTarget(this.info);
if (rawInfo) {
rawInfo.name = 'Bob'; // The Text component cannot be refreshed
}
})
}
}
}
参考链接
getTarget接口:获取状态管理框架代理前的原始对象
https://coub.com/view/4b8gt3
https://coub.com/view/4b8gt2
https://coub.com/view/4b8gt0
https://coub.com/view/4b8gsz
https://coub.com/view/4b8gsy
https://coub.com/view/4b8gsx
https://coub.com/view/4b8gsw
https://coub.com/view/4b8gsv
https://coub.com/view/4b8gsu
https://coub.com/view/4b8gst
https://coub.com/view/4b8gss
https://coub.com/view/4b8gsr
https://coub.com/view/4b8gsq
https://coub.com/view/4b8gsp
https://coub.com/view/4b8gsl
https://coub.com/view/4b8gsk
https://coub.com/view/4b8gsh
https://coub.com/view/4b8gsg
https://coub.com/view/4b8gsf
https://coub.com/view/4b8gse
https://coub.com/view/4b8gsa
https://coub.com/view/4b8gs9
https://coub.com/view/4b8gs4
https://coub.com/view/4b8gs3
https://coub.com/view/4b8gs2
https://coub.com/view/4b8grz
https://coub.com/view/4b8grv
https://coub.com/view/4b8gru
https://coub.com/view/4b8grs
https://coub.com/view/4b8grq
https://coub.com/view/4b8gro
https://coub.com/view/4b8grn
https://coub.com/view/4b8grj
https://coub.com/view/4b8gre
https://coub.com/view/4b8grb
https://coub.com/view/4b8gr9
https://coub.com/view/4b8gr8
https://coub.com/view/4b8gr7
https://coub.com/view/4b8gr4
https://coub.com/view/4b8gqz
https://coub.com/view/4b8gqv
https://coub.com/view/4b8gqu
https://coub.com/view/4b8gqs
https://coub.com/view/4b8gqr
https://coub.com/view/4b8gqp
https://coub.com/view/4b8gqo
https://coub.com/view/4b8gqn
https://coub.com/view/4b8gqm
https://coub.com/view/4b8gql
https://coub.com/view/4b8gqi
https://coub.com/view/4b8gqf
https://coub.com/view/4b8gqc
https://coub.com/view/4b8gqa
https://coub.com/view/4b8gq8
https://coub.com/view/4b8gq6
https://coub.com/view/4b8gq4
https://coub.com/view/4b8gq2
https://coub.com/view/4b8gq0
https://coub.com/view/4b8gpx
https://coub.com/view/4b8gpp
https://coub.com/view/4b8gpn
https://coub.com/view/4b8gpm
https://coub.com/view/4b8gpl
https://coub.com/view/4b8gpi
https://coub.com/view/4b8gpf
https://coub.com/view/4b8gpa
https://coub.com/view/4b8gp9
https://coub.com/view/4b8gp6
https://coub.com/view/4b8gp5
https://coub.com/view/4b8gp3
https://coub.com/view/4b8gp1
https://coub.com/view/4b8goz
https://coub.com/view/4b8goy
https://coub.com/view/4b8gox
https://coub.com/view/4b8gov
https://coub.com/view/4b8gou
https://coub.com/view/4b8got
https://coub.com/view/4b8gos
https://coub.com/view/4b8gop
https://coub.com/view/4b8goe
https://coub.com/view/4b8god
https://coub.com/view/4b8goa
https://coub.com/view/4b8go8
https://coub.com/view/4b8go3
https://coub.com/view/4b8go0
https://coub.com/view/4b8gny
https://coub.com/view/4b8gnw
https://coub.com/view/4b8gnv
https://coub.com/view/4b8gnu
https://coub.com/view/4b8gns
https://coub.com/view/4b8gnk
https://coub.com/view/4b8gnj
https://coub.com/view/4b8gni
https://coub.com/view/4b8gnh
https://coub.com/view/4b8gnb
https://coub.com/view/4b8gna
https://coub.com/view/4b8gn9
https://coub.com/view/4b8gn7
https://coub.com/view/4b8gn2
https://coub.com/view/4b8cf1
https://coub.com/view/4b8cez
https://coub.com/view/4b8cev
https://coub.com/view/4b8ceu
https://coub.com/view/4b8cep
https://coub.com/view/4b8cen
https://coub.com/view/4b8cel
https://coub.com/view/4b8ceh
https://coub.com/view/4b8ceg
https://coub.com/view/4b8cee
https://coub.com/view/4b8ced
https://coub.com/view/4b8ceb
https://coub.com/view/4b8cea
https://coub.com/view/4b8ce8
https://coub.com/view/4b8ce6
https://coub.com/view/4b8ce5
https://coub.com/view/4b8ce4
https://coub.com/view/4b8ce2
https://coub.com/view/4b8ce1
https://coub.com/view/4b8cdz
https://coub.com/view/4b8cdx
https://coub.com/view/4b8cdw
https://coub.com/view/4b8cdv
https://coub.com/view/4b8cdu
https://coub.com/view/4b8cdt
https://coub.com/view/4b8cdr
https://coub.com/view/4b8cdq
https://coub.com/view/4b8cdp
https://coub.com/view/4b8cdo
https://coub.com/view/4b8cdn
https://coub.com/view/4b8cdm
https://coub.com/view/4b8cdl
https://coub.com/view/4b8cdk
https://coub.com/view/4b8cdj
https://coub.com/view/4b8cdi
https://coub.com/view/4b8cdh
https://coub.com/view/4b8cdf
https://coub.com/view/4b8cde
https://coub.com/view/4b8cdc
https://coub.com/view/4b8cda
https://coub.com/view/4b8cd5
https://coub.com/view/4b8cd4
https://coub.com/view/4b8cd2
https://coub.com/view/4b8cd0
https://coub.com/view/4b8ccx
https://coub.com/view/4b8ccw
https://coub.com/view/4b8ccv
https://coub.com/view/4b8ccs
https://coub.com/view/4b8ccq
https://coub.com/view/4b8ccn
https://coub.com/view/4b8ccj
https://coub.com/view/4b8cci
https://coub.com/view/4b8cca
https://coub.com/view/4b8cc3
https://coub.com/view/4b8cc1
https://coub.com/view/4b8cbz
https://coub.com/view/4b8cbw
https://coub.com/view/4b8cbt
https://coub.com/view/4b8cbl
https://coub.com/view/4b8cbk
https://coub.com/view/4b8cbh
https://coub.com/view/4b8cbf
https://coub.com/view/4b8cbe
https://coub.com/view/4b8cbd
https://coub.com/view/4b8cbb
https://coub.com/view/4b8cba
https://coub.com/view/4b8cb9
https://coub.com/view/4b8cb7
https://coub.com/view/4b8cb5
https://coub.com/view/4b8cb4
https://coub.com/view/4b8cb3
https://coub.com/view/4b8cb0
https://coub.com/view/4b8cax
https://coub.com/view/4b8caw
https://coub.com/view/4b8cau
https://coub.com/view/4b8cat
https://coub.com/view/4b8car
https://coub.com/view/4b8caq
https://coub.com/view/4b8cai
https://coub.com/view/4b8cah
https://coub.com/view/4b8caf
https://coub.com/view/4b8cae
https://coub.com/view/4b8cac
https://coub.com/view/4b8cab
https://coub.com/view/4b8caa
https://coub.com/view/4b8ca8
https://coub.com/view/4b8ca7
https://coub.com/view/4b8ca6
https://coub.com/view/4b8ca4
https://coub.com/view/4b8ca3
https://coub.com/view/4b8ca2
https://coub.com/view/4b8ca1
https://coub.com/view/4b8ca0
https://coub.com/view/4b8c9y
https://coub.com/view/4b8c9x
https://coub.com/view/4b8c9w
https://coub.com/view/4b8c9v
https://coub.com/view/4b8c9u
使用getTarget接口获取状态管理框架代理前的原始对象。
参考示例如下:
import { UIUtils } from '@kit.ArkUI';
@Observed
class UserInfo {
name: string = 'Tom';
}
@Entry
@Component
struct GetTargetDemo {
@State info: UserInfo = new UserInfo();
build() {
Column() {
Text(info.name: ${this.info.name})
Button('Change the properties of the proxy object')
.onClick(() => {
this.info.name = 'Alice'; // The Text component can refresh
})
Button('更改原始对象的属性')
.onClick(() => {
let rawInfo: UserInfo = UIUtils.getTarget(this.info);
if (rawInfo) {
rawInfo.name = 'Bob'; // The Text component cannot be refreshed
}
})
}
}
}
参考链接
getTarget接口:获取状态管理框架代理前的原始对象
https://coub.com/view/4b8gt3
https://coub.com/view/4b8gt2
https://coub.com/view/4b8gt0
https://coub.com/view/4b8gsz
https://coub.com/view/4b8gsy
https://coub.com/view/4b8gsx
https://coub.com/view/4b8gsw
https://coub.com/view/4b8gsv
https://coub.com/view/4b8gsu
https://coub.com/view/4b8gst
https://coub.com/view/4b8gss
https://coub.com/view/4b8gsr
https://coub.com/view/4b8gsq
https://coub.com/view/4b8gsp
https://coub.com/view/4b8gsl
https://coub.com/view/4b8gsk
https://coub.com/view/4b8gsh
https://coub.com/view/4b8gsg
https://coub.com/view/4b8gsf
https://coub.com/view/4b8gse
https://coub.com/view/4b8gsa
https://coub.com/view/4b8gs9
https://coub.com/view/4b8gs4
https://coub.com/view/4b8gs3
https://coub.com/view/4b8gs2
https://coub.com/view/4b8grz
https://coub.com/view/4b8grv
https://coub.com/view/4b8gru
https://coub.com/view/4b8grs
https://coub.com/view/4b8grq
https://coub.com/view/4b8gro
https://coub.com/view/4b8grn
https://coub.com/view/4b8grj
https://coub.com/view/4b8gre
https://coub.com/view/4b8grb
https://coub.com/view/4b8gr9
https://coub.com/view/4b8gr8
https://coub.com/view/4b8gr7
https://coub.com/view/4b8gr4
https://coub.com/view/4b8gqz
https://coub.com/view/4b8gqv
https://coub.com/view/4b8gqu
https://coub.com/view/4b8gqs
https://coub.com/view/4b8gqr
https://coub.com/view/4b8gqp
https://coub.com/view/4b8gqo
https://coub.com/view/4b8gqn
https://coub.com/view/4b8gqm
https://coub.com/view/4b8gql
https://coub.com/view/4b8gqi
https://coub.com/view/4b8gqf
https://coub.com/view/4b8gqc
https://coub.com/view/4b8gqa
https://coub.com/view/4b8gq8
https://coub.com/view/4b8gq6
https://coub.com/view/4b8gq4
https://coub.com/view/4b8gq2
https://coub.com/view/4b8gq0
https://coub.com/view/4b8gpx
https://coub.com/view/4b8gpp
https://coub.com/view/4b8gpn
https://coub.com/view/4b8gpm
https://coub.com/view/4b8gpl
https://coub.com/view/4b8gpi
https://coub.com/view/4b8gpf
https://coub.com/view/4b8gpa
https://coub.com/view/4b8gp9
https://coub.com/view/4b8gp6
https://coub.com/view/4b8gp5
https://coub.com/view/4b8gp3
https://coub.com/view/4b8gp1
https://coub.com/view/4b8goz
https://coub.com/view/4b8goy
https://coub.com/view/4b8gox
https://coub.com/view/4b8gov
https://coub.com/view/4b8gou
https://coub.com/view/4b8got
https://coub.com/view/4b8gos
https://coub.com/view/4b8gop
https://coub.com/view/4b8goe
https://coub.com/view/4b8god
https://coub.com/view/4b8goa
https://coub.com/view/4b8go8
https://coub.com/view/4b8go3
https://coub.com/view/4b8go0
https://coub.com/view/4b8gny
https://coub.com/view/4b8gnw
https://coub.com/view/4b8gnv
https://coub.com/view/4b8gnu
https://coub.com/view/4b8gns
https://coub.com/view/4b8gnk
https://coub.com/view/4b8gnj
https://coub.com/view/4b8gni
https://coub.com/view/4b8gnh
https://coub.com/view/4b8gnb
https://coub.com/view/4b8gna
https://coub.com/view/4b8gn9
https://coub.com/view/4b8gn7
https://coub.com/view/4b8gn2
https://coub.com/view/4b8cf1
https://coub.com/view/4b8cez
https://coub.com/view/4b8cev
https://coub.com/view/4b8ceu
https://coub.com/view/4b8cep
https://coub.com/view/4b8cen
https://coub.com/view/4b8cel
https://coub.com/view/4b8ceh
https://coub.com/view/4b8ceg
https://coub.com/view/4b8cee
https://coub.com/view/4b8ced
https://coub.com/view/4b8ceb
https://coub.com/view/4b8cea
https://coub.com/view/4b8ce8
https://coub.com/view/4b8ce6
https://coub.com/view/4b8ce5
https://coub.com/view/4b8ce4
https://coub.com/view/4b8ce2
https://coub.com/view/4b8ce1
https://coub.com/view/4b8cdz
https://coub.com/view/4b8cdx
https://coub.com/view/4b8cdw
https://coub.com/view/4b8cdv
https://coub.com/view/4b8cdu
https://coub.com/view/4b8cdt
https://coub.com/view/4b8cdr
https://coub.com/view/4b8cdq
https://coub.com/view/4b8cdp
https://coub.com/view/4b8cdo
https://coub.com/view/4b8cdn
https://coub.com/view/4b8cdm
https://coub.com/view/4b8cdl
https://coub.com/view/4b8cdk
https://coub.com/view/4b8cdj
https://coub.com/view/4b8cdi
https://coub.com/view/4b8cdh
https://coub.com/view/4b8cdf
https://coub.com/view/4b8cde
https://coub.com/view/4b8cdc
https://coub.com/view/4b8cda
https://coub.com/view/4b8cd5
https://coub.com/view/4b8cd4
https://coub.com/view/4b8cd2
https://coub.com/view/4b8cd0
https://coub.com/view/4b8ccx
https://coub.com/view/4b8ccw
https://coub.com/view/4b8ccv
https://coub.com/view/4b8ccs
https://coub.com/view/4b8ccq
https://coub.com/view/4b8ccn
https://coub.com/view/4b8ccj
https://coub.com/view/4b8cci
https://coub.com/view/4b8cca
https://coub.com/view/4b8cc3
https://coub.com/view/4b8cc1
https://coub.com/view/4b8cbz
https://coub.com/view/4b8cbw
https://coub.com/view/4b8cbt
https://coub.com/view/4b8cbl
https://coub.com/view/4b8cbk
https://coub.com/view/4b8cbh
https://coub.com/view/4b8cbf
https://coub.com/view/4b8cbe
https://coub.com/view/4b8cbd
https://coub.com/view/4b8cbb
https://coub.com/view/4b8cba
https://coub.com/view/4b8cb9
https://coub.com/view/4b8cb7
https://coub.com/view/4b8cb5
https://coub.com/view/4b8cb4
https://coub.com/view/4b8cb3
https://coub.com/view/4b8cb0
https://coub.com/view/4b8cax
https://coub.com/view/4b8caw
https://coub.com/view/4b8cau
https://coub.com/view/4b8cat
https://coub.com/view/4b8car
https://coub.com/view/4b8caq
https://coub.com/view/4b8cai
https://coub.com/view/4b8cah
https://coub.com/view/4b8caf
https://coub.com/view/4b8cae
https://coub.com/view/4b8cac
https://coub.com/view/4b8cab
https://coub.com/view/4b8caa
https://coub.com/view/4b8ca8
https://coub.com/view/4b8ca7
https://coub.com/view/4b8ca6
https://coub.com/view/4b8ca4
https://coub.com/view/4b8ca3
https://coub.com/view/4b8ca2
https://coub.com/view/4b8ca1
https://coub.com/view/4b8ca0
https://coub.com/view/4b8c9y
https://coub.com/view/4b8c9x
https://coub.com/view/4b8c9w
https://coub.com/view/4b8c9v
https://coub.com/view/4b8c9u
如何解决组件消失动画偏移闪烁
问题描述
运行下面demo,点击使色块消失时,会突然出现在左上角闪烁。
@Entry
@ComponentV2
struct Index {
@Local isShow: boolean = true;
build() {
Stack() {
Stack() {
if (this.isShow) {
Row()
.width(100)
.height(100)
.backgroundColor(Color.Red)
.transition(TransitionEffect.OPACITY.animation({ duration: 150 }));
}
}
.backgroundColor(Color.Green)
}
.width('100%')
.height('100%')
.onClick(() => {
this.isShow = !this.isShow;
});
}
}
解决措施
原因是当变量置为false时,组件会从视图树中移除,导致内层Stack失去内容后位置重置到左上角,可以固定内层Stack组件的宽高来避免,示例如下:
@Entry
@ComponentV2
struct DisappearanceAnimationOffsetFlicker {
@Local isShow: boolean = true;
build() {
Stack() {
Stack() {
if (this.isShow) {
Row()
.width(100)
.height(100)
.backgroundColor(Color.Red)
.transition(TransitionEffect.OPACITY.animation({ duration: 150 }))
}
}
.width('100%')
.height('100%')
.onClick(() => {
this.isShow = !this.isShow
})
}
}
}
https://coub.com/view/4b8cf1
https://coub.com/view/4b8cez
https://coub.com/view/4b8cev
https://coub.com/view/4b8ceu
https://coub.com/view/4b8cep
https://coub.com/view/4b8cen
https://coub.com/view/4b8cel
https://coub.com/view/4b8ceh
https://coub.com/view/4b8ceg
https://coub.com/view/4b8cee
https://coub.com/view/4b8ced
https://coub.com/view/4b8ceb
https://coub.com/view/4b8cea
https://coub.com/view/4b8ce8
https://coub.com/view/4b8ce6
https://coub.com/view/4b8ce5
https://coub.com/view/4b8ce4
https://coub.com/view/4b8ce2
https://coub.com/view/4b8ce1
https://coub.com/view/4b8cdz
https://coub.com/view/4b8cdx
https://coub.com/view/4b8cdw
https://coub.com/view/4b8cdv
https://coub.com/view/4b8cdu
https://coub.com/view/4b8cdt
https://coub.com/view/4b8cdr
https://coub.com/view/4b8cdq
https://coub.com/view/4b8cdp
https://coub.com/view/4b8cdo
https://coub.com/view/4b8cdn
https://coub.com/view/4b8cdm
https://coub.com/view/4b8cdl
https://coub.com/view/4b8cdk
https://coub.com/view/4b8cdj
https://coub.com/view/4b8cdi
https://coub.com/view/4b8cdh
https://coub.com/view/4b8cdf
https://coub.com/view/4b8cde
https://coub.com/view/4b8cdc
https://coub.com/view/4b8cda
https://coub.com/view/4b8cd5
https://coub.com/view/4b8cd4
https://coub.com/view/4b8cd2
https://coub.com/view/4b8cd0
https://coub.com/view/4b8ccx
https://coub.com/view/4b8ccw
https://coub.com/view/4b8ccv
https://coub.com/view/4b8ccs
https://coub.com/view/4b8ccq
https://coub.com/view/4b8ccn
https://coub.com/view/4b8ccj
https://coub.com/view/4b8cci
https://coub.com/view/4b8cca
https://coub.com/view/4b8cc3
https://coub.com/view/4b8cc1
https://coub.com/view/4b8cbz
https://coub.com/view/4b8cbw
https://coub.com/view/4b8cbt
https://coub.com/view/4b8cbl
https://coub.com/view/4b8cbk
https://coub.com/view/4b8cbh
https://coub.com/view/4b8cbf
https://coub.com/view/4b8cbe
https://coub.com/view/4b8cbd
https://coub.com/view/4b8cbb
https://coub.com/view/4b8cba
https://coub.com/view/4b8cb9
https://coub.com/view/4b8cb7
https://coub.com/view/4b8cb5
https://coub.com/view/4b8cb4
https://coub.com/view/4b8cb3
https://coub.com/view/4b8cb0
https://coub.com/view/4b8cax
https://coub.com/view/4b8caw
https://coub.com/view/4b8cau
https://coub.com/view/4b8cat
https://coub.com/view/4b8car
https://coub.com/view/4b8caq
https://coub.com/view/4b8cai
https://coub.com/view/4b8cah
https://coub.com/view/4b8caf
https://coub.com/view/4b8cae
https://coub.com/view/4b8cac
https://coub.com/view/4b8cab
https://coub.com/view/4b8caa
https://coub.com/view/4b8ca8
https://coub.com/view/4b8ca7
https://coub.com/view/4b8ca6
https://coub.com/view/4b8ca4
https://coub.com/view/4b8ca3
https://coub.com/view/4b8ca2
https://coub.com/view/4b8ca1
https://coub.com/view/4b8ca0
https://coub.com/view/4b8c9y
https://coub.com/view/4b8c9x
https://coub.com/view/4b8c9w
https://coub.com/view/4b8c9v
https://coub.com/view/4b8c9u
https://coub.com/view/4b87sb
https://coub.com/view/4b7yjg
https://coub.com/view/4b7yjf
https://coub.com/view/4b7yjd
https://coub.com/view/4b7yiw
https://coub.com/view/4b7yis
https://coub.com/view/4b7yip
https://coub.com/view/4b7yik
https://coub.com/view/4b7yij
https://coub.com/view/4b7yig
https://coub.com/view/4b7yie
https://coub.com/view/4b7yic
https://coub.com/view/4b7yia
https://coub.com/view/4b7yi7
https://coub.com/view/4b7yi5
https://coub.com/view/4b7yi3
https://coub.com/view/4b7yhy
https://coub.com/view/4b7yht
https://coub.com/view/4b7yhr
https://coub.com/view/4b7yhp
https://coub.com/view/4b7yhh
https://coub.com/view/4b7yhg
https://coub.com/view/4b7yhf
https://coub.com/view/4b7yhb
https://coub.com/view/4b7yh9
https://coub.com/view/4b7yh6
https://coub.com/view/4b7yh4
https://coub.com/view/4b7ygz
https://coub.com/view/4b7ygx
https://coub.com/view/4b7ygu
https://coub.com/view/4b7ygr
https://coub.com/view/4b7ygn
https://coub.com/view/4b7ygk
https://coub.com/view/4b7ygh
https://coub.com/view/4b7ygf
https://coub.com/view/4b7ygd
https://coub.com/view/4b7ygb
https://coub.com/view/4b7yg8
https://coub.com/view/4b7yg5
https://coub.com/view/4b7yg2
https://coub.com/view/4b7yfr
https://coub.com/view/4b7yfo
https://coub.com/view/4b7yfk
https://coub.com/view/4b7yfh
https://coub.com/view/4b7yfe
https://coub.com/view/4b7yfc
https://coub.com/view/4b7yfa
https://coub.com/view/4b7yf7
https://coub.com/view/4b7yf5
https://coub.com/view/4b7yf1
https://coub.com/view/4b7yez
https://coub.com/view/4b7yet
https://coub.com/view/4b7yer
https://coub.com/view/4b7yep
https://coub.com/view/4b7yeo
https://coub.com/view/4b7ub4
https://coub.com/view/4b7ub3
https://coub.com/view/4b7ub2
https://coub.com/view/4b7ub0
https://coub.com/view/4b7uay
https://coub.com/view/4b7uax
https://coub.com/view/4b7uav
https://coub.com/view/4b7uap
https://coub.com/view/4b7uao
https://coub.com/view/4b7ual
https://coub.com/view/4b7uad
https://coub.com/view/4b7uaa
https://coub.com/view/4b7ua7
https://coub.com/view/4b7ua5
https://coub.com/view/4b7ua2
https://coub.com/view/4b7ua1
https://coub.com/view/4b7u9z
https://coub.com/view/4b7u9y
https://coub.com/view/4b7u9x
https://coub.com/view/4b7u9p
https://coub.com/view/4b7u9k
https://coub.com/view/4b7u9j
https://coub.com/view/4b7u9i
https://coub.com/view/4b7u9g
https://coub.com/view/4b7u9d
https://coub.com/view/4b7u9a
https://coub.com/view/4b7u98
https://coub.com/view/4b7u96
https://coub.com/view/4b7u94
https://coub.com/view/4b7u92
https://coub.com/view/4b7u91
https://coub.com/view/4b7u8z
https://coub.com/view/4b7u8t
https://coub.com/view/4b7u8q
https://coub.com/view/4b7u8p
https://coub.com/view/4b7u8n
https://coub.com/view/4b7u8j
https://coub.com/view/4b7u8i
https://coub.com/view/4b7u78
https://coub.com/view/4b7u76
https://coub.com/view/4b7u72
https://coub.com/view/4b7u6v
https://coub.com/view/4b7u6t
https://coub.com/view/4b7u6r
https://coub.com/view/4b7u6o
https://coub.com/view/4b7u6m
https://coub.com/view/4b7u6l
https://coub.com/view/4b7u6j
https://coub.com/view/4b7u6h
https://coub.com/view/4b7u6f
https://coub.com/view/4b7u6d
https://coub.com/view/4b7u6a
https://coub.com/view/4b7u67
https://coub.com/view/4b7u65
https://coub.com/view/4b7u60
https://coub.com/view/4b7u5t
https://coub.com/view/4b7u5p
https://coub.com/view/4b7u5i
https://coub.com/view/4b7u4t
https://coub.com/view/4b7u4p
https://coub.com/view/4b7u4l
https://coub.com/view/4b7u46
https://coub.com/view/4b7u3v
https://coub.com/view/4b7u3u
https://coub.com/view/4b7u3t
https://coub.com/view/4b7u3r
https://coub.com/view/4b7u3o
https://coub.com/view/4b7u3l
https://coub.com/view/4b7u3k
https://coub.com/view/4b7u3h
https://coub.com/view/4b7u3a
https://coub.com/view/4b7u38
https://coub.com/view/4b7u35
https://coub.com/view/4b7u34
https://coub.com/view/4b7u33
https://coub.com/view/4b7u30
https://coub.com/view/4b7u2z
https://coub.com/view/4b7u2w
问题描述
运行下面demo,点击使色块消失时,会突然出现在左上角闪烁。
@Entry
@ComponentV2
struct Index {
@Local isShow: boolean = true;
build() {
Stack() {
Stack() {
if (this.isShow) {
Row()
.width(100)
.height(100)
.backgroundColor(Color.Red)
.transition(TransitionEffect.OPACITY.animation({ duration: 150 }));
}
}
.backgroundColor(Color.Green)
}
.width('100%')
.height('100%')
.onClick(() => {
this.isShow = !this.isShow;
});
}
}
解决措施
原因是当变量置为false时,组件会从视图树中移除,导致内层Stack失去内容后位置重置到左上角,可以固定内层Stack组件的宽高来避免,示例如下:
@Entry
@ComponentV2
struct DisappearanceAnimationOffsetFlicker {
@Local isShow: boolean = true;
build() {
Stack() {
Stack() {
if (this.isShow) {
Row()
.width(100)
.height(100)
.backgroundColor(Color.Red)
.transition(TransitionEffect.OPACITY.animation({ duration: 150 }))
}
}
.width('100%')
.height('100%')
.onClick(() => {
this.isShow = !this.isShow
})
}
}
}
https://coub.com/view/4b8cf1
https://coub.com/view/4b8cez
https://coub.com/view/4b8cev
https://coub.com/view/4b8ceu
https://coub.com/view/4b8cep
https://coub.com/view/4b8cen
https://coub.com/view/4b8cel
https://coub.com/view/4b8ceh
https://coub.com/view/4b8ceg
https://coub.com/view/4b8cee
https://coub.com/view/4b8ced
https://coub.com/view/4b8ceb
https://coub.com/view/4b8cea
https://coub.com/view/4b8ce8
https://coub.com/view/4b8ce6
https://coub.com/view/4b8ce5
https://coub.com/view/4b8ce4
https://coub.com/view/4b8ce2
https://coub.com/view/4b8ce1
https://coub.com/view/4b8cdz
https://coub.com/view/4b8cdx
https://coub.com/view/4b8cdw
https://coub.com/view/4b8cdv
https://coub.com/view/4b8cdu
https://coub.com/view/4b8cdt
https://coub.com/view/4b8cdr
https://coub.com/view/4b8cdq
https://coub.com/view/4b8cdp
https://coub.com/view/4b8cdo
https://coub.com/view/4b8cdn
https://coub.com/view/4b8cdm
https://coub.com/view/4b8cdl
https://coub.com/view/4b8cdk
https://coub.com/view/4b8cdj
https://coub.com/view/4b8cdi
https://coub.com/view/4b8cdh
https://coub.com/view/4b8cdf
https://coub.com/view/4b8cde
https://coub.com/view/4b8cdc
https://coub.com/view/4b8cda
https://coub.com/view/4b8cd5
https://coub.com/view/4b8cd4
https://coub.com/view/4b8cd2
https://coub.com/view/4b8cd0
https://coub.com/view/4b8ccx
https://coub.com/view/4b8ccw
https://coub.com/view/4b8ccv
https://coub.com/view/4b8ccs
https://coub.com/view/4b8ccq
https://coub.com/view/4b8ccn
https://coub.com/view/4b8ccj
https://coub.com/view/4b8cci
https://coub.com/view/4b8cca
https://coub.com/view/4b8cc3
https://coub.com/view/4b8cc1
https://coub.com/view/4b8cbz
https://coub.com/view/4b8cbw
https://coub.com/view/4b8cbt
https://coub.com/view/4b8cbl
https://coub.com/view/4b8cbk
https://coub.com/view/4b8cbh
https://coub.com/view/4b8cbf
https://coub.com/view/4b8cbe
https://coub.com/view/4b8cbd
https://coub.com/view/4b8cbb
https://coub.com/view/4b8cba
https://coub.com/view/4b8cb9
https://coub.com/view/4b8cb7
https://coub.com/view/4b8cb5
https://coub.com/view/4b8cb4
https://coub.com/view/4b8cb3
https://coub.com/view/4b8cb0
https://coub.com/view/4b8cax
https://coub.com/view/4b8caw
https://coub.com/view/4b8cau
https://coub.com/view/4b8cat
https://coub.com/view/4b8car
https://coub.com/view/4b8caq
https://coub.com/view/4b8cai
https://coub.com/view/4b8cah
https://coub.com/view/4b8caf
https://coub.com/view/4b8cae
https://coub.com/view/4b8cac
https://coub.com/view/4b8cab
https://coub.com/view/4b8caa
https://coub.com/view/4b8ca8
https://coub.com/view/4b8ca7
https://coub.com/view/4b8ca6
https://coub.com/view/4b8ca4
https://coub.com/view/4b8ca3
https://coub.com/view/4b8ca2
https://coub.com/view/4b8ca1
https://coub.com/view/4b8ca0
https://coub.com/view/4b8c9y
https://coub.com/view/4b8c9x
https://coub.com/view/4b8c9w
https://coub.com/view/4b8c9v
https://coub.com/view/4b8c9u
https://coub.com/view/4b87sb
https://coub.com/view/4b7yjg
https://coub.com/view/4b7yjf
https://coub.com/view/4b7yjd
https://coub.com/view/4b7yiw
https://coub.com/view/4b7yis
https://coub.com/view/4b7yip
https://coub.com/view/4b7yik
https://coub.com/view/4b7yij
https://coub.com/view/4b7yig
https://coub.com/view/4b7yie
https://coub.com/view/4b7yic
https://coub.com/view/4b7yia
https://coub.com/view/4b7yi7
https://coub.com/view/4b7yi5
https://coub.com/view/4b7yi3
https://coub.com/view/4b7yhy
https://coub.com/view/4b7yht
https://coub.com/view/4b7yhr
https://coub.com/view/4b7yhp
https://coub.com/view/4b7yhh
https://coub.com/view/4b7yhg
https://coub.com/view/4b7yhf
https://coub.com/view/4b7yhb
https://coub.com/view/4b7yh9
https://coub.com/view/4b7yh6
https://coub.com/view/4b7yh4
https://coub.com/view/4b7ygz
https://coub.com/view/4b7ygx
https://coub.com/view/4b7ygu
https://coub.com/view/4b7ygr
https://coub.com/view/4b7ygn
https://coub.com/view/4b7ygk
https://coub.com/view/4b7ygh
https://coub.com/view/4b7ygf
https://coub.com/view/4b7ygd
https://coub.com/view/4b7ygb
https://coub.com/view/4b7yg8
https://coub.com/view/4b7yg5
https://coub.com/view/4b7yg2
https://coub.com/view/4b7yfr
https://coub.com/view/4b7yfo
https://coub.com/view/4b7yfk
https://coub.com/view/4b7yfh
https://coub.com/view/4b7yfe
https://coub.com/view/4b7yfc
https://coub.com/view/4b7yfa
https://coub.com/view/4b7yf7
https://coub.com/view/4b7yf5
https://coub.com/view/4b7yf1
https://coub.com/view/4b7yez
https://coub.com/view/4b7yet
https://coub.com/view/4b7yer
https://coub.com/view/4b7yep
https://coub.com/view/4b7yeo
https://coub.com/view/4b7ub4
https://coub.com/view/4b7ub3
https://coub.com/view/4b7ub2
https://coub.com/view/4b7ub0
https://coub.com/view/4b7uay
https://coub.com/view/4b7uax
https://coub.com/view/4b7uav
https://coub.com/view/4b7uap
https://coub.com/view/4b7uao
https://coub.com/view/4b7ual
https://coub.com/view/4b7uad
https://coub.com/view/4b7uaa
https://coub.com/view/4b7ua7
https://coub.com/view/4b7ua5
https://coub.com/view/4b7ua2
https://coub.com/view/4b7ua1
https://coub.com/view/4b7u9z
https://coub.com/view/4b7u9y
https://coub.com/view/4b7u9x
https://coub.com/view/4b7u9p
https://coub.com/view/4b7u9k
https://coub.com/view/4b7u9j
https://coub.com/view/4b7u9i
https://coub.com/view/4b7u9g
https://coub.com/view/4b7u9d
https://coub.com/view/4b7u9a
https://coub.com/view/4b7u98
https://coub.com/view/4b7u96
https://coub.com/view/4b7u94
https://coub.com/view/4b7u92
https://coub.com/view/4b7u91
https://coub.com/view/4b7u8z
https://coub.com/view/4b7u8t
https://coub.com/view/4b7u8q
https://coub.com/view/4b7u8p
https://coub.com/view/4b7u8n
https://coub.com/view/4b7u8j
https://coub.com/view/4b7u8i
https://coub.com/view/4b7u78
https://coub.com/view/4b7u76
https://coub.com/view/4b7u72
https://coub.com/view/4b7u6v
https://coub.com/view/4b7u6t
https://coub.com/view/4b7u6r
https://coub.com/view/4b7u6o
https://coub.com/view/4b7u6m
https://coub.com/view/4b7u6l
https://coub.com/view/4b7u6j
https://coub.com/view/4b7u6h
https://coub.com/view/4b7u6f
https://coub.com/view/4b7u6d
https://coub.com/view/4b7u6a
https://coub.com/view/4b7u67
https://coub.com/view/4b7u65
https://coub.com/view/4b7u60
https://coub.com/view/4b7u5t
https://coub.com/view/4b7u5p
https://coub.com/view/4b7u5i
https://coub.com/view/4b7u4t
https://coub.com/view/4b7u4p
https://coub.com/view/4b7u4l
https://coub.com/view/4b7u46
https://coub.com/view/4b7u3v
https://coub.com/view/4b7u3u
https://coub.com/view/4b7u3t
https://coub.com/view/4b7u3r
https://coub.com/view/4b7u3o
https://coub.com/view/4b7u3l
https://coub.com/view/4b7u3k
https://coub.com/view/4b7u3h
https://coub.com/view/4b7u3a
https://coub.com/view/4b7u38
https://coub.com/view/4b7u35
https://coub.com/view/4b7u34
https://coub.com/view/4b7u33
https://coub.com/view/4b7u30
https://coub.com/view/4b7u2z
https://coub.com/view/4b7u2w







