Compare commits

...

1015 Commits

Author SHA1 Message Date
老皮
2113bf7ce1 同步何春辉代码,更改pay_type字段的来源接口 2024-10-14 14:18:34 +08:00
老皮
20b3c421e7 增加链接包显示支付判断 2024-10-14 13:56:23 +08:00
老皮
e50fbefdc1 替换融云IM key 2024-10-14 09:46:08 +08:00
老皮
d79dba045d 修复直播间点击三个点--更多设置--举报闪退的问题(网速慢一点的情况下) 2024-09-23 13:14:08 +08:00
老皮
0646c2c6a7 优化为只有从主播PK中跳转直播间失败(被踢过)才不再随机匹配直播间了 2024-09-20 13:22:46 +08:00
老皮
58ba80929b 修复:主播PK中,用户点击PK对手头像的方式进入直播间(被踢出过的),提示“已被踢出直播间”,之后会自动进入到别的主播直播间 2024-09-20 10:52:34 +08:00
d63cba7db1 修复开播图片后摄像头卡住问题 2024-09-13 14:26:32 +08:00
a454a97c58 同步主分支代码 2024-09-13 10:37:19 +08:00
18142669586
0937186c93 修改推特登录 2024-09-13 09:56:48 +08:00
11e0d62dde 版本 2024-09-13 09:53:27 +08:00
eb90ae9e03 Merge remote-tracking branch 'origin/v6.8.1' into v6.8.1 2024-09-11 15:34:58 +08:00
ec5f32cf1e 调整三星包支付无法唤起问题(R文件被覆盖) 2024-09-11 15:34:43 +08:00
ec5d4d77db 调整三星包支付无法唤起问题(R文件被覆盖) 2024-09-11 15:34:29 +08:00
0c74d81cc6 调整三星包支付无法唤起问题(R文件被覆盖) 2024-09-11 15:23:01 +08:00
18142669586
8e860b80d4 修改三星支付回调 2024-09-11 14:41:22 +08:00
61b1101c3f 681终版 2024-08-24 14:19:17 +08:00
daf881f6a7 调整代码中包名获取方式
681送审版本
2024-08-22 10:04:55 +08:00
38f3b721e0 v681内部提测版本 2024-08-21 17:04:50 +08:00
04ae7eeacc Merge branch 'refs/heads/v681小游戏' 2024-08-21 10:43:27 +08:00
d863c0af5a update 2024-08-21 10:33:25 +08:00
99c1037a15 修复测试反馈问题 2024-08-20 18:19:31 +08:00
18142669586
3440b71229 修改包名 2024-08-20 18:11:31 +08:00
50686957dc update 2024-08-20 16:12:02 +08:00
18142669586
214e6f5d89 修改包名 2024-08-20 16:10:24 +08:00
2d9e1cd685 update风格化打包 2024-08-20 15:18:54 +08:00
60485deed5 fix修复测试反馈问题 2024-08-20 15:01:19 +08:00
7196ebd16e update 更新flavorDimensions配置和风格化google-services.json 2024-08-20 13:18:26 +08:00
6eee7b9ede update 2024-08-20 10:02:38 +08:00
e0405e9c13 修复测试反馈内容 2024-08-19 14:56:12 +08:00
e8e0fc32f9 修复测试反馈内容 2024-08-19 11:22:04 +08:00
3d4ad99c99 Merge branch 'refs/heads/master' into v681小游戏
# Conflicts:
#	live/src/main/java/com/yunbao/live/activity/SystemMessageActivity.java
#	main/src/main/java/com/yunbao/main/activity/MainActivity.java
2024-08-16 14:36:44 +08:00
a25e22b142 update 临时隐藏处罚通知入口 2024-08-16 14:33:57 +08:00
3aeabfa32b 修改更新版本:1、改成以服务形式下载。2、新增断点续传功能。3、新增下载后检查apk完整性。
修改主播设置联系方式接口地址
2024-08-16 14:22:58 +08:00
47da21351e update 版本 2024-08-15 17:31:39 +08:00
451a875526 Merge branch 'refs/heads/master' into v681小游戏
# Conflicts:
#	common/src/main/java/com/yunbao/common/http/PDLiveApi.java
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
2024-08-14 15:59:11 +08:00
dc7b987eda 小游戏提测版本 2024-08-13 10:15:11 +08:00
fe28d3508b 新增小游戏相关内容 2024-08-09 14:06:46 +08:00
gongduoxiang
cb87974320 处罚通知,修改网络请求和activity跳转代码 2024-08-07 10:46:21 +08:00
gongduoxiang
dbd684a6e2 增加处罚通知功能 2024-08-06 16:29:15 +08:00
abc37aa486 调整测试反馈内容:【从用户的个人信息界面进入全站展馆本期角逐页面,查看周星礼物应该调用 Gift.liveGiftHallDetail】
调整测试反馈内容:【从banner进周星榜 退出 一直显示加载中怎么回事】
调整测试反馈内容:【包裹冠名礼物送出id丢失】
2024-08-06 10:40:39 +08:00
3349b2d7df fix 图片资源中英文 2024-08-05 17:26:52 +08:00
97d636ddec 更新版本号 2024-08-05 13:13:55 +08:00
3b8aedaa17 调整SVGA播放工具 2024-08-02 16:23:42 +08:00
40ba4b8aa8 修复开播通知可能出现两天的情况
修复svga可能只播放一次的情况
2024-08-02 14:49:28 +08:00
f7db0b0768 修复打开直播间信件箱闪退问题 2024-08-02 09:44:05 +08:00
ed642f0137 Merge branch 'refs/heads/master' into dev_680_礼物展馆
# Conflicts:
#	gradle.properties
2024-07-31 15:55:39 +08:00
d1512bc256 调整三星包 2024-07-31 13:45:41 +08:00
9f90040168 修复测试反馈内容 2024-07-30 16:47:47 +08:00
9ae6fedd8d 修复测试反馈内容
新增临时需求:banner点进礼物墙
新增临时需求:主播下播后显示违规内容(未接入接口)
合并潘多拉修复问题
2024-07-30 10:29:34 +08:00
516a068c25 修复游戏SDK在Android14时无法加载的问题 2024-07-29 11:04:47 +08:00
63b7a18c0b 修复语言问题 2024-07-29 10:21:06 +08:00
余前卫
6af48002bb 6.8.0:天梯赛数据不显示bug修复 2024-07-27 15:15:37 +08:00
79c730c1b7 fix PK邀请问题:A邀请B,B邀请C,C同意B,B同意A,会都加入到PK中 2024-07-25 15:36:11 +08:00
c20a9804e9 修复测试反馈内容 2024-07-25 15:00:05 +08:00
Martin
96a6b05d03 修改佩戴勋章接口 2024-07-19 15:44:06 +08:00
ba8090eec7 Merge remote-tracking branch 'origin/dev_680_礼物展馆' into dev_680_礼物展馆 2024-07-19 09:27:12 +08:00
e1fd4949b5 update 2024-07-19 09:27:00 +08:00
Martin
08c46a7684 礼物墙-修复bug 2024-07-18 18:28:15 +08:00
gongduoxiang
3a12d848e1 Merge remote-tracking branch 'origin/master' 2024-07-18 18:20:53 +08:00
gongduoxiang
cf245b0df8 有些本地的东西不去提交 2024-07-18 18:19:08 +08:00
1cde41f2d0 fix 测试反馈内容 2024-07-18 18:16:34 +08:00
092d4cb914 调整礼物墙部分文案 2024-07-18 17:51:51 +08:00
余前卫
0ea92c00a2 6.8.0:小游戏结算界面,观众视角修改 2024-07-18 17:31:44 +08:00
21ae621343 fix 修复礼物墙测试问题 2024-07-18 16:55:36 +08:00
048c66736f fix 修复礼物墙测试问题 2024-07-18 16:39:06 +08:00
d3055d8fb2 fix 修复礼物墙测试问题 2024-07-18 13:25:42 +08:00
Martin
b9164b6a08 礼物墙
第三方登录-新增校验
2024-07-18 13:19:30 +08:00
Martin
67e5e4e02f 直播间礼物列表-冠名字段修改 2024-07-17 16:14:35 +08:00
Martin
2351618e5a 拒绝随机PK提示英化 2024-07-17 13:26:07 +08:00
Martin
b2e2ca7303 Merge remote-tracking branch 'origin/dev_680_礼物展馆' into dev_680_礼物展馆 2024-07-17 12:12:35 +08:00
714e51b621 update 2024-07-17 12:12:14 +08:00
efa4c25c4b update 2024-07-17 12:11:41 +08:00
Martin
854cad8ec6 Merge remote-tracking branch 'origin/dev_680_礼物展馆' into dev_680_礼物展馆 2024-07-17 11:07:20 +08:00
46fba9429d update 2024-07-17 11:06:52 +08:00
c67ed4b736 Merge branch 'refs/heads/master' into dev_680_礼物展馆 2024-07-17 10:55:38 +08:00
Martin
4a91aafb4b Merge branch 'master' into dev_680_礼物展馆 2024-07-17 10:54:41 +08:00
18142669586
a0f3f246a6 补充jar 2024-07-17 10:53:43 +08:00
Martin
2b20782def 修改配置文件 2024-07-17 10:18:41 +08:00
Martin
152848a04f Merge branch 'master' into dev_680_礼物展馆
# Conflicts:
#	common/src/main/res/values-zh-rHK/strings.xml
#	common/src/main/res/values-zh-rTW/strings.xml
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
#	gradle.properties
#	live/build.gradle
2024-07-17 09:53:22 +08:00
Martin
fe827482c9 游戏房上麦 2024-07-17 09:47:38 +08:00
7fc1c2e712 update 礼物墙 2024-07-16 17:24:42 +08:00
7a6dfe5a3d update 礼物墙 2024-07-16 16:39:30 +08:00
Martin
96c45820b0 荣誉成就 2024-07-16 16:11:27 +08:00
ac18150503 update 礼物墙 2024-07-16 11:15:12 +08:00
Martin
22b208bcd9 新增直播间冠名礼物特效 2024-07-16 10:31:18 +08:00
Martin
484069dac7 冠名礼物 2024-07-15 10:13:36 +08:00
ade837e85c update 礼物墙 调整间距 2024-07-15 10:06:06 +08:00
12a1f24101 update 礼物墙 布局 2024-07-15 09:47:12 +08:00
250d4832a3 update 礼物墙 布局文件 2024-07-13 21:45:10 +08:00
Martin
93db808f8b 佈局 2024-07-13 17:49:07 +08:00
Martin
59f0fa4acb 佈局 2024-07-13 17:02:59 +08:00
8fe6130c81 update 礼物墙 2024-07-13 16:51:37 +08:00
18142669586
a11d7f07e9 开播im未初始化 会在初始化一次。 2024-07-13 11:26:24 +08:00
18142669586
90bc70ebf7 Merge remote-tracking branch 'origin/master' 2024-07-13 10:38:03 +08:00
18142669586
4618c0a4f1 升级34 grd8.0 2024-07-13 10:37:37 +08:00
9292e6f5c5 Merge remote-tracking branch 'origin/dev_680_礼物展馆' into dev_680_礼物展馆
# Conflicts:
#	common/src/main/java/com/yunbao/common/http/PDLiveApi.java
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
2024-07-13 09:29:11 +08:00
845b217c4d 新增礼物墙相关内容 2024-07-13 09:28:02 +08:00
Martin
7edd75e223 礼物墙功能 2024-07-13 09:27:11 +08:00
Martin
15a52aaa62 1、开通守护,im消息优化
2、游戏房安全区域优化
3、举报接口新增字段区分
2024-07-11 13:07:17 +08:00
余前卫
6d6010d023 Merge branch 'master' of https://gitee.com/xxkp/pdlivexp
# Conflicts:
#	app/build.gradle
#	common/src/main/res/values-zh-rHK/strings.xml
#	common/src/main/res/values-zh-rTW/strings.xml
#	common/src/main/res/values/strings.xml
2024-07-10 10:53:01 +08:00
Martin
94156e2984 Merge branch '直播间提示优化'
# Conflicts:
#	config.gradle
2024-07-09 13:49:31 +08:00
Martin
769f527565 1、開通守護-使用優惠券
2、开通贵族,在当前直播间有围观按钮问题
2024-07-09 13:08:33 +08:00
Martin
eb9f615f70 将支付相关的异常抛出,改为异常输出 2024-07-09 11:11:05 +08:00
Martin
3dcc801331 1、開通守護-使用優惠券 2024-07-05 13:58:35 +08:00
Martin
963fe2c110 1、好友邀请守护内容修改 2024-07-04 17:59:56 +08:00
9c38f40098 调整礼物墙tab 2024-07-04 15:02:41 +08:00
8cae89e7cd 新增礼物墙弹窗主题 2024-07-04 13:41:37 +08:00
Martin
9dd3619049 1、全服通知,新增【围观】按钮显示判断逻辑 2024-07-04 13:16:48 +08:00
Martin
96b80a460b 1、游戏房,游戏操作区域距离顶部问题
2、直播间红包、神龙位置问题
2024-07-03 18:24:14 +08:00
Martin
a68bee94a9 直播间侧边栏美化 2024-07-03 15:21:32 +08:00
Martin
40a2843696 守护榜页面需美化 2024-07-03 11:21:25 +08:00
2cefd50ac3 配合H5调整部分webView页面 2024-07-02 18:06:56 +08:00
Martin
833b58d311 1、游戏房间界面需美化 2024-07-02 14:40:36 +08:00
Martin
f14fb4612b 1、直播间-互动游戏角标优化 2024-07-01 16:55:46 +08:00
3510a565f2 调整HTTP日志输出 2024-07-01 09:57:39 +08:00
e0669e98cf 新增上报日志功能 2024-06-29 10:11:42 +08:00
Martin
8f82c7c785 1.直播间游戏提示优化
2.战令经验值购买优化
2024-06-28 17:52:41 +08:00
余前卫
2eea1f1b75 6.8.0:谷歌内购版本升级,小游戏结算界面优化 2024-06-27 18:30:51 +08:00
f1a1aae787 优化打包脚本,兼容低版本AndroidStudio使用 2024-06-27 14:28:33 +08:00
Martin
b68c7f1c46 1.守护榜页面需美化
2.直播间功能栏美化
3.游戏房间界面需美化
2024-06-26 17:27:29 +08:00
Martin
dd7eb2326a [优化]:
1.直播间游戏提示优化
2.战令经验值购买优化
3.守护榜页面需美化
2024-06-25 16:52:09 +08:00
3f1fe93f3d Merge remote-tracking branch 'origin/master' 2024-06-20 17:03:38 +08:00
Martin
8c9cf4e3a8 [修复首页English分类下,下拉刷新没有数据问题] 2024-06-20 17:00:49 +08:00
0451f518d5 移除调试权限 2024-06-20 14:02:42 +08:00
a154f480f6 增强配置化打包程序,可有效防止手动打包配置错误 2024-06-20 14:02:15 +08:00
Martin
29badbf725 fix[修復小窗播放白屏問題] 2024-06-19 14:47:53 +08:00
Martin
e89f22329f Merge branch 'dev_6.7.0' 2024-06-19 11:13:16 +08:00
Martin
81e08d41af FIX[修复BUG] 2024-06-18 18:42:26 +08:00
d89b356395 修复PK时整蛊观众端联系方式会闪动问题 2024-06-18 18:22:06 +08:00
4d2985456d 修复整蛊设置连击数有xN的字符 2024-06-18 17:56:55 +08:00
48e3ff46ab 修复安卓用户主播关播后在关播顶面显示主播最后一真画面。
修复安卓主播关播后还在一直推流。
2024-06-18 17:39:05 +08:00
Martin
6dd3e02c1d add[新增添加整蠱禮物提示] 2024-06-18 16:31:13 +08:00
d8e9cc2189 调整进游戏房就关掉小窗 2024-06-18 15:09:18 +08:00
Martin
0310628ba3 Merge branch 'master' into dev_6.7.0 2024-06-18 14:48:28 +08:00
2f54fdab12 调整进游戏房就关掉小窗 2024-06-18 14:44:12 +08:00
Martin
a3228f48c2 add[修復BUG] 2024-06-18 13:50:21 +08:00
余前卫
35c83a255a 6.7.0用户端更新:列表和缩小图标同时出现bug 2024-06-18 10:15:34 +08:00
Martin
0aeb2d32c2 add[修復BUG] 2024-06-17 17:21:18 +08:00
Martin
313320473f add[修復BUG] 2024-06-17 15:35:58 +08:00
Martin
2b86927b5b Merge remote-tracking branch 'origin/dev_6.7.0' into dev_6.7.0 2024-06-17 11:02:17 +08:00
余前卫
bf3ebfedbd 6.7.0用户端更新: 2024-06-17 10:59:47 +08:00
Martin
1a2db91ceb Merge branch 'master' into dev_6.7.0 2024-06-17 10:55:47 +08:00
Martin
9fa4924278 add[修復BUG] 2024-06-16 16:26:01 +08:00
Martin
3318fa761c add[合併聲網] 2024-06-16 13:41:19 +08:00
a7421ba1a6 修复暂时离开功能无效问题
调整购买守护的动画效果
2024-06-16 11:09:10 +08:00
Martin
40a081caa6 Merge remote-tracking branch 'origin/dev_6.7.0' into dev_6.7.0 2024-06-16 10:48:45 +08:00
余前卫
1485173227 6.7.0用户端更新:整蛊图标闪烁 2024-06-16 10:45:44 +08:00
Martin
d5b60523a5 Merge remote-tracking branch 'origin/master' into dev_6.7.0
# Conflicts:
#	app/src/main/AndroidManifest.xml
#	app/src/main/java/com/shayu/phonelive/AppContext.java
#	common/src/main/java/com/yunbao/common/CommonAppConfig.java
#	common/src/main/java/com/yunbao/common/http/PDLiveApi.java
#	common/src/main/java/com/yunbao/common/http/live/LiveNetManager.java
#	common/src/main/java/com/yunbao/common/manager/OpenAdManager.java
#	common/src/main/res/values-zh-rHK/strings.xml
#	common/src/main/res/values-zh-rTW/strings.xml
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
#	live/src/main/java/com/yunbao/live/utils/LiveTextRender.java
#	live/src/main/java/com/yunbao/live/views/LiveRoomViewHolder.java
#	live/src/main/java/com/yunbao/live/views/LiveRyAnchorViewHolder.java
2024-06-16 10:28:31 +08:00
余前卫
309d04653e 6.7.0用户端更新:整蛊图标闪烁 2024-06-14 18:15:22 +08:00
Martin
45ae3b1624 add[轮盘整蛊] 2024-06-14 17:39:42 +08:00
Martin
e874c2ee16 add[修復BUG,新增整蛊IM消息类型] 2024-06-14 15:40:51 +08:00
余前卫
002f0ad654 Merge remote-tracking branch 'origin/dev_6.7.0' into dev_6.7.0 2024-06-14 14:40:51 +08:00
余前卫
bb49472817 6.7.0用户端更新:修复包裹礼物送礼数量不同步问题 2024-06-14 11:33:07 +08:00
Martin
05e4ff89d7 add[修復BUG,新增開播整蠱IM消息] 2024-06-13 18:25:43 +08:00
020b5c1025 同步潘多拉修复代码 2024-06-13 16:06:21 +08:00
余前卫
2cf10cc35a 6.7.0用户端更新:限制举报描述字数 2024-06-13 16:04:58 +08:00
余前卫
4c53c990e9 6.7.0用户端更新:限制举报描述字数 2024-06-13 15:51:27 +08:00
余前卫
cd8b58a0ec 6.7.0用户端更新:更改数据类型 2024-06-13 09:55:15 +08:00
b41bd3b89b 修复:
1:开播闪屏问题
2:游戏房消息重复(或不显示)
3:模拟器或部分机型滑动声网直播间会白屏问题
4:PK条在平板模式下显示太高问题
5:PK没断开能收到其他人邀请的问题
2024-06-13 09:47:38 +08:00
余前卫
d66f098692 Merge remote-tracking branch 'origin/dev_6.7.0' into dev_6.7.0 2024-06-12 16:45:16 +08:00
Martin
f131fcf546 add[主播端,整蛊礼物,待完成项完成] 2024-06-12 16:44:46 +08:00
余前卫
eeb1cc008a 6.7.0用户端更新:修复空指针 2024-06-12 16:42:27 +08:00
6eba9558e1 修复userlist可能为空的问题 2024-06-11 17:03:13 +08:00
Martin
0425b56106 add[整蛊翻译] 2024-06-11 16:55:07 +08:00
余前卫
c5de062171 6.7.0用户端更新:屏蔽空数据 2024-06-11 14:23:39 +08:00
余前卫
f82dbccaa5 6.7.0用户端更新:更改数据类型 2024-06-11 14:18:55 +08:00
07d6b959d5 Merge branch 'master' into 声网升级
# Conflicts:
#	config.gradle
#	live/src/main/java/com/yunbao/live/views/PortraitLiveManager.java
2024-06-11 14:16:00 +08:00
5ae55f87fa 修复声网直播间会白屏问题
调整模块间的引用
2024-06-11 14:08:45 +08:00
余前卫
f59e5e527a 6.7.0用户端更新 2024-06-11 09:40:24 +08:00
d70c3a8b27 修复游戏房发言重复/不显示问题
修复贵族不显示英文问题
2024-06-07 16:53:48 +08:00
余前卫
c20147bf8f Merge branch 'yuqianwei' into dev_6.7.0
# Conflicts:
#	live/src/main/res/values/strings.xml
2024-06-07 15:46:53 +08:00
余前卫
45743e3070 6.7.0用户端更新 2024-06-07 15:35:57 +08:00
余前卫
b8b43c1ef2 6.7.0用户端更新 2024-06-07 15:35:34 +08:00
73847c46a3 修复游戏房发言重复/不显示问题
修复贵族不显示英文问题
2024-06-07 14:50:39 +08:00
Martin
b7c53a3b21 add[整蛊礼物接口调用、整蛊说明] 2024-06-06 18:29:31 +08:00
c91947ce71 配置化声网AppId 2024-06-05 16:37:24 +08:00
Martin
62e50ae310 fix[声网APPId] 2024-06-05 16:06:04 +08:00
Martin
5baea0f156 fix[修复-游戏房消息问题] 2024-06-05 15:12:35 +08:00
Martin
419e3a7ab5 fix[修复BUG] 2024-06-05 13:27:22 +08:00
Martin
66822139ba fix[更新弹幕] 2024-06-05 09:52:37 +08:00
Martin
5a924d9fea fix[修复BUG] 2024-06-05 09:50:57 +08:00
cb0772c9e3 update 更新弹幕 2024-06-05 09:48:22 +08:00
Martin
9c9eb34756 fix[修复BUG-主播开麦按钮的字未显示全] 2024-06-04 09:50:46 +08:00
Martin
1b57d8bc8f fix[修复BUG] 2024-06-03 15:41:29 +08:00
edc738d80b 修复一堆图标+10字名字聊天框没有图标问题 2024-06-03 15:02:03 +08:00
b5417f90b4 调整弹窗 2024-06-03 09:41:51 +08:00
Martin
4bd1e4bc91 fix[修复融云直播间,多人PK主播信息问题] 2024-06-02 16:57:48 +08:00
18142669586
cb2729433e 修改上下滑动黑 2024-06-02 14:44:36 +08:00
Martin
985e626cb5 fix[修复融云直播间,上下滑动闪退问题] 2024-06-01 17:31:01 +08:00
Martin
efce2ad93c fix[声望升级-设置竖屏] 2024-06-01 15:21:23 +08:00
Martin
6132338c6c fix[声望升级-同步弹窗功能] 2024-06-01 14:33:27 +08:00
Martin
8c89fc1de1 Merge remote-tracking branch 'origin/声网升级' into 声网升级 2024-06-01 14:24:19 +08:00
Martin
a061637492 fix[声望升级-去掉多人PK,展示主播信息延迟代码] 2024-06-01 14:24:10 +08:00
98095e07ee 调整弹窗 2024-06-01 14:00:48 +08:00
9764b3370e 调整弹窗 2024-06-01 13:13:33 +08:00
18142669586
bbb5a12899 Merge remote-tracking branch 'origin/声网升级' into 声网升级 2024-06-01 12:08:08 +08:00
18142669586
680780145c 修改多人图标闪 2024-06-01 12:07:36 +08:00
Martin
19c270ad22 fix[声望升级-秒开] 2024-06-01 11:16:26 +08:00
18142669586
ce9336737f 新增声网正式服环境 2024-06-01 11:07:00 +08:00
0396e2d06d 调整弹窗 2024-06-01 11:06:40 +08:00
Martin
39cf76439f fix[声望升级-APPID 更换存储位置] 2024-06-01 10:32:32 +08:00
Martin
11c9a53028 fix[声望升级-修复BUG] 2024-06-01 10:16:31 +08:00
Martin
c4d378daba fix[声望升级-修复BUG] 2024-05-31 20:59:16 +08:00
18142669586
efa698ebfa 1 2024-05-31 20:55:09 +08:00
18142669586
6496440e5d 修改9发两次 2024-05-31 20:00:04 +08:00
18142669586
d3de01666c 修改上下滑动黑 2024-05-31 19:01:41 +08:00
9621d8a6d8 Merge remote-tracking branch 'origin/声网升级' into 声网升级 2024-05-31 18:01:36 +08:00
07f97fefdb 降低声网SDK版本,处理雷电模拟器闪退问题 2024-05-31 18:01:22 +08:00
186b9d61ee 調整主播端設置整蠱UI 2024-05-31 15:06:35 +08:00
b7c0f5d3ec 调整战令图标改为通过接口获取 2024-05-31 10:31:14 +08:00
949c0ab759 调整弹窗、签到弹窗、更新弹窗优先级 2024-05-31 10:31:14 +08:00
Martin
278801b77a fix[声望升级-修复BUG] 2024-05-30 18:25:01 +08:00
5f3ebb4235 修复直播间 用户等级+超皇+粉丝牌+图标+名字(10字)会丢失名字前面图标的问题 2024-05-30 15:52:34 +08:00
ab24348f72 调整弹窗展示逻辑 2024-05-30 15:14:52 +08:00
022806d9da 修复直播间 用户等级+超皇+粉丝牌+图标+名字(10字)会丢失名字前面图标的问题 2024-05-30 15:14:00 +08:00
Martin
b0c5d44d1c fix[声望升级-修复BUG] 2024-05-30 13:20:40 +08:00
Martin
d9b1149832 fix[声望升级-修复BUG] 2024-05-29 21:24:05 +08:00
Martin
9711b1f8d4 fix[声望升级-修复BUG] 2024-05-28 21:28:20 +08:00
Martin
0252963b2e Merge remote-tracking branch 'origin/声网升级' into 声网升级
# Conflicts:
#	live/src/main/java/com/yunbao/live/presenter/LiveSwLinkMicPkPresenter.java
2024-05-28 17:13:59 +08:00
Martin
ff8e75768e fix[声望升级-修复BUG] 2024-05-28 17:12:52 +08:00
c92e8dd8bc 调整弹窗逻辑 2024-05-28 17:12:38 +08:00
18142669586
01dfb81a46 点击【邀请】加上防抖的功能 2024-05-28 16:58:39 +08:00
18142669586
0afee3543e 邀请后不接受倒计时完了在邀请 不显示pk弹出框 2024-05-28 15:20:43 +08:00
8d5990c25b 调整弹窗逻辑 2024-05-28 15:02:15 +08:00
18142669586
d5f78cc83a 修改pk列表按钮错误 2024-05-28 14:26:29 +08:00
Martin
cf48be7d15 fix[声望升级-修复BUG] 2024-05-28 13:44:44 +08:00
225929c891 新增【全服公告优化】 2024-05-27 14:03:20 +08:00
bc63a3e601 新增【全服公告优化】 2024-05-27 13:48:52 +08:00
c33975fe67 优化首页右下角悬浮层隐藏显示问题
调整直播间购买推荐的UI显示不合设计问题
2024-05-24 15:45:06 +08:00
c033340aad 优化首页右下角悬浮层隐藏显示问题 2024-05-24 14:49:14 +08:00
24170d55e2 调整进入直播间等待时长15→4 2024-05-23 12:53:07 +08:00
Martin
26a71ff825 fix[声望升级-修复BUG] 2024-05-22 18:29:11 +08:00
e36fbdc6b8 调整【我的】个性签名中英文判断 2024-05-22 13:49:06 +08:00
Martin
b6d0ea1fba fix[声望升级-合并] 2024-05-22 13:46:52 +08:00
Martin
cbf517bed6 Merge branch 'master' into 声网升级
# Conflicts:
#	common/src/main/java/com/yunbao/common/event/SendBlindGiftEvent.java
#	common/src/main/java/com/yunbao/common/http/PDLiveApi.java
#	common/src/main/java/com/yunbao/common/http/live/LiveNetManager.java
#	common/src/main/java/com/yunbao/common/utils/DialogUitl.java
#	common/src/main/java/com/yunbao/common/utils/RouteUtil.java
#	live/src/main/java/com/yunbao/live/event/LiveAudienceEvent.java
#	live/src/main/java/com/yunbao/live/socket/SocketRyLinkMicPkUtil.java
#	main/src/main/java/com/yunbao/main/dialog/MainStartDialogFragment.java
2024-05-22 13:37:33 +08:00
Martin
dc0ae1124f add[声望升级-游戏房判断声网或者融云] 2024-05-22 13:32:05 +08:00
Martin
3167ab0be8 add[声望升级-游戏房判断声网或者融云] 2024-05-20 16:18:16 +08:00
Martin
0b218157d2 add[声望升级-AB面] 2024-05-18 18:27:38 +08:00
Martin
6a4ac35203 add[声望升级-AB面] 2024-05-18 14:36:35 +08:00
Martin
93dbee14b3 add[声望升级-修复BUG] 2024-05-15 15:55:31 +08:00
Martin
cb376aa83a add[声望升级-修复BUG] 2024-05-14 18:29:28 +08:00
92d1861d8f 调整回#1eb43fb8逻辑,不管单人还是多人PK都显示排位赛信息 2024-05-14 18:07:37 +08:00
0ff0cb52f6 修复主页战令悬浮问题
调整获取验证码接口设置语言
2024-05-14 17:16:28 +08:00
Martin
afef088555 add[声望升级-自動PK,直接PK] 2024-05-13 17:31:55 +08:00
Martin
73414f3349 add[声望升级-自動PK,直接PK] 2024-05-11 18:29:46 +08:00
Martin
abfd15f751 add[声望升级-小游戏多人连麦] 2024-05-09 16:01:39 +08:00
8d87dda691 修复33000问题 2024-05-09 14:07:37 +08:00
fcd13751c9 Merge remote-tracking branch 'origin/dev_6.6.8' into dev_6.6.8 2024-05-08 11:35:30 +08:00
e24805cf9b 移除友盟SDK 2024-05-08 11:34:56 +08:00
489a99e522 移除友盟SDK 2024-05-08 11:34:41 +08:00
6a51b39134 移除友盟SDK 2024-05-08 11:34:07 +08:00
86bbf6a7f3 更新小游戏混淆列表 2024-05-08 10:49:07 +08:00
Martin
95ded2f77c add[声望升级-多人PK] 2024-05-08 10:00:00 +08:00
599e88acc6 替换友盟为本地aar
调整小游戏为繁体中文
2024-05-08 09:52:35 +08:00
Martin
d78055cdd9 add[声望升级-多人PK] 2024-05-06 08:51:18 +08:00
2093306d36 修复测试反馈问题
调整小助手、机器人图标
调整PD一号的IM消息
调整PD一号的红包UI
调整翻译
2024-05-05 16:13:28 +08:00
Martin
b4adf4c77c add[声望升级-多人PK] 2024-04-30 18:29:09 +08:00
8e2acc3417 Merge branch 'master' into dev_6.6.8 2024-04-29 11:30:25 +08:00
d28d0dd3d7 修复测试反馈问题 2024-04-29 11:02:23 +08:00
Martin
7d46ab8520 add[声望升级-多人PK] 2024-04-29 09:30:04 +08:00
150bdba795 完成小游戏直播间半屏内容 2024-04-26 18:04:16 +08:00
651fe12edd 新增小助手中英文图标判定
新增消息页图标中英文判定
2024-04-26 14:47:58 +08:00
d0c0f8f82e 新增戰令一鍵領取按鈕 2024-04-26 11:11:48 +08:00
1f12187766 根据测试反馈修改帮助反馈问题
新增小游戏跳一跳回血和必中的回调
新增关闭发红包页面后自动打开礼物栏的功能
调整小游戏跳一跳回调防抖
2024-04-26 10:08:19 +08:00
18401019693
754f2a9294 修改问题全服跳转直播间问题 2024-04-25 15:32:38 +08:00
Martin
9320d6c209 add[声望升级-单人pk,进入直播间逻辑,单人PK逻辑] 2024-04-25 14:13:25 +08:00
1c9493d7c8 修改帮助反馈的UI 2024-04-24 18:00:46 +08:00
34e4765e13 修改帮助反馈的UI 2024-04-24 17:48:21 +08:00
d4d191fad6 新增帮助与反馈功能 2024-04-24 16:26:47 +08:00
Martin
8228c45a4d add[声望升级-多人PK] 2024-04-22 18:28:01 +08:00
Martin
4147e3c5f5 add[声望升级-多人PK] 2024-04-19 18:27:56 +08:00
Martin
583740e866 add[声望升级-接入美颜] 2024-04-18 09:47:38 +08:00
Martin
0db8eefbe6 add[声望升级] 2024-04-13 18:19:14 +08:00
Martin
3f68e2a3aa add[声望升级] 2024-04-12 18:29:26 +08:00
18401019693
11ba2c45f8 修改问题 2024-04-12 14:14:16 +08:00
18401019693
320584769d 修改问题 2024-04-11 16:32:21 +08:00
Martin
5922f4c08b add[声望升级] 2024-04-10 18:31:00 +08:00
18401019693
b29b0a1688 修改问题 2024-04-10 13:31:40 +08:00
18401019693
f88356cea2 修改问题 2024-04-10 10:31:21 +08:00
18401019693
2a67cf7228 修改问题 2024-04-10 09:51:28 +08:00
18401019693
a100ab1a4a 修改问题 2024-04-09 13:04:42 +08:00
18401019693
69b45f1d21 修改问题 2024-04-08 14:43:27 +08:00
18401019693
479a743d29 替换声网的RTC库 2024-04-08 14:04:15 +08:00
18401019693
382c53ab1f Merge remote-tracking branch 'origin/master' 2024-04-08 13:12:33 +08:00
18401019693
1c22f5fd48 修改守护购买页过期用户购买逻辑 2024-04-08 13:12:23 +08:00
hch
09bef7afa6 add [新增短剧埋点] 2024-04-08 13:08:56 +08:00
18401019693
88b96267ef 修改守护购买页过期用户购买逻辑 2024-04-08 10:01:42 +08:00
18401019693
7c006e8e03 修改守护榜位置信息 2024-04-07 17:21:17 +08:00
18401019693
f35b547c0d 自测修复,全服添加主播端 2024-04-07 15:32:42 +08:00
18401019693
490c558e03 自测修复,全服添加主播端 2024-04-07 14:39:48 +08:00
18401019693
bbf5d159b4 自测修复,全服添加主播端 2024-04-07 14:15:41 +08:00
hch
e982994ecd add [首页新增短剧入口] 2024-04-07 10:54:24 +08:00
hch
a0294a8405 Merge remote-tracking branch 'origin/master' 2024-04-07 10:05:51 +08:00
hch
756cbb1ce1 add [首页新增短剧入口] 2024-04-07 10:05:40 +08:00
9e5427bd35 update 登录接口加中英文标识 2024-04-07 09:54:45 +08:00
18401019693
f626ab03d4 添加短剧签到 2024-04-07 09:40:32 +08:00
18401019693
0d7bdce4f0 修改守护问题,添加全服通知功能 2024-04-03 16:05:53 +08:00
18401019693
cd3feebb94 修改问题 2024-04-01 14:32:46 +08:00
18401019693
3cca1a9e54 Merge remote-tracking branch 'origin/master' 2024-04-01 11:11:06 +08:00
18401019693
0b7b959151 修改问题 2024-04-01 11:10:54 +08:00
1f1ac71bb0 update 客服webview新增isZh判断 2024-04-01 10:56:44 +08:00
18401019693
ce7b6decc1 心愿单 2024-03-29 18:05:01 +08:00
18401019693
4a4a52aa61 修改守护列表页标签展示 2024-03-29 15:16:27 +08:00
18401019693
123b9dd74a Merge branch 'dev_6.6.6_开通守护' 2024-03-28 16:51:13 +08:00
18401019693
28dba93f17 修改守护列表页标签展示 2024-03-28 16:29:02 +08:00
18401019693
285515345b 修改守护列表页标签展示 2024-03-28 10:00:20 +08:00
18401019693
f24744afe8 修改测试问题 2024-03-28 09:50:29 +08:00
18401019693
1e466ac13d Merge branch 'dev_6.6.6_开通守护' 2024-03-27 16:19:31 +08:00
18401019693
d37b490088 修改测试问题,添加代理和vpn检测的开播检测 2024-03-27 16:19:08 +08:00
18401019693
a61bc8fbb8 Merge branch 'dev_6.6.6_开通守护' 2024-03-27 09:44:56 +08:00
9db23a32d1 fix [修复直播数量不足且战令关闭情况下,显示角标Banner白屏问题]
update [替换战令角标]
add [从首页Banner进入战令入口]
add [从弹窗进入战令入口]
2024-03-26 17:37:26 +08:00
18401019693
5f1eadc47b ui修改 2024-03-26 17:27:42 +08:00
18401019693
de11b5123d ui修改 2024-03-26 15:03:08 +08:00
18401019693
5b0f5f03ab 新增主播端 获取活动悬浮窗状态的接口:Live.getAnchorActiveImgStatus,替换原有的接口:Rank.isActivity, 2024-03-25 11:10:46 +08:00
18401019693
6095d2d04a 修改等级领取 2024-03-25 10:34:10 +08:00
18401019693
3953e146a5 修改问题 2024-03-23 17:33:09 +08:00
18401019693
f04a2865af 修改问题 2024-03-23 16:47:28 +08:00
18401019693
f94b3f27e3 守护团新增红点 2024-03-22 14:53:33 +08:00
18401019693
61a966ea41 修改直播间内红包和神龙送财的位置首页的位置 2024-03-22 11:23:30 +08:00
18401019693
d0f2ac2c60 修改测试问题, 2024-03-21 18:37:35 +08:00
18401019693
5dff64ec99 修改测试问题,对接H5页面 2024-03-21 18:13:24 +08:00
18401019693
cad8236e5c 修改测试问题 2024-03-21 17:36:07 +08:00
18401019693
faa401245f 修改测试问题 2024-03-21 15:48:50 +08:00
18401019693
4fc021e866 修改测试问题 2024-03-21 15:28:36 +08:00
18401019693
f9a21d0357 修改测试问题 2024-03-21 15:08:42 +08:00
18401019693
d72a9a39c6 修改测试问题 2024-03-20 16:29:37 +08:00
18401019693
0317b339f8 修改测试问题,新增直播列表页面的神龙送财的图标 2024-03-20 15:19:53 +08:00
18401019693
4fabf3aba3 修改测试问题 2024-03-20 13:40:30 +08:00
18401019693
4693954a72 神龙送财,逻辑修改,由开通接口触发 2024-03-20 10:02:14 +08:00
18401019693
859810f1f6 守护的全服通知,修改测试问题 2024-03-19 15:32:04 +08:00
18401019693
53f389ddc8 守護等級的檢查接口,領取接口。相關頁面的請求添加。h5方法新鄭 2024-03-18 14:52:24 +08:00
18401019693
ddc9bc344e 神龙送财结束Im结束调用接口,神龙列表倒计时修改 2024-03-18 10:24:05 +08:00
18401019693
cf34e73d32 神龙列表接口对接,神龙送财活动参加接口对接,ui更新 2024-03-14 18:21:55 +08:00
18401019693
1981fb553a 神龙列表接口对接,神龙送财活动参加接口对接,ui更新 2024-03-14 16:39:04 +08:00
18401019693
3b6d586b58 神龙送财开启接口。展示直播间神龙送财点击参与的图标 2024-03-14 10:44:40 +08:00
18401019693
50b6deaf01 對接開通接口神龍送財的im消息 2024-03-13 11:18:26 +08:00
18401019693
bc8d916445 神龍送財彈窗頁面。神龍送財規則頁面 2024-03-06 17:18:54 +08:00
18401019693
6870037683 新版守护,空页面,列表页面。守护类型开通页面。开通时间页面 2024-03-06 14:24:04 +08:00
hch
8af7f907eb 守护弹窗 2024-03-04 14:51:07 +08:00
4d96bcc667 Merge remote-tracking branch 'origin/dev_6.6.6_开通守护' into dev_6.6.6_开通守护 2024-02-27 17:38:30 +08:00
f7845dde6f 修复找回密码后无法登陆的问题 2024-02-26 16:04:47 +08:00
ae9ef1f8a1 新增守护测试页面 2024-02-26 14:41:01 +08:00
18401019693
1023c0ab4d 修改直播间发言报错提示 2024-02-23 09:47:49 +08:00
hch
8184cf6c7f 文字漸變色庫 2024-02-22 15:26:21 +08:00
df735af252 新增短剧页面的开播提醒浮窗 2024-02-17 13:43:30 +08:00
hch
6f71a825b5 Merge remote-tracking branch 'origin/master' 2024-02-16 10:34:23 +08:00
hch
08e009e5f9 修复bug上报问题 2024-02-16 10:34:10 +08:00
18401019693
1a4a0632ee Merge remote-tracking branch 'origin/master'
# Conflicts:
#	config.gradle
2024-02-16 10:31:56 +08:00
18401019693
b3a02f8c2c 短剧页面关闭直播小窗 2024-02-16 10:30:35 +08:00
aa9bc7326c 更新版本号 2024-01-31 16:08:30 +08:00
22769b981d 移除埋点空格 2024-01-31 10:25:54 +08:00
hch
85b4facfe8 调整UI 2024-01-26 17:04:11 +08:00
aef0435b93 战令相关UI调整 2024-01-26 16:18:23 +08:00
9cc30186cd 战令相关UI调整 2024-01-26 14:10:54 +08:00
f99641ed35 战令相关UI调整 2024-01-26 13:42:38 +08:00
hch
0552f56eb4 调整UI 2024-01-26 13:06:03 +08:00
18401019693
7ab4081f0b 優化頁面 2024-01-26 10:45:15 +08:00
hch
e7ef492015 调整UI 2024-01-26 10:18:58 +08:00
1aa6cb8f55 修复测试反馈问题 2024-01-25 16:38:44 +08:00
dc87fc7048 修复测试反馈问题 2024-01-25 14:15:46 +08:00
18401019693
04b70d910a 修复游戏房和ios不一样的数据 2024-01-25 13:07:17 +08:00
a4ce44d9ba 提交战令英文版入口图标
修复测试反馈问题
移除测试点
2024-01-25 11:40:13 +08:00
469e3e30ad 调整战令首页入口图片 2024-01-24 18:27:15 +08:00
18401019693
7e7616ef29 Merge remote-tracking branch 'origin/master' 2024-01-24 15:47:31 +08:00
18401019693
dd64568fdf 修复:连送重新选择礼物或者包裹按钮不消失问题 2024-01-24 15:47:19 +08:00
50fb801cf7 新增一个测试点 2024-01-24 14:52:44 +08:00
e0200a9c7f Merge remote-tracking branch 'origin/master' 2024-01-24 14:43:43 +08:00
5a32047c6d 调整客服WebView
新增客服url判断链接
2024-01-24 14:43:31 +08:00
18401019693
45fd56ba66 修复:手机开播邀请多人PK,多人PK一轮结束后,再次多人PK,在直播间的观众无法看到第二轮的PK倒计时 2024-01-24 14:39:00 +08:00
hch
30b2884c46 去掉短剧页面的顶部状态栏,修复闪退bug 2024-01-24 11:41:18 +08:00
0c07e8c2be Merge remote-tracking branch 'origin/master' 2024-01-24 11:30:26 +08:00
18401019693
508e9f1931 修复:手机开播邀请多人PK,多人PK一轮结束后,再次多人PK,在直播间的观众无法看到第二轮的PK倒计时 2024-01-24 11:29:00 +08:00
78d870ed89 update 2024-01-24 10:45:07 +08:00
18401019693
c00afb1db0 优化 2024-01-24 09:53:13 +08:00
18401019693
dfca615735 优化 2024-01-23 17:50:31 +08:00
hch
a920b61bc4 Merge remote-tracking branch 'origin/master' 2024-01-23 17:49:44 +08:00
hch
3effb630c5 去掉短剧页面的顶部状态栏,修复闪退bug 2024-01-23 17:49:37 +08:00
0e596e9be9 修复测试反馈BUG 2024-01-23 17:33:55 +08:00
hch
0a70cc027f 埋点-短剧 2024-01-23 16:09:51 +08:00
47077a465a 修复PK显示问题:【是用户先进 主播后挂断会显示对方信息角标】
调整直播间游戏半屏UI
调整战令UI
2024-01-23 15:28:13 +08:00
hch
aa62dda844 短剧优化 2024-01-23 14:05:33 +08:00
18401019693
004a5b2a53 优化 2024-01-23 11:14:58 +08:00
18401019693
64f9cee3af 优化 2024-01-23 09:50:34 +08:00
1eb43fb8cf 修复PK排位赛信息问题 2024-01-20 15:15:30 +08:00
18401019693
f770370be0 多人pk邀请人数限制 2024-01-20 13:59:27 +08:00
e185eb8612 调整webView
调整客服页面
2024-01-19 11:10:06 +08:00
299add0575 调整webView
调整客服页面
2024-01-19 11:06:35 +08:00
feb08e1759 调整webView
调整客服页面
2024-01-19 10:51:44 +08:00
16755a9c8c 调整webView
调整客服页面
2024-01-19 10:43:40 +08:00
hch
d6e6164549 新增谷歌服务判断 2024-01-19 10:28:46 +08:00
52c561fa08 修改战令入口颜色 2024-01-18 18:22:41 +08:00
c2979b4cf7 修改战令入口颜色 2024-01-18 18:04:07 +08:00
18401019693
0f70f10aed 多人PK结束以后横竖屏修改 2024-01-18 16:50:39 +08:00
45f471055e 調整戰令相關UI 2024-01-18 16:20:55 +08:00
18401019693
c49c9c2f81 Merge remote-tracking branch 'origin/master' 2024-01-18 16:09:45 +08:00
18401019693
74b19de11c 多人PK倒计时bug修改 2024-01-18 16:09:34 +08:00
hch
00b89aab69 合并 2024-01-18 15:15:04 +08:00
hch
6ff6cbbe6b Merge branch 'master' into pdlive_samsung
# Conflicts:
#	config.gradle
#	live/src/main/java/com/yunbao/live/views/SystemMessageViewHolder.java
2024-01-18 14:56:27 +08:00
hch
65d38de73c 修复 点击去支付无反应 问题 2024-01-18 14:48:49 +08:00
18401019693
ae6fdab7e8 多人PK倒计时bug修改 2024-01-18 14:48:29 +08:00
18401019693
3d1c4466e0 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	config.gradle
2024-01-18 13:38:34 +08:00
18401019693
9963b37b6f 多人PK倒计时bug修改 2024-01-18 13:37:49 +08:00
a8a1aa2e71 调整战令奖励列表的渲染
更改战令获取金币接口
2024-01-18 10:57:21 +08:00
2857127285 调整战令页面间距
修复【战令余额不足未跳转充值页面】问题
修复【战令开通等级时弹框金额偶尔不显示问题】
调整战令获取金额接口
调整战令item名字显示模式
调整战令购买经验弹框显示
2024-01-17 16:21:25 +08:00
hch
3a1268aac7 修改UI 2024-01-17 11:26:15 +08:00
hch
5b813aff88 修改UI 2024-01-17 11:12:53 +08:00
18401019693
d9951b5a35 添加了接口控制文字消息和语音的展示 2024-01-15 17:59:18 +08:00
18401019693
f928fce069 Merge remote-tracking branch 'origin/master' 2024-01-15 17:58:53 +08:00
18401019693
d365389ff8 添加了接口控制文字消息和语音的展示 2024-01-15 17:58:47 +08:00
a4e98f3703 调整弹幕统计接口字段 2024-01-15 17:52:53 +08:00
hch
a307e6f864 修复 点击去支付无反应 问题 2024-01-15 17:24:07 +08:00
hch
b5c8f14994 Merge remote-tracking branch 'origin/master' 2024-01-15 17:11:40 +08:00
hch
34323fd983 修复首页直播通行证展示问题 2024-01-15 17:11:24 +08:00
18401019693
1f7c6c8adc 添加了接口控制文字消息和语音的展示 2024-01-15 17:04:36 +08:00
18401019693
29c2334ee0 添加了接口控制文字消息和语音的展示 2024-01-15 16:22:29 +08:00
18401019693
2aa50ecfc7 添加了接口控制文字消息和语音的展示 2024-01-15 16:15:00 +08:00
18401019693
58b2d69601 添加了接口控制文字消息和语音的展示 2024-01-15 14:31:59 +08:00
18401019693
e061e61088 Merge remote-tracking branch 'origin/master' 2024-01-15 13:59:52 +08:00
18401019693
24788d7e21 添加了接口控制文字消息和语音的展示 2024-01-15 13:59:46 +08:00
54693c0d37 修复【升级战令未刷新钻石余额】的问题 2024-01-15 13:33:53 +08:00
hch
27f136d6ca 修复首页直播通行证展示问题 2024-01-15 10:49:23 +08:00
hch
92d271ff93 TopGradual 更改引用 2024-01-15 10:22:08 +08:00
18401019693
0b15df41b8 Merge remote-tracking branch 'origin/语音房_6.6.4_'
# Conflicts:
#	common/src/main/res/values-en-rUS/string.xml
#	common/src/main/res/values-zh-rHK/strings.xml
#	common/src/main/res/values-zh-rTW/strings.xml
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
2024-01-15 10:03:00 +08:00
18401019693
f22f8fcb4b 语音房bug修改 2024-01-15 10:01:00 +08:00
hch
7627219e77 新增首页战令入口 2024-01-12 15:33:16 +08:00
18401019693
cf749d85c6 语音房bug修改 2024-01-12 15:16:40 +08:00
hch
ac6eb21e06 Merge remote-tracking branch 'origin/master' into 6.6.4_推送管理
# Conflicts:
#	common/src/main/java/com/yunbao/common/utils/RouteUtil.java
#	common/src/main/res/values-en-rUS/string.xml
#	common/src/main/res/values-zh-rHK/strings.xml
#	common/src/main/res/values-zh-rTW/strings.xml
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
#	config.gradle
2024-01-12 13:52:03 +08:00
18401019693
5ca13b770c 语音房bug修改 2024-01-12 09:34:29 +08:00
7795317c14 修复【直播间信件往下滑,信件界面会显示为空】 2024-01-11 16:10:17 +08:00
18401019693
a036e5eff6 语音房接口添加 2024-01-11 15:15:21 +08:00
c6fb323c68 update 2024-01-11 13:23:20 +08:00
18401019693
926a1a3cb6 语音房添加 2024-01-10 15:48:10 +08:00
d629ee3f9f 修复切到其他页面后短剧不会暂停播放的问题
修复短剧购买卷重复弹出对话框问题
2024-01-09 17:31:13 +08:00
df0d9795ed 调整客服页面返回键交给h5处理(含系统按键/手势)
统一客服页面为同一个WebView
调整短剧与社区使用原生分开加载
调整送礼-包裹接口为新接口
调整粉丝团礼物送礼为新接口
修复战令-兑换-列表页面在名字过长晴空下高度不统一
2024-01-09 16:42:09 +08:00
1759757176 调整客服页面返回键交给h5处理(含系统按键/手势)
统一客服页面为同一个WebView
调整短剧与社区使用原生分开加载
调整送礼-包裹接口为新接口
调整粉丝团礼物送礼为新接口
修复战令-兑换-列表页面在名字过长晴空下高度不统一
2024-01-09 16:37:43 +08:00
hch
661c2fd334 推送管理 2024-01-09 14:52:27 +08:00
b175e42ee1 新增戰令入口
調整戰令UI顯示不全問題
2024-01-05 17:30:11 +08:00
6cdc648ea9 修复战令购买后未刷新UI问题 2024-01-05 16:37:04 +08:00
18401019693
85ccbcccae xiugai问题 2024-01-05 14:44:28 +08:00
0a6b210a76 修复合并重复问题 2024-01-05 14:09:08 +08:00
43d1abbae0 Merge branch 'dev_6.6.4_战令'
# Conflicts:
#	common/src/main/res/values-en-rUS/string.xml
#	common/src/main/res/values-zh-rHK/strings.xml
#	common/src/main/res/values-zh-rTW/strings.xml
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
#	config.gradle
2024-01-05 14:00:39 +08:00
4a5e581978 新增战令购买经验对话框经验条上加入经验文本 2024-01-05 13:57:55 +08:00
8a7b65955c Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2024-01-05 13:57:16 +08:00
18401019693
3445ee2a56 修改信件未读消息请求方式 2024-01-05 13:29:01 +08:00
cf2274195f Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2024-01-04 18:19:23 +08:00
18401019693
aad607ace8 删除老的用户等级主播等级的图标 2024-01-04 15:07:12 +08:00
17506f6cbf Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2024-01-04 10:36:22 +08:00
18401019693
418073f07e Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2024-01-04 10:35:51 +08:00
18401019693
319295de57 接口 改造 2024-01-04 10:35:42 +08:00
hch
dcf3f5c308 推送管理 2024-01-03 18:28:49 +08:00
30bdf83724 调整PK天梯赛信息更新由接口转为IM推送 2024-01-02 15:42:55 +08:00
fbb801bf62 调整战令接口
调整战令购买等级最大值
新增战令统计直播间发送消息接口
2023-12-26 18:24:46 +08:00
18401019693
44601c74b4 隐藏测试按钮 2023-12-23 16:39:58 +08:00
2808e41288 修复测试反馈问题 2023-12-23 14:14:32 +08:00
05aea2448f 修复测试反馈问题 2023-12-23 11:01:28 +08:00
34986d6897 修复测试反馈问题 2023-12-23 09:59:30 +08:00
924afce81e Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-22 14:32:33 +08:00
48dcfd4a04 新增战令系统 2023-12-22 14:32:18 +08:00
18401019693
163ab1c80d 隐藏测试按钮 2023-12-22 13:36:09 +08:00
e06f944def Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-22 10:08:35 +08:00
18401019693
a5d546d1d1 兑换奖励接口 2023-12-22 10:07:47 +08:00
1304ec6cde Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-21 16:26:04 +08:00
18401019693
f46d14ce28 购买经验 2023-12-21 15:24:43 +08:00
e2005582fe 修复活动弹窗重影问题 2023-12-21 10:22:36 +08:00
a56a5849fe Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-20 17:49:30 +08:00
18401019693
1495bfee99 兌換詳情弹窗 2023-12-20 17:37:18 +08:00
19eecd7022 Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-20 17:16:31 +08:00
18401019693
d415efde35 战令等级添加参数 2023-12-20 16:36:13 +08:00
61ee585ae9 Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-20 16:27:44 +08:00
18401019693
7b103aaf7c 添加开通接口弹窗回调添加 2023-12-20 16:27:22 +08:00
18401019693
9eba98f709 添加开通接口弹窗回调添加 2023-12-20 16:26:24 +08:00
dea3264577 Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-20 16:17:37 +08:00
18401019693
42cfcce1dc 添加开通接口弹窗回调添加 2023-12-20 15:28:41 +08:00
hch
f697e650a8 更新 2023-12-19 18:29:48 +08:00
18401019693
ac78268a85 活动结束弹窗 2023-12-19 15:59:55 +08:00
hch
9d543f2211 Merge remote-tracking branch 'origin/master' into pdlive_samsung
# Conflicts:
#	config.gradle
#	main/src/main/java/com/yunbao/main/activity/GoogleFragment.java
2023-12-19 09:41:04 +08:00
d001712067 Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-18 16:54:05 +08:00
18401019693
dc28a3d81e zhanling dengji 2023-12-18 16:53:37 +08:00
728dec9d97 Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-18 16:34:19 +08:00
18401019693
f65c698602 短剧 CookieManager cookieManager = CookieManager.getInstance(); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
    cookieManager.setAcceptThirdPartyCookies(mainWv, true);
    } else{
      cookieManager.setAcceptCookie(true);
    }
2023-12-18 15:30:29 +08:00
18401019693
215a2f8927 战令等级 2023-12-18 15:18:42 +08:00
hch
a21352302b 三星内购调用 2023-12-15 18:29:12 +08:00
18401019693
0b3ebdfc30 整蛊修改 2023-12-15 17:17:18 +08:00
18401019693
f10657f89c 弹窗整合 2023-12-15 17:01:35 +08:00
18401019693
0dc1009661 观看时长问题修改 2023-12-15 15:16:09 +08:00
18401019693
302b789970 多人Pk问题 2023-12-15 14:03:42 +08:00
18401019693
bd9c8c2265 直播间退出接口时机修改,免费礼物的修改 2023-12-15 10:17:47 +08:00
58ace7d613 Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-15 09:49:17 +08:00
hch
c723e14182 三星内购调用 2023-12-14 18:28:39 +08:00
18401019693
3982afb0d3 弹窗整合 2023-12-14 17:39:05 +08:00
18401019693
280920d5f0 弹窗整合 2023-12-14 17:28:49 +08:00
18401019693
e25980cec8 開通精英戰令 2023-12-14 15:10:25 +08:00
18401019693
e353638ec1 開通精英戰令 2023-12-14 14:49:45 +08:00
18401019693
65a3a64d7d 战令规则页面弹窗 2023-12-14 10:52:44 +08:00
b10312118b Merge remote-tracking branch 'origin/dev_6.6.4_战令' into dev_6.6.4_战令 2023-12-12 17:59:09 +08:00
18401019693
419b5d08b4 升級精英/尊享戰令弹窗 2023-12-12 17:43:12 +08:00
18401019693
b146f93029 添加弹窗,弹窗通用背景 2023-12-12 16:59:33 +08:00
18401019693
ef34a6cf4f 添加弹窗,弹窗通用背景 2023-12-12 16:36:31 +08:00
18401019693
213319d5d3 修改 测试问题 2023-12-12 15:17:09 +08:00
18401019693
ddb4e98afd 修改 测试问题 2023-12-12 14:40:48 +08:00
18401019693
8dbc0dad7a 修改 测试问题 2023-12-12 14:39:07 +08:00
cdf3c4e411 调整弹窗显示 2023-12-12 13:51:02 +08:00
18401019693
446dc271d6 Merge remote-tracking branch 'origin/master' 2023-12-12 10:51:41 +08:00
18401019693
ecca9b7b8b 修改 测试问题 2023-12-12 10:51:30 +08:00
hch
331b55a5ab 修复跳转到华为商店问题 2023-12-08 15:32:18 +08:00
hch
f8ac4617ed 新增判断 判断是否有google服务 2023-12-08 14:17:36 +08:00
18401019693
d0b7b065b2 修改 测试问题 2023-12-07 18:18:27 +08:00
53e6cb5a70 新增首页弹窗更多条件控制 2023-12-07 18:01:48 +08:00
hch
ad79190a61 三星内购 2023-12-07 17:46:22 +08:00
18401019693
5895fa8d71 修改 测试问题 2023-12-07 17:38:38 +08:00
hch
48886374d5 修复支付弹窗问题 2023-12-07 17:31:08 +08:00
18401019693
32d0fe497d 修改 测试问题 2023-12-07 16:29:28 +08:00
18401019693
6347473d0c 修改 测试问题 2023-12-07 13:24:41 +08:00
6df819f3b1 Merge remote-tracking branch 'origin/master' 2023-12-07 13:20:24 +08:00
39fbe90e8b Merge branch 'dev_6.6.3' 2023-12-07 13:19:53 +08:00
18401019693
3396ca63d0 修改 测试问题 2023-12-07 13:17:12 +08:00
8c5ae3b61a 新增首页弹窗更多条件控制 2023-12-07 13:04:41 +08:00
18401019693
484891cfe7 修改 测试问题 2023-12-06 17:13:14 +08:00
18401019693
5c00e065d2 Merge remote-tracking branch 'origin/dev_6.6.3'
# Conflicts:
#	common/src/main/java/com/yunbao/common/bean/IMLoginModel.java
#	config.gradle
2023-12-06 15:20:39 +08:00
18401019693
2857bb09a2 修改 测试问题 2023-12-06 15:18:42 +08:00
18401019693
79c1c01003 修改 测试问题 2023-12-06 13:45:37 +08:00
a2864187ed update 2023-12-06 13:42:10 +08:00
ef76b2c7e4 修复主页classtab可能为空导致的闪退问题(报错平台反馈) 2023-12-06 13:17:13 +08:00
18401019693
05e7c4ffdd 修改 测试问题 2023-12-05 18:28:53 +08:00
18401019693
b4b1efe730 修改 测试问题 2023-12-05 09:27:47 +08:00
0c7ae4e4f7 update 2023-12-04 18:19:22 +08:00
hch
b35d5d242e 修复支付弹窗问题 2023-12-01 13:33:49 +08:00
b8264997f4 修复礼物弹框闪退问题 2023-12-01 13:11:08 +08:00
18401019693
2ab1b31a94 修改 测试问题 2023-11-30 16:47:47 +08:00
hch
9dba41c22f 修复支付问题 2023-11-30 16:15:23 +08:00
18401019693
f1645cf72c 修改 测试问题 2023-11-30 15:18:28 +08:00
18401019693
cd35b9e40f 短剧 2023-11-30 14:37:16 +08:00
18401019693
33bce43737 短剧 2023-11-30 14:34:45 +08:00
18401019693
1f8025599b 小礼物逻辑上传 2023-11-30 10:45:45 +08:00
hch
7c44f09803 修复支付问题 2023-11-29 18:26:59 +08:00
hch
0b89ac21e8 lib_google
lib_huawei
2023-11-28 18:28:47 +08:00
hch
238181deca lib_google
lib_huawei
2023-11-27 17:12:27 +08:00
18401019693
71a7b47d42 修改语音连麦的逻辑 2023-11-27 17:00:30 +08:00
18401019693
59446d2518 小礼物逻辑第4次上传 2023-11-27 15:40:10 +08:00
18401019693
8a8a7a8326 小礼物逻辑第san次上传 2023-11-27 10:38:20 +08:00
18401019693
089b1881f1 小礼物逻辑第er次上传 2023-11-25 13:54:31 +08:00
18401019693
7b1f3ddd79 小礼物逻辑第一次上传 2023-11-24 13:41:21 +08:00
18401019693
56ae033523 修改中英文刷新数据问题 2023-11-16 18:30:10 +08:00
18401019693
f772a64507 修改中英文刷新数据问题 2023-11-16 13:05:56 +08:00
18401019693
8c75317ddc 修改测试问题 2023-11-15 14:27:56 +08:00
18401019693
256c19a09e 修改测试问题 2023-11-14 18:13:14 +08:00
18401019693
82c1d15ca5 修改测试问题 2023-11-14 16:05:43 +08:00
18401019693
aff5a178a0 普通公屏聊天把之前的限制50个字 修改成 100个字。其他两个个不变 2023-11-14 11:31:20 +08:00
18401019693
04b1764f7b 修改测试问题 2023-11-13 15:57:24 +08:00
18401019693
076f7130c8 修改测试问题 2023-11-13 14:24:07 +08:00
18401019693
b7842d5ac9 添加创建游戏的时候星币配置的接口 2023-11-11 15:43:41 +08:00
18401019693
eb5aebda51 修改bug 2023-11-11 14:17:16 +08:00
18401019693
6990bd13ed 修改判断 2023-11-11 10:43:05 +08:00
18401019693
c18af48fbb 修改中英文 2023-11-11 09:59:01 +08:00
18401019693
68e6f5df1b 添加新接口扣款添加星币筛选 2023-11-10 17:49:59 +08:00
18401019693
dc46ffc5b6 修改参数问题 2023-11-10 11:26:44 +08:00
18401019693
c58a5a9a55 注册的接口加一个字段 langue=语言 默认 'chinese' , 英语 'english'
Login.UserReg
Login.userLoginByThird
两个接口
2023-11-10 09:57:47 +08:00
18401019693
e2ee742091 当用户设置语言为英语时,将会调整首页推送逻辑,【英语】标签将会提前到第一位并默认展示【英语】标签内容; 2023-11-09 18:07:21 +08:00
18401019693
bcc86a899c 在游戏列表和游戏房间页面增加【游戏记录】按钮,点击可跳转至游戏记录界面,界面记录用户所参与的对局记录; 2023-11-09 15:48:28 +08:00
18401019693
b60031d2d0 游戏列表,搜索添加字段 2023-11-08 14:00:05 +08:00
18401019693
54aa5fee67 游戏规则页面 2023-11-08 10:23:45 +08:00
18401019693
816fd21fbc 1. 增加星币参与游戏的入口(先开通并隐藏,暂不开放)后台控制还未添加
在游戏门槛处,用户可自由选择金币或星币作为游戏门槛,并可输入相应数量,星币门槛为10~1000,数量必须为10的倍数;
2023-11-07 16:13:40 +08:00
18401019693
92e6b563e4 ● 游戏结束后,会出现两个选择:【再来一局】或【退出】,不论点击哪个按钮,都将回到游戏初始界面,用户都需手动再次上位、准备,修改为点击【退出】的逻辑不变,点击【再来一局】时,将会先判断用户的金豆数量是否足够参与下局游戏,若金豆数量不足,则弹出不足的提示,并回到游戏初始界面,若金豆数量足够,则直接回到座位,并自动准备本局游戏;
● 在点击加入游戏和开始游戏时,进行双重的金豆余额判断,如果余额不够进行游戏,则无法开始,并提示‘金豆不足’,如果两次判断余额充足,则正常开始游戏;
● 随机游戏房间名:1.一起玩吧! 2.來戰斗吧! 3.你的籌碼我收下了! 4.在線等遊戲夥伴~ 5.決戰到天亮 6.‘用户名’的房间(例:范德萨的房间
2023-11-07 14:46:45 +08:00
18401019693
d7bc2615d6 主播端不关闭连麦直接下播导致的问题。修复,用户端因为主播下播没关连麦导致的连麦状态不对的修复 2023-11-01 18:09:55 +08:00
18401019693
21473b1d69 修改已经连麦列表无名称问题,修改连麦列表覆盖问题,添加主播关播强制关闭连麦权限 2023-11-01 16:05:22 +08:00
18401019693
9bdee649d8 修改已经连麦列表无名称问题,修改连麦列表覆盖问题 2023-11-01 14:07:23 +08:00
18401019693
f1a8cd3a68 Android连麦申请列表重构,申请列表重构,连麦列表重构,按钮判断逻辑修改,主播断开连麦接口重构 2023-11-01 09:55:42 +08:00
18401019693
a39005b1a9 2、安卓主播开播, 安卓用户申请连麦后 ,安卓用户这边底部画面不全 2023-10-30 15:12:00 +08:00
18401019693
ec1197e5da 遊戲bug修復 2023-10-30 11:23:46 +08:00
18401019693
45372f37b8 遊戲bug修復 2023-10-28 17:55:36 +08:00
18401019693
df93b9495a 遊戲bug修復 2023-10-28 17:13:25 +08:00
18401019693
86d730cdf1 遊戲bug修復 2023-10-28 14:19:11 +08:00
18401019693
f356446c03 小遊戲bug修改 2023-10-27 16:20:19 +08:00
hch
bf7bc6b786 修复异常 Fragment 创建传值问题 2023-10-27 15:42:03 +08:00
18401019693
6a3c15e3e7 Merge remote-tracking branch 'origin/master' 2023-10-26 16:42:41 +08:00
18401019693
07d3b190e2 啟動頁跳轉廣告點擊返回鍵退出應用問題修復 2023-10-26 16:42:34 +08:00
18142669586
27b316d39b Merge remote-tracking branch 'origin/master' 2023-10-26 16:26:22 +08:00
18142669586
f928fcd4bc 去掉邀请码上传头像功能 2023-10-26 16:25:31 +08:00
hch
adcafc7e16 处理 空指针异常
CommonAppContext.getTopActivity()==null
主要处理了 ToastUtil,OpenAdManager
2023-10-26 14:50:17 +08:00
18401019693
b191dfb1be 小遊戲提醒 2023-10-26 14:42:09 +08:00
18401019693
192b0cb417 小遊戲提醒 2023-10-25 10:09:38 +08:00
18142669586
a2435cbe95 Merge remote-tracking branch 'origin/master' 2023-10-24 15:54:01 +08:00
18142669586
8925d478da 去掉邀请码上传头像功能 2023-10-24 15:51:48 +08:00
18401019693
926de0463c 小遊戲修改 2023-10-24 15:46:47 +08:00
18401019693
993b09b853 修改中英文 2023-10-19 09:45:45 +08:00
18142669586
e6c8495158 1 2023-10-18 22:21:57 +08:00
18401019693
e15953dc64 修改中英文 2023-10-18 17:26:37 +08:00
18401019693
4ff529ace9 修改中英文 2023-10-17 17:59:10 +08:00
18401019693
530622c72c 修改中英文 2023-10-17 17:47:32 +08:00
18401019693
7f38739ddc 修改中英文 2023-10-17 15:59:58 +08:00
18401019693
bc549d52f6 修改中英文 2023-10-17 15:00:20 +08:00
18401019693
14f0de093e 修改中英文 2023-10-16 18:23:59 +08:00
18401019693
51457738d2 修改中英文 2023-10-16 17:01:46 +08:00
18401019693
d8a0427791 修改中英文 2023-10-16 16:30:54 +08:00
18401019693
8abf92d42c 修改中英文 2023-10-14 17:05:14 +08:00
18401019693
fc2d700a31 修改中英文 2023-10-13 17:48:42 +08:00
18401019693
867029fa3a 修改中英文 2023-10-13 16:06:07 +08:00
18401019693
52e199b901 修改中英文 2023-10-13 14:09:34 +08:00
18401019693
c5d537fb4d 修改中英文 2023-10-12 16:19:27 +08:00
18401019693
e081f1e058 修改中英文 2023-10-12 14:26:10 +08:00
18401019693
7bedf93dd5 修改中英文 2023-10-11 17:26:07 +08:00
18401019693
000929fd02 修改中英文 2023-10-11 15:40:39 +08:00
18401019693
3e5ba2ba19 修改中英文 2023-10-11 13:39:31 +08:00
18401019693
a25ce2034f 修改中英文 2023-10-10 17:03:51 +08:00
18401019693
1625135d0c 修改中英文 2023-10-10 14:15:52 +08:00
18401019693
add3589683 修改中英文 2023-10-09 17:57:03 +08:00
18401019693
45a5d6ad61 修改中英文 2023-10-09 17:24:05 +08:00
18401019693
7b62409bf1 中英文修改 2023-10-09 16:27:54 +08:00
18401019693
45ad8a9810 盲盒头条英文图 2023-10-07 16:14:59 +08:00
18401019693
a788c347de 修改游戏的new图标展示,网页中英文检测 2023-10-07 15:20:13 +08:00
18401019693
c3d4cb8d62 Merge branch 'master' into master_sud
# Conflicts:
#	common/src/main/java/com/yunbao/common/http/PDLiveApi.java
#	common/src/main/java/com/yunbao/common/http/live/LiveNetManager.java
#	common/src/main/res/values-en-rUS/string.xml
#	common/src/main/res/values-zh-rHK/strings.xml
#	common/src/main/res/values-zh-rTW/strings.xml
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
#	config.gradle
2023-10-07 14:46:22 +08:00
18401019693
916a1447f0 修改游戏的new图标展示,网页中英文检测 2023-10-07 14:21:20 +08:00
18401019693
b2ee091c03 修改游戏的new图标展示,网页中英文检测 2023-10-07 11:21:03 +08:00
18401019693
cf169e9a4e 修改游戏的new图标展示,网页中英文检测 2023-10-07 10:31:13 +08:00
18401019693
ea0ff70595 网页拼接中英文字段 2023-10-06 17:04:52 +08:00
18401019693
ce37918745 修改游戏问题 2023-10-06 16:30:02 +08:00
18401019693
260b615d2b 修改游戏问题 2023-10-06 15:43:34 +08:00
18401019693
45566001a7 修改游戏问题 2023-10-06 15:22:59 +08:00
18401019693
12482b6624 修改游戏问题 2023-10-06 13:45:47 +08:00
18401019693
2d7684a5c8 添加游戏列表点击查询游戏状态 2023-10-06 10:01:39 +08:00
18401019693
c1d6b67322 游戏修改,粉丝团提示修改 2023-10-05 15:44:37 +08:00
18401019693
69c0d5f28a 游戏修改,粉丝团提示修改 2023-10-05 13:46:45 +08:00
18401019693
d541a4d506 游戏修改,粉丝团提示修改 2023-10-05 10:52:54 +08:00
18401019693
4b46c72f5a 中英文页面适配修改 2023-09-28 14:47:10 +08:00
18401019693
e191fb82a3 sud游戏搜索页面优化更改 2023-09-27 17:20:29 +08:00
18401019693
32ebfa719d sud游戏搜索页面优化更改 2023-09-27 17:08:14 +08:00
18401019693
c94f549531 修改随机开始,修改游戏的上座和结束的逻辑,添加中英文 2023-09-27 16:28:43 +08:00
18401019693
6d98afa319 全屏游戏 2023-09-27 14:06:22 +08:00
20ccb37ccf udpate 2023-09-27 09:34:07 +08:00
18401019693
79fa6be313 全屏游戏 2023-09-26 16:05:30 +08:00
18401019693
210e755fed 游戏列表跳转游戏 2023-09-26 14:56:55 +08:00
a6faf12ff6 update 首页游戏专区 2023-09-26 10:31:39 +08:00
18401019693
e2148c3f31 游戏列表跳转游戏 2023-09-26 09:53:00 +08:00
18401019693
869ca75464 游戏列表跳转游戏 2023-09-25 17:56:52 +08:00
18401019693
ce48a7dcf0 创建游戏,游戏界面完成 2023-09-25 17:24:49 +08:00
18401019693
866024ae95 创建游戏,游戏界面完成 2023-09-25 11:25:03 +08:00
b40abb9bc0 fix 修复PK过程中挂后台导致摄像头关闭问题 2023-09-25 09:54:39 +08:00
18401019693
37217e529e 创建游戏,游戏界面完成 2023-09-25 09:52:31 +08:00
18401019693
eedbbb5304 创建游戏房 2023-09-23 17:44:16 +08:00
4b3b47b65f update 布局&中英文 2023-09-23 10:31:06 +08:00
c015459cf1 fix 播放器url為空時不播放 2023-09-22 14:27:23 +08:00
326ced4646 update 餘額不足時彈框 2023-09-22 14:25:54 +08:00
18970c22cf update 餘額不足時彈框 2023-09-22 13:38:34 +08:00
00db8a00c7 fix 修复一些反馈的问题 2023-09-22 11:05:02 +08:00
18401019693
0756dd9560 游戏房入口对接 2023-09-21 17:58:53 +08:00
f3195bbcf8 Merge branch 'dev_6.5.6' into master_tmp1 2023-09-21 13:28:01 +08:00
4e4b5f90bb fix 重复调用弹窗接口 2023-09-21 11:14:01 +08:00
3150489384 update 中英文
fix 播放器监测
2023-09-20 18:26:59 +08:00
9ff382e35b update 排位赛中英文IM消息 2023-09-16 18:22:51 +08:00
bcd8f7af52 Merge branch 'master' into dev_6.5.6 2023-09-16 14:38:28 +08:00
3595b89672 fix 调整英文PK惩罚倒计时过长问题 2023-09-16 14:24:06 +08:00
6e8842d30d update config加入网络失败提示 2023-09-16 14:23:43 +08:00
7b5cd9b797 update 中英文 2023-09-16 10:53:08 +08:00
9ec241f2a3 update 弹窗 2023-09-15 18:28:46 +08:00
a876018b0a update 弹窗布局 2023-09-15 18:12:40 +08:00
ea397af123 update 弹窗接口 2023-09-15 16:55:36 +08:00
a475e5dc2f update 中英文 2023-09-15 16:15:51 +08:00
54258c3f13 新增【弹框功能优化】 2023-09-15 14:54:45 +08:00
d383a08013 新增【弹框功能优化】 2023-09-15 10:04:02 +08:00
18401019693
fed6289f67 修复在直播间内跳转自己的直播间问题 2023-09-14 14:45:29 +08:00
18401019693
9b301eba25 修复在直播间内跳转自己的直播间问题 2023-09-12 17:14:17 +08:00
d1edf6bdd8 新增主播关播时,用户端调用离开房间 2023-09-11 10:41:56 +08:00
18401019693
7ca8694aed 修复在直播间内跳转自己的直播间问题 2023-09-11 10:29:16 +08:00
18401019693
406c67bf3a 修复在直播间内跳转自己的直播间问题 2023-09-08 15:58:32 +08:00
18401019693
d643df72bc 埋点配置 2023-09-08 15:57:33 +08:00
a3dacf1f59 调整盲盒说明页面地址 2023-09-06 18:28:10 +08:00
37605f46b7 調整中英文 2023-09-06 15:27:03 +08:00
18401019693
a4895df622 埋点配置 2023-09-06 14:40:45 +08:00
fc63cbbf50 update 2023-09-06 11:09:50 +08:00
b7ba717cb9 Merge branch 'dev_6.5.5'
# Conflicts:
#	common/src/main/res/values-en-rUS/string.xml
#	common/src/main/res/values-zh-rHK/strings.xml
#	common/src/main/res/values-zh-rTW/strings.xml
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
#	live/src/main/java/com/yunbao/live/views/LiveRoomViewHolder.java
2023-09-06 10:42:05 +08:00
0c5ef0befe 跳转七日留存:新增点击打开用户卡片,新增@用户名颜色 2023-09-05 17:59:29 +08:00
18401019693
84d4423069 埋点配置 2023-09-05 14:25:50 +08:00
18401019693
0721ff4f37 Merge branch 'master_umeng'
# Conflicts:
#	app/proguard-rules.pro
#	common/src/main/res/values/strings.xml
#	live/src/main/java/com/yunbao/live/dialog/LiveGiftPopup.java
2023-09-05 14:18:42 +08:00
a5b0ee757c 新增H5网页跳转直播间并打开礼物栏选中礼物功能 2023-09-05 13:52:45 +08:00
18401019693
82fd35305a 埋点配置 2023-09-05 13:24:21 +08:00
e89e82483b 调整中英文
调整开播频道接口
2023-09-04 16:25:06 +08:00
57bda40a82 调整中英文
调整开播频道接口
2023-09-02 11:16:01 +08:00
18401019693
93aa8e55e5 埋点配置 2023-09-02 10:10:50 +08:00
2e907bc1ed 移除重复点击事件监听 2023-08-31 16:16:36 +08:00
8d984951c3 新增主動PK結束通知 2023-08-31 15:14:15 +08:00
18401019693
dd6600a89e 埋点配置 2023-08-31 09:56:41 +08:00
18401019693
233469c7e4 埋点配置 2023-08-30 13:44:56 +08:00
357ece2ebe 补充Android13权限问题 2023-08-30 13:28:42 +08:00
18401019693
72e9633045 埋点配置 2023-08-30 13:21:40 +08:00
18401019693
7a31ca52da 埋点配置 2023-08-30 09:44:26 +08:00
18401019693
33930f3e32 埋点配置 2023-08-29 13:35:38 +08:00
8e19ddcc99 兼容Android13 连麦权限问题 2023-08-29 09:54:21 +08:00
18401019693
1a58311e26 埋点配置 2023-08-28 16:52:19 +08:00
9360e5438c 修复svga库被混淆不显示问题 2023-08-28 10:30:56 +08:00
fa8d8a7421 调整红包倒计时范围 2023-08-28 10:14:38 +08:00
18401019693
bebf0820ca 埋点配置 2023-08-25 15:12:48 +08:00
e1f4e8dde4 update 6.5.5 2023-08-24 16:31:14 +08:00
2b59b1b4fe Merge branch 'master' into dev_6.5.5 2023-08-24 15:02:05 +08:00
cee1476ab1 update 6.5.5 2023-08-24 14:26:38 +08:00
20a6bae5ff 更新美颜中英文繁体 2023-08-24 13:34:08 +08:00
b6732adfd2 调整礼物栏触碰事件拦截 2023-08-24 10:54:12 +08:00
bec031688e 调整礼物栏触碰事件拦截 2023-08-24 10:46:40 +08:00
7f11195ede update 6.5.5 2023-08-24 09:43:33 +08:00
d4ff9c3072 兼容Android13权限问题 2023-08-23 17:10:52 +08:00
0a442e7df9 兼容Android13权限问题 2023-08-23 17:02:16 +08:00
52f1a78e36 update 红包优化 2023-08-23 16:37:53 +08:00
18401019693
6bcaf2ba9c 埋点配置 2023-08-23 15:05:32 +08:00
18401019693
75b176a81e 6.5.4礼物冠名 2023-08-22 14:10:09 +08:00
18401019693
3de972d12c 6.5.4礼物冠名 2023-08-22 14:03:04 +08:00
18401019693
95252f2f02 6.5.4礼物冠名 2023-08-22 13:31:32 +08:00
18401019693
3b351ffad8 6.5.4礼物冠名 2023-08-22 10:13:57 +08:00
2332255b37 礼物栏等级页面补充中英文标识 2023-08-21 17:38:41 +08:00
18401019693
90c2f3e2da 6.5.4礼物冠名 2023-08-21 17:20:29 +08:00
18401019693
644615ffc0 6.5.4礼物冠名 2023-08-21 15:28:29 +08:00
18401019693
345c9067aa 6.5.4礼物冠名 2023-08-19 16:05:30 +08:00
18401019693
c20312b982 6.5.4礼物冠名 2023-08-18 18:43:47 +08:00
18401019693
643568f63b 6.5.4礼物冠名 2023-08-18 18:08:15 +08:00
18401019693
f03ba293b5 6.5.4礼物冠名 2023-08-18 17:43:13 +08:00
a2f954b28a Merge remote-tracking branch 'origin/master' 2023-08-18 14:48:41 +08:00
d4549793d1 调整头像相关 2023-08-18 14:48:33 +08:00
18401019693
5da8855948 6.5.4礼物冠名 2023-08-18 14:17:46 +08:00
18401019693
348dca8474 6.5.4礼物冠名 2023-08-18 13:49:20 +08:00
18401019693
5afed0ee43 6.5.4礼物冠名 2023-08-18 11:04:21 +08:00
18401019693
d065726274 6.5.4礼物冠名 2023-08-17 18:20:53 +08:00
18401019693
8f9d558043 6.5.4礼物冠名 2023-08-17 18:15:25 +08:00
c594364574 调整关注逻辑 2023-08-17 17:17:06 +08:00
18401019693
68140c8a3a 6.5.4礼物冠名 2023-08-17 15:38:59 +08:00
5a4001c858 调整退出粉丝团逻辑 2023-08-17 13:45:40 +08:00
18401019693
d5953bd651 6.5.4礼物冠名 2023-08-17 13:19:04 +08:00
18401019693
17a2f5e091 6.5.4礼物冠名 2023-08-16 20:01:46 +08:00
18401019693
b2297b062e 6.5.4礼物冠名 2023-08-16 18:17:26 +08:00
b53472de28 調整文案 2023-08-16 17:32:29 +08:00
18401019693
9bce235817 6.5.4礼物冠名 2023-08-16 16:23:28 +08:00
18401019693
60ce5572cf 6.5.4礼物冠名 2023-08-16 15:37:07 +08:00
18401019693
3761cad653 6.5.4礼物冠名 2023-08-16 14:50:50 +08:00
18401019693
2a276503b5 6.5.4礼物冠名 2023-08-16 13:31:56 +08:00
fd0ad36a58 补充英文 2023-08-15 14:07:33 +08:00
41c8783b53 调整一处英文UI下字数过长问题 2023-08-15 14:00:36 +08:00
18401019693
7226add482 6.5.4礼物冠名 2023-08-15 11:29:39 +08:00
09e5f9b984 Merge remote-tracking branch 'origin/master' 2023-08-15 10:37:25 +08:00
83ee1f94df 修复一处闪退bug 2023-08-15 10:37:06 +08:00
18401019693
7e1b054a98 6.5.4礼物冠名 2023-08-14 21:05:15 +08:00
18401019693
9e0c437af5 6.5.4礼物冠名 2023-08-14 19:55:58 +08:00
18401019693
ad26ae7db8 6.5.4礼物冠名 2023-08-14 19:16:02 +08:00
18401019693
84230e517e 6.5.4礼物冠名 2023-08-14 18:40:25 +08:00
9818a1af74 新增关注的code提示 2023-08-14 18:05:55 +08:00
f91ba79c5c 新增关注的code提示 2023-08-14 17:54:27 +08:00
18401019693
0ea9ffc2e8 6.5.4礼物冠名 2023-08-14 17:40:52 +08:00
18401019693
defb5fdd02 6.5.4礼物冠名 2023-08-14 15:50:35 +08:00
b443fdd683 调整页面英文UI 2023-08-14 14:58:13 +08:00
18401019693
c11c66f56f 6.5.4礼物冠名 2023-08-14 13:47:09 +08:00
18401019693
a618bee6b7 6.5.4礼物冠名 2023-08-14 11:18:33 +08:00
399bedb874 调整购买守护页面英文UI 2023-08-14 10:57:38 +08:00
18401019693
3b6e2d47f4 6.5.4礼物冠名 2023-08-14 10:31:01 +08:00
18401019693
96daf73a5c 6.5.4礼物冠名 2023-08-14 10:16:06 +08:00
18401019693
55cbd0ff5b 6.5.4礼物冠名 2023-08-11 14:37:35 +08:00
18401019693
a3ab9ff03e 6.5.4礼物冠名 2023-08-11 14:19:15 +08:00
18401019693
92ed82ea2a 6.5.4礼物冠名 2023-08-11 13:59:54 +08:00
18401019693
6d262d0c94 6.5.4礼物冠名 2023-08-11 09:42:18 +08:00
18401019693
ce1174a88c 6.5.4礼物冠名 2023-08-10 17:40:57 +08:00
18401019693
2406e9bede 6.5.4礼物冠名 2023-08-10 17:38:06 +08:00
9b3a011499 修复英文say hi下格式问题#131 2023-08-10 17:25:53 +08:00
18401019693
7859e7cb29 6.5.4礼物冠名 2023-08-10 17:16:14 +08:00
18401019693
1fd36e3ba0 6.5.4礼物冠名 2023-08-10 17:04:26 +08:00
ec3722b7d0 新增粉丝团任务红点提示 2023-08-10 16:53:00 +08:00
18401019693
35917a5848 6.5.4礼物冠名 2023-08-10 15:40:13 +08:00
18401019693
7ea6bb570d 6.5.4礼物冠名 2023-08-10 14:20:40 +08:00
18401019693
43f95260e6 6.5.4礼物冠名 2023-08-10 13:51:49 +08:00
18142669586
54a4c0b590 Merge remote-tracking branch 'origin/master' 2023-08-10 11:15:31 +08:00
18142669586
2057a3575f 修改融云结束连麦还有对方画面的问题 2023-08-10 11:15:06 +08:00
18401019693
aa809ebaf4 6.5.4礼物冠名 2023-08-10 10:03:01 +08:00
18401019693
dffa71e346 6.5.4礼物冠名 2023-08-09 18:11:42 +08:00
18401019693
fea46aab1f 6.5.4礼物冠名 2023-08-09 17:05:51 +08:00
18401019693
e032e0807b 6.5.4礼物冠名 2023-08-09 14:38:33 +08:00
18401019693
24ed313f60 6.5.4礼物冠名 2023-08-09 14:15:03 +08:00
18142669586
02b70d559d Merge remote-tracking branch 'origin/master' 2023-08-09 14:14:11 +08:00
18142669586
7a26e81f67 修改融云合流不成功的问题,退出画面有问题 2023-08-09 14:13:52 +08:00
d07439efcf update 主播端点礼物墙按钮置灰 2023-08-09 13:53:16 +08:00
18401019693
7bca817633 6.5.4礼物冠名 2023-08-09 13:31:31 +08:00
18401019693
bad2b3689a 6.5.4礼物冠名 2023-08-09 11:01:28 +08:00
3cc9b90151 update 简繁 2023-08-09 10:24:13 +08:00
18401019693
e32e6f95b0 6.5.4礼物冠名 2023-08-08 17:09:35 +08:00
18401019693
2162645da1 6.5.4礼物冠名 2023-08-08 16:12:07 +08:00
18401019693
af17c85366 6.5.4礼物冠名 2023-08-08 16:08:43 +08:00
18401019693
20c00e2896 6.5.4礼物冠名 2023-08-08 15:37:11 +08:00
18401019693
44b95cb1c6 6.5.4礼物冠名 2023-08-08 14:50:13 +08:00
40e744a533 修复粉丝团包裹为空时会关掉对话框的情况 2023-08-08 13:32:42 +08:00
18401019693
82ccd2d89c 6.5.4礼物冠名 2023-08-08 11:37:19 +08:00
18401019693
994bd2a3aa 6.5.4礼物冠名 2023-08-08 09:34:15 +08:00
3ab91a74d1 修复礼物栏自定义数量过长导致的闪退问题 2023-08-07 18:26:45 +08:00
18142669586
d899975495 修改融云合流不成功的问题 2023-08-07 18:06:46 +08:00
dbbe753492 调整【围观】为繁体 2023-08-07 17:05:33 +08:00
18401019693
017320ac09 6.5.4礼物冠名 2023-08-07 16:29:02 +08:00
7ce0bb3a7c 修复#93测试问题 2023-08-07 15:18:34 +08:00
18401019693
a78d5b4aba 6.5.4礼物冠名 2023-08-07 14:50:29 +08:00
37fd1e9f47 调整粉丝团入场英文翻译 2023-08-07 14:39:35 +08:00
18401019693
97ad28ba62 6.5.4礼物冠名 2023-08-07 13:36:59 +08:00
3304e8f673 update 粉丝团 2023-08-05 18:04:36 +08:00
e7daabe588 Merge branch 'dev_6.5.4' 2023-08-05 16:53:13 +08:00
d912ad6119 update 粉丝团 2023-08-05 16:52:48 +08:00
18401019693
dfeb1c246b 6.5.4礼物冠名 2023-08-05 15:22:49 +08:00
18401019693
9588619b73 6.5.4礼物冠名 2023-08-04 16:54:28 +08:00
18401019693
6ba71508ca 6.5.4礼物冠名 2023-08-04 16:28:43 +08:00
c8f443c105 Merge branch 'master' into dev_6.5.4_tmp 2023-08-04 16:15:25 +08:00
18401019693
ebcfddf450 6.5.4礼物冠名 2023-08-04 16:14:36 +08:00
e43fb5e479 Merge branch 'master' into dev_6.5.4_tmp 2023-08-04 16:13:14 +08:00
18401019693
0c096dcafc 6.5.4礼物冠名 2023-08-04 15:59:20 +08:00
18401019693
545c69ff8b 6.5.4礼物冠名 2023-08-04 15:50:32 +08:00
bf8d88608d Merge branch 'master' into dev_6.5.4_tmp
# Conflicts:
#	common/src/main/java/com/yunbao/common/http/PDLiveApi.java
#	common/src/main/java/com/yunbao/common/http/live/LiveNetManager.java
#	common/src/main/java/com/yunbao/common/utils/JavascriptInterfaceUtils.java
#	common/src/main/res/values-en-rUS/string.xml
#	common/src/main/res/values-zh-rHK/strings.xml
#	common/src/main/res/values-zh-rTW/strings.xml
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
#	live/src/main/java/com/yunbao/live/views/LiveRoomViewHolder.java
2023-08-04 15:41:11 +08:00
18401019693
45c256cbbb 6.5.4礼物冠名 2023-08-04 15:18:06 +08:00
18401019693
fb1d6c0688 6.5.4礼物冠名 2023-08-04 14:58:26 +08:00
fe921e25bb update 粉丝团 2023-08-04 14:25:49 +08:00
18401019693
d5ca010ec7 6.5.4礼物冠名 2023-08-04 13:57:36 +08:00
18401019693
04259472a1 Merge remote-tracking branch 'origin/dev_gift_6.5.4' 2023-08-04 10:19:18 +08:00
18401019693
d6032a0a0a 6.5.4礼物冠名 2023-08-04 10:18:05 +08:00
18401019693
4a7790d968 6.5.4礼物冠名 2023-08-04 10:11:03 +08:00
18401019693
49810407ce 6.5.4礼物冠名 2023-08-04 09:47:42 +08:00
18401019693
fa086dc5cb 6.5.4礼物冠名 2023-08-03 17:54:14 +08:00
18401019693
3f8d6ed4af 6.5.4礼物冠名 2023-08-03 16:08:43 +08:00
18401019693
986902886a 6.5.4礼物冠名 2023-08-03 14:29:04 +08:00
18401019693
8ed7595aec 6.5.4礼物冠名 2023-08-02 17:47:00 +08:00
18401019693
b78fef6adc 6.5.4礼物冠名 2023-08-02 16:55:36 +08:00
fae0d01dc2 update 粉丝团 2023-08-02 15:48:51 +08:00
9a8bc0505d 修复#248问题 2023-08-02 15:08:11 +08:00
e31fa0310c 修复#250问题 2023-08-02 14:43:20 +08:00
2705342e18 修复#249问题 2023-08-02 14:39:56 +08:00
1bdf3a2ba3 修复#246问题 2023-08-02 14:20:47 +08:00
18401019693
5afa3bd30b 6.5.4礼物冠名 2023-08-02 13:23:37 +08:00
18401019693
54458bd0c0 6.5.4礼物冠名 2023-07-31 18:25:32 +08:00
c53b9a6064 update 粉丝团相关 2023-07-31 16:28:42 +08:00
92d4130c45 update 粉丝团相关 2023-07-28 10:01:41 +08:00
18401019693
a353e51ba8 6.5.4礼物栏优化 2023-07-25 15:25:01 +08:00
18401019693
0646f245b6 6.5.4礼物栏优化 2023-07-25 14:08:52 +08:00
7e3ca79a01 调整随机PK弹框UI 2023-07-21 14:19:20 +08:00
dfeb3a78c1 修复英文模式下,购买是守护文案超长问题 2023-07-21 13:34:31 +08:00
e32d450ca1 修复随机PK文案在英文模式下过长问题 2023-07-21 09:45:11 +08:00
f29766731c 修复#232问题 2023-07-20 18:12:09 +08:00
736592b4e1 新增头像选择界面 2023-07-19 18:30:32 +08:00
3108c898c2 Merge branch 'dev_6.5.4_头像优化' into dev_6.5.4_联系方式 2023-07-19 15:35:48 +08:00
dd2f3db16a 新增头像选择界面 2023-07-19 15:35:30 +08:00
8af231e526 新增头像选择界面 2023-07-19 13:07:27 +08:00
fdbbc3b224 修復紅點檢測 2023-07-19 10:25:11 +08:00
1e4b0bb536 补充信箱角标点亮和防抖 2023-07-18 18:09:27 +08:00
4729f93515 新增获取联系方式的弹框新样式 2023-07-18 15:40:15 +08:00
18401019693
3376a57f9d 6.5.4版本更新-“@”功能优化 2023-07-18 13:48:37 +08:00
18401019693
410fcb5895 修改json的版本号 2023-07-17 16:56:29 +08:00
18401019693
9b62ab6011 修改json的版本号 2023-07-17 16:42:51 +08:00
18401019693
62aa1d63df 修改json的版本号 2023-07-17 16:15:04 +08:00
18142669586
034f2dba5d Merge remote-tracking branch 'origin/master'
# Conflicts:
#	app/src/main/AndroidManifest.xml
#	config.gradle
2023-07-17 16:11:17 +08:00
18142669586
aee1d6cad4 修改上架 2023-07-17 16:10:29 +08:00
9b01ae4d82 修改用户协议地址 2023-07-17 15:18:47 +08:00
16f23519b1 移除百度语言残留内容 2023-07-17 13:35:59 +08:00
9bfed0bc7b 移除腾讯地图残留配置和代码 2023-07-17 10:16:24 +08:00
18401019693
bc0ed26377 修复 2023-07-14 13:13:25 +08:00
18401019693
0d14ce5aee 修复 2023-07-14 11:14:10 +08:00
18142669586
4a7423a5b9 修改全服 2023-07-14 10:43:14 +08:00
18401019693
0a4425d705 修复 2023-07-14 10:18:50 +08:00
d4c2ef71cb 新增主播可导出日志功能 2023-07-13 10:39:33 +08:00
18401019693
7184b147d9 修复 2023-07-12 15:32:44 +08:00
39e9670cff 6.5.3 432 2023-07-12 15:05:39 +08:00
18401019693
7d7535935a 修复 2023-07-12 14:58:34 +08:00
18401019693
dd657e9918 修复 2023-07-12 13:50:00 +08:00
09cef4ade6 6.5.3 更新发送红包说明图片 2023-07-12 13:11:19 +08:00
7038681f8c 6.5.3 2023-07-11 17:57:55 +08:00
6cf68838f8 修改红包样式和文案 2023-07-11 17:25:51 +08:00
c3a08974d1 修改发红包样式 2023-07-11 14:14:33 +08:00
18401019693
20c25a51ef 修复 2023-07-11 11:44:02 +08:00
18401019693
9dfb772224 修复 2023-07-11 11:43:12 +08:00
18401019693
f5f68f16b7 Merge remote-tracking branch 'origin/master' 2023-07-11 11:34:47 +08:00
18401019693
7672cea263 修复 2023-07-11 11:34:40 +08:00
0a02b39c12 根据产品需求修改英文翻译
根据产品需求修改UI样式
2023-07-11 10:51:03 +08:00
18401019693
e786ae16c2 修复 2023-07-11 10:13:29 +08:00
18401019693
1d447b0a90 修复 2023-07-10 16:36:48 +08:00
18401019693
4918f9ba20 修复 2023-07-08 15:52:18 +08:00
18401019693
124b18dcb0 修复 2023-07-08 15:15:40 +08:00
18401019693
f55e9c0cfe 修复 2023-07-08 13:40:50 +08:00
18401019693
84c43f74bc 修复 2023-07-07 16:14:28 +08:00
18401019693
674346b6de 修复 2023-07-07 16:01:29 +08:00
18401019693
64f40d52da 修复 2023-07-07 10:34:25 +08:00
18401019693
75e342975c 修复 2023-07-07 10:15:11 +08:00
18401019693
dd4172fc3d 修复 2023-07-07 09:51:29 +08:00
6b7a98dbc3 修复#220问题 2023-07-06 17:59:00 +08:00
640dea5c53 修复#218问题 2023-07-06 17:47:03 +08:00
a907897239 修复#217问题 2023-07-06 17:45:23 +08:00
3807ff8916 修复红包出现在联系方式选项里 2023-07-06 17:00:02 +08:00
36b85c15f5 修复飘屏弹幕被禁言没提示问题 2023-07-06 16:44:07 +08:00
3e91068866 按测试反馈修改红包详情UI逻辑和新增礼物显示 2023-07-06 15:13:33 +08:00
18401019693
4d61c7e176 添加翻译 2023-07-06 10:17:14 +08:00
18401019693
c6d9dbc118 添加翻译 2023-07-06 10:02:18 +08:00
6743ec3cf6 调整红包查看手气的UI 2023-07-06 10:02:04 +08:00
18401019693
addab80997 添加翻译 2023-07-05 18:15:18 +08:00
18401019693
9081ba4cd6 添加翻译 2023-07-05 17:47:50 +08:00
18401019693
12bedd630e 修复问题 2023-07-05 17:07:11 +08:00
18401019693
003c7f9e12 修复问题 2023-07-05 16:19:30 +08:00
18401019693
9089e96856 修复问题 2023-07-05 13:29:46 +08:00
60c14f720d 修复【钻石不够的情况下,也成功发起红包】 2023-07-04 17:15:25 +08:00
18401019693
c139b03417 修复问题 2023-07-04 17:02:05 +08:00
b3fb6e62ae Merge remote-tracking branch 'origin/master' 2023-07-04 16:37:34 +08:00
613185db40 修复红包记录样式问题 2023-07-04 16:36:46 +08:00
18401019693
291492f2d0 修复问题 2023-07-04 16:29:34 +08:00
18401019693
cbe91c84e9 红包全服通知 2023-07-04 16:02:45 +08:00
f417e4ead8 修复部分机型开播异常问题 2023-07-04 13:03:19 +08:00
18401019693
08bc6d554a Merge remote-tracking branch 'origin/dev_red_packet'
# Conflicts:
#	common/src/main/java/com/yunbao/common/http/PDLiveApi.java
#	common/src/main/java/com/yunbao/common/http/live/LiveNetManager.java
#	common/src/main/res/values/strings.xml
2023-07-03 17:57:35 +08:00
18401019693
895a1c6951 红包中奖列表 2023-07-03 17:48:02 +08:00
b9f1474362 【绑定账号】和【忘记密码】页面手机号码栏新增国家名字提示 2023-07-03 16:43:55 +08:00
18401019693
58508ca0ae 红包中奖列表 2023-07-03 13:05:29 +08:00
18401019693
5736265388 展示红包,领取红包接口逻辑对接 2023-06-30 16:03:37 +08:00
18401019693
e68d20252a 创建接口,红包展示接口 2023-06-30 10:17:33 +08:00
44b4b06592 退出直播间时移除加载错误的提示 2023-06-29 15:09:46 +08:00
fb7551f7d0 Merge branch 'master' into 6.5.3 2023-06-29 14:09:33 +08:00
18401019693
40455fffc9 红包打开创建领取的样式逻辑构建 2023-06-29 10:15:24 +08:00
195035c456 update 更换插件包下载地址 2023-06-28 18:22:30 +08:00
492003ff01 update 调整禁言对话框的文案 2023-06-28 17:37:30 +08:00
17c2e7f795 update 插件化判断代码 2023-06-28 17:37:10 +08:00
1a3d66553c 修复【直播间-多次点击用户名称,弹出多个主页资料框窗口】问题 2023-06-28 17:36:22 +08:00
710cddd3a7 Merge branch 'dev_6.5.3_红包' into 6.5.3 2023-06-28 16:49:23 +08:00
da7ebb2663 add 新增红包专区
add 新增红包专区进去是只有发放红包的直播间
add 新增首页红包浮窗
add 新增红包记录
2023-06-28 16:44:42 +08:00
6a6696f355 fix 修复测试服ios发消息安卓端收不到问题 2023-06-28 16:43:26 +08:00
368b002db6 update 直播间超时进入的提示 2023-06-27 14:19:34 +08:00
501a7b482b update 红包专区 2023-06-27 13:54:18 +08:00
18401019693
279575d80b 红包直播间展示样式构建 2023-06-26 18:04:58 +08:00
3bf594d0b5 update 红包记录列表 2023-06-26 14:57:11 +08:00
5f33fa38f6 update 禁言弹框ui标题 2023-06-26 10:24:19 +08:00
e0312eedd1 update 禁言弹框ui标题 2023-06-26 10:17:45 +08:00
69d8feb9a9 update 红包 2023-06-26 10:12:38 +08:00
500588c842 update 红包 2023-06-26 10:09:37 +08:00
0a70da82f0 Merge branch '6.5.3' into dev_6.5.3_红包 2023-06-25 17:02:33 +08:00
abbb8c524c 新增被禁言的提示 2023-06-25 16:19:55 +08:00
1334d173cf update 红包详情入口 2023-06-25 16:09:47 +08:00
f3ff37aea7 Merge branch 'fix_webview' into 6.5.3 2023-06-25 15:50:04 +08:00
9741be743c 新增直播播放器报错时提示
新增观众端看异常下播直播间提示
2023-06-25 14:18:40 +08:00
f54054283c 调整混淆清单 2023-06-25 13:50:48 +08:00
fd06243c22 优化下载直播插件流程 2023-06-25 11:31:19 +08:00
cef94a248e Merge branch 'dev_proguard' into 6.5.3 2023-06-25 09:53:43 +08:00
beb566b294 新增期间禁言功能 2023-06-25 09:51:29 +08:00
18401019693
6889828c00 消息中心新增【系统消息】类型 2023-06-20 17:46:31 +08:00
9c57f0bd96 修复webView显示问题 2023-06-17 17:22:11 +08:00
a1c96cfd0f 调整直播间分享链接 2023-06-16 15:30:21 +08:00
18401019693
1198a8f1d8 送礼交互优化 2023-06-15 15:27:46 +08:00
a255d1f2b5 update 2023-06-15 13:41:57 +08:00
53ce1c703e update 2023-06-14 17:59:08 +08:00
3f9e95ce7d 新增插件模块管理 2023-06-14 13:41:12 +08:00
18401019693
8e07d4523f 修改完善资料和礼物点击 2023-06-12 13:25:59 +08:00
29ddc54303 Merge branch 'master' into dev_proguard 2023-06-12 10:50:27 +08:00
ae9c050ae6 Merge remote-tracking branch 'origin/master' 2023-06-08 17:02:32 +08:00
7b61e8c562 修复Ranking.getAnchorRankData多次调用问题 2023-06-08 17:02:20 +08:00
18142669586
23a396864f 修改全服 2023-06-08 17:02:11 +08:00
af8052f65b 修复Ranking.getAnchorRankData多次调用问题 2023-06-08 15:21:24 +08:00
6603a2dbb5 修复Ranking.getAnchorRankData多次调用问题 2023-06-08 14:33:44 +08:00
f3a0b4d145 修复Ranking.getAnchorRankData多次调用问题 2023-06-08 14:20:15 +08:00
0c99324904 Merge remote-tracking branch 'origin/master' 2023-06-08 11:13:55 +08:00
8d077e8062 修复PK结束时会隐藏投票问题 2023-06-08 11:13:38 +08:00
18401019693
3aaafdbbbe 修改完善资料和礼物点击 2023-06-08 10:12:37 +08:00
18401019693
f4016089a1 修改完善资料和礼物点击 2023-06-06 15:28:00 +08:00
c53757c64b 调整WebView 2023-06-06 14:41:53 +08:00
c8c4352284 Merge branch 'master' into dev_proguard 2023-06-05 16:42:18 +08:00
18401019693
cd2e176f0a Merge branch 'dev_6.5.3' 2023-06-05 14:09:42 +08:00
e6d92ffc86 调整投票ui 2023-06-05 13:24:13 +08:00
ea0a3375e4 修复非粉丝通过送礼打开的粉丝团url不正确问题 2023-06-05 11:42:56 +08:00
18401019693
06357088c1 修改礼物类型 2023-06-05 10:36:27 +08:00
18401019693
fa24d226ee 6.5.2 2023-06-03 18:30:08 +08:00
18401019693
7eb8153744 6.5.2 2023-06-03 18:02:49 +08:00
18401019693
35f16bdcae 修改盲盒问题 2023-06-03 15:20:18 +08:00
18401019693
aacd8b8735 修改盲盒问题 2023-06-03 14:35:39 +08:00
9926997fd7 Merge remote-tracking branch 'origin/master' 2023-06-03 14:13:47 +08:00
b02cbd7831 Home.getConfig接口新增对1000错误码的处理 2023-06-03 14:13:29 +08:00
18401019693
4150eeed64 修改盲盒问题 2023-06-03 13:51:19 +08:00
18142669586
6e595ff1e4 Merge remote-tracking branch 'origin/master' 2023-06-03 11:48:37 +08:00
18142669586
fbc50229e8 修改关播页面英文显示问题 2023-06-03 11:45:57 +08:00
1edb07d76c 调整投票主播二次点击关闭按钮后直接关闭浮窗 2023-06-03 11:45:23 +08:00
9641d90f32 Merge remote-tracking branch 'origin/master' 2023-06-03 11:33:13 +08:00
3095438a5c 调整排行榜榜单样式
调整排行榜神秘人隐藏关注按钮导致整体UI下降
2023-06-03 11:08:17 +08:00
18401019693
067bbc70c5 修改盲盒问题 2023-06-03 10:35:52 +08:00
7201fd37f3 修复排行榜神秘人ui错乱问题 2023-06-02 18:00:28 +08:00
18401019693
0bab58d507 修改盲盒问题 2023-06-02 13:52:29 +08:00
18401019693
f1ac21fe87 Merge remote-tracking branch 'origin/master' 2023-06-02 13:13:14 +08:00
18401019693
f4b5cdfed3 修改直播间展示ID 2023-06-02 13:13:07 +08:00
9021659770 修复在进入直播间瞬间关播的“抱歉出错了”提示 2023-06-02 11:31:25 +08:00
e04d277560 修复修改投票中英文导致的界面语言混乱问题 2023-06-02 11:17:08 +08:00
18142669586
a7a9a28c09 Merge remote-tracking branch 'origin/master' 2023-06-02 10:27:48 +08:00
18142669586
bbd394044f 修改关播页面英文显示问题 2023-06-02 10:27:36 +08:00
18401019693
49df393781 Merge remote-tracking branch 'origin/master' 2023-06-02 10:04:40 +08:00
18401019693
f0a8c17401 修改盲盒连宋 2023-06-02 10:04:32 +08:00
a01ddd310b 新增投票观众端的中英文切换 2023-06-02 09:56:24 +08:00
18401019693
26ec74932f 修改盲盒蒙版 2023-06-01 18:16:58 +08:00
18401019693
3aa85ca9bb 直播间房间号加ID 2023-06-01 15:56:06 +08:00
18401019693
a67c2b4009 修改榜单头像框问题 2023-06-01 15:39:24 +08:00
9486a1a572 修复下播页面关注按钮在英文状态下自动换行问题 2023-06-01 15:12:17 +08:00
c1aa8188ce 修复机器人自动打招呼条数超过20条问题 #74 2023-06-01 14:58:42 +08:00
18401019693
a83e3e0511 添加中英文翻译 2023-06-01 13:56:05 +08:00
18401019693
3488da27f0 盲盒加判断是不是本人 2023-06-01 10:26:08 +08:00
56845de59d 修复在线客服页面输入法会挡住输入框的问题 2023-05-31 16:51:00 +08:00
18401019693
282790ead5 Merge remote-tracking branch 'origin/master' 2023-05-31 13:59:28 +08:00
18401019693
db165cc05d 盲盒礼物开箱动画 2023-05-31 13:22:14 +08:00
49313ca326 修复PK滑动直播间可能画面错乱问题 2023-05-29 15:25:15 +08:00
18401019693
b0008405e2 Merge branch 'master' into dev_blind_box 2023-05-29 11:35:17 +08:00
18401019693
9c32c6af25 添加盲盒礼物蒙版 2023-05-29 11:34:34 +08:00
baa2ec127f 调整播放器在停止播放时把play2也一并停止 2023-05-29 11:25:30 +08:00
18401019693
c3b5f17f44 Merge branch 'master' into dev_blind_box
# Conflicts:
#	common/src/main/res/values-zh/strings.xml
#	common/src/main/res/values/strings.xml
2023-05-27 18:25:14 +08:00
18401019693
c28a91b128 修复榜单头像框问题 2023-05-27 17:22:13 +08:00
18401019693
f064864d76 修改榜单神秘人 2023-05-27 15:17:31 +08:00
18401019693
66290323d8 盲盒全服通知逻辑 2023-05-27 11:40:09 +08:00
9526a14ca2 调整聊天区高度 2023-05-27 11:24:28 +08:00
27715cda2c 新增投票创建失败时的提示语 2023-05-26 14:24:29 +08:00
65bfc77376 修复直播结束数据界面,直播时长未对齐问题 2023-05-25 14:34:39 +08:00
99618a2779 新增IM消息:endPK,用于显示最终单人PK数据 2023-05-25 14:25:57 +08:00
1b637b7a77 修復开屏动态聚焦banner有图片溢出问题 2023-05-25 14:18:00 +08:00
6ee678bd74 开播频道屏蔽【男神】 2023-05-25 13:41:39 +08:00
18401019693
b4dc035a1b 盲盒礼物具体业务逻辑的构建,IM消息的构建 2023-05-25 13:40:49 +08:00
18401019693
1f7aef91a8 礼物盲盒基本逻辑 2023-05-24 14:50:39 +08:00
18401019693
3e72b4a9eb 修改设置页面防抖问题,登录loading加载问题 2023-05-22 16:57:12 +08:00
18401019693
4f103dadc9 修改防抖点击问题 2023-05-22 16:48:55 +08:00
18401019693
055d002a34 Merge remote-tracking branch 'origin/master' 2023-05-19 13:33:17 +08:00
18401019693
426f7450a2 修复心愿单问题 2023-05-19 13:33:09 +08:00
73a71dabc6 Merge remote-tracking branch 'origin/master' 2023-05-19 10:43:11 +08:00
435dc1c4fb 调整投票UI 2023-05-19 10:36:06 +08:00
18401019693
f347d283a0 修复心愿单问题 2023-05-19 10:36:03 +08:00
34c569ecb5 update config 2023-05-09 14:40:38 +08:00
a041450766 update config 2023-05-09 10:09:03 +08:00
e9488df780 update 2023-05-08 13:29:24 +08:00
2022 changed files with 126768 additions and 14134 deletions

View File

@@ -1,12 +1,12 @@
apply plugin: 'com.android.library'
apply plugin: 'img-optimizer'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-parcelize'
apply from: "../package_config.gradle"
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
namespace "com.yunbao.faceunity"
compileSdk rootProject.ext.android.compileSdkVersion
packagingOptions {
pickFirst "lib/armeabi/libyuvutils.so"
pickFirst "lib/arm64-v8a/libyuvutils.so"
@@ -33,7 +33,7 @@ android {
versionName rootProject.ext.android.versionName
manifestPlaceholders = rootProject.ext.manifestPlaceholders
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
abiFilters "armeabi-v7a", "arm64-v8a","x86","x86_64"
}
}
aaptOptions {
@@ -48,8 +48,8 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_18
targetCompatibility JavaVersion.VERSION_18
}
}
repositories {
@@ -58,15 +58,15 @@ repositories {
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation rootProject.ext.dependencies["appcompat-androidx"]
implementation rootProject.ext.dependencies["recyclerview-androidx"]
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api fileTree(dir: 'libs', include: ['*.jar'])
api rootProject.ext.dependencies["appcompat-androidx"]
api rootProject.ext.dependencies["recyclerview-androidx"]
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//common
implementation project(path: ':common')
api project(path: ':common')
implementation 'com.faceunity:core:8.3.1'
implementation 'com.faceunity:model:8.3.1'
api 'com.faceunity:core:8.7.0'
api 'com.faceunity:model:8.7.0'
//implementation 'com.faceunity:nama:8.3.1' //底层库-标准版

View File

@@ -2,7 +2,7 @@ package com.yunbao.faceunity;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.platform.app.Instrimport com.yunbao.common.utils.MobclickAgent;ntationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;

View File

@@ -1,7 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.yunbao.faceunity"
>
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />

View File

@@ -96,11 +96,17 @@ public class FaceManager implements SensorEventListener {
faceUnityView.setIFaceUnityInter(new FaceUnityView.IFaceUnityInter() {
@Override
public void onPause() {
if(onMirrorChanged!=null){
onMirrorChanged.onChange(false);
}
pauseFace = true;
}
@Override
public void onStart() {
if(onMirrorChanged!=null){
onMirrorChanged.onChange(true);
}
pauseFace = false;
}
});
@@ -295,7 +301,18 @@ public class FaceManager implements SensorEventListener {
}
OnMirrorChanged onMirrorChanged;
public void setOnMirrorChanged(OnMirrorChanged onMirrorChanged) {
this.onMirrorChanged = onMirrorChanged;
}
public interface FaceStatusChanged {
void onFaceChanged(int num);
}
public interface OnMirrorChanged{
void onChange(boolean falg);
}
}

View File

@@ -52,11 +52,11 @@ public class FURenderer extends IFURenderer {
/* 特效FURenderKit*/
private FURenderKit mFURenderKit;
public FURenderKit mFURenderKit;
/* AI道具*/
private String BUNDLE_AI_FACE = "model" + File.separator + "ai_face_processor_lite.bundle";
private String BUNDLE_AI_HUMAN = "model" + File.separator + "ai_human_processor.bundle";
public static String BUNDLE_AI_FACE = "model" + File.separator + "ai_face_processor.bundle";
public static String BUNDLE_AI_HUMAN = "model" + File.separator + "ai_human_processor.bundle";
/* GL 线程 ID */
private Long mGlThreadId = 0L;

View File

@@ -15,7 +15,7 @@ public class FaceUnityConfig {
/************************** 算法Model ******************************/
// 人脸识别
public static String BUNDLE_AI_FACE = "model" + File.separator + "ai_face_processor_lite.bundle";
public static String BUNDLE_AI_FACE = "model" + File.separator + "ai_face_processor.bundle";
// 手势
public static String BUNDLE_AI_HAND = "model" + File.separator + "ai_hand_processor.bundle";

View File

@@ -314,6 +314,35 @@ public class FileUtils {
return null;
}
public static String copyAssetsFile(Context context, String assetsPath, String fileName, String saveFileDir) {
File fileDir = new File(saveFileDir);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
File file = new File(fileDir, fileName);
if (file.exists()) {
return file.getAbsolutePath();
}
try {
InputStream inputStream = context.getAssets().open(assetsPath);
FileOutputStream fos = new FileOutputStream(file);
BufferedInputStream bis = new BufferedInputStream(inputStream);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] byteArray = new byte[1024];
int bytes = bis.read(byteArray);
while (bytes > 0) {
bos.write(byteArray, 0, bytes);
bos.flush();
bytes = bis.read(byteArray);
}
bos.close();
fos.close();
return file.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取Uri文件绝对路径
@@ -522,6 +551,7 @@ public class FileUtils {
/**
* 遍历一个文件夹获取改文件夹下所有文件名
*
* @param path
* @return
*/
@@ -563,7 +593,7 @@ public class FileUtils {
* @param path String
* @return Boolean
*/
public static Boolean checkIsVideo(Context context,String path) {
public static Boolean checkIsVideo(Context context, String path) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(context, Uri.fromFile(new File(path)));

View File

@@ -13,7 +13,7 @@ import com.google.gson.JsonObject;
import com.yunbao.faceunity.entity.net.FineStickerEntity;
import com.yunbao.faceunity.utils.FaceUnityData;
import com.yunbao.faceunity.utils.FileUtils;
import com.yunbao.faceunity.utils.ZipUtils;
import com.yunbao.common.utils.ZipUtils;
import java.io.File;
import java.util.ArrayList;

View File

@@ -1,96 +1,96 @@
<resources>
<string name="camera_dialog_title">警告</string>
<string name="sorry_no_permission">抱歉,你所使用的证书权限或SDK不包括功能。</string>
<string name="camera_dialog_message">机权限被禁用或者相机被别的应用占用!</string>
<string name="camera_dialog_open"></string>
<string name="sorry_no_permission">抱歉,你所使用的證書權限或SDK不包括功能。</string>
<string name="camera_dialog_message">機權限被禁用或者相機被別的應用佔用!</string>
<string name="camera_dialog_open"></string>
<string name="camera_dialog_back">退出</string>
<string name="fu_base_debug">Resolution:\n\t%dX%d\nFPS: %d\nRender time:\n\t%dms</string>
<string name="save_photo_success">保存照片成功!</string>
<string name="save_video_success">保存视频成功!</string>
<string name="save_video_failed">保存视频失败</string>
<string name="save_video_too_short">视频太短啦!</string>
<string name="save_video_wait">视频处理中稍等</string>
<string name="fu_base_is_tracking_text">检测到人</string>
<string name="fu_base_incomplete_face_text">不全</string>
<string name="fu_base_input_type_single">单输</string>
<string name="fu_base_input_type_double">双输</string>
<string name="save_video_success">保存視頻成功!</string>
<string name="save_video_failed">保存視頻失敗</string>
<string name="save_video_too_short">視頻太短啦!</string>
<string name="save_video_wait">視頻處理中稍等</string>
<string name="fu_base_is_tracking_text">檢測到人</string>
<string name="fu_base_incomplete_face_text">不全</string>
<string name="fu_base_input_type_single">單輸</string>
<string name="fu_base_input_type_double">雙輸</string>
<string name="beauty_box_heavy_blur_fine">磨皮</string>
<string name="beauty_box_color_level">美白</string>
<string name="beauty_box_red_level">红润</string>
<string name="beauty_box_sharpen"></string>
<string name="beauty_box_red_level">紅潤</string>
<string name="beauty_box_sharpen"></string>
<string name="beauty_box_eye_bright">亮眼</string>
<string name="beauty_box_tooth_whiten">美牙</string>
<string name="beauty_box_eye_enlarge">大眼</string>
<string name="beauty_box_eye_circle"></string>
<string name="beauty_box_eye_circle"></string>
<string name="beauty_box_cheek_natural">自然</string>
<string name="beauty_box_cheek_goddess">女神</string>
<string name="beauty_box_cheek_long_face">长脸</string>
<string name="beauty_box_cheek_round_face">圆脸</string>
<string name="beauty_box_cheek_thinning"></string>
<string name="beauty_box_cheek_v">V</string>
<string name="beauty_box_cheek_narrow"></string>
<string name="beauty_box_cheek_short"></string>
<string name="beauty_box_cheek_small"></string>
<string name="beauty_box_cheek_long_face">長臉</string>
<string name="beauty_box_cheek_round_face">圓臉</string>
<string name="beauty_box_cheek_thinning"></string>
<string name="beauty_box_cheek_v">V</string>
<string name="beauty_box_cheek_narrow"></string>
<string name="beauty_box_cheek_short"></string>
<string name="beauty_box_cheek_small"></string>
<string name="beauty_box_intensity_chin">下巴</string>
<string name="beauty_box_intensity_forehead">额头</string>
<string name="beauty_box_intensity_forehead">額頭</string>
<string name="beauty_box_intensity_nose">瘦鼻</string>
<string name="beauty_box_intensity_mouth">嘴型</string>
<string name="beauty_box_cheekbones"></string>
<string name="beauty_box_lower_jaw">瘦下</string>
<string name="beauty_radio_skin_beauty"></string>
<string name="beauty_box_cheekbones"></string>
<string name="beauty_box_lower_jaw">瘦下</string>
<string name="beauty_radio_skin_beauty"></string>
<string name="beauty_radio_face_shape">美型</string>
<string name="beauty_radio_filter">滤镜</string>
<string name="beauty_radio_style">格推</string>
<string name="beauty_radio_filter">濾鏡</string>
<string name="beauty_radio_style">格推</string>
<string name="beauty_micro_pouch">去黑眼圈</string>
<string name="beauty_micro_nasolabial">去法令</string>
<string name="beauty_micro_nasolabial">去法令</string>
<string name="beauty_micro_smile">微笑嘴角</string>
<string name="beauty_brow_height">眉毛上下</string>
<string name="beauty_brow_space"></string>
<string name="beauty_micro_canthus">眼角</string>
<string name="beauty_micro_philtrum">人中</string>
<string name="beauty_micro_long_nose"></string>
<string name="beauty_brow_space"></string>
<string name="beauty_micro_canthus">眼角</string>
<string name="beauty_micro_philtrum">人中</string>
<string name="beauty_micro_long_nose"></string>
<string name="beauty_micro_eye_space">眼距</string>
<string name="beauty_micro_eye_rotate">眼睛角度</string>
<string name="makeup_radio_lipstick"></string>
<string name="makeup_radio_blusher"></string>
<string name="makeup_radio_lipstick"></string>
<string name="makeup_radio_blusher"></string>
<string name="makeup_radio_eyebrow">眉毛</string>
<string name="makeup_radio_eye_shadow">眼影</string>
<string name="makeup_radio_eye_liner">线</string>
<string name="makeup_radio_eye_liner"></string>
<string name="makeup_radio_eyelash">睫毛</string>
<string name="makeup_radio_contact_lens">美瞳</string>
<string name="makeup_radio_foundation">粉底</string>
<string name="makeup_radio_highlight">高光</string>
<string name="makeup_radio_shadow"></string>
<string name="makeup_radio_remove"></string>
<string name="makeup_customize">自定</string>
<string name="makeup_radio_shadow"></string>
<string name="makeup_radio_remove"></string>
<string name="makeup_customize">自定</string>
<string name="makeup_peach_blossom">桃花</string>
<string name="makeup_boyfriend">男友</string>
<string name="makeup_clear">清透</string>
<string name="makeup_grapefruit">西柚</string>
<string name="select_data_photo">选择图</string>
<string name="select_data_video">选择视频</string>
<string name="select_data_title">请从相册中选择图片或视频</string>
<string name="image_file_does_not_exist">选图片文件不存在。</string>
<string name="video_file_does_not_exist">选视频文件不存在。</string>
<string name="select_data_photo">選擇圖</string>
<string name="select_data_video">選擇視頻</string>
<string name="select_data_title">請從相冊中選擇圖片或視頻</string>
<string name="image_file_does_not_exist">選圖片文件不存在。</string>
<string name="video_file_does_not_exist">選視頻文件不存在。</string>
<string name="future_warrior">张嘴试试</string>
<string name="jet_mask">鼓腮</string>
<string name="sdx2">皱眉试试</string>
<string name="future_warrior">張嘴試試</string>
<string name="jet_mask">鼓腮</string>
<string name="sdx2">皺眉試試</string>
<string name="luhantongkuan_ztt_fu">眨一眨眼</string>
<string name="qingqing_ztt_fu">嘟嘴试试</string>
<string name="xiaobianzi_zh_fu">微笑触发</string>
<string name="xiaoxueshen_ztt_fu">气触发</string>
<string name="hez_ztt_fu">张嘴试试</string>
<string name="qingqing_ztt_fu">嘟嘴試試</string>
<string name="xiaobianzi_zh_fu">微笑觸發</string>
<string name="xiaoxueshen_ztt_fu">氣觸發</string>
<string name="hez_ztt_fu">張嘴試試</string>
<string name="push_hand">推出手掌</string>
<string name="fu_lm_koreaheart">手手指比心</string>
<string name="ssd_thread_six"></string>
<string name="ssd_thread_cute">拳靠近脸颊卖</string>
<string name="fu_lm_koreaheart">手手指比心</string>
<string name="ssd_thread_six"></string>
<string name="ssd_thread_cute">拳靠近臉頰賣</string>
<string name="origin"></string>
<string name="origin"></string>
<string name="bailiang_1">白亮 1</string>
<string name="bailiang_2">白亮 2</string>
<string name="bailiang_3">白亮 3</string>
@@ -109,23 +109,23 @@
<string name="xiaoqingxin_3">小清新 3</string>
<string name="xiaoqingxin_4">小清新 4</string>
<string name="xiaoqingxin_6">小清新 6</string>
<string name="lengsediao_1">冷色 1</string>
<string name="lengsediao_2">冷色 2</string>
<string name="lengsediao_3">冷色 3</string>
<string name="lengsediao_4">冷色 4</string>
<string name="lengsediao_7">冷色 7</string>
<string name="lengsediao_8">冷色 8</string>
<string name="lengsediao_11">冷色 11</string>
<string name="nuansediao_1">暖色 1</string>
<string name="nuansediao_2">暖色 2</string>
<string name="gexing_1">性 1</string>
<string name="gexing_2">性 2</string>
<string name="gexing_3">性 3</string>
<string name="gexing_4">性 4</string>
<string name="gexing_5">性 5</string>
<string name="gexing_7">性 7</string>
<string name="gexing_10">性 10</string>
<string name="gexing_11">性 11</string>
<string name="lengsediao_1">冷色調 1</string>
<string name="lengsediao_2">冷色調 2</string>
<string name="lengsediao_3">冷色調 3</string>
<string name="lengsediao_4">冷色調 4</string>
<string name="lengsediao_7">冷色調 7</string>
<string name="lengsediao_8">冷色調 8</string>
<string name="lengsediao_11">冷色調 11</string>
<string name="nuansediao_1">暖色調 1</string>
<string name="nuansediao_2">暖色調 2</string>
<string name="gexing_1">性 1</string>
<string name="gexing_2">性 2</string>
<string name="gexing_3">性 3</string>
<string name="gexing_4">性 4</string>
<string name="gexing_5">性 5</string>
<string name="gexing_7">性 7</string>
<string name="gexing_10">性 10</string>
<string name="gexing_11">性 11</string>
<string name="heibai_1">黑白 1</string>
<string name="heibai_2">黑白 2</string>
<string name="heibai_3">黑白 3</string>
@@ -138,14 +138,14 @@
<string name="ziran_6">自然 6</string>
<string name="ziran_7">自然 7</string>
<string name="ziran_8">自然 8</string>
<string name="zhiganhui_1">感灰 1</string>
<string name="zhiganhui_2">感灰 2</string>
<string name="zhiganhui_3">感灰 3</string>
<string name="zhiganhui_4">感灰 4</string>
<string name="zhiganhui_5">感灰 5</string>
<string name="zhiganhui_6">感灰 6</string>
<string name="zhiganhui_7">感灰 7</string>
<string name="zhiganhui_8">感灰 8</string>
<string name="zhiganhui_1">感灰 1</string>
<string name="zhiganhui_2">感灰 2</string>
<string name="zhiganhui_3">感灰 3</string>
<string name="zhiganhui_4">感灰 4</string>
<string name="zhiganhui_5">感灰 5</string>
<string name="zhiganhui_6">感灰 6</string>
<string name="zhiganhui_7">感灰 7</string>
<string name="zhiganhui_8">感灰 8</string>
<string name="mitao_1">蜜桃 1</string>
<string name="mitao_2">蜜桃 2</string>
<string name="mitao_3">蜜桃 3</string>
@@ -155,85 +155,85 @@
<string name="mitao_7">蜜桃 7</string>
<string name="mitao_8">蜜桃 8</string>
<string name="beauty_face_style_none"></string>
<string name="beauty_face_style_1">格 1</string>
<string name="beauty_face_style_2">格 2</string>
<string name="beauty_face_style_3">格 3</string>
<string name="beauty_face_style_4">格 4</string>
<string name="beauty_face_style_5">格 5</string>
<string name="beauty_face_style_6">格 6</string>
<string name="beauty_face_style_7">格 7</string>
<string name="beauty_face_style_toast">使用%s先取消“格推</string>
<string name="beauty_face_style_none"></string>
<string name="beauty_face_style_1">格 1</string>
<string name="beauty_face_style_2">格 2</string>
<string name="beauty_face_style_3">格 3</string>
<string name="beauty_face_style_4">格 4</string>
<string name="beauty_face_style_5">格 5</string>
<string name="beauty_face_style_6">格 6</string>
<string name="beauty_face_style_7">格 7</string>
<string name="beauty_face_style_toast">使用%s先取消“格推</string>
<string name="poster_take_photo">对准线框 正脸拍摄</string>
<string name="poster_change_face_error">换失败</string>
<string name="poster_template_face_none">识别模板的人脸,请重新选择模板</string>
<string name="dialog_no_track_face">检测到人脸,请重新拍</string>
<string name="dialog_no_incomplete_face">不全,重新拍</string>
<string name="dialog_face_rotation_not_valid">脸偏转角度大,请正脸拍摄</string>
<string name="poster_take_photo">對準線框 正臉拍攝</string>
<string name="poster_change_face_error">換失敗</string>
<string name="poster_template_face_none">識別模板的人臉,請重新選擇模板</string>
<string name="dialog_no_track_face">檢測到人臉,請重新拍</string>
<string name="dialog_no_incomplete_face">不全,重新拍</string>
<string name="dialog_face_rotation_not_valid">臉偏轉角度大,請正臉拍攝</string>
<string name="dialog_got">知道啦</string>
<string name="tip_dual_face">检测到多人,请选择一人进行换脸</string>
<string name="tip_dual_face">檢測到多人,請選擇一人進行換臉</string>
<string name="animoji_filter">Animoji</string>
<string name="cartoon_filter">动漫滤镜</string>
<string name="cartoon_filter">動漫濾鏡</string>
<string name="delete_avatar_model">除模型</string>
<string name="delete_avatar_model">除模型</string>
<string name="new_avatar_model">新建模型</string>
<string name="edit_avatar_model">编辑模型</string>
<string name="edit_avatar_model">編輯模型</string>
<string name="avatar_face_hair"></string>
<string name="avatar_face_face"></string>
<string name="avatar_face_hair"></string>
<string name="avatar_face_face"></string>
<string name="avatar_face_eye">眼睛</string>
<string name="avatar_face_lip">嘴唇</string>
<string name="avatar_face_nose">鼻子</string>
<string name="avatar_face_length">脸型长</string>
<string name="avatar_face_width">脸颊宽</string>
<string name="avatar_chin_width">颚宽</string>
<string name="avatar_face_length">臉型長</string>
<string name="avatar_face_width">臉頰寬</string>
<string name="avatar_chin_width">顎寬</string>
<string name="avatar_chin_height">下巴高低</string>
<string name="avatar_eye_position">眼睛位置</string>
<string name="avatar_eye_corner_height">眼角高度</string>
<string name="avatar_eye_height">眼睛高低</string>
<string name="avatar_eye_width">眼睛</string>
<string name="avatar_eye_width">眼睛</string>
<string name="avatar_nose_position">鼻子位置</string>
<string name="avatar_nose_width">鼻翼</string>
<string name="avatar_nose_height">高低</string>
<string name="avatar_nose_width">鼻翼</string>
<string name="avatar_nose_height">高低</string>
<string name="avatar_mouth_position">嘴部位置</string>
<string name="avatar_up_lip_thickness">上唇厚度</string>
<string name="avatar_down_lip_thickness">下唇厚度</string>
<string name="avatar_lip_width">嘴唇</string>
<string name="model_empty_tip">还没有创建过模型哦</string>
<string name="dialog_reset_avatar_model">是否所有参数恢复到默值?</string>
<string name="avatar_face_customize">自定</string>
<string name="avatar_lip_width">嘴唇</string>
<string name="model_empty_tip">還沒有創建過模型哦</string>
<string name="dialog_reset_avatar_model">是否所有參數恢復到默值?</string>
<string name="avatar_face_customize">自定</string>
<string name="avatar_save_succeed">保存成功</string>
<string name="live_photo_back_not_save">返回后前操作将不会被保存哦</string>
<string name="live_photo_btn_delete"></string>
<string name="live_photo_back_not_save">返回后前操作將不會被保存哦</string>
<string name="live_photo_btn_delete"></string>
<string name="live_photo_btn_cancel">取消</string>
<string name="live_photo_btn_delete_">除(%d)</string>
<string name="live_photo_delete_effect">除道具</string>
<string name="live_photo__delete_all"></string>
<string name="live_photo_empty_list_tip">还没有创建过道具哦</string>
<string name="confirm"></string>
<string name="live_photo_btn_delete_">除(%d)</string>
<string name="live_photo_delete_effect">除道具</string>
<string name="live_photo__delete_all"></string>
<string name="live_photo_empty_list_tip">還沒有創建過道具哦</string>
<string name="confirm"></string>
<string name="cancel">取消</string>
<string name="dialog_confirm_delete">确定删除所中的道具?</string>
<string name="toast_delete_succeed">除成功</string>
<string name="toast_delete_failed">除失</string>
<string name="dialog_confirm_delete">確定刪除所中的道具?</string>
<string name="toast_delete_succeed">除成功</string>
<string name="toast_delete_failed">除失</string>
<string name="live_photo_save_succeed">道具保存成功</string>
<string name="recover"></string>
<string name="recover"></string>
<string name="makeup_lip_fog"></string>
<string name="makeup_lip_moist1">润泽</string>
<string name="makeup_lip_moist2">润泽</string>
<string name="makeup_lip_fog"></string>
<string name="makeup_lip_moist1">潤澤</string>
<string name="makeup_lip_moist2">潤澤</string>
<string name="makeup_lip_pearl">珠光</string>
<string name="makeup_lip_bitelip">咬唇</string>
<string name="makeup_blusher_apple">果肌</string>
<string name="makeup_blusher_apple">果肌</string>
<string name="makeup_blusher_fan">扇形</string>
<string name="makeup_blusher_eye_corner">眼角</string>
<string name="makeup_blusher_slight_drunk">微醺</string>
<string name="makeup_highlight_one">高光 I</string>
<string name="makeup_highlight_two">高光 II</string>
<string name="makeup_shadow_one">影 I</string>
<string name="makeup_shadow_one">影 I</string>
<string name="makeup_pupil_1">蜜糖</string>
<string name="makeup_pupil_2">奶茶</string>
<string name="makeup_pupil_3">水波</string>
@@ -241,106 +241,106 @@
<string name="makeup_pupil_5">孔雀</string>
<string name="makeup_pupil_6">星河</string>
<string name="makeup_pupil_7">落目</string>
<string name="makeup_pupil_8"></string>
<string name="makeup_eyebrow_willow"></string>
<string name="makeup_pupil_8"></string>
<string name="makeup_eyebrow_willow">恭弘=叶 恭弘</string>
<string name="makeup_eyebrow_wild">野生眉</string>
<string name="makeup_eyebrow_classical">古典眉</string>
<string name="makeup_eyebrow_standard">标准</string>
<string name="makeup_eye_shadow_single">色眼影</string>
<string name="makeup_eye_shadow_double1">色眼影 I</string>
<string name="makeup_eye_shadow_double2">色眼影 II</string>
<string name="makeup_eye_shadow_double3">色眼影 III</string>
<string name="makeup_eyebrow_standard">標準</string>
<string name="makeup_eye_shadow_single">色眼影</string>
<string name="makeup_eye_shadow_double1">色眼影 I</string>
<string name="makeup_eye_shadow_double2">色眼影 II</string>
<string name="makeup_eye_shadow_double3">色眼影 III</string>
<string name="makeup_eye_shadow_triple1">三色眼影 I</string>
<string name="makeup_eye_shadow_triple2">三色眼影 II</string>
<string name="makeup_eyelash_natural1">自然型 I</string>
<string name="makeup_eyelash_natural2">自然型 II</string>
<string name="makeup_eyelash_thick1">密型 I</string>
<string name="makeup_eyelash_thick2">密型 II</string>
<string name="makeup_eyelash_exaggerate1">夸张型 I</string>
<string name="makeup_eyelash_exaggerate2">夸张型 II</string>
<string name="makeup_eye_linear_cat"></string>
<string name="makeup_eyelash_thick1">密型 I</string>
<string name="makeup_eyelash_thick2">密型 II</string>
<string name="makeup_eyelash_exaggerate1">誇張型 I</string>
<string name="makeup_eyelash_exaggerate2">誇張型 II</string>
<string name="makeup_eye_linear_cat"></string>
<string name="makeup_eye_linear_drooping">下垂眼</string>
<string name="makeup_eye_linear_pull_open">眼距</string>
<string name="makeup_eye_linear_pull_open">眼距</string>
<string name="makeup_eye_linear_pull_close">拉近眼距</string>
<string name="makeup_eye_linear_long"></string>
<string name="makeup_eye_linear_circular"></string>
<string name="makeup_eye_linear_long"></string>
<string name="makeup_eye_linear_circular"></string>
<string name="makeup_combination_diadiatu">嗲嗲兔</string>
<string name="makeup_combination_dongling">冻龄</string>
<string name="makeup_combination_guofeng">国风</string>
<string name="makeup_combination_dongling">凍齡</string>
<string name="makeup_combination_guofeng">國風</string>
<string name="makeup_combination_hunxie">混血</string>
<string name="makeup_combination_sexy">性感</string>
<string name="makeup_combination_sweet">甜美</string>
<string name="makeup_combination_neighbor"></string>
<string name="makeup_combination_occident"></string>
<string name="makeup_combination_charming"></string>
<string name="makeup_combination_jianling">减龄</string>
<string name="makeup_combination_neighbor"></string>
<string name="makeup_combination_occident"></string>
<string name="makeup_combination_charming"></string>
<string name="makeup_combination_jianling">減齡</string>
<string name="makeup_combination_nuandong">暖冬</string>
<string name="makeup_combination_hongfeng">红枫</string>
<string name="makeup_combination_hongfeng">紅楓</string>
<string name="makeup_combination_shaonv">少女</string>
<string name="makeup_combination_ziyun"></string>
<string name="makeup_combination_yanshimao">厌世猫</string>
<string name="makeup_combination_renyu"></string>
<string name="makeup_combination_ziyun"></string>
<string name="makeup_combination_yanshimao">厭世貓</string>
<string name="makeup_combination_renyu"></string>
<string name="makeup_combination_chuqiu">初秋</string>
<string name="makeup_combination_qianzhihe">纸鹤</string>
<string name="makeup_combination_qianzhihe">紙鶴</string>
<string name="makeup_combination_chaomo">超模</string>
<string name="makeup_combination_chuju"></string>
<string name="makeup_combination_gangfeng"></string>
<string name="makeup_combination_chuju"></string>
<string name="makeup_combination_gangfeng"></string>
<string name="makeup_combination_rose">Rose</string>
<string name="slimming">瘦身</string>
<string name="long_legs"></string>
<string name="thin_waist"></string>
<string name="long_legs"></string>
<string name="thin_waist"></string>
<string name="beautify_shoulder">美肩</string>
<string name="beautify_hip_slim">美臀</string>
<string name="beautify_head_slim"></string>
<string name="beautify_head_slim"></string>
<string name="beautify_leg_thin_slim">瘦腿</string>
<string name="toast_not_detect_body">检测到人</string>
<string name="pta_human_full_body">全身驱动</string>
<string name="pta_human_half_body">半身驱动</string>
<string name="toast_not_detect_body">檢測到人</string>
<string name="pta_human_full_body">全身驅動</string>
<string name="pta_human_half_body">半身驅動</string>
<string name="select_data_photo_or_video">载入图片或视频</string>
<string name="toast_not_detect_gesture">检测到手</string>
<string name="select_data_photo_or_video">載入圖片或視頻</string>
<string name="toast_not_detect_gesture">檢測到手</string>
<string name="bg_seg_green_graphic"></string>
<string name="bg_seg_green_graphic"></string>
<string name="bg_seg_green_background">背景</string>
<string name="bg_seg_green_key_color">关键颜</string>
<string name="bg_seg_green_key_color">關鍵顏</string>
<string name="bg_seg_green_similarity">相似度</string>
<string name="bg_seg_green_smooth">平滑</string>
<string name="bg_seg_green_alpha">祛色度</string>
<string name="bg_seg_green_safe_area">安全</string>
<string name="bg_seg_green_safe_area">安全</string>
<string name="bg_seg_green_science">科技</string>
<string name="bg_seg_green_beach"></string>
<string name="bg_seg_green_beach"></string>
<string name="bg_seg_green_classroom">教室</string>
<string name="bg_seg_green_forest">森林</string>
<string name="bg_seg_green_ink">水墨</string>
<string name="dialog_guide_bg_seg_green">使用色背景拍,推荐绿色幕布效果最佳</string>
<string name="bg_seg_green_ink">水墨</string>
<string name="dialog_guide_bg_seg_green">使用色背景拍,推薦綠色幕布效果最佳</string>
<string name="dialog_i_know">我知道了</string>
<string name="download_error">载失败</string>
<string name="download_error">載失敗</string>
<string name="back">返回</string>
<string name="safe_area_tips">白色区域为安全域,不参与绿幕抠</string>
<string name="safe_area_tips">白色區域為安全域,不參与綠幕摳</string>
<string name="brow_height_tips">眉毛上下功能支持在高端上使用</string>
<string name="brow_space_tips">距功能支持在高端上使用</string>
<string name="brow_height_tips">眉毛上下功能支持在高端上使用</string>
<string name="brow_space_tips">距功能支持在高端上使用</string>
<string name="home_function_name_beauty"></string>
<string name="home_function_name_makeup"></string>
<string name="home_function_name_sticker">贴纸</string>
<string name="home_function_name_beauty_body"></string>
<string name="home_function_name_beauty"></string>
<string name="home_function_name_makeup"></string>
<string name="home_function_name_sticker">貼紙</string>
<string name="home_function_name_beauty_body"></string>
<string name="toast_not_detect_face">检测到人</string>
<string name="toast_not_detect_face_or_body">检测到人或人</string>
<string name="toast_not_detect_face">檢測到人</string>
<string name="toast_not_detect_face_or_body">檢測到人或人</string>
<string name="makeup_combination_naicha">奶茶</string>
<string name="makeup_combination_dousha">豆沙</string>
<string name="makeup_combination_chaoa">超A</string>
<string name="home_function_name_big_head">搞笑大</string>
<string name="home_function_name_big_head">搞笑大</string>
<string name="home_function_name_animoji">Animoji</string>
<string name="home_function_name_fine_sticker">精品贴纸</string>
<string name="home_function_name_fine_sticker">精品貼紙</string>
<string name="dialog_reset">重置</string>
<string name="menu_diy">自定義</string>

View File

@@ -16,33 +16,33 @@
<string name="fu_base_input_type_single">SingleInput</string>
<string name="fu_base_input_type_double">DualInput</string>
<string name="beauty_box_heavy_blur_fine">Fine smooth</string>
<string name="beauty_box_color_level">Whiten</string>
<string name="beauty_box_red_level">Ruddy</string>
<string name="beauty_box_heavy_blur_fine">Buffing</string>
<string name="beauty_box_color_level">Skin Tone</string>
<string name="beauty_box_red_level">Rosy</string>
<string name="beauty_box_sharpen">Sharpen</string>
<string name="beauty_box_eye_bright">Eye brighten</string>
<string name="beauty_box_tooth_whiten">Tooth whiten</string>
<string name="beauty_box_eye_enlarge">Eye enlarge</string>
<string name="beauty_box_eye_circle">Eye round</string>
<string name="beauty_box_cheek_natural">Natural</string>
<string name="beauty_box_eye_bright">Brighen</string>
<string name="beauty_box_tooth_whiten">Whiten</string>
<string name="beauty_box_eye_enlarge">Enlarge</string>
<string name="beauty_box_eye_circle">Round</string>
<string name="beauty_box_cheek_natural">Origin</string>
<string name="beauty_box_cheek_goddess">Goddess</string>
<string name="beauty_box_cheek_long_face">Long face</string>
<string name="beauty_box_cheek_round_face">Round face</string>
<string name="beauty_box_cheekbones">Cheekbone</string>
<string name="beauty_box_lower_jaw">Jawbone</string>
<string name="beauty_box_cheek_thinning">Cheek thin</string>
<string name="beauty_box_cheek_v">V face</string>
<string name="beauty_box_cheek_narrow">CheekNarrow</string>
<string name="beauty_box_cheek_short">Cheek short</string>
<string name="beauty_box_cheek_small">Cheek small</string>
<string name="beauty_box_intensity_chin">Chin</string>
<string name="beauty_box_intensity_forehead">Forehead</string>
<string name="beauty_box_intensity_nose">Nose</string>
<string name="beauty_box_intensity_mouth">Mouth</string>
<string name="beauty_radio_skin_beauty">Skin</string>
<string name="beauty_radio_face_shape">Reshape</string>
<string name="beauty_box_cheekbones">Cheek</string>
<string name="beauty_box_lower_jaw">Jaw</string>
<string name="beauty_box_cheek_thinning">Lower Width</string>
<string name="beauty_box_cheek_v">V Shape</string>
<string name="beauty_box_cheek_narrow">Upper Width</string>
<string name="beauty_box_cheek_short">Short Face</string>
<string name="beauty_box_cheek_small">Size Face</string>
<string name="beauty_box_intensity_chin">Chin Length</string>
<string name="beauty_box_intensity_forehead">Hairline</string>
<string name="beauty_box_intensity_nose">Nose Size</string>
<string name="beauty_box_intensity_mouth">Mouth Size</string>
<string name="beauty_radio_skin_beauty">Skincare</string>
<string name="beauty_radio_face_shape">Beauty type</string>
<string name="beauty_radio_filter">Filter</string>
<string name="beauty_radio_style">Presets</string>
<string name="beauty_radio_style">Style recommend</string>
<string name="makeup_radio_lipstick">Lipstick</string>
<string name="makeup_radio_blusher">Blush</string>
<string name="makeup_radio_eyebrow">Eyebrow</string>
@@ -59,16 +59,16 @@
<string name="makeup_boyfriend">Boyfriend</string>
<string name="makeup_clear">Clear</string>
<string name="makeup_grapefruit">Grapefruit</string>
<string name="beauty_micro_pouch">Circle</string>
<string name="beauty_micro_nasolabial">Wrinkles</string>
<string name="beauty_micro_pouch">Dark Circles</string>
<string name="beauty_micro_nasolabial">Laugh Line</string>
<string name="beauty_micro_smile">Smile</string>
<string name="beauty_brow_height">Brow height</string>
<string name="beauty_brow_space">Brow space</string>
<string name="beauty_micro_canthus">Canthus</string>
<string name="beauty_micro_philtrum">Philtrum</string>
<string name="beauty_micro_long_nose">Length</string>
<string name="beauty_micro_eye_space">Eye distance</string>
<string name="beauty_micro_eye_rotate">Slant</string>
<string name="beauty_brow_height">Brow Position</string>
<string name="beauty_brow_space">Brow Distance</string>
<string name="beauty_micro_canthus">Inner Corner</string>
<string name="beauty_micro_philtrum">Mouth Position</string>
<string name="beauty_micro_long_nose">Nose Lift</string>
<string name="beauty_micro_eye_space">Eye Distance</string>
<string name="beauty_micro_eye_rotate">Eye Upturn</string>
<string name="beauty_face_style_none">None</string>
<string name="beauty_face_style_1">Style 1</string>
<string name="beauty_face_style_2">Style 2</string>
@@ -77,7 +77,7 @@
<string name="beauty_face_style_5">Style 5</string>
<string name="beauty_face_style_6">Style 6</string>
<string name="beauty_face_style_7">Style 7</string>
<string name="beauty_face_style_toast">To use %s, cancel \'Presets\' first.</string>
<string name="beauty_face_style_toast">To use %s, cancel \'Style recommend\' first.</string>
<string name="select_data_photo">Photo</string>
<string name="select_data_video">Video</string>
@@ -334,4 +334,7 @@
<string name="home_function_name_fine_sticker">Exquisite sticker</string>
<string name="dialog_reset">Reset</string>
<string name="menu_diy">Custom</string>
<string name="toast_not_detect_face">No face tracking</string>
<string name="toast_not_detect_face_or_body">No face or body tracking</string>
</resources>

View File

@@ -1,8 +1,5 @@
package com.yunbao.faceunity;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
@@ -10,8 +7,5 @@ import static org.junit.Assert.*;
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

36
IAP6Helper/build.gradle Normal file
View File

@@ -0,0 +1,36 @@
apply plugin: 'com.android.library'
apply from: "../package_config.gradle"
android {
namespace "com.samsung.iap6helper"
compileSdk rootProject.ext.android.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
targetSdkVersion rootProject.ext.android.targetSdkVersion
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
buildFeatures {
buildConfig = true
}
}
repositories {
flatDir {
dirs 'libs', '../libs'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'com.google.code.gson:gson:2.8.6'
}

Binary file not shown.

25
IAP6Helper/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\sbkim\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,19 @@
package com.samsung.android.iap;
import com.samsung.android.iap.IAPServiceCallback;
interface IAPConnector {
boolean requestCmd(IAPServiceCallback callback, in Bundle bundle);
boolean unregisterCallback(IAPServiceCallback callback);
///////////////////////////// IAP 5.0
Bundle getProductsDetails(String packageName, String itemIds, int pagingIndex, int mode);
Bundle getOwnedList(String packageName, String itemType, int pagingIndex, int mode);
Bundle consumePurchasedItems(String packageName, String purchaseIds, int mode);
Bundle requestServiceAPI(String packageName, String requestId, String extraData);
}

View File

@@ -0,0 +1,7 @@
package com.samsung.android.iap;
import android.os.Bundle;
interface IAPServiceCallback {
oneway void responseCallback(in Bundle bundle);
}

View File

@@ -0,0 +1,134 @@
package com.samsung.utils;
import android.content.Context;
import android.util.Log;
import com.google.gson.Gson;
import com.samsung.android.sdk.iap.lib.constants.HelperDefine;
import com.samsung.android.sdk.iap.lib.helper.IapHelper;
import com.samsung.android.sdk.iap.lib.listener.OnConsumePurchasedItemsListener;
import com.samsung.android.sdk.iap.lib.listener.OnGetOwnedListListener;
import com.samsung.android.sdk.iap.lib.vo.ConsumeVo;
import com.samsung.android.sdk.iap.lib.vo.ErrorVo;
import com.samsung.android.sdk.iap.lib.vo.OwnedProductVo;
import com.samsung.iap6helper.R;
import java.util.ArrayList;
public class SamsungUtil {
private Context mContext;
IapHelper iapHelper;
private static SamsungUtil samsungUtil;
public static SamsungUtil newInstance(Context context) {
if (samsungUtil == null) {
samsungUtil = new SamsungUtil(context);
}
return samsungUtil;
}
public SamsungUtil(Context mContext) {
this.mContext = mContext;
}
/**
* 初始化
*/
public void init() {
iapHelper = IapHelper.getInstance(mContext);
//设置支付模式 OPERATION_MODE_PRODUCTION 正式模式 OPERATION_MODE_TEST 测试模式
iapHelper.setOperationMode(HelperDefine.OperationMode.OPERATION_MODE_PRODUCTION);
}
public void dispose() {
if (iapHelper != null) {
iapHelper.dispose();
}
}
/**
* 购买
*
* @param skuId
*/
public void buy(String skuId, OnPaymentListener onPaymentListener) {
//购买
iapHelper.startPayment(skuId, "", (errorVo, purchaseVo) -> {
if (purchaseVo != null) {
onPaymentListener.onPaymentSuccess(purchaseVo.getPurchaseId());
} else {
if (errorVo.getErrorCode() == HelperDefine.IAP_PAYMENT_IS_CANCELED) {
onPaymentListener.onPaymentFailed(mContext.getString(R.string.pay_cancel));
} else {
onPaymentListener.onPaymentFailed(errorVo.getErrorString());
}
}
});
}
public interface OnPaymentListener {
void onPaymentSuccess(String purchaseVo);
void onPaymentFailed(String errorVo);
}
/**
* 消耗指定商品
*
* @param skuId
*/
public void consume(String skuId) {
//消耗
iapHelper.consumePurchasedItems(skuId, new OnConsumePurchasedItemsListener() {
@Override
public void onConsumePurchasedItems(ErrorVo _errorVO, ArrayList<ConsumeVo> _consumeList) {
if (_consumeList != null) {
Log.e("samsung","消耗:" + new Gson().toJson(_consumeList));
Log.e("samsung","ErrorVo" + _errorVO.dump());
}
}
});
}
public void consumeAll(ArrayList<OwnedProductVo> _ownedList) {
if (_ownedList.size() > 0) {
iapHelper.consumePurchasedItems(_ownedList.get(0).getPurchaseId(), new OnConsumePurchasedItemsListener() {
@Override
public void onConsumePurchasedItems(ErrorVo _errorVO, ArrayList<ConsumeVo> _consumeList1) {
if (_errorVO.getErrorCode() == IapHelper.IAP_ERROR_NONE) {
Log.e("samsung","消耗:" + new Gson().toJson(_consumeList1));
Log.e("samsung","ErrorVo" + _errorVO.dump());
_ownedList.remove(0);
consumeAll(_ownedList);
}
}
});
}
}
/**
* 消耗所有未消耗的消耗品
*/
public void query() {
//查询商品 PRODUCT_TYPE_ITEM 消耗品&非消耗品
iapHelper.getOwnedList(HelperDefine.PRODUCT_TYPE_ITEM, new OnGetOwnedListListener() {
@Override
public void onGetOwnedProducts(ErrorVo _errorVo, ArrayList<OwnedProductVo> _ownedList) {
if (_errorVo != null) {
if (_errorVo.getErrorCode() == IapHelper.IAP_ERROR_NONE) {
if (_ownedList != null) {
consumeAll(_ownedList);
}
}
}
}
});
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
**
** Created with STMS Automation System
*/ -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="pay_cancel">支付取消</string>
<string name="pay_suc">支付成功</string>
<string name="pay_fail">支付失敗</string>
</resources>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
**
** Created with STMS Automation System
*/ -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="pay_cancel">支付取消</string>
<string name="pay_suc">支付成功</string>
<string name="pay_fail">支付失敗</string>
</resources>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
**
** Created with STMS Automation System
*/ -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="pay_cancel">支付取消</string>
<string name="pay_suc">支付成功</string>
<string name="pay_fail">支付失敗</string>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pay_cancel">Payment cancellation</string>
<string name="pay_suc">Payment successful</string>
<string name="pay_fail">Payment failed</string>
</resources>

2
SVGAlibrary/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/build
*.iml

37
SVGAlibrary/build.gradle Normal file
View File

@@ -0,0 +1,37 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
}
compileOptions {
kotlinOptions.freeCompilerArgs += ['-module-name', "com.opensource.svgaplayer"]
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
exclude 'META-INF/ASL2.0'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/MANIFEST.MF'
}
}
dependencies {
implementation 'com.squareup.wire:wire-runtime:4.4.1'
}

17
SVGAlibrary/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/PonyCui_Home/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.opensource.svgaplayer">
<application android:allowBackup="true" android:label="@string/app_name">
</application>
</manifest>

View File

@@ -0,0 +1,9 @@
package com.opensource.svgaplayer
/**
* Created by miaojun on 2019/6/21.
* mail:1290846731@qq.com
*/
interface IClickAreaListener{
fun onResponseArea(key : String,x0 : Int, y0 : Int, x1 : Int, y1 : Int)
}

View File

@@ -0,0 +1,119 @@
package com.opensource.svgaplayer
import android.content.Context
import com.opensource.svgaplayer.utils.log.LogUtils
import java.io.File
import java.net.URL
import java.security.MessageDigest
/**
* SVGA 缓存管理
*/
object SVGACache {
enum class Type {
DEFAULT,
FILE
}
private const val TAG = "SVGACache"
private var type: Type = Type.DEFAULT
private var cacheDir: String = "/"
get() {
if (field != "/") {
val dir = File(field)
if (!dir.exists()) {
dir.mkdirs()
}
}
return field
}
fun onCreate(context: Context?) {
onCreate(context, Type.DEFAULT)
}
fun onCreate(context: Context?, type: Type) {
if (isInitialized()) return
context ?: return
cacheDir = "${context.cacheDir.absolutePath}/svga/"
File(cacheDir).takeIf { !it.exists() }?.mkdirs()
this.type = type
}
/**
* 清理缓存
*/
fun clearCache() {
if (!isInitialized()) {
LogUtils.error(TAG, "SVGACache is not init!")
return
}
SVGAParser.threadPoolExecutor.execute {
clearDir(cacheDir)
LogUtils.info(TAG, "Clear svga cache done!")
}
}
// 清除目录下的所有文件
internal fun clearDir(path: String) {
try {
val dir = File(path)
dir.takeIf { it.exists() }?.let { parentDir ->
parentDir.listFiles()?.forEach { file ->
if (!file.exists()) {
return@forEach
}
if (file.isDirectory) {
clearDir(file.absolutePath)
}
file.delete()
}
}
} catch (e: Exception) {
LogUtils.error(TAG, "Clear svga cache path: $path fail", e)
}
}
fun isInitialized(): Boolean {
return "/" != cacheDir && File(cacheDir).exists()
}
fun isDefaultCache(): Boolean = type == Type.DEFAULT
fun isCached(cacheKey: String): Boolean {
return if (isDefaultCache()) {
buildCacheDir(cacheKey)
} else {
buildSvgaFile(
cacheKey
)
}.exists()
}
fun buildCacheKey(str: String): String {
val messageDigest = MessageDigest.getInstance("MD5")
messageDigest.update(str.toByteArray(charset("UTF-8")))
val digest = messageDigest.digest()
var sb = ""
for (b in digest) {
sb += String.format("%02x", b)
}
return sb
}
fun buildCacheKey(url: URL): String = buildCacheKey(url.toString())
fun buildCacheDir(cacheKey: String): File {
return File("$cacheDir$cacheKey/")
}
fun buildSvgaFile(cacheKey: String): File {
return File("$cacheDir$cacheKey.svga")
}
fun buildAudioFile(audio: String): File {
return File("$cacheDir$audio.mp3")
}
}

View File

@@ -0,0 +1,13 @@
package com.opensource.svgaplayer
/**
* Created by cuiminghui on 2017/3/30.
*/
interface SVGACallback {
fun onPause()
fun onFinished()
fun onRepeat()
fun onStep(frame: Int, percentage: Double)
}

View File

@@ -0,0 +1,9 @@
package com.opensource.svgaplayer
/**
* Created by miaojun on 2019/6/21.
* mail:1290846731@qq.com
*/
interface SVGAClickAreaListener{
fun onClick(clickKey : String)
}

View File

@@ -0,0 +1,106 @@
package com.opensource.svgaplayer
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import android.widget.ImageView
import com.opensource.svgaplayer.drawer.SVGACanvasDrawer
class SVGADrawable(val videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity): Drawable() {
constructor(videoItem: SVGAVideoEntity): this(videoItem, SVGADynamicEntity())
var cleared = true
internal set (value) {
if (field == value) {
return
}
field = value
invalidateSelf()
}
var currentFrame = 0
internal set (value) {
if (field == value) {
return
}
field = value
invalidateSelf()
}
var scaleType: ImageView.ScaleType = ImageView.ScaleType.MATRIX
private val drawer = SVGACanvasDrawer(videoItem, dynamicItem)
override fun draw(canvas: Canvas?) {
if (cleared) {
return
}
canvas?.let {
drawer.drawFrame(it,currentFrame, scaleType)
}
}
override fun setAlpha(alpha: Int) {
}
override fun getOpacity(): Int {
return PixelFormat.TRANSPARENT
}
override fun setColorFilter(colorFilter: ColorFilter?) {
}
fun resume() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
if (SVGASoundManager.isInit()){
SVGASoundManager.resume(it)
}else{
videoItem.soundPool?.resume(it)
}
}
}
}
fun pause() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
if (SVGASoundManager.isInit()){
SVGASoundManager.pause(it)
}else{
videoItem.soundPool?.pause(it)
}
}
}
}
fun stop() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
if (SVGASoundManager.isInit()){
SVGASoundManager.stop(it)
}else{
videoItem.soundPool?.stop(it)
}
}
}
}
fun clear() {
videoItem.audioList.forEach { audio ->
audio.playID?.let {
if (SVGASoundManager.isInit()){
SVGASoundManager.stop(it)
}else{
videoItem.soundPool?.stop(it)
}
}
audio.playID = null
}
videoItem.clear()
}
}

View File

@@ -0,0 +1,153 @@
package com.opensource.svgaplayer
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.text.BoringLayout
import android.text.StaticLayout
import android.text.TextPaint
import java.net.HttpURLConnection
import java.net.URL
/**
* Created by cuiminghui on 2017/3/30.
*/
class SVGADynamicEntity {
internal var dynamicHidden: HashMap<String, Boolean> = hashMapOf()
internal var dynamicImage: HashMap<String, Bitmap> = hashMapOf()
internal var dynamicText: HashMap<String, String> = hashMapOf()
internal var dynamicTextPaint: HashMap<String, TextPaint> = hashMapOf()
internal var dynamicStaticLayoutText: HashMap<String, StaticLayout> = hashMapOf()
internal var dynamicBoringLayoutText: HashMap<String, BoringLayout> = hashMapOf()
internal var dynamicDrawer: HashMap<String, (canvas: Canvas, frameIndex: Int) -> Boolean> = hashMapOf()
//点击事件回调map
internal var mClickMap : HashMap<String, IntArray> = hashMapOf()
internal var dynamicIClickArea: HashMap<String, IClickAreaListener> = hashMapOf()
internal var dynamicDrawerSized: HashMap<String, (canvas: Canvas, frameIndex: Int, width: Int, height: Int) -> Boolean> = hashMapOf()
internal var isTextDirty = false
fun setHidden(value: Boolean, forKey: String) {
this.dynamicHidden.put(forKey, value)
}
fun setDynamicImage(bitmap: Bitmap, forKey: String) {
this.dynamicImage.put(forKey, bitmap)
}
fun setDynamicImage(url: String, forKey: String) {
val handler = android.os.Handler()
SVGAParser.threadPoolExecutor.execute {
(URL(url).openConnection() as? HttpURLConnection)?.let {
try {
it.connectTimeout = 20 * 1000
it.requestMethod = "GET"
it.connect()
it.inputStream.use { stream ->
BitmapFactory.decodeStream(stream)?.let {
handler.post { setDynamicImage(it, forKey) }
}
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
it.disconnect()
} catch (disconnectException: Throwable) {
// ignored here
}
}
}
}
}
fun setDynamicText(text: String, textPaint: TextPaint, forKey: String) {
this.isTextDirty = true
this.dynamicText.put(forKey, text)
this.dynamicTextPaint.put(forKey, textPaint)
}
fun setDynamicText(layoutText: StaticLayout, forKey: String) {
this.isTextDirty = true
this.dynamicStaticLayoutText.put(forKey, layoutText)
}
fun setDynamicText(layoutText: BoringLayout, forKey: String) {
this.isTextDirty = true
BoringLayout.isBoring(layoutText.text,layoutText.paint)?.let {
this.dynamicBoringLayoutText.put(forKey,layoutText)
}
}
fun setDynamicDrawer(drawer: (canvas: Canvas, frameIndex: Int) -> Boolean, forKey: String) {
this.dynamicDrawer.put(forKey, drawer)
}
fun setClickArea(clickKey: List<String>) {
for(itemKey in clickKey){
dynamicIClickArea.put(itemKey,object : IClickAreaListener {
override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) {
mClickMap.let {
if(it.get(key) == null){
it.put(key, intArrayOf(x0,y0,x1,y1))
}else{
it.get(key)?.let {
it[0] = x0
it[1] = y0
it[2] = x1
it[3] = y1
}
}
}
}
})
}
}
fun setClickArea(clickKey: String) {
dynamicIClickArea.put(clickKey, object : IClickAreaListener {
override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) {
mClickMap.let {
if (it.get(key) == null) {
it.put(key, intArrayOf(x0, y0, x1, y1))
} else {
it.get(key)?.let {
it[0] = x0
it[1] = y0
it[2] = x1
it[3] = y1
}
}
}
}
})
}
fun setDynamicDrawerSized(drawer: (canvas: Canvas, frameIndex: Int, width: Int, height: Int) -> Boolean, forKey: String) {
this.dynamicDrawerSized.put(forKey, drawer)
}
fun clearDynamicObjects() {
this.isTextDirty = true
this.dynamicHidden.clear()
this.dynamicImage.clear()
this.dynamicText.clear()
this.dynamicTextPaint.clear()
this.dynamicStaticLayoutText.clear()
this.dynamicBoringLayoutText.clear()
this.dynamicDrawer.clear()
this.dynamicIClickArea.clear()
this.mClickMap.clear()
this.dynamicDrawerSized.clear()
}
}

View File

@@ -0,0 +1,329 @@
package com.opensource.svgaplayer
import android.animation.Animator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.ImageView
import com.opensource.svgaplayer.utils.SVGARange
import com.opensource.svgaplayer.utils.log.LogUtils
import java.lang.ref.WeakReference
import java.net.URL
/**
* Created by PonyCui on 2017/3/29.
*/
open class SVGAImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
private val TAG = "SVGAImageView"
enum class FillMode {
Backward,
Forward,
Clear,
}
var isAnimating = false
private set
var loops = 0
@Deprecated(
"It is recommended to use clearAfterDetached, or manually call to SVGAVideoEntity#clear." +
"If you just consider cleaning up the canvas after playing, you can use FillMode#Clear.",
level = DeprecationLevel.WARNING
)
var clearsAfterStop = false
var clearsAfterDetached = false
var fillMode: FillMode = FillMode.Forward
var callback: SVGACallback? = null
private var mAnimator: ValueAnimator? = null
private var mItemClickAreaListener: SVGAClickAreaListener? = null
private var mAntiAlias = true
private var mAutoPlay = true
private val mAnimatorListener = AnimatorListener(this)
private val mAnimatorUpdateListener = AnimatorUpdateListener(this)
private var mStartFrame = 0
private var mEndFrame = 0
init {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
}
attrs?.let { loadAttrs(it) }
}
private fun loadAttrs(attrs: AttributeSet) {
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.SVGAImageView, 0, 0)
loops = typedArray.getInt(R.styleable.SVGAImageView_loopCount, 0)
clearsAfterStop = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterStop, false)
clearsAfterDetached = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterDetached, false)
mAntiAlias = typedArray.getBoolean(R.styleable.SVGAImageView_antiAlias, true)
mAutoPlay = typedArray.getBoolean(R.styleable.SVGAImageView_autoPlay, true)
typedArray.getString(R.styleable.SVGAImageView_fillMode)?.let {
when (it) {
"0" -> {
fillMode = FillMode.Backward
}
"1" -> {
fillMode = FillMode.Forward
}
"2" -> {
fillMode = FillMode.Clear
}
}
}
typedArray.getString(R.styleable.SVGAImageView_source)?.let {
parserSource(it)
}
typedArray.recycle()
}
private fun parserSource(source: String) {
val refImgView = WeakReference<SVGAImageView>(this)
val parser = SVGAParser(context)
if (source.startsWith("http://") || source.startsWith("https://")) {
parser.decodeFromURL(URL(source), createParseCompletion(refImgView))
} else {
parser.decodeFromAssets(source, createParseCompletion(refImgView))
}
}
private fun createParseCompletion(ref: WeakReference<SVGAImageView>): SVGAParser.ParseCompletion {
return object : SVGAParser.ParseCompletion {
override fun onComplete(videoItem: SVGAVideoEntity) {
ref.get()?.startAnimation(videoItem)
}
override fun onError() {}
}
}
private fun startAnimation(videoItem: SVGAVideoEntity) {
this@SVGAImageView.post {
videoItem.antiAlias = mAntiAlias
setVideoItem(videoItem)
getSVGADrawable()?.scaleType = scaleType
if (mAutoPlay) {
startAnimation()
}
}
}
fun startAnimation() {
startAnimation(null, false)
}
fun startAnimation(range: SVGARange?, reverse: Boolean = false) {
stopAnimation(false)
play(range, reverse)
}
private fun play(range: SVGARange?, reverse: Boolean) {
LogUtils.info(TAG, "================ start animation ================")
val drawable = getSVGADrawable() ?: return
setupDrawable()
mStartFrame = Math.max(0, range?.location ?: 0)
val videoItem = drawable.videoItem
mEndFrame = Math.min(videoItem.frames - 1, ((range?.location ?: 0) + (range?.length ?: Int.MAX_VALUE) - 1))
val animator = ValueAnimator.ofInt(mStartFrame, mEndFrame)
animator.interpolator = LinearInterpolator()
animator.duration = ((mEndFrame - mStartFrame + 1) * (1000 / videoItem.FPS) / generateScale()).toLong()
animator.repeatCount = if (loops <= 0) 99999 else loops - 1
animator.addUpdateListener(mAnimatorUpdateListener)
animator.addListener(mAnimatorListener)
if (reverse) {
animator.reverse()
} else {
animator.start()
}
mAnimator = animator
}
private fun setupDrawable() {
val drawable = getSVGADrawable() ?: return
drawable.cleared = false
drawable.scaleType = scaleType
}
private fun getSVGADrawable(): SVGADrawable? {
return drawable as? SVGADrawable
}
@Suppress("UNNECESSARY_SAFE_CALL")
private fun generateScale(): Double {
var scale = 1.0
try {
val animatorClass = Class.forName("android.animation.ValueAnimator") ?: return scale
val getMethod = animatorClass.getDeclaredMethod("getDurationScale") ?: return scale
scale = (getMethod.invoke(animatorClass) as Float).toDouble()
if (scale == 0.0) {
val setMethod = animatorClass.getDeclaredMethod("setDurationScale",Float::class.java) ?: return scale
setMethod.isAccessible = true
setMethod.invoke(animatorClass,1.0f)
scale = 1.0
LogUtils.info(TAG,
"The animation duration scale has been reset to" +
" 1.0x, because you closed it on developer options.")
}
} catch (ignore: Exception) {
ignore.printStackTrace()
}
return scale
}
private fun onAnimatorUpdate(animator: ValueAnimator?) {
val drawable = getSVGADrawable() ?: return
drawable.currentFrame = animator?.animatedValue as Int
val percentage = (drawable.currentFrame + 1).toDouble() / drawable.videoItem.frames.toDouble()
callback?.onStep(drawable.currentFrame, percentage)
}
private fun onAnimationEnd(animation: Animator?) {
isAnimating = false
stopAnimation()
val drawable = getSVGADrawable()
if (drawable != null) {
when (fillMode) {
FillMode.Backward -> {
drawable.currentFrame = mStartFrame
}
FillMode.Forward -> {
drawable.currentFrame = mEndFrame
}
FillMode.Clear -> {
drawable.cleared = true
}
}
}
callback?.onFinished()
}
fun clear() {
getSVGADrawable()?.cleared = true
getSVGADrawable()?.clear()
// 清除对 drawable 的引用
setImageDrawable(null)
}
fun pauseAnimation() {
stopAnimation(false)
callback?.onPause()
}
fun stopAnimation() {
stopAnimation(clear = clearsAfterStop)
}
fun stopAnimation(clear: Boolean) {
mAnimator?.cancel()
mAnimator?.removeAllListeners()
mAnimator?.removeAllUpdateListeners()
getSVGADrawable()?.stop()
getSVGADrawable()?.cleared = clear
}
fun setVideoItem(videoItem: SVGAVideoEntity?) {
setVideoItem(videoItem, SVGADynamicEntity())
}
fun setVideoItem(videoItem: SVGAVideoEntity?, dynamicItem: SVGADynamicEntity?) {
if (videoItem == null) {
setImageDrawable(null)
} else {
val drawable = SVGADrawable(videoItem, dynamicItem ?: SVGADynamicEntity())
drawable.cleared = true
setImageDrawable(drawable)
}
}
fun stepToFrame(frame: Int, andPlay: Boolean) {
pauseAnimation()
val drawable = getSVGADrawable() ?: return
drawable.currentFrame = frame
if (andPlay) {
startAnimation()
mAnimator?.let {
it.currentPlayTime = (Math.max(0.0f, Math.min(1.0f, (frame.toFloat() / drawable.videoItem.frames.toFloat()))) * it.duration).toLong()
}
}
}
fun stepToPercentage(percentage: Double, andPlay: Boolean) {
val drawable = drawable as? SVGADrawable ?: return
var frame = (drawable.videoItem.frames * percentage).toInt()
if (frame >= drawable.videoItem.frames && frame > 0) {
frame = drawable.videoItem.frames - 1
}
stepToFrame(frame, andPlay)
}
fun setOnAnimKeyClickListener(clickListener : SVGAClickAreaListener){
mItemClickAreaListener = clickListener
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event?.action != MotionEvent.ACTION_DOWN) {
return super.onTouchEvent(event)
}
val drawable = getSVGADrawable() ?: return super.onTouchEvent(event)
for ((key, value) in drawable.dynamicItem.mClickMap) {
if (event.x >= value[0] && event.x <= value[2] && event.y >= value[1] && event.y <= value[3]) {
mItemClickAreaListener?.let {
it.onClick(key)
return true
}
}
}
return super.onTouchEvent(event)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
stopAnimation(clearsAfterDetached)
if (clearsAfterDetached) {
clear()
}
}
private class AnimatorListener(view: SVGAImageView) : Animator.AnimatorListener {
private val weakReference = WeakReference<SVGAImageView>(view)
override fun onAnimationRepeat(animation: Animator?) {
weakReference.get()?.callback?.onRepeat()
}
override fun onAnimationEnd(animation: Animator?) {
weakReference.get()?.onAnimationEnd(animation)
}
override fun onAnimationCancel(animation: Animator?) {
weakReference.get()?.isAnimating = false
}
override fun onAnimationStart(animation: Animator?) {
weakReference.get()?.isAnimating = true
}
} // end of AnimatorListener
private class AnimatorUpdateListener(view: SVGAImageView) : ValueAnimator.AnimatorUpdateListener {
private val weakReference = WeakReference<SVGAImageView>(view)
override fun onAnimationUpdate(animation: ValueAnimator?) {
weakReference.get()?.onAnimatorUpdate(animation)
}
} // end of AnimatorUpdateListener
}

View File

@@ -0,0 +1,565 @@
package com.opensource.svgaplayer
import android.content.Context
import android.net.http.HttpResponseCache
import android.os.Handler
import android.os.Looper
import com.opensource.svgaplayer.proto.MovieEntity
import com.opensource.svgaplayer.utils.log.LogUtils
import org.json.JSONObject
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.Executors
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.atomic.AtomicInteger
import java.util.zip.Inflater
import java.util.zip.ZipInputStream
/**
* Created by PonyCui 16/6/18.
*/
private var fileLock: Int = 0
private var isUnzipping = false
class SVGAParser(context: Context?) {
private var mContext = context?.applicationContext
init {
SVGACache.onCreate(context)
}
@Volatile
private var mFrameWidth: Int = 0
@Volatile
private var mFrameHeight: Int = 0
interface ParseCompletion {
fun onComplete(videoItem: SVGAVideoEntity)
fun onError()
}
interface PlayCallback{
fun onPlay(file: List<File>)
}
open class FileDownloader {
var noCache = false
open fun resume(url: URL, complete: (inputStream: InputStream) -> Unit, failure: (e: Exception) -> Unit): () -> Unit {
var cancelled = false
val cancelBlock = {
cancelled = true
}
threadPoolExecutor.execute {
try {
LogUtils.info(TAG, "================ svga file download start ================")
if (HttpResponseCache.getInstalled() == null && !noCache) {
LogUtils.error(TAG, "SVGAParser can not handle cache before install HttpResponseCache. see https://github.com/yyued/SVGAPlayer-Android#cache")
LogUtils.error(TAG, "在配置 HttpResponseCache 前 SVGAParser 无法缓存. 查看 https://github.com/yyued/SVGAPlayer-Android#cache ")
}
(url.openConnection() as? HttpURLConnection)?.let {
it.connectTimeout = 20 * 1000
it.requestMethod = "GET"
it.setRequestProperty("Connection", "close")
it.connect()
it.inputStream.use { inputStream ->
ByteArrayOutputStream().use { outputStream ->
val buffer = ByteArray(4096)
var count: Int
while (true) {
if (cancelled) {
LogUtils.warn(TAG, "================ svga file download canceled ================")
break
}
count = inputStream.read(buffer, 0, 4096)
if (count == -1) {
break
}
outputStream.write(buffer, 0, count)
}
if (cancelled) {
LogUtils.warn(TAG, "================ svga file download canceled ================")
return@execute
}
ByteArrayInputStream(outputStream.toByteArray()).use {
LogUtils.info(TAG, "================ svga file download complete ================")
complete(it)
}
}
}
}
} catch (e: Exception) {
LogUtils.error(TAG, "================ svga file download fail ================")
LogUtils.error(TAG, "error: ${e.message}")
e.printStackTrace()
failure(e)
}
}
return cancelBlock
}
}
var fileDownloader = FileDownloader()
companion object {
private const val TAG = "SVGAParser"
private val threadNum = AtomicInteger(0)
private var mShareParser = SVGAParser(null)
internal var threadPoolExecutor = Executors.newCachedThreadPool { r ->
Thread(r, "SVGAParser-Thread-${threadNum.getAndIncrement()}")
}
fun setThreadPoolExecutor(executor: ThreadPoolExecutor) {
threadPoolExecutor = executor
}
fun shareParser(): SVGAParser {
return mShareParser
}
}
fun init(context: Context) {
mContext = context.applicationContext
SVGACache.onCreate(mContext)
}
fun setFrameSize(frameWidth: Int, frameHeight: Int) {
mFrameWidth = frameWidth
mFrameHeight = frameHeight
}
fun decodeFromAssets(
name: String,
callback: ParseCompletion?,
playCallback: PlayCallback? = null
) {
if (mContext == null) {
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
return
}
LogUtils.info(TAG, "================ decode $name from assets ================")
threadPoolExecutor.execute {
try {
mContext?.assets?.open(name)?.let {
this.decodeFromInputStream(
it,
SVGACache.buildCacheKey("file:///assets/$name"),
callback,
true,
playCallback,
alias = name
)
}
} catch (e: Exception) {
this.invokeErrorCallback(e, callback, name)
}
}
}
fun decodeFromURL(
url: URL,
callback: ParseCompletion?,
playCallback: PlayCallback? = null
): (() -> Unit)? {
if (mContext == null) {
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
return null
}
val urlPath = url.toString()
LogUtils.info(TAG, "================ decode from url: $urlPath ================")
val cacheKey = SVGACache.buildCacheKey(url);
return if (SVGACache.isCached(cacheKey)) {
LogUtils.info(TAG, "this url cached")
threadPoolExecutor.execute {
if (SVGACache.isDefaultCache()) {
this.decodeFromCacheKey(cacheKey, callback, alias = urlPath)
} else {
this.decodeFromSVGAFileCacheKey(cacheKey, callback, playCallback, alias = urlPath)
}
}
return null
} else {
LogUtils.info(TAG, "no cached, prepare to download")
fileDownloader.resume(url, {
this.decodeFromInputStream(
it,
cacheKey,
callback,
false,
playCallback,
alias = urlPath
)
}, {
LogUtils.error(
TAG,
"================ svga file: $url download fail ================"
)
this.invokeErrorCallback(it, callback, alias = urlPath)
})
}
}
/**
* 读取解析本地缓存的 svga 文件.
*/
fun decodeFromSVGAFileCacheKey(
cacheKey: String,
callback: ParseCompletion?,
playCallback: PlayCallback?,
alias: String? = null
) {
threadPoolExecutor.execute {
try {
LogUtils.info(TAG, "================ decode $alias from svga cachel file to entity ================")
FileInputStream(SVGACache.buildSvgaFile(cacheKey)).use { inputStream ->
readAsBytes(inputStream)?.let { bytes ->
if (isZipFile(bytes)) {
this.decodeFromCacheKey(cacheKey, callback, alias)
} else {
LogUtils.info(TAG, "inflate start")
inflate(bytes)?.let {
LogUtils.info(TAG, "inflate complete")
val videoItem = SVGAVideoEntity(
MovieEntity.ADAPTER.decode(it),
File(cacheKey),
mFrameWidth,
mFrameHeight
)
LogUtils.info(TAG, "SVGAVideoEntity prepare start")
videoItem.prepare({
LogUtils.info(TAG, "SVGAVideoEntity prepare success")
this.invokeCompleteCallback(videoItem, callback, alias)
},playCallback)
} ?: this.invokeErrorCallback(
Exception("inflate(bytes) cause exception"),
callback,
alias
)
}
} ?: this.invokeErrorCallback(
Exception("readAsBytes(inputStream) cause exception"),
callback,
alias
)
}
} catch (e: java.lang.Exception) {
this.invokeErrorCallback(e, callback, alias)
} finally {
LogUtils.info(TAG, "================ decode $alias from svga cachel file to entity end ================")
}
}
}
fun decodeFromInputStream(
inputStream: InputStream,
cacheKey: String,
callback: ParseCompletion?,
closeInputStream: Boolean = false,
playCallback: PlayCallback? = null,
alias: String? = null
) {
if (mContext == null) {
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
return
}
LogUtils.info(TAG, "================ decode $alias from input stream ================")
threadPoolExecutor.execute {
try {
readAsBytes(inputStream)?.let { bytes ->
if (isZipFile(bytes)) {
LogUtils.info(TAG, "decode from zip file")
if (!SVGACache.buildCacheDir(cacheKey).exists() || isUnzipping) {
synchronized(fileLock) {
if (!SVGACache.buildCacheDir(cacheKey).exists()) {
isUnzipping = true
LogUtils.info(TAG, "no cached, prepare to unzip")
ByteArrayInputStream(bytes).use {
unzip(it, cacheKey)
isUnzipping = false
LogUtils.info(TAG, "unzip success")
}
}
}
}
this.decodeFromCacheKey(cacheKey, callback, alias)
} else {
if (!SVGACache.isDefaultCache()) {
// 如果 SVGACache 设置类型为 FILE
threadPoolExecutor.execute {
SVGACache.buildSvgaFile(cacheKey).let { cacheFile ->
try {
cacheFile.takeIf { !it.exists() }?.createNewFile()
FileOutputStream(cacheFile).write(bytes)
} catch (e: Exception) {
LogUtils.error(TAG, "create cache file fail.", e)
cacheFile.delete()
}
}
}
}
LogUtils.info(TAG, "inflate start")
inflate(bytes)?.let {
LogUtils.info(TAG, "inflate complete")
val videoItem = SVGAVideoEntity(
MovieEntity.ADAPTER.decode(it),
File(cacheKey),
mFrameWidth,
mFrameHeight
)
LogUtils.info(TAG, "SVGAVideoEntity prepare start")
videoItem.prepare({
LogUtils.info(TAG, "SVGAVideoEntity prepare success")
this.invokeCompleteCallback(videoItem, callback, alias)
},playCallback)
} ?: this.invokeErrorCallback(
Exception("inflate(bytes) cause exception"),
callback,
alias
)
}
} ?: this.invokeErrorCallback(
Exception("readAsBytes(inputStream) cause exception"),
callback,
alias
)
} catch (e: java.lang.Exception) {
this.invokeErrorCallback(e, callback, alias)
} finally {
if (closeInputStream) {
inputStream.close()
}
LogUtils.info(TAG, "================ decode $alias from input stream end ================")
}
}
}
/**
* @deprecated from 2.4.0
*/
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromAssets(assetsName, callback)"))
fun parse(assetsName: String, callback: ParseCompletion?) {
this.decodeFromAssets(assetsName, callback,null)
}
/**
* @deprecated from 2.4.0
*/
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromURL(url, callback)"))
fun parse(url: URL, callback: ParseCompletion?) {
this.decodeFromURL(url, callback,null)
}
/**
* @deprecated from 2.4.0
*/
@Deprecated("This method has been deprecated from 2.4.0.", ReplaceWith("this.decodeFromInputStream(inputStream, cacheKey, callback, closeInputStream)"))
fun parse(
inputStream: InputStream,
cacheKey: String,
callback: ParseCompletion?,
closeInputStream: Boolean = false
) {
this.decodeFromInputStream(inputStream, cacheKey, callback, closeInputStream,null)
}
private fun invokeCompleteCallback(
videoItem: SVGAVideoEntity,
callback: ParseCompletion?,
alias: String?
) {
Handler(Looper.getMainLooper()).post {
LogUtils.info(TAG, "================ $alias parser complete ================")
callback?.onComplete(videoItem)
}
}
private fun invokeErrorCallback(
e: Exception,
callback: ParseCompletion?,
alias: String?
) {
e.printStackTrace()
LogUtils.error(TAG, "================ $alias parser error ================")
LogUtils.error(TAG, "$alias parse error", e)
Handler(Looper.getMainLooper()).post {
callback?.onError()
}
}
private fun decodeFromCacheKey(
cacheKey: String,
callback: ParseCompletion?,
alias: String?
) {
LogUtils.info(TAG, "================ decode $alias from cache ================")
LogUtils.debug(TAG, "decodeFromCacheKey called with cacheKey : $cacheKey")
if (mContext == null) {
LogUtils.error(TAG, "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")
return
}
try {
val cacheDir = SVGACache.buildCacheDir(cacheKey)
File(cacheDir, "movie.binary").takeIf { it.isFile }?.let { binaryFile ->
try {
LogUtils.info(TAG, "binary change to entity")
FileInputStream(binaryFile).use {
LogUtils.info(TAG, "binary change to entity success")
this.invokeCompleteCallback(
SVGAVideoEntity(
MovieEntity.ADAPTER.decode(it),
cacheDir,
mFrameWidth,
mFrameHeight
),
callback,
alias
)
}
} catch (e: Exception) {
LogUtils.error(TAG, "binary change to entity fail", e)
cacheDir.delete()
binaryFile.delete()
throw e
}
}
File(cacheDir, "movie.spec").takeIf { it.isFile }?.let { jsonFile ->
try {
LogUtils.info(TAG, "spec change to entity")
FileInputStream(jsonFile).use { fileInputStream ->
ByteArrayOutputStream().use { byteArrayOutputStream ->
val buffer = ByteArray(2048)
while (true) {
val size = fileInputStream.read(buffer, 0, buffer.size)
if (size == -1) {
break
}
byteArrayOutputStream.write(buffer, 0, size)
}
byteArrayOutputStream.toString().let {
JSONObject(it).let {
LogUtils.info(TAG, "spec change to entity success")
this.invokeCompleteCallback(
SVGAVideoEntity(
it,
cacheDir,
mFrameWidth,
mFrameHeight
),
callback,
alias
)
}
}
}
}
} catch (e: Exception) {
LogUtils.error(TAG, "$alias movie.spec change to entity fail", e)
cacheDir.delete()
jsonFile.delete()
throw e
}
}
} catch (e: Exception) {
this.invokeErrorCallback(e, callback, alias)
}
}
private fun readAsBytes(inputStream: InputStream): ByteArray? {
ByteArrayOutputStream().use { byteArrayOutputStream ->
val byteArray = ByteArray(2048)
while (true) {
val count = inputStream.read(byteArray, 0, 2048)
if (count <= 0) {
break
} else {
byteArrayOutputStream.write(byteArray, 0, count)
}
}
return byteArrayOutputStream.toByteArray()
}
}
private fun inflate(byteArray: ByteArray): ByteArray? {
val inflater = Inflater()
inflater.setInput(byteArray, 0, byteArray.size)
val inflatedBytes = ByteArray(2048)
ByteArrayOutputStream().use { inflatedOutputStream ->
while (true) {
val count = inflater.inflate(inflatedBytes, 0, 2048)
if (count <= 0) {
break
} else {
inflatedOutputStream.write(inflatedBytes, 0, count)
}
}
inflater.end()
return inflatedOutputStream.toByteArray()
}
}
// 是否是 zip 文件
private fun isZipFile(bytes: ByteArray): Boolean {
return bytes.size > 4 && bytes[0].toInt() == 80 && bytes[1].toInt() == 75 && bytes[2].toInt() == 3 && bytes[3].toInt() == 4
}
// 解压
private fun unzip(inputStream: InputStream, cacheKey: String) {
LogUtils.info(TAG, "================ unzip prepare ================")
val cacheDir = SVGACache.buildCacheDir(cacheKey)
cacheDir.mkdirs()
try {
BufferedInputStream(inputStream).use {
ZipInputStream(it).use { zipInputStream ->
while (true) {
val zipItem = zipInputStream.nextEntry ?: break
if (zipItem.name.contains("../")) {
// 解压路径存在路径穿越问题,直接过滤
continue
}
if (zipItem.name.contains("/")) {
continue
}
val file = File(cacheDir, zipItem.name)
ensureUnzipSafety(file, cacheDir.absolutePath)
FileOutputStream(file).use { fileOutputStream ->
val buff = ByteArray(2048)
while (true) {
val readBytes = zipInputStream.read(buff)
if (readBytes <= 0) {
break
}
fileOutputStream.write(buff, 0, readBytes)
}
}
LogUtils.error(TAG, "================ unzip complete ================")
zipInputStream.closeEntry()
}
}
}
} catch (e: Exception) {
LogUtils.error(TAG, "================ unzip error ================")
LogUtils.error(TAG, "error", e)
SVGACache.clearDir(cacheDir.absolutePath)
cacheDir.delete()
throw e
}
}
// 检查 zip 路径穿透
private fun ensureUnzipSafety(outputFile: File, dstDirPath: String) {
val dstDirCanonicalPath = File(dstDirPath).canonicalPath
val outputFileCanonicalPath = outputFile.canonicalPath
if (!outputFileCanonicalPath.startsWith(dstDirCanonicalPath)) {
throw IOException("Found Zip Path Traversal Vulnerability with $dstDirCanonicalPath")
}
}
}

View File

@@ -0,0 +1,19 @@
package com.opensource.svgaplayer
import android.content.Context
import android.util.AttributeSet
/**
* Created by cuiminghui on 2017/3/30.
* @deprecated from 2.4.0
*/
@Deprecated("This class has been deprecated from 2.4.0. We don't recommend you to use it.")
class SVGAPlayer: SVGAImageView {
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
}

View File

@@ -0,0 +1,194 @@
package com.opensource.svgaplayer
/**
* @author Devin
*
* Created on 2/24/21.
*/
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.SoundPool
import android.os.Build
import com.opensource.svgaplayer.utils.log.LogUtils
import java.io.FileDescriptor
/**
* Author : llk
* Time : 2020/10/24
* Description : svga 音频加载管理类
* 将 SoundPool 抽取到单例里边,规避 load 资源之后不回调 onLoadComplete 的问题。
*
* 需要对 SVGASoundManager 进行初始化
*
* 相关文章Android SoundPool 崩溃问题研究
* https://zhuanlan.zhihu.com/p/29985198
*/
object SVGASoundManager {
private val TAG = SVGASoundManager::class.java.simpleName
private var soundPool: SoundPool? = null
private val soundCallBackMap: MutableMap<Int, SVGASoundCallBack> = mutableMapOf()
/**
* 音量设置,范围在 [0, 1] 之间
*/
private var volume: Float = 1f
/**
* 音频回调
*/
internal interface SVGASoundCallBack {
// 音量发生变化
fun onVolumeChange(value: Float)
// 音频加载完成
fun onComplete()
}
fun init() {
init(20)
}
fun init(maxStreams: Int) {
LogUtils.debug(TAG, "**************** init **************** $maxStreams")
if (soundPool != null) {
return
}
soundPool = getSoundPool(maxStreams)
soundPool?.setOnLoadCompleteListener { _, soundId, status ->
LogUtils.debug(TAG, "SoundPool onLoadComplete soundId=$soundId status=$status")
if (status == 0) { //加载该声音成功
if (soundCallBackMap.containsKey(soundId)) {
soundCallBackMap[soundId]?.onComplete()
}
}
}
}
fun release() {
LogUtils.debug(TAG, "**************** release ****************")
if (soundCallBackMap.isNotEmpty()) {
soundCallBackMap.clear()
}
}
/**
* 根据当前播放实体,设置音量
*
* @param volume 范围在 [0, 1]
* @param entity 根据需要控制对应 entity 音量大小,若为空则控制所有正在播放的音频音量
*/
fun setVolume(volume: Float, entity: SVGAVideoEntity? = null) {
if (!checkInit()) {
return
}
if (volume < 0f || volume > 1f) {
LogUtils.error(TAG, "The volume level is in the range of 0 to 1 ")
return
}
if (entity == null) {
this.volume = volume
val iterator = soundCallBackMap.entries.iterator()
while (iterator.hasNext()) {
val e = iterator.next()
e.value.onVolumeChange(volume)
}
return
}
val soundPool = soundPool ?: return
entity.audioList.forEach { audio ->
val streamId = audio.playID ?: return
soundPool.setVolume(streamId, volume, volume)
}
}
/**
* 是否初始化
* 如果没有初始化就使用原来SvgaPlayer库的音频加载逻辑。
* @return true 则已初始化, 否则为 false
*/
internal fun isInit(): Boolean {
return soundPool != null
}
private fun checkInit(): Boolean {
val isInit = isInit()
if (!isInit) {
LogUtils.error(TAG, "soundPool is null, you need call init() !!!")
}
return isInit
}
private fun getSoundPool(maxStreams: Int) = if (Build.VERSION.SDK_INT >= 21) {
val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.build()
SoundPool.Builder().setAudioAttributes(attributes)
.setMaxStreams(maxStreams)
.build()
} else {
SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0)
}
internal fun load(callBack: SVGASoundCallBack?,
fd: FileDescriptor?,
offset: Long,
length: Long,
priority: Int): Int {
if (!checkInit()) return -1
val soundId = soundPool!!.load(fd, offset, length, priority)
LogUtils.debug(TAG, "load soundId=$soundId callBack=$callBack")
if (callBack != null && !soundCallBackMap.containsKey(soundId)) {
soundCallBackMap[soundId] = callBack
}
return soundId
}
internal fun unload(soundId: Int) {
if (!checkInit()) return
LogUtils.debug(TAG, "unload soundId=$soundId")
soundPool!!.unload(soundId)
soundCallBackMap.remove(soundId)
}
internal fun play(soundId: Int): Int {
if (!checkInit()) return -1
LogUtils.debug(TAG, "play soundId=$soundId")
return soundPool!!.play(soundId, volume, volume, 1, 0, 1.0f)
}
internal fun stop(soundId: Int) {
if (!checkInit()) return
LogUtils.debug(TAG, "stop soundId=$soundId")
soundPool!!.stop(soundId)
}
internal fun resume(soundId: Int) {
if (!checkInit()) return
LogUtils.debug(TAG, "stop soundId=$soundId")
soundPool!!.resume(soundId)
}
internal fun pause(soundId: Int) {
if (!checkInit()) return
LogUtils.debug(TAG, "pause soundId=$soundId")
soundPool!!.pause(soundId)
}
}

View File

@@ -0,0 +1,347 @@
package com.opensource.svgaplayer
import android.graphics.Bitmap
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.SoundPool
import android.os.Build
import com.opensource.svgaplayer.bitmap.SVGABitmapByteArrayDecoder
import com.opensource.svgaplayer.bitmap.SVGABitmapFileDecoder
import com.opensource.svgaplayer.entities.SVGAAudioEntity
import com.opensource.svgaplayer.entities.SVGAVideoSpriteEntity
import com.opensource.svgaplayer.proto.AudioEntity
import com.opensource.svgaplayer.proto.MovieEntity
import com.opensource.svgaplayer.proto.MovieParams
import com.opensource.svgaplayer.utils.SVGARect
import com.opensource.svgaplayer.utils.log.LogUtils
import org.json.JSONObject
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.*
import kotlin.collections.ArrayList
/**
* Created by PonyCui on 16/6/18.
*/
class SVGAVideoEntity {
private val TAG = "SVGAVideoEntity"
var antiAlias = true
var movieItem: MovieEntity? = null
var videoSize = SVGARect(0.0, 0.0, 0.0, 0.0)
private set
var FPS = 15
private set
var frames: Int = 0
private set
internal var spriteList: List<SVGAVideoSpriteEntity> = emptyList()
internal var audioList: List<SVGAAudioEntity> = emptyList()
internal var soundPool: SoundPool? = null
private var soundCallback: SVGASoundManager.SVGASoundCallBack? = null
internal var imageMap = HashMap<String, Bitmap>()
private var mCacheDir: File
private var mFrameHeight = 0
private var mFrameWidth = 0
private var mPlayCallback: SVGAParser.PlayCallback?=null
private lateinit var mCallback: () -> Unit
constructor(json: JSONObject, cacheDir: File) : this(json, cacheDir, 0, 0)
constructor(json: JSONObject, cacheDir: File, frameWidth: Int, frameHeight: Int) {
mFrameWidth = frameWidth
mFrameHeight = frameHeight
mCacheDir = cacheDir
val movieJsonObject = json.optJSONObject("movie") ?: return
setupByJson(movieJsonObject)
try {
parserImages(json)
} catch (e: Exception) {
e.printStackTrace()
} catch (e: OutOfMemoryError) {
e.printStackTrace()
}
resetSprites(json)
}
private fun setupByJson(movieObject: JSONObject) {
movieObject.optJSONObject("viewBox")?.let { viewBoxObject ->
val width = viewBoxObject.optDouble("width", 0.0)
val height = viewBoxObject.optDouble("height", 0.0)
videoSize = SVGARect(0.0, 0.0, width, height)
}
FPS = movieObject.optInt("fps", 20)
frames = movieObject.optInt("frames", 0)
}
constructor(entity: MovieEntity, cacheDir: File) : this(entity, cacheDir, 0, 0)
constructor(entity: MovieEntity, cacheDir: File, frameWidth: Int, frameHeight: Int) {
this.mFrameWidth = frameWidth
this.mFrameHeight = frameHeight
this.mCacheDir = cacheDir
this.movieItem = entity
entity.params?.let(this::setupByMovie)
try {
parserImages(entity)
} catch (e: Exception) {
e.printStackTrace()
} catch (e: OutOfMemoryError) {
e.printStackTrace()
}
resetSprites(entity)
}
private fun setupByMovie(movieParams: MovieParams) {
val width = (movieParams.viewBoxWidth ?: 0.0f).toDouble()
val height = (movieParams.viewBoxHeight ?: 0.0f).toDouble()
videoSize = SVGARect(0.0, 0.0, width, height)
FPS = movieParams.fps ?: 20
frames = movieParams.frames ?: 0
}
internal fun prepare(callback: () -> Unit, playCallback: SVGAParser.PlayCallback?) {
mCallback = callback
mPlayCallback = playCallback
if (movieItem == null) {
mCallback()
} else {
setupAudios(movieItem!!) {
mCallback()
}
}
}
private fun parserImages(json: JSONObject) {
val imgJson = json.optJSONObject("images") ?: return
imgJson.keys().forEach { imgKey ->
val filePath = generateBitmapFilePath(imgJson[imgKey].toString(), imgKey)
if (filePath.isEmpty()) {
return
}
val bitmapKey = imgKey.replace(".matte", "")
val bitmap = createBitmap(filePath)
if (bitmap != null) {
imageMap[bitmapKey] = bitmap
}
}
}
private fun generateBitmapFilePath(imgName: String, imgKey: String): String {
val path = mCacheDir.absolutePath + "/" + imgName
val path1 = "$path.png"
val path2 = mCacheDir.absolutePath + "/" + imgKey + ".png"
return when {
File(path).exists() -> path
File(path1).exists() -> path1
File(path2).exists() -> path2
else -> ""
}
}
private fun createBitmap(filePath: String): Bitmap? {
return SVGABitmapFileDecoder.decodeBitmapFrom(filePath, mFrameWidth, mFrameHeight)
}
private fun parserImages(obj: MovieEntity) {
obj.images?.entries?.forEach { entry ->
val byteArray = entry.value.toByteArray()
if (byteArray.count() < 4) {
return@forEach
}
val fileTag = byteArray.slice(IntRange(0, 3))
if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) {
return@forEach
}
val filePath = generateBitmapFilePath(entry.value.utf8(), entry.key)
createBitmap(byteArray, filePath)?.let { bitmap ->
imageMap[entry.key] = bitmap
}
}
}
private fun createBitmap(byteArray: ByteArray, filePath: String): Bitmap? {
val bitmap = SVGABitmapByteArrayDecoder.decodeBitmapFrom(byteArray, mFrameWidth, mFrameHeight)
return bitmap ?: createBitmap(filePath)
}
private fun resetSprites(json: JSONObject) {
val mutableList: MutableList<SVGAVideoSpriteEntity> = mutableListOf()
json.optJSONArray("sprites")?.let { item ->
for (i in 0 until item.length()) {
item.optJSONObject(i)?.let { entryJson ->
mutableList.add(SVGAVideoSpriteEntity(entryJson))
}
}
}
spriteList = mutableList.toList()
}
private fun resetSprites(entity: MovieEntity) {
spriteList = entity.sprites?.map {
return@map SVGAVideoSpriteEntity(it)
} ?: listOf()
}
private fun setupAudios(entity: MovieEntity, completionBlock: () -> Unit) {
if (entity.audios == null || entity.audios.isEmpty()) {
run(completionBlock)
return
}
setupSoundPool(entity, completionBlock)
val audiosFileMap = generateAudioFileMap(entity)
//repair when audioEntity error can not callback
//如果audiosFileMap为空 soundPool?.load 不会走 导致 setOnLoadCompleteListener 不会回调 导致外层prepare不回调卡住
if (audiosFileMap.size == 0) {
run(completionBlock)
return
}
this.audioList = entity.audios.map { audio ->
return@map createSvgaAudioEntity(audio, audiosFileMap)
}
}
private fun createSvgaAudioEntity(audio: AudioEntity, audiosFileMap: HashMap<String, File>): SVGAAudioEntity {
val item = SVGAAudioEntity(audio)
val startTime = (audio.startTime ?: 0).toDouble()
val totalTime = (audio.totalTime ?: 0).toDouble()
if (totalTime.toInt() == 0) {
// 除数不能为 0
return item
}
// 直接回调文件,后续播放都不走
mPlayCallback?.let {
val fileList: MutableList<File> = ArrayList()
audiosFileMap.forEach { entity ->
fileList.add(entity.value)
}
it.onPlay(fileList)
mCallback()
return item
}
audiosFileMap[audio.audioKey]?.let { file ->
FileInputStream(file).use {
val length = it.available().toDouble()
val offset = ((startTime / totalTime) * length).toLong()
if (SVGASoundManager.isInit()) {
item.soundID = SVGASoundManager.load(soundCallback,
it.fd,
offset,
length.toLong(),
1)
} else {
item.soundID = soundPool?.load(it.fd, offset, length.toLong(), 1)
}
}
}
return item
}
private fun generateAudioFile(audioCache: File, value: ByteArray): File {
audioCache.createNewFile()
FileOutputStream(audioCache).write(value)
return audioCache
}
private fun generateAudioFileMap(entity: MovieEntity): HashMap<String, File> {
val audiosDataMap = generateAudioMap(entity)
val audiosFileMap = HashMap<String, File>()
if (audiosDataMap.count() > 0) {
audiosDataMap.forEach {
val audioCache = SVGACache.buildAudioFile(it.key)
audiosFileMap[it.key] =
audioCache.takeIf { file -> file.exists() } ?: generateAudioFile(
audioCache,
it.value
)
}
}
return audiosFileMap
}
private fun generateAudioMap(entity: MovieEntity): HashMap<String, ByteArray> {
val audiosDataMap = HashMap<String, ByteArray>()
entity.images?.entries?.forEach {
val imageKey = it.key
val byteArray = it.value.toByteArray()
if (byteArray.count() < 4) {
return@forEach
}
val fileTag = byteArray.slice(IntRange(0, 3))
if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) {
audiosDataMap[imageKey] = byteArray
}else if(fileTag[0].toInt() == -1 && fileTag[1].toInt() == -5 && fileTag[2].toInt() == -108){
audiosDataMap[imageKey] = byteArray
}
}
return audiosDataMap
}
private fun setupSoundPool(entity: MovieEntity, completionBlock: () -> Unit) {
var soundLoaded = 0
if (SVGASoundManager.isInit()) {
soundCallback = object : SVGASoundManager.SVGASoundCallBack {
override fun onVolumeChange(value: Float) {
SVGASoundManager.setVolume(value, this@SVGAVideoEntity)
}
override fun onComplete() {
soundLoaded++
if (soundLoaded >= entity.audios.count()) {
completionBlock()
}
}
}
return
}
soundPool = generateSoundPool(entity)
LogUtils.info("SVGAParser", "pool_start")
soundPool?.setOnLoadCompleteListener { _, _, _ ->
LogUtils.info("SVGAParser", "pool_complete")
soundLoaded++
if (soundLoaded >= entity.audios.count()) {
completionBlock()
}
}
}
private fun generateSoundPool(entity: MovieEntity): SoundPool? {
return try {
if (Build.VERSION.SDK_INT >= 21) {
val attributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.build()
SoundPool.Builder().setAudioAttributes(attributes)
.setMaxStreams(12.coerceAtMost(entity.audios.count()))
.build()
} else {
SoundPool(12.coerceAtMost(entity.audios.count()), AudioManager.STREAM_MUSIC, 0)
}
} catch (e: Exception) {
LogUtils.error(TAG, e)
null
}
}
fun clear() {
if (SVGASoundManager.isInit()) {
this.audioList.forEach {
it.soundID?.let { id -> SVGASoundManager.unload(id) }
}
soundCallback = null
}
soundPool?.release()
soundPool = null
audioList = emptyList()
spriteList = emptyList()
imageMap.clear()
}
}

View File

@@ -0,0 +1,33 @@
package com.opensource.svgaplayer.bitmap
import android.graphics.BitmapFactory
/**
*
* Create by im_dsd 2020/7/7 17:59
*/
internal object BitmapSampleSizeCalculator {
fun calculate(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
// Raw height and width of image
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (reqHeight <= 0 || reqWidth <= 0) {
return inSampleSize
}
if (height > reqHeight || width > reqWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
}

View File

@@ -0,0 +1,16 @@
package com.opensource.svgaplayer.bitmap
import android.graphics.Bitmap
import android.graphics.BitmapFactory
/**
* 通过字节码解码 Bitmap
*
* Create by im_dsd 2020/7/7 17:50
*/
internal object SVGABitmapByteArrayDecoder : SVGABitmapDecoder<ByteArray>() {
override fun onDecode(data: ByteArray, ops: BitmapFactory.Options): Bitmap? {
return BitmapFactory.decodeByteArray(data, 0, data.count(), ops)
}
}

View File

@@ -0,0 +1,35 @@
package com.opensource.svgaplayer.bitmap
import android.graphics.Bitmap
import android.graphics.BitmapFactory
/**
* Bitmap 解码器
*
* <T> 需要加载的数据类型
*
* Create by im_dsd 2020/7/7 17:39
*/
internal abstract class SVGABitmapDecoder<T> {
fun decodeBitmapFrom(data: T, reqWidth: Int, reqHeight: Int): Bitmap? {
return BitmapFactory.Options().run {
// 如果期望的宽高是合法的, 则开启检测尺寸模式
inJustDecodeBounds = (reqWidth > 0 && reqHeight > 0)
inPreferredConfig = Bitmap.Config.RGB_565
val bitmap = onDecode(data, this)
if (!inJustDecodeBounds) {
return bitmap
}
// Calculate inSampleSize
inSampleSize = BitmapSampleSizeCalculator.calculate(this, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
onDecode(data, this)
}
}
abstract fun onDecode(data: T, ops: BitmapFactory.Options): Bitmap?
}

View File

@@ -0,0 +1,16 @@
package com.opensource.svgaplayer.bitmap
import android.graphics.Bitmap
import android.graphics.BitmapFactory
/**
* 通过文件解码 Bitmap
*
* Create by im_dsd 2020/7/7 17:50
*/
internal object SVGABitmapFileDecoder : SVGABitmapDecoder<String>() {
override fun onDecode(data: String, ops: BitmapFactory.Options): Bitmap? {
return BitmapFactory.decodeFile(data, ops)
}
}

View File

@@ -0,0 +1,53 @@
package com.opensource.svgaplayer.drawer
import android.graphics.Canvas
import android.widget.ImageView
import com.opensource.svgaplayer.SVGAVideoEntity
import com.opensource.svgaplayer.entities.SVGAVideoSpriteFrameEntity
import com.opensource.svgaplayer.utils.Pools
import com.opensource.svgaplayer.utils.SVGAScaleInfo
import kotlin.math.max
/**
* Created by cuiminghui on 2017/3/29.
*/
open internal class SGVADrawer(val videoItem: SVGAVideoEntity) {
val scaleInfo = SVGAScaleInfo()
private val spritePool = Pools.SimplePool<SVGADrawerSprite>(max(1, videoItem.spriteList.size))
inner class SVGADrawerSprite(var _matteKey: String? = null, var _imageKey: String? = null, var _frameEntity: SVGAVideoSpriteFrameEntity? = null) {
val matteKey get() = _matteKey
val imageKey get() = _imageKey
val frameEntity get() = _frameEntity!!
}
internal fun requestFrameSprites(frameIndex: Int): List<SVGADrawerSprite> {
return videoItem.spriteList.mapNotNull {
if (frameIndex >= 0 && frameIndex < it.frames.size) {
it.imageKey?.let { imageKey ->
if (!imageKey.endsWith(".matte") && it.frames[frameIndex].alpha <= 0.0) {
return@mapNotNull null
}
return@mapNotNull (spritePool.acquire() ?: SVGADrawerSprite()).apply {
_matteKey = it.matteKey
_imageKey = it.imageKey
_frameEntity = it.frames[frameIndex]
}
}
}
return@mapNotNull null
}
}
internal fun releaseFrameSprites(sprites: List<SVGADrawerSprite>) {
sprites.forEach { spritePool.release(it) }
}
open fun drawFrame(canvas : Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
scaleInfo.performScaleType(canvas.width.toFloat(),canvas.height.toFloat(), videoItem.videoSize.width.toFloat(), videoItem.videoSize.height.toFloat(), scaleType)
}
}

View File

@@ -0,0 +1,559 @@
package com.opensource.svgaplayer.drawer
import android.graphics.*
import android.os.Build
import android.text.StaticLayout
import android.text.TextUtils
import android.widget.ImageView
import com.opensource.svgaplayer.SVGADynamicEntity
import com.opensource.svgaplayer.SVGASoundManager
import com.opensource.svgaplayer.SVGAVideoEntity
import com.opensource.svgaplayer.entities.SVGAVideoShapeEntity
/**
* Created by cuiminghui on 2017/3/29.
*/
internal class SVGACanvasDrawer(videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity) : SGVADrawer(videoItem) {
private val sharedValues = ShareValues()
private val drawTextCache: HashMap<String, Bitmap> = hashMapOf()
private val pathCache = PathCache()
private var beginIndexList: Array<Boolean>? = null
private var endIndexList: Array<Boolean>? = null
override fun drawFrame(canvas: Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) {
super.drawFrame(canvas, frameIndex, scaleType)
playAudio(frameIndex)
this.pathCache.onSizeChanged(canvas)
val sprites = requestFrameSprites(frameIndex)
// Filter null sprites
if (sprites.count() <= 0) return
val matteSprites = mutableMapOf<String, SVGADrawerSprite>()
var saveID = -1
beginIndexList = null
endIndexList = null
// Filter no matte layer
var hasMatteLayer = false
sprites.get(0).imageKey?.let {
if (it.endsWith(".matte")) {
hasMatteLayer = true
}
}
sprites.forEachIndexed { index, svgaDrawerSprite ->
// Save matte sprite
svgaDrawerSprite.imageKey?.let {
/// No matte layer included or VERSION Unsopport matte
if (!hasMatteLayer || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// Normal sprite
drawSprite(svgaDrawerSprite, canvas, frameIndex)
// Continue
return@forEachIndexed
}
/// Cache matte sprite
if (it.endsWith(".matte")) {
matteSprites.put(it, svgaDrawerSprite)
// Continue
return@forEachIndexed
}
}
/// Is matte begin
if (isMatteBegin(index, sprites)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
saveID = canvas.saveLayer(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), null)
} else {
canvas.save()
}
}
/// Normal matte
drawSprite(svgaDrawerSprite, canvas, frameIndex)
/// Is matte end
if (isMatteEnd(index, sprites)) {
matteSprites.get(svgaDrawerSprite.matteKey)?.let {
drawSprite(it, this.sharedValues.shareMatteCanvas(canvas.width, canvas.height), frameIndex)
canvas.drawBitmap(this.sharedValues.sharedMatteBitmap(), 0f, 0f, this.sharedValues.shareMattePaint())
if (saveID != -1) {
canvas.restoreToCount(saveID)
} else {
canvas.restore()
}
// Continue
return@forEachIndexed
}
}
}
releaseFrameSprites(sprites)
}
private fun isMatteBegin(spriteIndex: Int, sprites: List<SVGADrawerSprite>): Boolean {
if (beginIndexList == null) {
val boolArray = Array(sprites.count()) { false }
sprites.forEachIndexed { index, svgaDrawerSprite ->
svgaDrawerSprite.imageKey?.let {
/// Filter matte sprite
if (it.endsWith(".matte")) {
// Continue
return@forEachIndexed
}
}
svgaDrawerSprite.matteKey?.let {
if (it.length > 0) {
sprites.get(index - 1)?.let { lastSprite ->
if (lastSprite.matteKey.isNullOrEmpty()) {
boolArray[index] = true
} else {
if (lastSprite.matteKey != svgaDrawerSprite.matteKey) {
boolArray[index] = true
}
}
}
}
}
}
beginIndexList = boolArray
}
return beginIndexList?.get(spriteIndex) ?: false
}
private fun isMatteEnd(spriteIndex: Int, sprites: List<SVGADrawerSprite>): Boolean {
if (endIndexList == null) {
val boolArray = Array(sprites.count()) { false }
sprites.forEachIndexed { index, svgaDrawerSprite ->
svgaDrawerSprite.imageKey?.let {
/// Filter matte sprite
if (it.endsWith(".matte")) {
// Continue
return@forEachIndexed
}
}
svgaDrawerSprite.matteKey?.let {
if (it.length > 0) {
// Last one
if (index == sprites.count() - 1) {
boolArray[index] = true
} else {
sprites.get(index + 1)?.let { nextSprite ->
if (nextSprite.matteKey.isNullOrEmpty()) {
boolArray[index] = true
} else {
if (nextSprite.matteKey != svgaDrawerSprite.matteKey) {
boolArray[index] = true
}
}
}
}
}
}
}
endIndexList = boolArray
}
return endIndexList?.get(spriteIndex) ?: false
}
private fun playAudio(frameIndex: Int) {
this.videoItem.audioList.forEach { audio ->
if (audio.startFrame == frameIndex) {
if (SVGASoundManager.isInit()) {
audio.soundID?.let { soundID ->
audio.playID = SVGASoundManager.play(soundID)
}
} else {
this.videoItem.soundPool?.let { soundPool ->
audio.soundID?.let { soundID ->
audio.playID = soundPool.play(soundID, 1.0f, 1.0f, 1, 0, 1.0f)
}
}
}
}
if (audio.endFrame <= frameIndex) {
audio.playID?.let {
if (SVGASoundManager.isInit()) {
SVGASoundManager.stop(it)
} else {
this.videoItem.soundPool?.stop(it)
}
}
audio.playID = null
}
}
}
private fun shareFrameMatrix(transform: Matrix): Matrix {
val matrix = this.sharedValues.sharedMatrix()
matrix.postScale(scaleInfo.scaleFx, scaleInfo.scaleFy)
matrix.postTranslate(scaleInfo.tranFx, scaleInfo.tranFy)
matrix.preConcat(transform)
return matrix
}
private fun drawSprite(sprite: SVGADrawerSprite, canvas: Canvas, frameIndex: Int) {
drawImage(sprite, canvas)
drawShape(sprite, canvas)
drawDynamic(sprite, canvas, frameIndex)
}
private fun drawImage(sprite: SVGADrawerSprite, canvas: Canvas) {
val imageKey = sprite.imageKey ?: return
val isHidden = dynamicItem.dynamicHidden[imageKey] == true
if (isHidden) {
return
}
val bitmapKey = if (imageKey.endsWith(".matte")) imageKey.substring(0, imageKey.length - 6) else imageKey
val drawingBitmap = (dynamicItem.dynamicImage[bitmapKey] ?: videoItem.imageMap[bitmapKey])
?: return
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
val paint = this.sharedValues.sharedPaint()
paint.isAntiAlias = videoItem.antiAlias
paint.isFilterBitmap = videoItem.antiAlias
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
if (sprite.frameEntity.maskPath != null) {
val maskPath = sprite.frameEntity.maskPath ?: return
canvas.save()
val path = this.sharedValues.sharedPath()
maskPath.buildPath(path)
path.transform(frameMatrix)
canvas.clipPath(path)
frameMatrix.preScale((sprite.frameEntity.layout.width / drawingBitmap.width).toFloat(), (sprite.frameEntity.layout.height / drawingBitmap.height).toFloat())
if (!drawingBitmap.isRecycled) {
canvas.drawBitmap(drawingBitmap, frameMatrix, paint)
}
canvas.restore()
} else {
frameMatrix.preScale((sprite.frameEntity.layout.width / drawingBitmap.width).toFloat(), (sprite.frameEntity.layout.height / drawingBitmap.height).toFloat())
if (!drawingBitmap.isRecycled) {
canvas.drawBitmap(drawingBitmap, frameMatrix, paint)
}
}
dynamicItem.dynamicIClickArea.let {
it.get(imageKey)?.let { listener ->
val matrixArray = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
frameMatrix.getValues(matrixArray)
listener.onResponseArea(imageKey, matrixArray[2].toInt()
, matrixArray[5].toInt()
, (drawingBitmap.width * matrixArray[0] + matrixArray[2]).toInt()
, (drawingBitmap.height * matrixArray[4] + matrixArray[5]).toInt())
}
}
drawTextOnBitmap(canvas, drawingBitmap, sprite, frameMatrix)
}
private fun drawTextOnBitmap(canvas: Canvas, drawingBitmap: Bitmap, sprite: SVGADrawerSprite, frameMatrix: Matrix) {
if (dynamicItem.isTextDirty) {
this.drawTextCache.clear()
dynamicItem.isTextDirty = false
}
val imageKey = sprite.imageKey ?: return
var textBitmap: Bitmap? = null
dynamicItem.dynamicText[imageKey]?.let { drawingText ->
dynamicItem.dynamicTextPaint[imageKey]?.let { drawingTextPaint ->
drawTextCache[imageKey]?.let {
textBitmap = it
} ?: kotlin.run {
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
val drawRect = Rect(0, 0, drawingBitmap.width, drawingBitmap.height)
val textCanvas = Canvas(textBitmap)
drawingTextPaint.isAntiAlias = true
val fontMetrics = drawingTextPaint.getFontMetrics();
val top = fontMetrics.top
val bottom = fontMetrics.bottom
val baseLineY = drawRect.centerY() - top / 2 - bottom / 2
textCanvas.drawText(drawingText, drawRect.centerX().toFloat(), baseLineY, drawingTextPaint);
drawTextCache.put(imageKey, textBitmap as Bitmap)
}
}
}
dynamicItem.dynamicBoringLayoutText[imageKey]?.let {
drawTextCache[imageKey]?.let {
textBitmap = it
} ?: kotlin.run {
it.paint.isAntiAlias = true
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
val textCanvas = Canvas(textBitmap)
textCanvas.translate(0f, ((drawingBitmap.height - it.height) / 2).toFloat())
it.draw(textCanvas)
drawTextCache.put(imageKey, textBitmap as Bitmap)
}
}
dynamicItem.dynamicStaticLayoutText[imageKey]?.let {
drawTextCache[imageKey]?.let {
textBitmap = it
} ?: kotlin.run {
it.paint.isAntiAlias = true
var layout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
var lineMax = try {
val field = StaticLayout::class.java.getDeclaredField("mMaximumVisibleLineCount")
field.isAccessible = true
field.getInt(it)
} catch (e: Exception) {
Int.MAX_VALUE
}
StaticLayout.Builder
.obtain(it.text, 0, it.text.length, it.paint, drawingBitmap.width)
.setAlignment(it.alignment)
.setMaxLines(lineMax)
.setEllipsize(TextUtils.TruncateAt.END)
.build()
} else {
StaticLayout(it.text, 0, it.text.length, it.paint, drawingBitmap.width, it.alignment, it.spacingMultiplier, it.spacingAdd, false)
}
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
val textCanvas = Canvas(textBitmap)
textCanvas.translate(0f, ((drawingBitmap.height - layout.height) / 2).toFloat())
layout.draw(textCanvas)
drawTextCache.put(imageKey, textBitmap as Bitmap)
}
}
textBitmap?.let { textBitmap ->
val paint = this.sharedValues.sharedPaint()
paint.isAntiAlias = videoItem.antiAlias
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
if (sprite.frameEntity.maskPath != null) {
val maskPath = sprite.frameEntity.maskPath ?: return@let
canvas.save()
canvas.concat(frameMatrix)
canvas.clipRect(0, 0, drawingBitmap.width, drawingBitmap.height)
val bitmapShader = BitmapShader(textBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
paint.shader = bitmapShader
val path = this.sharedValues.sharedPath()
maskPath.buildPath(path)
canvas.drawPath(path, paint)
canvas.restore()
} else {
paint.isFilterBitmap = videoItem.antiAlias
canvas.drawBitmap(textBitmap, frameMatrix, paint)
}
}
}
private fun drawShape(sprite: SVGADrawerSprite, canvas: Canvas) {
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
sprite.frameEntity.shapes.forEach { shape ->
shape.buildPath()
shape.shapePath?.let {
val paint = this.sharedValues.sharedPaint()
paint.reset()
paint.isAntiAlias = videoItem.antiAlias
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
val path = this.sharedValues.sharedPath()
path.reset()
path.addPath(this.pathCache.buildPath(shape))
val shapeMatrix = this.sharedValues.sharedMatrix2()
shapeMatrix.reset()
shape.transform?.let {
shapeMatrix.postConcat(it)
}
shapeMatrix.postConcat(frameMatrix)
path.transform(shapeMatrix)
shape.styles?.fill?.let {
if (it != 0x00000000) {
paint.style = Paint.Style.FILL
paint.color = it
val alpha = Math.min(255, Math.max(0, (sprite.frameEntity.alpha * 255).toInt()))
if (alpha != 255) {
paint.alpha = alpha
}
if (sprite.frameEntity.maskPath !== null) canvas.save()
sprite.frameEntity.maskPath?.let { maskPath ->
val path2 = this.sharedValues.sharedPath2()
maskPath.buildPath(path2)
path2.transform(frameMatrix)
canvas.clipPath(path2)
}
canvas.drawPath(path, paint)
if (sprite.frameEntity.maskPath !== null) canvas.restore()
}
}
shape.styles?.strokeWidth?.let {
if (it > 0) {
paint.alpha = (sprite.frameEntity.alpha * 255).toInt()
paint.style = Paint.Style.STROKE
shape.styles?.stroke?.let {
paint.color = it
val alpha = Math.min(255, Math.max(0, (sprite.frameEntity.alpha * 255).toInt()))
if (alpha != 255) {
paint.alpha = alpha
}
}
val scale = matrixScale(frameMatrix)
shape.styles?.strokeWidth?.let {
paint.strokeWidth = it * scale
}
shape.styles?.lineCap?.let {
when {
it.equals("butt", true) -> paint.strokeCap = Paint.Cap.BUTT
it.equals("round", true) -> paint.strokeCap = Paint.Cap.ROUND
it.equals("square", true) -> paint.strokeCap = Paint.Cap.SQUARE
}
}
shape.styles?.lineJoin?.let {
when {
it.equals("miter", true) -> paint.strokeJoin = Paint.Join.MITER
it.equals("round", true) -> paint.strokeJoin = Paint.Join.ROUND
it.equals("bevel", true) -> paint.strokeJoin = Paint.Join.BEVEL
}
}
shape.styles?.miterLimit?.let {
paint.strokeMiter = it.toFloat() * scale
}
shape.styles?.lineDash?.let {
if (it.size == 3 && (it[0] > 0 || it[1] > 0)) {
paint.pathEffect = DashPathEffect(floatArrayOf(
(if (it[0] < 1.0f) 1.0f else it[0]) * scale,
(if (it[1] < 0.1f) 0.1f else it[1]) * scale
), it[2] * scale)
}
}
if (sprite.frameEntity.maskPath !== null) canvas.save()
sprite.frameEntity.maskPath?.let { maskPath ->
val path2 = this.sharedValues.sharedPath2()
maskPath.buildPath(path2)
path2.transform(frameMatrix)
canvas.clipPath(path2)
}
canvas.drawPath(path, paint)
if (sprite.frameEntity.maskPath !== null) canvas.restore()
}
}
}
}
}
private val matrixScaleTempValues = FloatArray(16)
private fun matrixScale(matrix: Matrix): Float {
matrix.getValues(matrixScaleTempValues)
if (matrixScaleTempValues[0] == 0f) {
return 0f
}
var A = matrixScaleTempValues[0].toDouble()
var B = matrixScaleTempValues[3].toDouble()
var C = matrixScaleTempValues[1].toDouble()
var D = matrixScaleTempValues[4].toDouble()
if (A * D == B * C) return 0f
var scaleX = Math.sqrt(A * A + B * B)
A /= scaleX
B /= scaleX
var skew = A * C + B * D
C -= A * skew
D -= B * skew
var scaleY = Math.sqrt(C * C + D * D)
C /= scaleY
D /= scaleY
skew /= scaleY
if (A * D < B * C) {
scaleX = -scaleX
}
return if (scaleInfo.ratioX) Math.abs(scaleX.toFloat()) else Math.abs(scaleY.toFloat())
}
private fun drawDynamic(sprite: SVGADrawerSprite, canvas: Canvas, frameIndex: Int) {
val imageKey = sprite.imageKey ?: return
dynamicItem.dynamicDrawer[imageKey]?.let {
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
canvas.save()
canvas.concat(frameMatrix)
it.invoke(canvas, frameIndex)
canvas.restore()
}
dynamicItem.dynamicDrawerSized[imageKey]?.let {
val frameMatrix = shareFrameMatrix(sprite.frameEntity.transform)
canvas.save()
canvas.concat(frameMatrix)
it.invoke(canvas, frameIndex, sprite.frameEntity.layout.width.toInt(), sprite.frameEntity.layout.height.toInt())
canvas.restore()
}
}
class ShareValues {
private val sharedPaint = Paint()
private val sharedPath = Path()
private val sharedPath2 = Path()
private val sharedMatrix = Matrix()
private val sharedMatrix2 = Matrix()
private val shareMattePaint = Paint()
private var shareMatteCanvas: Canvas? = null
private var sharedMatteBitmap: Bitmap? = null
fun sharedPaint(): Paint {
sharedPaint.reset()
return sharedPaint
}
fun sharedPath(): Path {
sharedPath.reset()
return sharedPath
}
fun sharedPath2(): Path {
sharedPath2.reset()
return sharedPath2
}
fun sharedMatrix(): Matrix {
sharedMatrix.reset()
return sharedMatrix
}
fun sharedMatrix2(): Matrix {
sharedMatrix2.reset()
return sharedMatrix2
}
fun shareMattePaint(): Paint {
shareMattePaint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_IN))
return shareMattePaint
}
fun sharedMatteBitmap(): Bitmap {
return sharedMatteBitmap as Bitmap
}
fun shareMatteCanvas(width: Int, height: Int): Canvas {
if (shareMatteCanvas == null) {
sharedMatteBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8)
// shareMatteCanvas = Canvas(sharedMatteBitmap)
}
// val matteCanvas = shareMatteCanvas as Canvas
// matteCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
// return matteCanvas
return Canvas(sharedMatteBitmap)
}
}
class PathCache {
private var canvasWidth: Int = 0
private var canvasHeight: Int = 0
private val cache = HashMap<SVGAVideoShapeEntity, Path>()
fun onSizeChanged(canvas: Canvas) {
if (this.canvasWidth != canvas.width || this.canvasHeight != canvas.height) {
this.cache.clear()
}
this.canvasWidth = canvas.width
this.canvasHeight = canvas.height
}
fun buildPath(shape: SVGAVideoShapeEntity): Path {
if (!this.cache.containsKey(shape)) {
val path = Path()
path.set(shape.shapePath)
this.cache[shape] = path
}
return this.cache[shape]!!
}
}
}

View File

@@ -0,0 +1,24 @@
package com.opensource.svgaplayer.entities
import com.opensource.svgaplayer.proto.AudioEntity
import java.io.FileInputStream
internal class SVGAAudioEntity {
val audioKey: String?
val startFrame: Int
val endFrame: Int
val startTime: Int
val totalTime: Int
var soundID: Int? = null
var playID: Int? = null
constructor(audioItem: AudioEntity) {
this.audioKey = audioItem.audioKey
this.startFrame = audioItem.startFrame ?: 0
this.endFrame = audioItem.endFrame ?: 0
this.startTime = audioItem.startTime ?: 0
this.totalTime = audioItem.totalTime ?: 0
}
}

View File

@@ -0,0 +1,100 @@
package com.opensource.svgaplayer.entities
import android.graphics.Path
import com.opensource.svgaplayer.utils.SVGAPoint
import java.util.*
private val VALID_METHODS: Set<String> = setOf("M", "L", "H", "V", "C", "S", "Q", "R", "A", "Z", "m", "l", "h", "v", "c", "s", "q", "r", "a", "z")
class SVGAPathEntity(originValue: String) {
private val replacedValue: String = if (originValue.contains(",")) originValue.replace(",", " ") else originValue
private var cachedPath: Path? = null
fun buildPath(toPath: Path) {
cachedPath?.let {
toPath.set(it)
return
}
val cachedPath = Path()
val segments = StringTokenizer(this.replacedValue, "MLHVCSQRAZmlhvcsqraz", true)
var currentMethod = ""
while (segments.hasMoreTokens()) {
val segment = segments.nextToken()
if (segment.isEmpty()) { continue }
if (VALID_METHODS.contains(segment)) {
currentMethod = segment
if (currentMethod == "Z" || currentMethod == "z") { operate(cachedPath, currentMethod, StringTokenizer("", "")) }
}
else {
operate(cachedPath, currentMethod, StringTokenizer(segment, " "))
}
}
this.cachedPath = cachedPath
toPath.set(cachedPath)
}
private fun operate(finalPath: Path, method: String, args: StringTokenizer) {
var x0 = 0.0f
var y0 = 0.0f
var x1 = 0.0f
var y1 = 0.0f
var x2 = 0.0f
var y2 = 0.0f
try {
var index = 0
while (args.hasMoreTokens()) {
val s = args.nextToken()
if (s.isEmpty()) {continue}
if (index == 0) { x0 = s.toFloat() }
if (index == 1) { y0 = s.toFloat() }
if (index == 2) { x1 = s.toFloat() }
if (index == 3) { y1 = s.toFloat() }
if (index == 4) { x2 = s.toFloat() }
if (index == 5) { y2 = s.toFloat() }
index++
}
} catch (e: Exception) {}
var currentPoint = SVGAPoint(0.0f, 0.0f, 0.0f)
if (method == "M") {
finalPath.moveTo(x0, y0)
currentPoint = SVGAPoint(x0, y0, 0.0f)
} else if (method == "m") {
finalPath.rMoveTo(x0, y0)
currentPoint = SVGAPoint(currentPoint.x + x0, currentPoint.y + y0, 0.0f)
}
if (method == "L") {
finalPath.lineTo(x0, y0)
} else if (method == "l") {
finalPath.rLineTo(x0, y0)
}
if (method == "C") {
finalPath.cubicTo(x0, y0, x1, y1, x2, y2)
} else if (method == "c") {
finalPath.rCubicTo(x0, y0, x1, y1, x2, y2)
}
if (method == "Q") {
finalPath.quadTo(x0, y0, x1, y1)
} else if (method == "q") {
finalPath.rQuadTo(x0, y0, x1, y1)
}
if (method == "H") {
finalPath.lineTo(x0, currentPoint.y)
} else if (method == "h") {
finalPath.rLineTo(x0, 0f)
}
if (method == "V") {
finalPath.lineTo(currentPoint.x, x0)
} else if (method == "v") {
finalPath.rLineTo(0f, x0)
}
if (method == "Z") {
finalPath.close()
}
else if (method == "z") {
finalPath.close()
}
}
}

View File

@@ -0,0 +1,356 @@
package com.opensource.svgaplayer.entities
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.Path
import android.graphics.RectF
import com.opensource.svgaplayer.proto.ShapeEntity
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
/**
* Created by cuiminghui on 2017/2/22.
*/
val sharedPath = Path()
internal class SVGAVideoShapeEntity {
enum class Type {
shape,
rect,
ellipse,
keep
}
class Styles {
var fill = 0x00000000
internal set
var stroke = 0x00000000
internal set
var strokeWidth = 0.0f
internal set
var lineCap = "butt"
internal set
var lineJoin = "miter"
internal set
var miterLimit = 0
internal set
var lineDash = FloatArray(0)
internal set
}
var type = Type.shape
private set
var args: Map<String, Any>? = null
private set
var styles: Styles? = null
private set
var transform: Matrix? = null
private set
constructor(obj: JSONObject) {
parseType(obj)
parseArgs(obj)
parseStyles(obj)
parseTransform(obj)
}
constructor(obj: ShapeEntity) {
parseType(obj)
parseArgs(obj)
parseStyles(obj)
parseTransform(obj)
}
val isKeep: Boolean
get() = type == Type.keep
var shapePath: Path? = null
private fun parseType(obj: JSONObject) {
obj.optString("type")?.let {
when {
it.equals("shape", ignoreCase = true) -> type = Type.shape
it.equals("rect", ignoreCase = true) -> type = Type.rect
it.equals("ellipse", ignoreCase = true) -> type = Type.ellipse
it.equals("keep", ignoreCase = true) -> type = Type.keep
}
}
}
private fun parseType(obj: ShapeEntity) {
obj.type?.let {
type = when (it) {
ShapeEntity.ShapeType.SHAPE -> Type.shape
ShapeEntity.ShapeType.RECT -> Type.rect
ShapeEntity.ShapeType.ELLIPSE -> Type.ellipse
ShapeEntity.ShapeType.KEEP -> Type.keep
}
}
}
private fun parseArgs(obj: JSONObject) {
val args = HashMap<String, Any>()
obj.optJSONObject("args")?.let { values ->
values.keys().forEach { key ->
values.get(key)?.let {
args.put(key, it)
}
}
this.args = args
}
}
private fun parseArgs(obj: ShapeEntity) {
val args = HashMap<String, Any>()
obj.shape?.let {
it.d?.let { args.put("d", it) }
}
obj.ellipse?.let {
args.put("x", it.x ?: 0.0f)
args.put("y", it.y ?: 0.0f)
args.put("radiusX", it.radiusX ?: 0.0f)
args.put("radiusY", it.radiusY ?: 0.0f)
}
obj.rect?.let {
args.put("x", it.x ?: 0.0f)
args.put("y", it.y ?: 0.0f)
args.put("width", it.width ?: 0.0f)
args.put("height", it.height ?: 0.0f)
args.put("cornerRadius", it.cornerRadius ?: 0.0f)
}
this.args = args
}
// 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f]
private fun checkValueRange(obj: JSONArray): Float {
return if (
obj.optDouble(0) <= 1 &&
obj.optDouble(1) <= 1 &&
obj.optDouble(2) <= 1
) {
255f
} else {
1f
}
}
// 检查 alpha 的范围是否是 [0f, 1f],或者是 [0f, 255f]
private fun checkAlphaValueRange(obj: JSONArray): Float {
return if (obj.optDouble(3) <= 1) {
255f
} else {
1f
}
}
private fun parseStyles(obj: JSONObject) {
obj.optJSONObject("styles")?.let {
val styles = Styles()
it.optJSONArray("fill")?.let {
if (it.length() == 4) {
val mulValue = checkValueRange(it)
val alphaRangeValue = checkAlphaValueRange(it)
styles.fill = Color.argb(
(it.optDouble(3) * alphaRangeValue).toInt(),
(it.optDouble(0) * mulValue).toInt(),
(it.optDouble(1) * mulValue).toInt(),
(it.optDouble(2) * mulValue).toInt()
)
}
}
it.optJSONArray("stroke")?.let {
if (it.length() == 4) {
val mulValue = checkValueRange(it)
val alphaRangeValue = checkAlphaValueRange(it)
styles.stroke = Color.argb(
(it.optDouble(3) * alphaRangeValue).toInt(),
(it.optDouble(0) * mulValue).toInt(),
(it.optDouble(1) * mulValue).toInt(),
(it.optDouble(2) * mulValue).toInt()
)
}
}
styles.strokeWidth = it.optDouble("strokeWidth", 0.0).toFloat()
styles.lineCap = it.optString("lineCap", "butt")
styles.lineJoin = it.optString("lineJoin", "miter")
styles.miterLimit = it.optInt("miterLimit", 0)
it.optJSONArray("lineDash")?.let {
styles.lineDash = FloatArray(it.length())
for (i in 0 until it.length()) {
styles.lineDash[i] = it.optDouble(i, 0.0).toFloat()
}
}
this.styles = styles
}
}
// 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f]
private fun checkValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float {
return if (
(color.r ?: 0f) <= 1 &&
(color.g ?: 0f) <= 1 &&
(color.b ?: 0f) <= 1
) {
255f
} else {
1f
}
}
// 检查 alpha 范围是否是 [0f, 1f],有可能是 [0f, 255f]
private fun checkAlphaValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float {
return if (color.a <= 1f) {
255f
} else {
1f
}
}
private fun parseStyles(obj: ShapeEntity) {
obj.styles?.let {
val styles = Styles()
it.fill?.let {
val mulValue = checkValueRange(it)
val alphaRangeValue = checkAlphaValueRange(it)
styles.fill = Color.argb(
((it.a ?: 0f) * alphaRangeValue).toInt(),
((it.r ?: 0f) * mulValue).toInt(),
((it.g ?: 0f) * mulValue).toInt(),
((it.b ?: 0f) * mulValue).toInt()
)
}
it.stroke?.let {
val mulValue = checkValueRange(it)
val alphaRangeValue = checkAlphaValueRange(it)
styles.stroke = Color.argb(
((it.a ?: 0f) * alphaRangeValue).toInt(),
((it.r ?: 0f) * mulValue).toInt(),
((it.g ?: 0f) * mulValue).toInt(),
((it.b ?: 0f) * mulValue).toInt()
)
}
styles.strokeWidth = it.strokeWidth ?: 0.0f
it.lineCap?.let {
when (it) {
ShapeEntity.ShapeStyle.LineCap.LineCap_BUTT -> styles.lineCap = "butt"
ShapeEntity.ShapeStyle.LineCap.LineCap_ROUND -> styles.lineCap = "round"
ShapeEntity.ShapeStyle.LineCap.LineCap_SQUARE -> styles.lineCap = "square"
}
}
it.lineJoin?.let {
when (it) {
ShapeEntity.ShapeStyle.LineJoin.LineJoin_BEVEL -> styles.lineJoin = "bevel"
ShapeEntity.ShapeStyle.LineJoin.LineJoin_MITER -> styles.lineJoin = "miter"
ShapeEntity.ShapeStyle.LineJoin.LineJoin_ROUND -> styles.lineJoin = "round"
}
}
styles.miterLimit = (it.miterLimit ?: 0.0f).toInt()
styles.lineDash = kotlin.FloatArray(3)
it.lineDashI?.let { styles.lineDash[0] = it }
it.lineDashII?.let { styles.lineDash[1] = it }
it.lineDashIII?.let { styles.lineDash[2] = it }
this.styles = styles
}
}
private fun parseTransform(obj: JSONObject) {
obj.optJSONObject("transform")?.let {
val transform = Matrix()
val arr = FloatArray(9)
val a = it.optDouble("a", 1.0)
val b = it.optDouble("b", 0.0)
val c = it.optDouble("c", 0.0)
val d = it.optDouble("d", 1.0)
val tx = it.optDouble("tx", 0.0)
val ty = it.optDouble("ty", 0.0)
arr[0] = a.toFloat() // a
arr[1] = c.toFloat() // c
arr[2] = tx.toFloat() // tx
arr[3] = b.toFloat() // b
arr[4] = d.toFloat() // d
arr[5] = ty.toFloat() // ty
arr[6] = 0.0.toFloat()
arr[7] = 0.0.toFloat()
arr[8] = 1.0.toFloat()
transform.setValues(arr)
this.transform = transform
}
}
private fun parseTransform(obj: ShapeEntity) {
obj.transform?.let {
val transform = Matrix()
val arr = FloatArray(9)
val a = it.a ?: 1.0f
val b = it.b ?: 0.0f
val c = it.c ?: 0.0f
val d = it.d ?: 1.0f
val tx = it.tx ?: 0.0f
val ty = it.ty ?: 0.0f
arr[0] = a
arr[1] = c
arr[2] = tx
arr[3] = b
arr[4] = d
arr[5] = ty
arr[6] = 0.0f
arr[7] = 0.0f
arr[8] = 1.0f
transform.setValues(arr)
this.transform = transform
}
}
fun buildPath() {
if (this.shapePath != null) {
return
}
sharedPath.reset()
if (this.type == Type.shape) {
(this.args?.get("d") as? String)?.let {
SVGAPathEntity(it).buildPath(sharedPath)
}
} else if (this.type == Type.ellipse) {
val xv = this.args?.get("x") as? Number ?: return
val yv = this.args?.get("y") as? Number ?: return
val rxv = this.args?.get("radiusX") as? Number ?: return
val ryv = this.args?.get("radiusY") as? Number ?: return
val x = xv.toFloat()
val y = yv.toFloat()
val rx = rxv.toFloat()
val ry = ryv.toFloat()
sharedPath.addOval(RectF(x - rx, y - ry, x + rx, y + ry), Path.Direction.CW)
} else if (this.type == Type.rect) {
val xv = this.args?.get("x") as? Number ?: return
val yv = this.args?.get("y") as? Number ?: return
val wv = this.args?.get("width") as? Number ?: return
val hv = this.args?.get("height") as? Number ?: return
val crv = this.args?.get("cornerRadius") as? Number ?: return
val x = xv.toFloat()
val y = yv.toFloat()
val width = wv.toFloat()
val height = hv.toFloat()
val cornerRadius = crv.toFloat()
sharedPath.addRoundRect(RectF(x, y, x + width, y + height), cornerRadius, cornerRadius, Path.Direction.CW)
}
this.shapePath = Path()
this.shapePath?.set(sharedPath)
}
}

View File

@@ -0,0 +1,60 @@
package com.opensource.svgaplayer.entities
import com.opensource.svgaplayer.proto.SpriteEntity
import org.json.JSONObject
/**
* Created by cuiminghui on 2016/10/17.
*/
internal class SVGAVideoSpriteEntity {
val imageKey: String?
val matteKey: String?
val frames: List<SVGAVideoSpriteFrameEntity>
constructor(obj: JSONObject) {
this.imageKey = obj.optString("imageKey")
this.matteKey = obj.optString("matteKey")
val mutableFrames: MutableList<SVGAVideoSpriteFrameEntity> = mutableListOf()
obj.optJSONArray("frames")?.let {
for (i in 0 until it.length()) {
it.optJSONObject(i)?.let {
val frameItem = SVGAVideoSpriteFrameEntity(it)
if (frameItem.shapes.isNotEmpty()) {
frameItem.shapes.first().let {
if (it.isKeep && mutableFrames.size > 0) {
frameItem.shapes = mutableFrames.last().shapes
}
}
}
mutableFrames.add(frameItem)
}
}
}
frames = mutableFrames.toList()
}
constructor(obj: SpriteEntity) {
this.imageKey = obj.imageKey
this.matteKey = obj.matteKey
var lastFrame: SVGAVideoSpriteFrameEntity? = null
frames = obj.frames?.map {
val frameItem = SVGAVideoSpriteFrameEntity(it)
if (frameItem.shapes.isNotEmpty()) {
frameItem.shapes.first().let {
if (it.isKeep) {
lastFrame?.let {
frameItem.shapes = it.shapes
}
}
}
}
lastFrame = frameItem
return@map frameItem
} ?: listOf()
}
}

View File

@@ -0,0 +1,94 @@
package com.opensource.svgaplayer.entities
import android.graphics.Matrix
import com.opensource.svgaplayer.proto.FrameEntity
import com.opensource.svgaplayer.utils.SVGARect
import org.json.JSONObject
/**
* Created by cuiminghui on 2016/10/17.
*/
internal class SVGAVideoSpriteFrameEntity {
var alpha: Double
var layout = SVGARect(0.0, 0.0, 0.0, 0.0)
var transform = Matrix()
var maskPath: SVGAPathEntity? = null
var shapes: List<SVGAVideoShapeEntity> = listOf()
constructor(obj: JSONObject) {
this.alpha = obj.optDouble("alpha", 0.0)
obj.optJSONObject("layout")?.let {
layout = SVGARect(it.optDouble("x", 0.0), it.optDouble("y", 0.0), it.optDouble("width", 0.0), it.optDouble("height", 0.0))
}
obj.optJSONObject("transform")?.let {
val arr = FloatArray(9)
val a = it.optDouble("a", 1.0)
val b = it.optDouble("b", 0.0)
val c = it.optDouble("c", 0.0)
val d = it.optDouble("d", 1.0)
val tx = it.optDouble("tx", 0.0)
val ty = it.optDouble("ty", 0.0)
arr[0] = a.toFloat()
arr[1] = c.toFloat()
arr[2] = tx.toFloat()
arr[3] = b.toFloat()
arr[4] = d.toFloat()
arr[5] = ty.toFloat()
arr[6] = 0.0.toFloat()
arr[7] = 0.0.toFloat()
arr[8] = 1.0.toFloat()
transform.setValues(arr)
}
obj.optString("clipPath")?.let { d ->
if (d.isNotEmpty()) {
maskPath = SVGAPathEntity(d)
}
}
obj.optJSONArray("shapes")?.let {
val mutableList: MutableList<SVGAVideoShapeEntity> = mutableListOf()
for (i in 0 until it.length()) {
it.optJSONObject(i)?.let {
mutableList.add(SVGAVideoShapeEntity(it))
}
}
shapes = mutableList.toList()
}
}
constructor(obj: FrameEntity) {
this.alpha = (obj.alpha ?: 0.0f).toDouble()
obj.layout?.let {
this.layout = SVGARect((it.x ?: 0.0f).toDouble(), (it.y
?: 0.0f).toDouble(), (it.width ?: 0.0f).toDouble(), (it.height
?: 0.0f).toDouble())
}
obj.transform?.let {
val arr = FloatArray(9)
val a = it.a ?: 1.0f
val b = it.b ?: 0.0f
val c = it.c ?: 0.0f
val d = it.d ?: 1.0f
val tx = it.tx ?: 0.0f
val ty = it.ty ?: 0.0f
arr[0] = a
arr[1] = c
arr[2] = tx
arr[3] = b
arr[4] = d
arr[5] = ty
arr[6] = 0.0f
arr[7] = 0.0f
arr[8] = 1.0f
transform.setValues(arr)
}
obj.clipPath?.takeIf { it.isNotEmpty() }?.let {
maskPath = SVGAPathEntity(it)
}
this.shapes = obj.shapes.map {
return@map SVGAVideoShapeEntity(it)
}
}
}

View File

@@ -0,0 +1,258 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 19:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Integer;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import okio.ByteString;
public final class AudioEntity extends Message<AudioEntity, AudioEntity.Builder> {
public static final ProtoAdapter<AudioEntity> ADAPTER = new ProtoAdapter_AudioEntity();
private static final long serialVersionUID = 0L;
public static final String DEFAULT_AUDIOKEY = "";
public static final Integer DEFAULT_STARTFRAME = 0;
public static final Integer DEFAULT_ENDFRAME = 0;
public static final Integer DEFAULT_STARTTIME = 0;
public static final Integer DEFAULT_TOTALTIME = 0;
/**
* 音频文件名
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String audioKey;
/**
* 音频播放起始帧
*/
@WireField(
tag = 2,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer startFrame;
/**
* 音频播放结束帧
*/
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer endFrame;
/**
* 音频播放起始时间(相对音频长度)
*/
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer startTime;
/**
* 音频总长度
*/
@WireField(
tag = 5,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer totalTime;
public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime) {
this(audioKey, startFrame, endFrame, startTime, totalTime, ByteString.EMPTY);
}
public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.audioKey = audioKey;
this.startFrame = startFrame;
this.endFrame = endFrame;
this.startTime = startTime;
this.totalTime = totalTime;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.audioKey = audioKey;
builder.startFrame = startFrame;
builder.endFrame = endFrame;
builder.startTime = startTime;
builder.totalTime = totalTime;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof AudioEntity)) return false;
AudioEntity o = (AudioEntity) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(audioKey, o.audioKey)
&& Internal.equals(startFrame, o.startFrame)
&& Internal.equals(endFrame, o.endFrame)
&& Internal.equals(startTime, o.startTime)
&& Internal.equals(totalTime, o.totalTime);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (audioKey != null ? audioKey.hashCode() : 0);
result = result * 37 + (startFrame != null ? startFrame.hashCode() : 0);
result = result * 37 + (endFrame != null ? endFrame.hashCode() : 0);
result = result * 37 + (startTime != null ? startTime.hashCode() : 0);
result = result * 37 + (totalTime != null ? totalTime.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (audioKey != null) builder.append(", audioKey=").append(audioKey);
if (startFrame != null) builder.append(", startFrame=").append(startFrame);
if (endFrame != null) builder.append(", endFrame=").append(endFrame);
if (startTime != null) builder.append(", startTime=").append(startTime);
if (totalTime != null) builder.append(", totalTime=").append(totalTime);
return builder.replace(0, 2, "AudioEntity{").append('}').toString();
}
public static final class Builder extends Message.Builder<AudioEntity, Builder> {
public String audioKey;
public Integer startFrame;
public Integer endFrame;
public Integer startTime;
public Integer totalTime;
public Builder() {
}
/**
* 音频文件名
*/
public Builder audioKey(String audioKey) {
this.audioKey = audioKey;
return this;
}
/**
* 音频播放起始帧
*/
public Builder startFrame(Integer startFrame) {
this.startFrame = startFrame;
return this;
}
/**
* 音频播放结束帧
*/
public Builder endFrame(Integer endFrame) {
this.endFrame = endFrame;
return this;
}
/**
* 音频播放起始时间(相对音频长度)
*/
public Builder startTime(Integer startTime) {
this.startTime = startTime;
return this;
}
/**
* 音频总长度
*/
public Builder totalTime(Integer totalTime) {
this.totalTime = totalTime;
return this;
}
@Override
public AudioEntity build() {
return new AudioEntity(audioKey, startFrame, endFrame, startTime, totalTime, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_AudioEntity extends ProtoAdapter<AudioEntity> {
ProtoAdapter_AudioEntity() {
super(FieldEncoding.LENGTH_DELIMITED, AudioEntity.class);
}
@Override
public int encodedSize(AudioEntity value) {
return (value.audioKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.audioKey) : 0)
+ (value.startFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(2, value.startFrame) : 0)
+ (value.endFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.endFrame) : 0)
+ (value.startTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.startTime) : 0)
+ (value.totalTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(5, value.totalTime) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, AudioEntity value) throws IOException {
if (value.audioKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.audioKey);
if (value.startFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 2, value.startFrame);
if (value.endFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.endFrame);
if (value.startTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.startTime);
if (value.totalTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 5, value.totalTime);
writer.writeBytes(value.unknownFields());
}
@Override
public AudioEntity decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.audioKey(ProtoAdapter.STRING.decode(reader)); break;
case 2: builder.startFrame(ProtoAdapter.INT32.decode(reader)); break;
case 3: builder.endFrame(ProtoAdapter.INT32.decode(reader)); break;
case 4: builder.startTime(ProtoAdapter.INT32.decode(reader)); break;
case 5: builder.totalTime(ProtoAdapter.INT32.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public AudioEntity redact(AudioEntity value) {
Builder builder = value.newBuilder();
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -0,0 +1,259 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 115:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Float;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import okio.ByteString;
public final class FrameEntity extends Message<FrameEntity, FrameEntity.Builder> {
public static final ProtoAdapter<FrameEntity> ADAPTER = new ProtoAdapter_FrameEntity();
private static final long serialVersionUID = 0L;
public static final Float DEFAULT_ALPHA = 0.0f;
public static final String DEFAULT_CLIPPATH = "";
/**
* 透明度
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float alpha;
/**
* 初始约束大小
*/
@WireField(
tag = 2,
adapter = "com.opensource.svgaplayer.proto.Layout#ADAPTER"
)
public final Layout layout;
/**
* 2D 变换矩阵
*/
@WireField(
tag = 3,
adapter = "com.opensource.svgaplayer.proto.Transform#ADAPTER"
)
public final Transform transform;
/**
* 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。
*/
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String clipPath;
/**
* 矢量元素列表
*/
@WireField(
tag = 5,
adapter = "com.opensource.svgaplayer.proto.ShapeEntity#ADAPTER",
label = WireField.Label.REPEATED
)
public final List<ShapeEntity> shapes;
public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List<ShapeEntity> shapes) {
this(alpha, layout, transform, clipPath, shapes, ByteString.EMPTY);
}
public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List<ShapeEntity> shapes, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.alpha = alpha;
this.layout = layout;
this.transform = transform;
this.clipPath = clipPath;
this.shapes = Internal.immutableCopyOf("shapes", shapes);
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.alpha = alpha;
builder.layout = layout;
builder.transform = transform;
builder.clipPath = clipPath;
builder.shapes = Internal.copyOf("shapes", shapes);
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof FrameEntity)) return false;
FrameEntity o = (FrameEntity) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(alpha, o.alpha)
&& Internal.equals(layout, o.layout)
&& Internal.equals(transform, o.transform)
&& Internal.equals(clipPath, o.clipPath)
&& shapes.equals(o.shapes);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (alpha != null ? alpha.hashCode() : 0);
result = result * 37 + (layout != null ? layout.hashCode() : 0);
result = result * 37 + (transform != null ? transform.hashCode() : 0);
result = result * 37 + (clipPath != null ? clipPath.hashCode() : 0);
result = result * 37 + shapes.hashCode();
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (alpha != null) builder.append(", alpha=").append(alpha);
if (layout != null) builder.append(", layout=").append(layout);
if (transform != null) builder.append(", transform=").append(transform);
if (clipPath != null) builder.append(", clipPath=").append(clipPath);
if (!shapes.isEmpty()) builder.append(", shapes=").append(shapes);
return builder.replace(0, 2, "FrameEntity{").append('}').toString();
}
public static final class Builder extends Message.Builder<FrameEntity, Builder> {
public Float alpha;
public Layout layout;
public Transform transform;
public String clipPath;
public List<ShapeEntity> shapes;
public Builder() {
shapes = Internal.newMutableList();
}
/**
* 透明度
*/
public Builder alpha(Float alpha) {
this.alpha = alpha;
return this;
}
/**
* 初始约束大小
*/
public Builder layout(Layout layout) {
this.layout = layout;
return this;
}
/**
* 2D 变换矩阵
*/
public Builder transform(Transform transform) {
this.transform = transform;
return this;
}
/**
* 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。
*/
public Builder clipPath(String clipPath) {
this.clipPath = clipPath;
return this;
}
/**
* 矢量元素列表
*/
public Builder shapes(List<ShapeEntity> shapes) {
Internal.checkElementsNotNull(shapes);
this.shapes = shapes;
return this;
}
@Override
public FrameEntity build() {
return new FrameEntity(alpha, layout, transform, clipPath, shapes, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_FrameEntity extends ProtoAdapter<FrameEntity> {
ProtoAdapter_FrameEntity() {
super(FieldEncoding.LENGTH_DELIMITED, FrameEntity.class);
}
@Override
public int encodedSize(FrameEntity value) {
return (value.alpha != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.alpha) : 0)
+ (value.layout != null ? Layout.ADAPTER.encodedSizeWithTag(2, value.layout) : 0)
+ (value.transform != null ? Transform.ADAPTER.encodedSizeWithTag(3, value.transform) : 0)
+ (value.clipPath != null ? ProtoAdapter.STRING.encodedSizeWithTag(4, value.clipPath) : 0)
+ ShapeEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.shapes)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, FrameEntity value) throws IOException {
if (value.alpha != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.alpha);
if (value.layout != null) Layout.ADAPTER.encodeWithTag(writer, 2, value.layout);
if (value.transform != null) Transform.ADAPTER.encodeWithTag(writer, 3, value.transform);
if (value.clipPath != null) ProtoAdapter.STRING.encodeWithTag(writer, 4, value.clipPath);
ShapeEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.shapes);
writer.writeBytes(value.unknownFields());
}
@Override
public FrameEntity decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.alpha(ProtoAdapter.FLOAT.decode(reader)); break;
case 2: builder.layout(Layout.ADAPTER.decode(reader)); break;
case 3: builder.transform(Transform.ADAPTER.decode(reader)); break;
case 4: builder.clipPath(ProtoAdapter.STRING.decode(reader)); break;
case 5: builder.shapes.add(ShapeEntity.ADAPTER.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public FrameEntity redact(FrameEntity value) {
Builder builder = value.newBuilder();
if (builder.layout != null) builder.layout = Layout.ADAPTER.redact(builder.layout);
if (builder.transform != null) builder.transform = Transform.ADAPTER.redact(builder.transform);
Internal.redactElements(builder.shapes, ShapeEntity.ADAPTER);
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -0,0 +1,205 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 27:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Float;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import okio.ByteString;
public final class Layout extends Message<Layout, Layout.Builder> {
public static final ProtoAdapter<Layout> ADAPTER = new ProtoAdapter_Layout();
private static final long serialVersionUID = 0L;
public static final Float DEFAULT_X = 0.0f;
public static final Float DEFAULT_Y = 0.0f;
public static final Float DEFAULT_WIDTH = 0.0f;
public static final Float DEFAULT_HEIGHT = 0.0f;
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float x;
@WireField(
tag = 2,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float y;
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float width;
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float height;
public Layout(Float x, Float y, Float width, Float height) {
this(x, y, width, height, ByteString.EMPTY);
}
public Layout(Float x, Float y, Float width, Float height, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.x = x;
builder.y = y;
builder.width = width;
builder.height = height;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof Layout)) return false;
Layout o = (Layout) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(x, o.x)
&& Internal.equals(y, o.y)
&& Internal.equals(width, o.width)
&& Internal.equals(height, o.height);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (x != null ? x.hashCode() : 0);
result = result * 37 + (y != null ? y.hashCode() : 0);
result = result * 37 + (width != null ? width.hashCode() : 0);
result = result * 37 + (height != null ? height.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (x != null) builder.append(", x=").append(x);
if (y != null) builder.append(", y=").append(y);
if (width != null) builder.append(", width=").append(width);
if (height != null) builder.append(", height=").append(height);
return builder.replace(0, 2, "Layout{").append('}').toString();
}
public static final class Builder extends Message.Builder<Layout, Builder> {
public Float x;
public Float y;
public Float width;
public Float height;
public Builder() {
}
public Builder x(Float x) {
this.x = x;
return this;
}
public Builder y(Float y) {
this.y = y;
return this;
}
public Builder width(Float width) {
this.width = width;
return this;
}
public Builder height(Float height) {
this.height = height;
return this;
}
@Override
public Layout build() {
return new Layout(x, y, width, height, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_Layout extends ProtoAdapter<Layout> {
ProtoAdapter_Layout() {
super(FieldEncoding.LENGTH_DELIMITED, Layout.class);
}
@Override
public int encodedSize(Layout value) {
return (value.x != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.x) : 0)
+ (value.y != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.y) : 0)
+ (value.width != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.width) : 0)
+ (value.height != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.height) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, Layout value) throws IOException {
if (value.x != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.x);
if (value.y != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.y);
if (value.width != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.width);
if (value.height != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.height);
writer.writeBytes(value.unknownFields());
}
@Override
public Layout decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.x(ProtoAdapter.FLOAT.decode(reader)); break;
case 2: builder.y(ProtoAdapter.FLOAT.decode(reader)); break;
case 3: builder.width(ProtoAdapter.FLOAT.decode(reader)); break;
case 4: builder.height(ProtoAdapter.FLOAT.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public Layout redact(Layout value) {
Builder builder = value.newBuilder();
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -0,0 +1,265 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 123:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import java.util.Map;
import okio.ByteString;
public final class MovieEntity extends Message<MovieEntity, MovieEntity.Builder> {
public static final ProtoAdapter<MovieEntity> ADAPTER = new ProtoAdapter_MovieEntity();
private static final long serialVersionUID = 0L;
public static final String DEFAULT_VERSION = "";
/**
* SVGA 格式版本号
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String version;
/**
* 动画参数
*/
@WireField(
tag = 2,
adapter = "com.opensource.svgaplayer.proto.MovieParams#ADAPTER"
)
public final MovieParams params;
/**
* Key 是位图键名Value 是位图文件名或二进制 PNG 数据。
*/
@WireField(
tag = 3,
keyAdapter = "com.squareup.wire.ProtoAdapter#STRING",
adapter = "com.squareup.wire.ProtoAdapter#BYTES"
)
public final Map<String, ByteString> images;
/**
* 元素列表
*/
@WireField(
tag = 4,
adapter = "com.opensource.svgaplayer.proto.SpriteEntity#ADAPTER",
label = WireField.Label.REPEATED
)
public final List<SpriteEntity> sprites;
/**
* 音频列表
*/
@WireField(
tag = 5,
adapter = "com.opensource.svgaplayer.proto.AudioEntity#ADAPTER",
label = WireField.Label.REPEATED
)
public final List<AudioEntity> audios;
public MovieEntity(String version, MovieParams params, Map<String, ByteString> images, List<SpriteEntity> sprites, List<AudioEntity> audios) {
this(version, params, images, sprites, audios, ByteString.EMPTY);
}
public MovieEntity(String version, MovieParams params, Map<String, ByteString> images, List<SpriteEntity> sprites, List<AudioEntity> audios, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.version = version;
this.params = params;
this.images = Internal.immutableCopyOf("images", images);
this.sprites = Internal.immutableCopyOf("sprites", sprites);
this.audios = Internal.immutableCopyOf("audios", audios);
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.version = version;
builder.params = params;
builder.images = Internal.copyOf("images", images);
builder.sprites = Internal.copyOf("sprites", sprites);
builder.audios = Internal.copyOf("audios", audios);
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof MovieEntity)) return false;
MovieEntity o = (MovieEntity) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(version, o.version)
&& Internal.equals(params, o.params)
&& images.equals(o.images)
&& sprites.equals(o.sprites)
&& audios.equals(o.audios);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (version != null ? version.hashCode() : 0);
result = result * 37 + (params != null ? params.hashCode() : 0);
result = result * 37 + images.hashCode();
result = result * 37 + sprites.hashCode();
result = result * 37 + audios.hashCode();
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (version != null) builder.append(", version=").append(version);
if (params != null) builder.append(", params=").append(params);
if (!images.isEmpty()) builder.append(", images=").append(images);
if (!sprites.isEmpty()) builder.append(", sprites=").append(sprites);
if (!audios.isEmpty()) builder.append(", audios=").append(audios);
return builder.replace(0, 2, "MovieEntity{").append('}').toString();
}
public static final class Builder extends Message.Builder<MovieEntity, Builder> {
public String version;
public MovieParams params;
public Map<String, ByteString> images;
public List<SpriteEntity> sprites;
public List<AudioEntity> audios;
public Builder() {
images = Internal.newMutableMap();
sprites = Internal.newMutableList();
audios = Internal.newMutableList();
}
/**
* SVGA 格式版本号
*/
public Builder version(String version) {
this.version = version;
return this;
}
/**
* 动画参数
*/
public Builder params(MovieParams params) {
this.params = params;
return this;
}
/**
* Key 是位图键名Value 是位图文件名或二进制 PNG 数据。
*/
public Builder images(Map<String, ByteString> images) {
Internal.checkElementsNotNull(images);
this.images = images;
return this;
}
/**
* 元素列表
*/
public Builder sprites(List<SpriteEntity> sprites) {
Internal.checkElementsNotNull(sprites);
this.sprites = sprites;
return this;
}
/**
* 音频列表
*/
public Builder audios(List<AudioEntity> audios) {
Internal.checkElementsNotNull(audios);
this.audios = audios;
return this;
}
@Override
public MovieEntity build() {
return new MovieEntity(version, params, images, sprites, audios, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_MovieEntity extends ProtoAdapter<MovieEntity> {
private final ProtoAdapter<Map<String, ByteString>> images = ProtoAdapter.newMapAdapter(ProtoAdapter.STRING, ProtoAdapter.BYTES);
ProtoAdapter_MovieEntity() {
super(FieldEncoding.LENGTH_DELIMITED, MovieEntity.class);
}
@Override
public int encodedSize(MovieEntity value) {
return (value.version != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.version) : 0)
+ (value.params != null ? MovieParams.ADAPTER.encodedSizeWithTag(2, value.params) : 0)
+ images.encodedSizeWithTag(3, value.images)
+ SpriteEntity.ADAPTER.asRepeated().encodedSizeWithTag(4, value.sprites)
+ AudioEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.audios)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, MovieEntity value) throws IOException {
if (value.version != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.version);
if (value.params != null) MovieParams.ADAPTER.encodeWithTag(writer, 2, value.params);
images.encodeWithTag(writer, 3, value.images);
SpriteEntity.ADAPTER.asRepeated().encodeWithTag(writer, 4, value.sprites);
AudioEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.audios);
writer.writeBytes(value.unknownFields());
}
@Override
public MovieEntity decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.version(ProtoAdapter.STRING.decode(reader)); break;
case 2: builder.params(MovieParams.ADAPTER.decode(reader)); break;
case 3: builder.images.putAll(images.decode(reader)); break;
case 4: builder.sprites.add(SpriteEntity.ADAPTER.decode(reader)); break;
case 5: builder.audios.add(AudioEntity.ADAPTER.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public MovieEntity redact(MovieEntity value) {
Builder builder = value.newBuilder();
if (builder.params != null) builder.params = MovieParams.ADAPTER.redact(builder.params);
Internal.redactElements(builder.sprites, SpriteEntity.ADAPTER);
Internal.redactElements(builder.audios, AudioEntity.ADAPTER);
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -0,0 +1,230 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 6:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Float;
import java.lang.Integer;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import okio.ByteString;
public final class MovieParams extends Message<MovieParams, MovieParams.Builder> {
public static final ProtoAdapter<MovieParams> ADAPTER = new ProtoAdapter_MovieParams();
private static final long serialVersionUID = 0L;
public static final Float DEFAULT_VIEWBOXWIDTH = 0.0f;
public static final Float DEFAULT_VIEWBOXHEIGHT = 0.0f;
public static final Integer DEFAULT_FPS = 0;
public static final Integer DEFAULT_FRAMES = 0;
/**
* 画布宽
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float viewBoxWidth;
/**
* 画布高
*/
@WireField(
tag = 2,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float viewBoxHeight;
/**
* 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。
*/
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer fps;
/**
* 动画总帧数
*/
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#INT32"
)
public final Integer frames;
public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames) {
this(viewBoxWidth, viewBoxHeight, fps, frames, ByteString.EMPTY);
}
public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.viewBoxWidth = viewBoxWidth;
this.viewBoxHeight = viewBoxHeight;
this.fps = fps;
this.frames = frames;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.viewBoxWidth = viewBoxWidth;
builder.viewBoxHeight = viewBoxHeight;
builder.fps = fps;
builder.frames = frames;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof MovieParams)) return false;
MovieParams o = (MovieParams) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(viewBoxWidth, o.viewBoxWidth)
&& Internal.equals(viewBoxHeight, o.viewBoxHeight)
&& Internal.equals(fps, o.fps)
&& Internal.equals(frames, o.frames);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (viewBoxWidth != null ? viewBoxWidth.hashCode() : 0);
result = result * 37 + (viewBoxHeight != null ? viewBoxHeight.hashCode() : 0);
result = result * 37 + (fps != null ? fps.hashCode() : 0);
result = result * 37 + (frames != null ? frames.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (viewBoxWidth != null) builder.append(", viewBoxWidth=").append(viewBoxWidth);
if (viewBoxHeight != null) builder.append(", viewBoxHeight=").append(viewBoxHeight);
if (fps != null) builder.append(", fps=").append(fps);
if (frames != null) builder.append(", frames=").append(frames);
return builder.replace(0, 2, "MovieParams{").append('}').toString();
}
public static final class Builder extends Message.Builder<MovieParams, Builder> {
public Float viewBoxWidth;
public Float viewBoxHeight;
public Integer fps;
public Integer frames;
public Builder() {
}
/**
* 画布宽
*/
public Builder viewBoxWidth(Float viewBoxWidth) {
this.viewBoxWidth = viewBoxWidth;
return this;
}
/**
* 画布高
*/
public Builder viewBoxHeight(Float viewBoxHeight) {
this.viewBoxHeight = viewBoxHeight;
return this;
}
/**
* 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。
*/
public Builder fps(Integer fps) {
this.fps = fps;
return this;
}
/**
* 动画总帧数
*/
public Builder frames(Integer frames) {
this.frames = frames;
return this;
}
@Override
public MovieParams build() {
return new MovieParams(viewBoxWidth, viewBoxHeight, fps, frames, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_MovieParams extends ProtoAdapter<MovieParams> {
ProtoAdapter_MovieParams() {
super(FieldEncoding.LENGTH_DELIMITED, MovieParams.class);
}
@Override
public int encodedSize(MovieParams value) {
return (value.viewBoxWidth != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.viewBoxWidth) : 0)
+ (value.viewBoxHeight != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.viewBoxHeight) : 0)
+ (value.fps != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.fps) : 0)
+ (value.frames != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.frames) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, MovieParams value) throws IOException {
if (value.viewBoxWidth != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.viewBoxWidth);
if (value.viewBoxHeight != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.viewBoxHeight);
if (value.fps != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.fps);
if (value.frames != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.frames);
writer.writeBytes(value.unknownFields());
}
@Override
public MovieParams decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.viewBoxWidth(ProtoAdapter.FLOAT.decode(reader)); break;
case 2: builder.viewBoxHeight(ProtoAdapter.FLOAT.decode(reader)); break;
case 3: builder.fps(ProtoAdapter.INT32.decode(reader)); break;
case 4: builder.frames(ProtoAdapter.INT32.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public MovieParams redact(MovieParams value) {
Builder builder = value.newBuilder();
builder.clearUnknownFields();
return builder.build();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,202 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 13:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import okio.ByteString;
public final class SpriteEntity extends Message<SpriteEntity, SpriteEntity.Builder> {
public static final ProtoAdapter<SpriteEntity> ADAPTER = new ProtoAdapter_SpriteEntity();
private static final long serialVersionUID = 0L;
public static final String DEFAULT_IMAGEKEY = "";
public static final String DEFAULT_MATTEKEY = "";
/**
* 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String imageKey;
/**
* 帧列表
*/
@WireField(
tag = 2,
adapter = "com.opensource.svgaplayer.proto.FrameEntity#ADAPTER",
label = WireField.Label.REPEATED
)
public final List<FrameEntity> frames;
/**
* 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey.
*/
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String matteKey;
public SpriteEntity(String imageKey, List<FrameEntity> frames, String matteKey) {
this(imageKey, frames, matteKey, ByteString.EMPTY);
}
public SpriteEntity(String imageKey, List<FrameEntity> frames, String matteKey, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.imageKey = imageKey;
this.frames = Internal.immutableCopyOf("frames", frames);
this.matteKey = matteKey;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.imageKey = imageKey;
builder.frames = Internal.copyOf("frames", frames);
builder.matteKey = matteKey;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof SpriteEntity)) return false;
SpriteEntity o = (SpriteEntity) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(imageKey, o.imageKey)
&& frames.equals(o.frames)
&& Internal.equals(matteKey, o.matteKey);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (imageKey != null ? imageKey.hashCode() : 0);
result = result * 37 + frames.hashCode();
result = result * 37 + (matteKey != null ? matteKey.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (imageKey != null) builder.append(", imageKey=").append(imageKey);
if (!frames.isEmpty()) builder.append(", frames=").append(frames);
if (matteKey != null) builder.append(", matteKey=").append(matteKey);
return builder.replace(0, 2, "SpriteEntity{").append('}').toString();
}
public static final class Builder extends Message.Builder<SpriteEntity, Builder> {
public String imageKey;
public List<FrameEntity> frames;
public String matteKey;
public Builder() {
frames = Internal.newMutableList();
}
/**
* 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。
*/
public Builder imageKey(String imageKey) {
this.imageKey = imageKey;
return this;
}
/**
* 帧列表
*/
public Builder frames(List<FrameEntity> frames) {
Internal.checkElementsNotNull(frames);
this.frames = frames;
return this;
}
/**
* 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey.
*/
public Builder matteKey(String matteKey) {
this.matteKey = matteKey;
return this;
}
@Override
public SpriteEntity build() {
return new SpriteEntity(imageKey, frames, matteKey, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_SpriteEntity extends ProtoAdapter<SpriteEntity> {
ProtoAdapter_SpriteEntity() {
super(FieldEncoding.LENGTH_DELIMITED, SpriteEntity.class);
}
@Override
public int encodedSize(SpriteEntity value) {
return (value.imageKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.imageKey) : 0)
+ FrameEntity.ADAPTER.asRepeated().encodedSizeWithTag(2, value.frames)
+ (value.matteKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(3, value.matteKey) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, SpriteEntity value) throws IOException {
if (value.imageKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.imageKey);
FrameEntity.ADAPTER.asRepeated().encodeWithTag(writer, 2, value.frames);
if (value.matteKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 3, value.matteKey);
writer.writeBytes(value.unknownFields());
}
@Override
public SpriteEntity decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.imageKey(ProtoAdapter.STRING.decode(reader)); break;
case 2: builder.frames.add(FrameEntity.ADAPTER.decode(reader)); break;
case 3: builder.matteKey(ProtoAdapter.STRING.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public SpriteEntity redact(SpriteEntity value) {
Builder builder = value.newBuilder();
Internal.redactElements(builder.frames, FrameEntity.ADAPTER);
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -0,0 +1,251 @@
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: svga.proto at 34:1
package com.opensource.svgaplayer.proto;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import com.squareup.wire.WireField;
import com.squareup.wire.internal.Internal;
import java.io.IOException;
import java.lang.Float;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import okio.ByteString;
public final class Transform extends Message<Transform, Transform.Builder> {
public static final ProtoAdapter<Transform> ADAPTER = new ProtoAdapter_Transform();
private static final long serialVersionUID = 0L;
public static final Float DEFAULT_A = 0.0f;
public static final Float DEFAULT_B = 0.0f;
public static final Float DEFAULT_C = 0.0f;
public static final Float DEFAULT_D = 0.0f;
public static final Float DEFAULT_TX = 0.0f;
public static final Float DEFAULT_TY = 0.0f;
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float a;
@WireField(
tag = 2,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float b;
@WireField(
tag = 3,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float c;
@WireField(
tag = 4,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float d;
@WireField(
tag = 5,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float tx;
@WireField(
tag = 6,
adapter = "com.squareup.wire.ProtoAdapter#FLOAT"
)
public final Float ty;
public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty) {
this(a, b, c, d, tx, ty, ByteString.EMPTY);
}
public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty, ByteString unknownFields) {
super(ADAPTER, unknownFields);
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.tx = tx;
this.ty = ty;
}
@Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.a = a;
builder.b = b;
builder.c = c;
builder.d = d;
builder.tx = tx;
builder.ty = ty;
builder.addUnknownFields(unknownFields());
return builder;
}
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof Transform)) return false;
Transform o = (Transform) other;
return unknownFields().equals(o.unknownFields())
&& Internal.equals(a, o.a)
&& Internal.equals(b, o.b)
&& Internal.equals(c, o.c)
&& Internal.equals(d, o.d)
&& Internal.equals(tx, o.tx)
&& Internal.equals(ty, o.ty);
}
@Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (a != null ? a.hashCode() : 0);
result = result * 37 + (b != null ? b.hashCode() : 0);
result = result * 37 + (c != null ? c.hashCode() : 0);
result = result * 37 + (d != null ? d.hashCode() : 0);
result = result * 37 + (tx != null ? tx.hashCode() : 0);
result = result * 37 + (ty != null ? ty.hashCode() : 0);
super.hashCode = result;
}
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (a != null) builder.append(", a=").append(a);
if (b != null) builder.append(", b=").append(b);
if (c != null) builder.append(", c=").append(c);
if (d != null) builder.append(", d=").append(d);
if (tx != null) builder.append(", tx=").append(tx);
if (ty != null) builder.append(", ty=").append(ty);
return builder.replace(0, 2, "Transform{").append('}').toString();
}
public static final class Builder extends Message.Builder<Transform, Builder> {
public Float a;
public Float b;
public Float c;
public Float d;
public Float tx;
public Float ty;
public Builder() {
}
public Builder a(Float a) {
this.a = a;
return this;
}
public Builder b(Float b) {
this.b = b;
return this;
}
public Builder c(Float c) {
this.c = c;
return this;
}
public Builder d(Float d) {
this.d = d;
return this;
}
public Builder tx(Float tx) {
this.tx = tx;
return this;
}
public Builder ty(Float ty) {
this.ty = ty;
return this;
}
@Override
public Transform build() {
return new Transform(a, b, c, d, tx, ty, super.buildUnknownFields());
}
}
private static final class ProtoAdapter_Transform extends ProtoAdapter<Transform> {
ProtoAdapter_Transform() {
super(FieldEncoding.LENGTH_DELIMITED, Transform.class);
}
@Override
public int encodedSize(Transform value) {
return (value.a != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.a) : 0)
+ (value.b != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.b) : 0)
+ (value.c != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.c) : 0)
+ (value.d != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.d) : 0)
+ (value.tx != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(5, value.tx) : 0)
+ (value.ty != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(6, value.ty) : 0)
+ value.unknownFields().size();
}
@Override
public void encode(ProtoWriter writer, Transform value) throws IOException {
if (value.a != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.a);
if (value.b != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.b);
if (value.c != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.c);
if (value.d != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.d);
if (value.tx != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 5, value.tx);
if (value.ty != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 6, value.ty);
writer.writeBytes(value.unknownFields());
}
@Override
public Transform decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.a(ProtoAdapter.FLOAT.decode(reader)); break;
case 2: builder.b(ProtoAdapter.FLOAT.decode(reader)); break;
case 3: builder.c(ProtoAdapter.FLOAT.decode(reader)); break;
case 4: builder.d(ProtoAdapter.FLOAT.decode(reader)); break;
case 5: builder.tx(ProtoAdapter.FLOAT.decode(reader)); break;
case 6: builder.ty(ProtoAdapter.FLOAT.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
@Override
public Transform redact(Transform value) {
Builder builder = value.newBuilder();
builder.clearUnknownFields();
return builder.build();
}
}
}

View File

@@ -0,0 +1,102 @@
package com.opensource.svgaplayer.utils
/**
* Helper class for creating pools of objects. An example use looks like this:
* <pre>
* public class MyPooledClass {
*
* private static final SynchronizedPool<MyPooledClass> sPool =
* new SynchronizedPool<MyPooledClass>(10);
*
* public static MyPooledClass obtain() {
* MyPooledClass instance = sPool.acquire();
* return (instance != null) ? instance : new MyPooledClass();
* }
*
* public void recycle() {
* // Clear state if needed.
* sPool.release(this);
* }
*
* . . .
* }
* </pre>
*
*/
class Pools private constructor() {
/**
* Interface for managing a pool of objects.
*
* @param <T> The pooled type.
*/
interface Pool<T> {
/**
* @return An instance from the pool if such, null otherwise.
*/
fun acquire(): T?
/**
* Release an instance to the pool.
*
* @param instance The instance to release.
* @return Whether the instance was put in the pool.
*
* @throws IllegalStateException If the instance is already in the pool.
*/
fun release(instance: T): Boolean
}
/**
* Simple (non-synchronized) pool of objects.
*
* @param maxPoolSize The max pool size.
*
* @throws IllegalArgumentException If the max pool size is less than zero.
*
* @param <T> The pooled type.
*/
open class SimplePool<T>(maxPoolSize: Int) : Pool<T> {
private val mPool: Array<Any?>
private var mPoolSize = 0
init {
require(maxPoolSize > 0) { "The max pool size must be > 0" }
mPool = arrayOfNulls(maxPoolSize)
}
@Suppress("UNCHECKED_CAST")
override fun acquire(): T? {
if (mPoolSize > 0) {
val lastPooledIndex = mPoolSize - 1
val instance = mPool[lastPooledIndex] as T?
mPool[lastPooledIndex] = null
mPoolSize--
return instance
}
return null
}
override fun release(instance: T): Boolean {
check(!isInPool(instance)) { "Already in the pool!" }
if (mPoolSize < mPool.size) {
mPool[mPoolSize] = instance
mPoolSize++
return true
}
return false
}
private fun isInPool(instance: T): Boolean {
for (i in 0 until mPoolSize) {
if (mPool[i] === instance) {
return true
}
}
return false
}
}
}

View File

@@ -0,0 +1,146 @@
package com.opensource.svgaplayer.utils
import android.widget.ImageView
/**
* Created by ubt on 2018/1/19.
*/
class SVGAScaleInfo {
var tranFx : Float = 0.0f
var tranFy : Float = 0.0f
var scaleFx : Float = 1.0f
var scaleFy : Float = 1.0f
var ratio = 1.0f
var ratioX = false
private fun resetVar(){
tranFx = 0.0f
tranFy = 0.0f
scaleFx = 1.0f
scaleFy = 1.0f
ratio = 1.0f
ratioX = false
}
fun performScaleType(canvasWidth : Float, canvasHeight: Float, videoWidth : Float, videoHeight : Float, scaleType: ImageView.ScaleType) {
if (canvasWidth == 0.0f || canvasHeight == 0.0f || videoWidth == 0.0f || videoHeight == 0.0f) {
return
}
resetVar()
val canW_vidW_f = (canvasWidth - videoWidth) / 2.0f
val canH_vidH_f = (canvasHeight - videoHeight) / 2.0f
val videoRatio = videoWidth / videoHeight
val canvasRatio = canvasWidth / canvasHeight
val canH_d_vidH = canvasHeight / videoHeight
val canW_d_vidW = canvasWidth / videoWidth
when (scaleType) {
ImageView.ScaleType.CENTER -> {
tranFx = canW_vidW_f
tranFy = canH_vidH_f
}
ImageView.ScaleType.CENTER_CROP -> {
if (videoRatio > canvasRatio) {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
}
else {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
}
}
ImageView.ScaleType.CENTER_INSIDE -> {
if (videoWidth < canvasWidth && videoHeight < canvasHeight) {
tranFx = canW_vidW_f
tranFy = canH_vidH_f
}
else {
if (videoRatio > canvasRatio) {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
}
else {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
}
}
}
ImageView.ScaleType.FIT_CENTER -> {
if (videoRatio > canvasRatio) {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
}
else {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
}
}
ImageView.ScaleType.FIT_START -> {
if (videoRatio > canvasRatio) {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
}
else {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
}
}
ImageView.ScaleType.FIT_END -> {
if (videoRatio > canvasRatio) {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
tranFy= canvasHeight - videoHeight * (canW_d_vidW)
}
else {
ratio = canH_d_vidH
ratioX = false
scaleFx = canH_d_vidH
scaleFy = canH_d_vidH
tranFx = canvasWidth - videoWidth * (canH_d_vidH)
}
}
ImageView.ScaleType.FIT_XY -> {
ratio = Math.max(canW_d_vidW, canH_d_vidH)
ratioX = canW_d_vidW > canH_d_vidH
scaleFx = canW_d_vidW
scaleFy = canH_d_vidH
}
else -> {
ratio = canW_d_vidW
ratioX = true
scaleFx = canW_d_vidW
scaleFy = canW_d_vidW
}
}
}
}

View File

@@ -0,0 +1,11 @@
package com.opensource.svgaplayer.utils
/**
* Created by cuiminghui on 2017/3/29.
*/
class SVGAPoint(val x: Float, val y: Float, val value: Float)
class SVGARect(val x: Double, val y: Double, val width: Double, val height: Double)
class SVGARange(val location: Int, val length: Int)

View File

@@ -0,0 +1,28 @@
package com.opensource.svgaplayer.utils.log
import android.util.Log
/**
* 内部默认 ILogger 接口实现
*/
class DefaultLogCat : ILogger {
override fun verbose(tag: String, msg: String) {
Log.v(tag, msg)
}
override fun info(tag: String, msg: String) {
Log.i(tag, msg)
}
override fun debug(tag: String, msg: String) {
Log.d(tag, msg)
}
override fun warn(tag: String, msg: String) {
Log.w(tag, msg)
}
override fun error(tag: String, msg: String?, error: Throwable?) {
Log.e(tag, msg, error)
}
}

View File

@@ -0,0 +1,12 @@
package com.opensource.svgaplayer.utils.log
/**
* log 外部接管接口
**/
interface ILogger {
fun verbose(tag: String, msg: String)
fun info(tag: String, msg: String)
fun debug(tag: String, msg: String)
fun warn(tag: String, msg: String)
fun error(tag: String, msg: String?, error: Throwable?)
}

View File

@@ -0,0 +1,57 @@
package com.opensource.svgaplayer.utils.log
/**
* 日志输出
*/
internal object LogUtils {
private const val TAG = "SVGALog"
fun verbose(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.verbose(tag, msg)
}
fun info(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.info(tag, msg)
}
fun debug(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.debug(tag, msg)
}
fun warn(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.warn(tag, msg)
}
fun error(tag: String = TAG, msg: String) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.error(tag, msg, null)
}
fun error(tag: String, error: Throwable) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.error(tag, error.message, error)
}
fun error(tag: String = TAG, msg: String, error: Throwable) {
if (!SVGALogger.isLogEnabled()) {
return
}
SVGALogger.getSVGALogger()?.error(tag, msg, error)
}
}

View File

@@ -0,0 +1,40 @@
package com.opensource.svgaplayer.utils.log
/**
* SVGA logger 配置管理
**/
object SVGALogger {
private var mLogger: ILogger? = DefaultLogCat()
private var isLogEnabled = false
/**
* log 接管注入
*/
fun injectSVGALoggerImp(logImp: ILogger): SVGALogger {
mLogger = logImp
return this
}
/**
* 设置是否开启 log
*/
fun setLogEnabled(isEnabled: Boolean): SVGALogger {
isLogEnabled = isEnabled
return this
}
/**
* 获取当前 ILogger 实现类
*/
fun getSVGALogger(): ILogger? {
return mLogger
}
/**
* 是否开启 log
*/
fun isLogEnabled(): Boolean {
return isLogEnabled
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SVGAImageView">
<attr name="source" format="string" />
<attr name="autoPlay" format="boolean" />
<attr name="antiAlias" format="boolean" />
<attr name="loopCount" format="integer" />
<attr name="clearsAfterStop" format="boolean" />
<attr name="clearsAfterDetached" format="boolean" />
<attr name="fillMode" format="enum">
<enum name="Backward" value="0" />
<enum name="Forward" value="1" />
<enum name="Clear" value="2"/>
</attr>
</declare-styleable>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">SVGAPlayer</string>
</resources>

View File

@@ -1,10 +1,11 @@
apply plugin: 'com.android.library'
apply plugin: 'img-optimizer'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-parcelize'
apply from: "../package_config.gradle"
android {
namespace "com.newpdlive.sy"
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
packagingOptions {
@@ -33,7 +34,7 @@ android {
versionName rootProject.ext.android.versionName
manifestPlaceholders = rootProject.ext.manifestPlaceholders
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
abiFilters "armeabi-v7a", "arm64-v8a","x86","x86_64"
}
}
aaptOptions {

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.pdlive.shayu">
xmlns:tools="http://schemas.android.com/tools">
<queries>
<package android:name="com.pdlive.shayu"/>
<package android:name="com.newpdlive.sy"/>
<package android:name="com.facebook.orca"/>
<package

View File

@@ -13,7 +13,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.RecyclerView;
import com.pdlive.shayu.R;
import com.newpdlive.sy.R;
import com.yunbao.share.ICallback;
import com.yunbao.share.bean.ShareBuilder;
import com.yunbao.share.platform.FacebookShare;

View File

@@ -30,24 +30,18 @@ public class ShareBuilder {
private String anchorAvatar;
public static String createLiveShareLink(String shareUid, String anchorId, String anchorName, String anchorAvatar) {
try {
return String.format(CommonAppConfig.HOST +
"/h5/activity/FriendInvitation/liveShare.html?user_id=%s&anchor_id=%s&anchor_name=%s&anchor_avatar=%s&isGoogle=%s",
shareUid,
anchorId,
URLEncoder.encode(anchorName, "UTF-8"),
URLEncoder.encode(anchorAvatar, "UTF-8"),
CommonAppConfig.IS_GOOGLE_PLAY ? "1" : "0"
) ;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return String.format(CommonAppConfig.HOST +
"/index.php?g=Appapi&m=home&a=share&uid=%s&user_id=%s&isGoogle=%s",
anchorId,
shareUid,
CommonAppConfig.IS_GOOGLE_PLAY
);
}
public static String createInviteLink(String shareUid) {
return String.format("https://www.pdlive.com/public/app/download/index.html?user_id=%s&isGoogle=%s",
shareUid,
CommonAppConfig.IS_GOOGLE_PLAY ? "1" : "0"
CommonAppConfig.IS_GOOGLE_PLAY
);
}

View File

@@ -19,7 +19,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.lxj.xpopup.XPopup;
import com.makeramen.roundedimageview.RoundedImageView;
import com.pdlive.shayu.R;
import com.newpdlive.sy.R;
import com.yunbao.common.CommonAppConfig;
import com.yunbao.common.dialog.AbsDialogPopupWindow;
import com.yunbao.common.utils.DialogUitl;
@@ -99,7 +99,7 @@ public class InvitePopDialog extends AbsDialogPopupWindow {
list.setAdapter(adapter);
initData();
link.setText(url.substring(0, 40));
info.setText(R.string.dialog_invite_info);
info.setText(mContext.getString(R.string.dialog_invite_info));
avatar.setImageResource(R.mipmap.ic_launcher);
//title.setTextColor(getContext().getResources().getColorStateList(R.drawable.bg_invite_title));
title.setText(R.string.dialog_invite_title);
@@ -152,7 +152,7 @@ public class InvitePopDialog extends AbsDialogPopupWindow {
}
public InvitePopDialog setUrl(String data) {
this.url = data + "&isGoogle=" + (CommonAppConfig.IS_GOOGLE_PLAY ? "1" : "0");
this.url = data + "&isGoogle=" + CommonAppConfig.IS_GOOGLE_PLAY;
return this;
}
}

View File

@@ -13,14 +13,14 @@ import androidx.recyclerview.widget.RecyclerView;
import com.lxj.xpopup.XPopup;
import com.makeramen.roundedimageview.RoundedImageView;
import com.pdlive.shayu.R;
import com.newpdlive.sy.R;
import com.yunbao.common.dialog.AbsDialogPopupWindow;
import com.yunbao.common.glide.ImgLoader;
import com.yunbao.common.utils.StringUtil;
import com.yunbao.common.utils.ToastUtil;
import com.yunbao.common.utils.WordUtil;
import com.yunbao.share.bean.ShareBuilder;
import com.yunbao.share.adapters.ShareAppAdapter;
import com.yunbao.share.bean.ShareBuilder;
import java.util.ArrayList;
import java.util.List;
@@ -64,7 +64,7 @@ public class SharePopDialog extends AbsDialogPopupWindow {
}
public SharePopDialog setShareLink(String link) {
this.shareLink = link;
this.shareLink = link + "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
return this;
}
@@ -113,6 +113,7 @@ public class SharePopDialog extends AbsDialogPopupWindow {
url = shareLink;
}
}
url = url + "&isZh=" + (WordUtil.isNewZh() ? "1" : 0);
link.setText(url);
info.setText(String.format(getContext().getString(R.string.dialog_share_info), StringUtil.isEmpty(anchorName) ? "" : anchorName));
ImgLoader.display(getContext(), anchorAvatar, avatar);
@@ -139,7 +140,7 @@ public class SharePopDialog extends AbsDialogPopupWindow {
url = ShareBuilder.createLiveShareLink(uid, anchorId, anchorName, anchorAvatar);
}
ClipboardManager cm = (ClipboardManager) getContext().getSystemService(CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText("text",info.getText().toString()+"\n"+url);
ClipData clipData = ClipData.newPlainText("text", info.getText().toString() + "\n" + url + "&isZh=" + (WordUtil.isNewZh() ? "1" : 0));
cm.setPrimaryClip(clipData);
ToastUtil.show(getContext().getString(com.yunbao.common.R.string.copy_success));
}

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="dialog_share_title">Share</string>
<string name="dialog_share_info">Come and watch %s live on PDLIVE and meet more interesting people!</string>
<string name="dialog_invite_title">Invite Friends</string>
<string name="dialog_invite_info">Come to PDLIVE to discover more and better live streams.</string>
<string name="dialog_share_copy">Copy</string>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="com.twitter.sdk.android.CONSUMER_KEY" >ZWRrZnRUNlBlcHVxMXpsMzVmb2k6MTpjaQ</string>
<string name="com.twitter.sdk.android.CONSUMER_SECRET">aq0eV4R1pqMK_AAeKRWnjPr7ErGMGgTPGgZJdm73WeRY-Kluws</string>
<string name="dialog_share_title">分享</string>
<string name="dialog_share_info">快來 PDLIVE觀看%s直播認識更多有趣的朋友吧</string>
<string name="dialog_share_app_facebook" >Facebook</string>
<string name="dialog_share_app_line" >Line</string>
<string name="dialog_share_app_twitter">Twitter</string>
<string name="dialog_share_app_whatsapp" >WhatsApp</string>
<string name="dialog_share_app_messenger">Messenger</string>
<string name="dialog_share_app_instagram" >Instagram</string>
<string name="dialog_invite_title">邀請好友</string>
<string name="dialog_invite_info">快來 PDLIVE觀看直播認識更多有趣的朋友吧</string>
<string name="dialog_share_copy">複製</string>
</resources>

View File

@@ -1,17 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="com.twitter.sdk.android.CONSUMER_KEY" translatable="false">ZWRrZnRUNlBlcHVxMXpsMzVmb2k6MTpjaQ</string>
<string name="com.twitter.sdk.android.CONSUMER_SECRET" translatable="false">aq0eV4R1pqMK_AAeKRWnjPr7ErGMGgTPGgZJdm73WeRY-Kluws</string>
<string name="dialog_share_title">分享</string>
<string name="dialog_share_info">快來 PDLIVE觀看%s直播認識更多有趣的朋友吧</string>
<string name="dialog_share_app_facebook" translatable="false">Facebook</string>
<string name="dialog_share_app_line" translatable="false">Line</string>
<string name="dialog_share_app_twitter" translatable="false">Twitter</string>
<string name="dialog_share_app_whatsapp" translatable="false">WhatsApp</string>
<string name="dialog_share_app_messenger" translatable="false">Messenger</string>
<string name="dialog_share_app_instagram" translatable="false">Instagram</string>
<string name="dialog_invite_title">邀請好友</string>
<string name="dialog_invite_info">快來 PDLIVE觀看直播認識更多有趣的朋友吧</string>
<string name="dialog_share_copy">複製</string>
<string name="com.twitter.sdk.android.CONSUMER_KEY" >ZWRrZnRUNlBlcHVxMXpsMzVmb2k6MTpjaQ</string>
<string name="com.twitter.sdk.android.CONSUMER_SECRET" >aq0eV4R1pqMK_AAeKRWnjPr7ErGMGgTPGgZJdm73WeRY-Kluws</string>
<string name="dialog_share_app_facebook" >Facebook</string>
<string name="dialog_share_app_line" >Line</string>
<string name="dialog_share_app_twitter">Twitter</string>
<string name="dialog_share_app_whatsapp" >WhatsApp</string>
<string name="dialog_share_app_messenger">Messenger</string>
<string name="dialog_share_app_instagram" >Instagram</string>
<string name="dialog_share_title">Share</string>
<string name="dialog_share_info">Come and watch %s live on PDLIVE and meet more interesting people!</string>
<string name="dialog_invite_title">Invite Friends</string>
<string name="dialog_invite_info">Come to PDLIVE to discover more and better live streams.</string>
<string name="dialog_share_copy">Copy</string>
</resources>

1
TabLayout/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

37
TabLayout/build.gradle Normal file
View File

@@ -0,0 +1,37 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace "com.angcyo.tablayout"
compileSdk rootProject.ext.android.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
manifestPlaceholders = rootProject.ext.manifestPlaceholders
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_18
targetCompatibility JavaVersion.VERSION_18
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
}
//apply from: "$gradleHost/master/publish.gradle"

View File

21
TabLayout/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
/>

View File

@@ -0,0 +1,200 @@
package com.angcyo.tablayout
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.*
import android.graphics.drawable.Drawable
import android.text.TextPaint
import android.util.AttributeSet
import android.view.View
import androidx.core.view.ViewCompat
/**
* 基础自绘Drawable
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/25
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
abstract class AbsDslDrawable : Drawable() {
companion object {
/**不绘制*/
const val DRAW_TYPE_DRAW_NONE = 0x00
/**[android.view.View.draw]*/
const val DRAW_TYPE_DRAW_AFTER = 0x01
const val DRAW_TYPE_DRAW_BEFORE = 0x02
/**[android.view.View.onDraw]*/
const val DRAW_TYPE_ON_DRAW_AFTER = 0x04
const val DRAW_TYPE_ON_DRAW_BEFORE = 0x08
}
/**画笔*/
val textPaint: TextPaint by lazy {
TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
isFilterBitmap = true
style = Paint.Style.FILL
textSize = 12 * dp
}
}
val drawRect = Rect()
val drawRectF = RectF()
/**需要在那个方法中触发绘制*/
var drawType = DRAW_TYPE_ON_DRAW_AFTER
/**xml属性读取*/
open fun initAttribute(
context: Context,
attributeSet: AttributeSet? = null
) {
//val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.xxx)
//typedArray.recycle()
}
//<editor-fold desc="View相关方法">
/**附着的[View]*/
val attachView: View?
get() = if (callback is View) callback as? View else null
val isInEditMode: Boolean
get() = attachView?.isInEditMode ?: false
val paddingLeft: Int
get() = attachView?.paddingLeft ?: 0
val paddingRight: Int
get() = attachView?.paddingRight ?: 0
val paddingTop: Int
get() = attachView?.paddingTop ?: 0
val paddingBottom: Int
get() = attachView?.paddingBottom ?: 0
val viewHeight: Int
get() = attachView?.measuredHeight ?: 0
val viewWidth: Int
get() = attachView?.measuredWidth ?: 0
val viewDrawHeight: Int
get() = viewHeight - paddingTop - paddingBottom
val viewDrawWidth: Int
get() = viewWidth - paddingLeft - paddingRight
val isViewRtl: Boolean
get() = attachView != null && ViewCompat.getLayoutDirection(attachView!!) == ViewCompat.LAYOUT_DIRECTION_RTL
//</editor-fold desc="View相关方法">
//<editor-fold desc="基类方法">
/**核心方法, 绘制*/
override fun draw(canvas: Canvas) {
}
override fun getIntrinsicWidth(): Int {
return super.getIntrinsicWidth()
}
override fun getMinimumWidth(): Int {
return super.getMinimumWidth()
}
override fun getIntrinsicHeight(): Int {
return super.getIntrinsicHeight()
}
override fun getMinimumHeight(): Int {
return super.getMinimumHeight()
}
override fun setAlpha(alpha: Int) {
if (textPaint.alpha != alpha) {
textPaint.alpha = alpha
invalidateSelf()
}
}
override fun getAlpha(): Int {
return textPaint.alpha
}
override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
super.setBounds(left, top, right, bottom)
}
override fun setBounds(bounds: Rect) {
super.setBounds(bounds)
}
//不透明度
override fun getOpacity(): Int {
return if (alpha < 255) PixelFormat.TRANSLUCENT else PixelFormat.OPAQUE
}
override fun getColorFilter(): ColorFilter? {
return textPaint.colorFilter
}
override fun setColorFilter(colorFilter: ColorFilter?) {
textPaint.colorFilter = colorFilter
invalidateSelf()
}
override fun mutate(): Drawable {
return super.mutate()
}
override fun setDither(dither: Boolean) {
textPaint.isDither = dither
invalidateSelf()
}
override fun setFilterBitmap(filter: Boolean) {
textPaint.isFilterBitmap = filter
invalidateSelf()
}
override fun isFilterBitmap(): Boolean {
return textPaint.isFilterBitmap
}
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
}
override fun onLevelChange(level: Int): Boolean {
return super.onLevelChange(level)
}
override fun onStateChange(state: IntArray): Boolean {
return super.onStateChange(state)
}
override fun setTintList(tint: ColorStateList?) {
super.setTintList(tint)
}
override fun setTintMode(tintMode: PorterDuff.Mode?) {
super.setTintMode(tintMode)
}
override fun setTintBlendMode(blendMode: BlendMode?) {
super.setTintBlendMode(blendMode)
}
override fun setHotspot(x: Float, y: Float) {
super.setHotspot(x, y)
}
override fun setHotspotBounds(left: Int, top: Int, right: Int, bottom: Int) {
super.setHotspotBounds(left, top, right, bottom)
}
override fun getHotspotBounds(outRect: Rect) {
super.getHotspotBounds(outRect)
}
//</editor-fold desc="基类方法">
}

View File

@@ -0,0 +1,275 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.text.TextUtils
import android.util.AttributeSet
import android.view.Gravity
import kotlin.math.max
/**
* 未读数, 未读小红点, 角标绘制Drawable
* Email:angcyo@126.com
* @author angcyo
* @date 2019/12/13
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslBadgeDrawable : DslGradientDrawable() {
val dslGravity = DslGravity()
/**重力*/
var badgeGravity: Int = Gravity.CENTER //Gravity.TOP or Gravity.RIGHT
/**角标文本颜色*/
var badgeTextColor = Color.WHITE
/**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
* null 不绘制角标
* "" 空字符绘制圆点
* 其他 正常绘制
* */
var badgeText: String? = null
/**角标的文本大小*/
var badgeTextSize: Float = 12 * dp
set(value) {
field = value
textPaint.textSize = field
}
/**当[badgeText]只有1个字符时, 使用圆形背景*/
var badgeAutoCircle: Boolean = true
/**圆点状态时的半径大小*/
var badgeCircleRadius = 4 * dpi
/**原点状态下, 单独配置的偏移*/
var badgeCircleOffsetX: Int = 0
var badgeCircleOffsetY: Int = 0
/**额外偏移距离, 会根据[Gravity]自动取负值*/
var badgeOffsetX: Int = 0
var badgeOffsetY: Int = 0
/**文本偏移*/
var badgeTextOffsetX: Int = 0
var badgeTextOffsetY: Int = 0
/**圆点状态时无效*/
var badgePaddingLeft = 0
var badgePaddingRight = 0
var badgePaddingTop = 0
var badgePaddingBottom = 0
/**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
var badgeMinHeight = -2
/**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
* -1 表示使用使用计算出来的高度值*/
var badgeMinWidth = -2
//计算属性
val textWidth: Float
get() = textPaint.textWidth(badgeText)
//最大的宽度
val maxWidth: Int
get() = max(
textWidth.toInt(),
originDrawable?.minimumWidth ?: 0
) + badgePaddingLeft + badgePaddingRight
//最大的高度
val maxHeight: Int
get() = max(
textHeight.toInt(),
originDrawable?.minimumHeight ?: 0
) + badgePaddingTop + badgePaddingBottom
val textHeight: Float
get() = textPaint.textHeight()
//原型状态
val isCircle: Boolean
get() = TextUtils.isEmpty(badgeText)
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
super.initAttribute(context, attributeSet)
updateOriginDrawable()
}
override fun draw(canvas: Canvas) {
//super.draw(canvas)
if (badgeText == null) {
return
}
with(dslGravity) {
gravity = if (isViewRtl) {
when (badgeGravity) {
Gravity.RIGHT -> {
Gravity.LEFT
}
Gravity.LEFT -> {
Gravity.RIGHT
}
else -> {
badgeGravity
}
}
} else {
badgeGravity
}
setGravityBounds(bounds)
if (isCircle) {
gravityOffsetX = badgeCircleOffsetX
gravityOffsetY = badgeCircleOffsetY
} else {
gravityOffsetX = badgeOffsetX
gravityOffsetY = badgeOffsetY
}
val textWidth = textPaint.textWidth(badgeText)
val textHeight = textPaint.textHeight()
val drawHeight = if (isCircle) {
badgeCircleRadius.toFloat()
} else {
val height = textHeight + badgePaddingTop + badgePaddingBottom
if (badgeMinHeight > 0) {
max(height, badgeMinHeight.toFloat())
} else {
height
}
}
val drawWidth = if (isCircle) {
badgeCircleRadius.toFloat()
} else {
val width = textWidth + badgePaddingLeft + badgePaddingRight
if (badgeMinWidth == -1) {
max(width, drawHeight)
} else if (badgeMinWidth > 0) {
max(width, badgeMinWidth.toFloat())
} else {
width
}
}
applyGravity(drawWidth, drawHeight) { centerX, centerY ->
if (isCircle) {
textPaint.color = gradientSolidColor
//圆心计算
val cx: Float
val cy: Float
if (gravity.isCenter()) {
cx = centerX.toFloat()
cy = centerY.toFloat()
} else {
cx = centerX.toFloat() + _gravityOffsetX
cy = centerY.toFloat() + _gravityOffsetY
}
//绘制圆
textPaint.color = gradientSolidColor
canvas.drawCircle(
cx,
cy,
badgeCircleRadius.toFloat(),
textPaint
)
//圆的描边
if (gradientStrokeWidth > 0 && gradientStrokeColor != Color.TRANSPARENT) {
val oldWidth = textPaint.strokeWidth
val oldStyle = textPaint.style
textPaint.color = gradientStrokeColor
textPaint.strokeWidth = gradientStrokeWidth.toFloat()
textPaint.style = Paint.Style.STROKE
canvas.drawCircle(
cx,
cy,
badgeCircleRadius.toFloat(),
textPaint
)
textPaint.strokeWidth = oldWidth
textPaint.style = oldStyle
}
} else {
textPaint.color = badgeTextColor
val textDrawX: Float = centerX - textWidth / 2
val textDrawY: Float = centerY + textHeight / 2
val bgLeft = _gravityLeft
val bgTop = _gravityTop
//绘制背景
if (badgeAutoCircle && badgeText?.length == 1) {
if (gradientSolidColor != Color.TRANSPARENT) {
textPaint.color = gradientSolidColor
canvas.drawCircle(
centerX.toFloat(),
centerY.toFloat(),
max(maxWidth, maxHeight).toFloat() / 2,
textPaint
)
}
} else {
originDrawable?.apply {
setBounds(
bgLeft, bgTop,
(bgLeft + drawWidth).toInt(),
(bgTop + drawHeight).toInt()
)
draw(canvas)
}
}
//绘制文本
textPaint.color = badgeTextColor
canvas.drawText(
badgeText!!,
textDrawX + badgeTextOffsetX,
textDrawY - textPaint.descent() + badgeTextOffsetY,
textPaint
)
}
}
}
}
override fun getIntrinsicWidth(): Int {
val width = if (isCircle) {
badgeCircleRadius * 2
} else if (badgeAutoCircle && badgeText?.length == 1) {
max(maxWidth, maxHeight)
} else {
maxWidth
}
return max(badgeMinWidth, width)
}
override fun getIntrinsicHeight(): Int {
val height = if (isCircle) {
badgeCircleRadius * 2
} else if (badgeAutoCircle && badgeText?.length == 1) {
max(maxWidth, maxHeight)
} else {
maxHeight
}
return max(badgeMinHeight, height)
}
}

View File

@@ -0,0 +1,306 @@
package com.angcyo.tablayout
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Build
import androidx.annotation.IntDef
import java.util.*
/**
* 用来构建GradientDrawable
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/27
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslGradientDrawable : AbsDslDrawable() {
/**形状*/
@Shape
var gradientShape = GradientDrawable.RECTANGLE
/**填充的颜色*/
var gradientSolidColor = Color.TRANSPARENT
/**边框的颜色*/
var gradientStrokeColor = Color.TRANSPARENT
/**边框的宽度*/
var gradientStrokeWidth = 0
/**蚂蚁线的宽度*/
var gradientDashWidth = 0f
/**蚂蚁线之间的间距*/
var gradientDashGap = 0f
/**
* 四个角, 8个设置点的圆角信息
* 从 左上y轴->左上x轴->右上x轴->右上y轴..., 开始设置.
*/
var gradientRadii = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
/**颜色渐变*/
var gradientColors: IntArray? = null
var gradientColorsOffsets: FloatArray? = null
/**渐变中心点坐标*/
var gradientCenterX = 0.5f
var gradientCenterY = 0.5f
/**渐变半径, 非比例值, 是px值. [GradientDrawable.RADIAL_GRADIENT]类型才有效*/
var gradientRadius = 0.5f
/** 渐变方向, 默认从左到右 */
var gradientOrientation = GradientDrawable.Orientation.LEFT_RIGHT
/** 渐变类型 */
@GradientType
var gradientType = GradientDrawable.LINEAR_GRADIENT
/**真正绘制的[Drawable]*/
var originDrawable: Drawable? = null
/**宽度补偿*/
var gradientWidthOffset: Int = 0
/**高度补偿*/
var gradientHeightOffset: Int = 0
/**当前的配置, 是否能生成有效的[GradientDrawable]*/
open fun isValidConfig(): Boolean {
return gradientSolidColor != Color.TRANSPARENT ||
gradientStrokeColor != Color.TRANSPARENT ||
gradientColors != null
}
fun _fillRadii(array: FloatArray, radii: String?) {
if (radii.isNullOrEmpty()) {
return
}
val split = radii.split(",")
if (split.size != 8) {
throw IllegalArgumentException("radii 需要8个值.")
} else {
val dp = Resources.getSystem().displayMetrics.density
for (i in split.indices) {
array[i] = split[i].toFloat() * dp
}
}
}
fun fillRadii(radius: Float) {
Arrays.fill(gradientRadii, radius)
}
fun fillRadii(radius: Int) {
_fillRadii(gradientRadii, radius.toFloat())
}
fun _fillRadii(array: FloatArray, radius: Float) {
Arrays.fill(array, radius)
}
fun _fillRadii(array: FloatArray, radius: Int) {
_fillRadii(array, radius.toFloat())
}
fun _fillColor(colors: String?): IntArray? {
if (colors.isNullOrEmpty()) {
return null
}
val split = colors.split(",")
return IntArray(split.size) {
val str = split[it]
if (str.startsWith("#")) {
Color.parseColor(str)
} else {
str.toInt()
}
}
}
/**构建或者更新[originDrawable]*/
open fun updateOriginDrawable(): GradientDrawable? {
val drawable: GradientDrawable? = when (originDrawable) {
null -> GradientDrawable()
is GradientDrawable -> originDrawable as GradientDrawable
else -> {
null
}
}
drawable?.apply {
bounds = this@DslGradientDrawable.bounds
shape = gradientShape
setStroke(
gradientStrokeWidth,
gradientStrokeColor,
gradientDashWidth,
gradientDashGap
)
setColor(gradientSolidColor)
cornerRadii = gradientRadii
if (gradientColors != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setGradientCenter(
this@DslGradientDrawable.gradientCenterX,
this@DslGradientDrawable.gradientCenterY
)
}
gradientRadius = this@DslGradientDrawable.gradientRadius
gradientType = this@DslGradientDrawable.gradientType
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
orientation = gradientOrientation
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setColors(gradientColors, gradientColorsOffsets)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
colors = gradientColors
}
}
originDrawable = this
invalidateSelf()
}
return drawable
}
open fun configDrawable(config: DslGradientDrawable.() -> Unit): DslGradientDrawable {
this.config()
updateOriginDrawable()
return this
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
originDrawable?.apply {
setBounds(
this@DslGradientDrawable.bounds.left - gradientWidthOffset / 2,
this@DslGradientDrawable.bounds.top - gradientHeightOffset / 2,
this@DslGradientDrawable.bounds.right + gradientWidthOffset / 2,
this@DslGradientDrawable.bounds.bottom + gradientHeightOffset / 2
)
draw(canvas)
}
}
//<editor-fold desc="圆角相关配置">
/**
* 4个角, 8个点 圆角配置
*/
fun cornerRadii(radii: FloatArray) {
gradientRadii = radii
}
fun cornerRadius(radii: Float) {
Arrays.fill(gradientRadii, radii)
}
fun cornerRadius(
leftTop: Float = 0f,
rightTop: Float = 0f,
rightBottom: Float = 0f,
leftBottom: Float = 0f
) {
gradientRadii[0] = leftTop
gradientRadii[1] = leftTop
gradientRadii[2] = rightTop
gradientRadii[3] = rightTop
gradientRadii[4] = rightBottom
gradientRadii[5] = rightBottom
gradientRadii[6] = leftBottom
gradientRadii[7] = leftBottom
}
/**
* 只配置左边的圆角
*/
fun cornerRadiiLeft(radii: Float) {
gradientRadii[0] = radii
gradientRadii[1] = radii
gradientRadii[6] = radii
gradientRadii[7] = radii
}
fun cornerRadiiRight(radii: Float) {
gradientRadii[2] = radii
gradientRadii[3] = radii
gradientRadii[4] = radii
gradientRadii[5] = radii
}
fun cornerRadiiTop(radii: Float) {
gradientRadii[0] = radii
gradientRadii[1] = radii
gradientRadii[2] = radii
gradientRadii[3] = radii
}
fun cornerRadiiBottom(radii: Float) {
gradientRadii[4] = radii
gradientRadii[5] = radii
gradientRadii[6] = radii
gradientRadii[7] = radii
}
//</editor-fold desc="圆角相关配置">
//<editor-fold desc="传递属性">
override fun setColorFilter(colorFilter: ColorFilter?) {
super.setColorFilter(colorFilter)
originDrawable?.colorFilter = colorFilter
}
override fun setTintList(tint: ColorStateList?) {
super.setTintList(tint)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
originDrawable?.setTintList(tint)
}
}
override fun setState(stateSet: IntArray): Boolean {
return originDrawable?.setState(stateSet) ?: super.setState(stateSet)
}
override fun getState(): IntArray {
return originDrawable?.state ?: super.getState()
}
//</editor-fold desc="传递属性">
}
@IntDef(
GradientDrawable.RECTANGLE,
GradientDrawable.OVAL,
GradientDrawable.LINE,
GradientDrawable.RING
)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation class Shape
@IntDef(
GradientDrawable.LINEAR_GRADIENT,
GradientDrawable.RADIAL_GRADIENT,
GradientDrawable.SWEEP_GRADIENT
)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation class GradientType
/**快速创建[GradientDrawable]*/
fun dslGradientDrawable(action: DslGradientDrawable.() -> Unit): GradientDrawable {
return DslGradientDrawable().run {
action()
updateOriginDrawable()!!
}
}

View File

@@ -0,0 +1,215 @@
package com.angcyo.tablayout
import android.graphics.Rect
import android.graphics.RectF
import android.view.Gravity
/**
* [android.view.Gravity] 辅助计算类
* Email:angcyo@126.com
* @author angcyo
* @date 2019/12/13
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
class DslGravity {
/**束缚范围*/
val gravityBounds: RectF = RectF()
/**束缚重力*/
var gravity: Int = Gravity.LEFT or Gravity.TOP
/**使用中心坐标作为定位参考, 否则就是四条边*/
var gravityRelativeCenter: Boolean = true
/**额外偏移距离, 会根据[Gravity]自动取负值*/
var gravityOffsetX: Int = 0
var gravityOffsetY: Int = 0
fun setGravityBounds(rectF: RectF) {
gravityBounds.set(rectF)
}
fun setGravityBounds(rect: Rect) {
gravityBounds.set(rect)
}
fun setGravityBounds(
left: Int,
top: Int,
right: Int,
bottom: Int
) {
gravityBounds.set(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
}
fun setGravityBounds(
left: Float,
top: Float,
right: Float,
bottom: Float
) {
gravityBounds.set(left, top, right, bottom)
}
//计算后的属性
var _horizontalGravity: Int = Gravity.LEFT
var _verticalGravity: Int = Gravity.TOP
var _isCenterGravity: Boolean = false
var _targetWidth = 0f
var _targetHeight = 0f
var _gravityLeft = 0
var _gravityTop = 0
var _gravityRight = 0
var _gravityBottom = 0
//根据gravity调整后的offset
var _gravityOffsetX = 0
var _gravityOffsetY = 0
/**根据[gravity]返回在[gravityBounds]中的[left] [top]位置*/
fun applyGravity(
width: Float = _targetWidth,
height: Float = _targetHeight,
callback: (centerX: Int, centerY: Int) -> Unit
) {
_targetWidth = width
_targetHeight = height
val layoutDirection = 0
val absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection)
val verticalGravity = gravity and Gravity.VERTICAL_GRAVITY_MASK
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
//调整offset
_gravityOffsetX = when (horizontalGravity) {
Gravity.RIGHT -> -gravityOffsetX
Gravity.END -> -gravityOffsetX
else -> gravityOffsetX
}
_gravityOffsetY = when (verticalGravity) {
Gravity.BOTTOM -> -gravityOffsetY
else -> gravityOffsetY
}
//计算居中的坐标
val centerX = when (horizontalGravity) {
Gravity.CENTER_HORIZONTAL -> (gravityBounds.left + gravityBounds.width() / 2 + _gravityOffsetX).toInt()
Gravity.RIGHT -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
Gravity.END -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
else -> (gravityBounds.left + _gravityOffsetX + if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
}
val centerY = when (verticalGravity) {
Gravity.CENTER_VERTICAL -> (gravityBounds.top + gravityBounds.height() / 2 + _gravityOffsetY).toInt()
Gravity.BOTTOM -> (gravityBounds.bottom + _gravityOffsetY - if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
else -> (gravityBounds.top + _gravityOffsetY + if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
}
//缓存
_horizontalGravity = horizontalGravity
_verticalGravity = verticalGravity
_isCenterGravity = horizontalGravity == Gravity.CENTER_HORIZONTAL &&
verticalGravity == Gravity.CENTER_VERTICAL
_gravityLeft = (centerX - _targetWidth / 2).toInt()
_gravityRight = (centerX + _targetWidth / 2).toInt()
_gravityTop = (centerY - _targetHeight / 2).toInt()
_gravityBottom = (centerY + _targetHeight / 2).toInt()
//回调
callback(centerX, centerY)
}
}
/**
* 默认计算出的是目标中心点坐标参考距离
* [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
* */
fun dslGravity(
rect: RectF, //计算的矩形
gravity: Int, //重力
width: Float, //放置目标的宽度
height: Float, //放置目标的高度
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
offsetY: Int = 0,
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
): DslGravity {
val _dslGravity = DslGravity()
_dslGravity.setGravityBounds(rect)
_config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
return _dslGravity
}
/**
* 默认计算出的是目标中心点坐标参考距离
* [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
* */
fun dslGravity(
rect: Rect, //计算的矩形
gravity: Int, //重力
width: Float, //放置目标的宽度
height: Float, //放置目标的高度
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
offsetY: Int = 0,
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
): DslGravity {
val _dslGravity = DslGravity()
_dslGravity.setGravityBounds(rect)
_config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
return _dslGravity
}
private fun _config(
_dslGravity: DslGravity,
gravity: Int, //重力
width: Float, //放置目标的宽度
height: Float, //放置目标的高度
offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
offsetY: Int = 0,
callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
) {
_dslGravity.gravity = gravity
_dslGravity.gravityOffsetX = offsetX
_dslGravity.gravityOffsetY = offsetY
_dslGravity.applyGravity(width, height) { centerX, centerY ->
callback(_dslGravity, centerX, centerY)
}
}
/**居中*/
fun Int.isCenter(): Boolean {
val layoutDirection = 0
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
return verticalGravity == Gravity.CENTER_VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL
}
fun Int.isLeft(): Boolean {
val layoutDirection = 0
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
return horizontalGravity == Gravity.LEFT
}
fun Int.isRight(): Boolean {
val layoutDirection = 0
val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
return horizontalGravity == Gravity.RIGHT
}
fun Int.isTop(): Boolean {
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
return verticalGravity == Gravity.TOP
}
fun Int.isBottom(): Boolean {
val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
return verticalGravity == Gravity.BOTTOM
}

View File

@@ -0,0 +1,438 @@
package com.angcyo.tablayout
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
/**
* 用来操作[ViewGroup]中的[child], 支持单选, 多选, 拦截.
* 操作的都是可见性为[VISIBLE]的[View]
*
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/24
*/
open class DslSelector {
var parent: ViewGroup? = null
var dslSelectorConfig: DslSelectorConfig = DslSelectorConfig()
//可见view列表
val visibleViewList: MutableList<View> = mutableListOf()
/**
* 选中的索引列表
* */
val selectorIndexList: MutableList<Int> = mutableListOf()
get() {
field.clear()
visibleViewList.forEachIndexed { index, view ->
if (view.isSe()) {
field.add(index)
}
}
return field
}
/**
* 选中的View列表
* */
val selectorViewList: MutableList<View> = mutableListOf()
get() {
field.clear()
visibleViewList.forEachIndexed { index, view ->
if (view.isSe() || index == dslSelectIndex) {
field.add(view)
}
}
return field
}
//child 点击事件处理
val _onChildClickListener = View.OnClickListener {
val index = visibleViewList.indexOf(it)
val select =
if (dslSelectorConfig.dslMultiMode || dslSelectorConfig.dslMinSelectLimit < 1) {
!it.isSe()
} else {
true
}
if (!interceptSelector(index, select, true)) {
selector(
visibleViewList.indexOf(it),
select,
notify = true,
fromUser = true,
forceNotify = it is CompoundButton && dslSelectorConfig.dslMultiMode
)
}
}
/**兼容[CompoundButton]*/
val _onCheckedChangeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
buttonView.isChecked = buttonView.isSelected //恢复状态 不做任何处理, 在[OnClickListener]中处理
/*val index = visibleViewList.indexOf(buttonView)
if (interceptSelector(index, isChecked, false)) {
//拦截了此操作
buttonView.isChecked = !isChecked //恢复状态
}
val selectorViewList = selectorViewList
val sum = selectorViewList.size
//Limit 过滤
if (buttonView.isChecked) {
if (sum > dslSelectorConfig.dslMaxSelectLimit) {
//不允许选择
buttonView.isChecked = false //恢复状态
}
} else {
//取消选择, 检查是否达到了 limit
if (sum < dslSelectorConfig.dslMinSelectLimit) {
//不允许取消选择
buttonView.isChecked = true //恢复状态
}
}
if (isChecked) {
//已经选中了控件
} else {
//已经取消了控件
}*/
}
/**当前选中的索引*/
var dslSelectIndex = -1
/**安装*/
fun install(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
dslSelectIndex = -1
parent = viewGroup
updateVisibleList()
dslSelectorConfig.config()
updateStyle()
updateClickListener()
if (dslSelectIndex in 0 until visibleViewList.size) {
selector(dslSelectIndex)
}
return this
}
/**更新样式*/
fun updateStyle() {
visibleViewList.forEachIndexed { index, view ->
val selector = dslSelectIndex == index || view.isSe()
dslSelectorConfig.onStyleItemView(view, index, selector)
}
}
/**更新child的点击事件*/
fun updateClickListener() {
parent?.apply {
for (i in 0 until childCount) {
getChildAt(i)?.let {
it.setOnClickListener(_onChildClickListener)
if (it is CompoundButton) {
it.setOnCheckedChangeListener(_onCheckedChangeListener)
}
}
}
}
}
/**更新可见视图列表*/
fun updateVisibleList(): List<View> {
visibleViewList.clear()
parent?.apply {
for (i in 0 until childCount) {
getChildAt(i)?.let {
if (it.visibility == View.VISIBLE) {
visibleViewList.add(it)
}
}
}
}
if (dslSelectIndex in visibleViewList.indices) {
if (!visibleViewList[dslSelectIndex].isSe()) {
visibleViewList[dslSelectIndex].setSe(true)
}
} else {
//如果当前选中的索引, 不在可见视图列表中
dslSelectIndex = -1
}
return visibleViewList
}
/**
* 操作单个
* @param index 操作目标的索引值
* @param select 选中 or 取消选中
* @param notify 是否需要通知事件
* @param forceNotify 是否强制通知事件.child使用[CompoundButton]时, 推荐使用
* */
fun selector(
index: Int,
select: Boolean = true,
notify: Boolean = true,
fromUser: Boolean = false,
forceNotify: Boolean = false
) {
val oldSelectorList = selectorIndexList.toList()
val lastSelectorIndex: Int? = oldSelectorList.lastOrNull()
val reselect = !dslSelectorConfig.dslMultiMode &&
oldSelectorList.isNotEmpty() &&
oldSelectorList.contains(index)
var needNotify = _selector(index, select, fromUser) || forceNotify
if (!oldSelectorList.isChange(selectorIndexList)) {
//选中项, 未改变时不通知
needNotify = false
}
if (needNotify || reselect) {
dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
if (notify) {
notifySelectChange(lastSelectorIndex ?: -1, reselect, fromUser)
}
}
}
/**选择所有
* [select] true:选择所有, false:取消所有*/
fun selectorAll(
select: Boolean = true,
notify: Boolean = true,
fromUser: Boolean = true
) {
val indexList = visibleViewList.mapIndexedTo(mutableListOf()) { index, _ ->
index
}
selector(indexList, select, notify, fromUser)
}
/**
* 操作多个
* @param select 选中 or 取消选中
* [selector]
* */
fun selector(
indexList: MutableList<Int>,
select: Boolean = true,
notify: Boolean = true,
fromUser: Boolean = false
) {
val oldSelectorIndexList = selectorIndexList
val lastSelectorIndex: Int? = oldSelectorIndexList.lastOrNull()
var result = false
indexList.forEach {
result = _selector(it, select, fromUser) || result
}
if (result) {
dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
if (notify) {
notifySelectChange(lastSelectorIndex ?: -1, false, fromUser)
}
}
}
/**通知事件*/
fun notifySelectChange(lastSelectorIndex: Int, reselect: Boolean, fromUser: Boolean) {
val indexSelectorList = selectorIndexList
dslSelectorConfig.onSelectViewChange(
visibleViewList.getOrNull(lastSelectorIndex),
selectorViewList,
reselect,
fromUser
)
dslSelectorConfig.onSelectIndexChange(
lastSelectorIndex,
indexSelectorList,
reselect,
fromUser
)
}
/**当前的操作是否被拦截*/
fun interceptSelector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
val visibleViewList = visibleViewList
if (index !in 0 until visibleViewList.size) {
return true
}
return dslSelectorConfig.onSelectItemView(visibleViewList[index], index, select, fromUser)
}
/**@return 是否发生过改变*/
fun _selector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
val visibleViewList = visibleViewList
//超范围过滤
if (index !in 0 until visibleViewList.size) {
"index out of list.".logi()
return false
}
val selectorIndexList = selectorIndexList
val selectorViewList = selectorViewList
if (selectorIndexList.isNotEmpty()) {
if (select) {
//需要选中某项
if (dslSelectorConfig.dslMultiMode) {
//多选模式
if (selectorIndexList.contains(index)) {
//已经选中
return false
}
} else {
//单选模式
//取消之前选中
selectorIndexList.forEach {
if (it != index) {
visibleViewList[it].setSe(false)
}
}
if (selectorIndexList.contains(index)) {
//已经选中
return true
}
}
} else {
//需要取消选中
if (!selectorIndexList.contains(index)) {
//目标已经是未选中
return false
}
}
}
//Limit 过滤
if (select) {
val sum = selectorViewList.size + 1
if (sum > dslSelectorConfig.dslMaxSelectLimit) {
//不允许选择
return false
}
} else {
//取消选择, 检查是否达到了 limit
val sum = selectorViewList.size - 1
if (sum < dslSelectorConfig.dslMinSelectLimit) {
//不允许取消选择
return false
}
}
val selectorView = visibleViewList[index]
//更新选中样式
selectorView.setSe(select)
if (dslSelectorConfig.dslMultiMode) {
//多选
} else {
//单选
//取消之前
selectorViewList.forEach { view ->
//更新样式
val indexOf = visibleViewList.indexOf(view)
if (indexOf != index &&
!dslSelectorConfig.onSelectItemView(view, indexOf, false, fromUser)
) {
view.setSe(false)
dslSelectorConfig.onStyleItemView(view, indexOf, false)
}
}
}
dslSelectorConfig.onStyleItemView(selectorView, index, select)
return true
}
/**是否选中状态*/
fun View.isSe(): Boolean {
return isSelected || if (this is CompoundButton) isChecked else false
}
fun View.setSe(se: Boolean) {
isSelected = se
if (this is CompoundButton) isChecked = se
}
}
/**
* Dsl配置项
* */
open class DslSelectorConfig {
/**取消选择时, 最小需要保持多个选中. 可以决定单选时, 是否允许取消所有选中*/
var dslMinSelectLimit = 1
/**多选时, 最大允许多个选中*/
var dslMaxSelectLimit = Int.MAX_VALUE
/**是否是多选模式*/
var dslMultiMode: Boolean = false
/**
* 用来初始化[itemView]的样式
* [onSelectItemView]
* */
var onStyleItemView: (itemView: View, index: Int, select: Boolean) -> Unit =
{ _, _, _ ->
}
/**
* 选中[View]改变回调, 优先于[onSelectIndexChange]触发, 区别在于参数类型不一样
* @param fromView 单选模式下有效, 表示之前选中的[View]
* @param reselect 是否是重复选择, 只在单选模式下有效
* @param fromUser 是否是用户产生的回调, 而非代码设置
* */
var onSelectViewChange: (fromView: View?, selectViewList: List<View>, reselect: Boolean, fromUser: Boolean) -> Unit =
{ _, _, _, _ ->
}
/**
* 选中改变回调
* [onSelectViewChange]
* @param fromIndex 单选模式下有效, 表示之前选中的[View], 在可见性[child]列表中的索引
* */
var onSelectIndexChange: (fromIndex: Int, selectIndexList: List<Int>, reselect: Boolean, fromUser: Boolean) -> Unit =
{ fromIndex, selectList, reselect, fromUser ->
"选择:[$fromIndex]->${selectList} reselect:$reselect fromUser:$fromUser".logi()
}
/**
* 当需要选中[itemView]时回调, 返回[true]表示拦截默认处理
* @param itemView 操作的[View]
* @param index [itemView]在可见性view列表中的索引. 非ViewGroup中的索引
* @param select 选中 or 取消选中
* @return true 表示拦截默认处理
* */
var onSelectItemView: (itemView: View, index: Int, select: Boolean, fromUser: Boolean) -> Boolean =
{ _, _, _, _ ->
false
}
}
/**[DslSelector]组件*/
fun dslSelector(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
return DslSelector().apply {
install(viewGroup, config)
}
}

View File

@@ -0,0 +1,222 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.Gravity
import androidx.annotation.Px
/**
* 角标
* Email:angcyo@126.com
* @author angcyo
* @date 2019/12/13
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabBadge : DslBadgeDrawable() {
/**角标默认配置项*/
val defaultBadgeConfig = TabBadgeConfig()
/**预览的角标属性*/
var xmlBadgeText: String? = null
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
val typedArray =
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
gradientSolidColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_badge_solid_color,
defaultBadgeConfig.badgeSolidColor
)
defaultBadgeConfig.badgeSolidColor = gradientSolidColor
badgeTextColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_badge_text_color,
defaultBadgeConfig.badgeTextColor
)
defaultBadgeConfig.badgeTextColor = badgeTextColor
gradientStrokeColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_badge_stroke_color,
defaultBadgeConfig.badgeStrokeColor
)
defaultBadgeConfig.badgeStrokeColor = gradientStrokeColor
gradientStrokeWidth =
typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_stroke_width,
defaultBadgeConfig.badgeStrokeWidth
)
defaultBadgeConfig.badgeStrokeWidth = gradientStrokeWidth
badgeGravity = typedArray.getInt(
R.styleable.DslTabLayout_tab_badge_gravity,
defaultBadgeConfig.badgeGravity
)
defaultBadgeConfig.badgeGravity = badgeGravity
badgeOffsetX = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_offset_x,
defaultBadgeConfig.badgeOffsetX
)
defaultBadgeConfig.badgeOffsetX = badgeOffsetX
badgeOffsetY = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_offset_y,
defaultBadgeConfig.badgeOffsetY
)
defaultBadgeConfig.badgeOffsetY = badgeOffsetY
badgeCircleOffsetX = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_circle_offset_x,
defaultBadgeConfig.badgeOffsetX
)
defaultBadgeConfig.badgeCircleOffsetX = badgeCircleOffsetX
badgeCircleOffsetY = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_circle_offset_y,
defaultBadgeConfig.badgeOffsetY
)
defaultBadgeConfig.badgeCircleOffsetY = badgeCircleOffsetY
badgeCircleRadius = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_circle_radius,
defaultBadgeConfig.badgeCircleRadius
)
defaultBadgeConfig.badgeCircleRadius = badgeCircleRadius
val badgeRadius = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_radius,
defaultBadgeConfig.badgeRadius
)
cornerRadius(badgeRadius.toFloat())
defaultBadgeConfig.badgeRadius = badgeRadius
badgePaddingLeft = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_padding_left,
defaultBadgeConfig.badgePaddingLeft
)
defaultBadgeConfig.badgePaddingLeft = badgePaddingLeft
badgePaddingRight = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_padding_right,
defaultBadgeConfig.badgePaddingRight
)
defaultBadgeConfig.badgePaddingRight = badgePaddingRight
badgePaddingTop = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_padding_top,
defaultBadgeConfig.badgePaddingTop
)
defaultBadgeConfig.badgePaddingTop = badgePaddingTop
badgePaddingBottom = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_padding_bottom,
defaultBadgeConfig.badgePaddingBottom
)
defaultBadgeConfig.badgePaddingBottom = badgePaddingBottom
xmlBadgeText = typedArray.getString(R.styleable.DslTabLayout_tab_badge_text)
badgeTextSize = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_badge_text_size,
defaultBadgeConfig.badgeTextSize.toInt()
).toFloat()
defaultBadgeConfig.badgeTextSize = badgeTextSize
defaultBadgeConfig.badgeAnchorChildIndex =
typedArray.getInteger(
R.styleable.DslTabLayout_tab_badge_anchor_child_index,
defaultBadgeConfig.badgeAnchorChildIndex
)
defaultBadgeConfig.badgeIgnoreChildPadding = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_badge_ignore_child_padding,
defaultBadgeConfig.badgeIgnoreChildPadding
)
defaultBadgeConfig.badgeMinWidth = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_badge_min_width,
defaultBadgeConfig.badgeMinWidth
)
defaultBadgeConfig.badgeMinHeight = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_badge_min_height,
defaultBadgeConfig.badgeMinHeight
)
typedArray.recycle()
super.initAttribute(context, attributeSet)
}
/**使用指定配置, 更新[DslBadgeDrawable]*/
fun updateBadgeConfig(badgeConfig: TabBadgeConfig) {
gradientSolidColor = badgeConfig.badgeSolidColor
gradientStrokeColor = badgeConfig.badgeStrokeColor
gradientStrokeWidth = badgeConfig.badgeStrokeWidth
badgeTextColor = badgeConfig.badgeTextColor
badgeGravity = badgeConfig.badgeGravity
badgeOffsetX = badgeConfig.badgeOffsetX
badgeOffsetY = badgeConfig.badgeOffsetY
badgeCircleOffsetX = badgeConfig.badgeCircleOffsetX
badgeCircleOffsetY = badgeConfig.badgeCircleOffsetY
badgeCircleRadius = badgeConfig.badgeCircleRadius
badgePaddingLeft = badgeConfig.badgePaddingLeft
badgePaddingRight = badgeConfig.badgePaddingRight
badgePaddingTop = badgeConfig.badgePaddingTop
badgePaddingBottom = badgeConfig.badgePaddingBottom
badgeTextSize = badgeConfig.badgeTextSize
cornerRadius(badgeConfig.badgeRadius.toFloat())
badgeMinHeight = badgeConfig.badgeMinHeight
badgeMinWidth = badgeConfig.badgeMinWidth
badgeText = badgeConfig.badgeText
}
}
/**角标绘制参数配置*/
data class TabBadgeConfig(
/**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
* null 不绘制角标
* "" 空字符绘制圆点
* 其他 正常绘制
* */
var badgeText: String? = null,
/**重力*/
var badgeGravity: Int = Gravity.CENTER,
/**角标背景颜色*/
var badgeSolidColor: Int = Color.RED,
/**角标边框颜色*/
var badgeStrokeColor: Int = Color.TRANSPARENT,
/**角标边框宽度*/
var badgeStrokeWidth: Int = 0,
/**角标文本颜色*/
var badgeTextColor: Int = Color.WHITE,
/**角标文本字体大小*/
@Px
var badgeTextSize: Float = 12 * dp,
/**圆点状态时的半径大小*/
var badgeCircleRadius: Int = 4 * dpi,
/**圆角大小*/
var badgeRadius: Int = 10 * dpi,
/**额外偏移距离, 会根据[Gravity]自动取负值*/
var badgeOffsetX: Int = 0,
var badgeOffsetY: Int = 0,
var badgeCircleOffsetX: Int = 0,
var badgeCircleOffsetY: Int = 0,
/**圆点状态时无效*/
var badgePaddingLeft: Int = 4 * dpi,
var badgePaddingRight: Int = 4 * dpi,
var badgePaddingTop: Int = 0,
var badgePaddingBottom: Int = 0,
var badgeAnchorChildIndex: Int = -1,
var badgeIgnoreChildPadding: Boolean = true,
/**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
var badgeMinHeight: Int = -2,
/**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
* -1 表示使用使用计算出来的高度值*/
var badgeMinWidth: Int = -1
)

View File

@@ -0,0 +1,279 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import androidx.core.view.ViewCompat
/**
* 边框绘制, 支持首尾圆角中间不圆角的样式
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/27
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabBorder : DslGradientDrawable() {
/**
* 是否要接管[itemView]背景的绘制
* [updateItemBackground]
* */
var borderDrawItemBackground: Boolean = true
/**是否保持每个[itemView]的圆角都一样, 否则首尾有圆角, 中间没有圆角*/
var borderKeepItemRadius: Boolean = false
var borderBackgroundDrawable: Drawable? = null
/**宽度补偿*/
var borderBackgroundWidthOffset: Int = 0
/**高度补偿*/
var borderBackgroundHeightOffset: Int = 0
/**强制指定选中item的背景颜色*/
var borderItemBackgroundSolidColor: Int? = null
/**当item不可选中时的背景绘制颜色
* [com.angcyo.tablayout.DslTabLayout.itemEnableSelector]
* [borderItemBackgroundSolidColor]*/
var borderItemBackgroundSolidDisableColor: Int? = null
/**强制指定选中item的背景渐变颜色*/
var borderItemBackgroundGradientColors: IntArray? = null
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
val typedArray =
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
val borderBackgroundColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_border_solid_color, gradientSolidColor)
gradientStrokeColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_stroke_color,
gradientStrokeColor
)
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_border_stroke_width,
2 * dpi
)
val radiusSize =
typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_border_radius_size, 0)
cornerRadius(radiusSize.toFloat())
originDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_border_drawable)
borderDrawItemBackground = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_border_draw_item_background,
borderDrawItemBackground
)
borderKeepItemRadius = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_border_keep_item_radius,
borderKeepItemRadius
)
borderBackgroundWidthOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_border_item_background_width_offset,
borderBackgroundWidthOffset
)
borderBackgroundHeightOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_border_item_background_height_offset,
borderBackgroundHeightOffset
)
//
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_solid_color)) {
borderItemBackgroundSolidColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_item_background_solid_color,
gradientStrokeColor
)
}
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_solid_disable_color)) {
borderItemBackgroundSolidDisableColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_item_background_solid_disable_color,
borderItemBackgroundSolidColor ?: gradientStrokeColor
)
}
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_gradient_start_color) ||
typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_gradient_end_color)
) {
val startColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_item_background_gradient_start_color,
gradientStrokeColor
)
val endColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_item_background_gradient_end_color,
gradientStrokeColor
)
borderItemBackgroundGradientColors = intArrayOf(startColor, endColor)
}
typedArray.recycle()
if (originDrawable == null) {
//无自定义的drawable, 那么自绘.
borderBackgroundDrawable = DslGradientDrawable().configDrawable {
gradientSolidColor = borderBackgroundColor
gradientRadii = this@DslTabBorder.gradientRadii
}.originDrawable
updateOriginDrawable()
}
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
originDrawable?.apply {
setBounds(
paddingLeft,
paddingBottom,
viewWidth - paddingRight,
viewHeight - paddingBottom
)
draw(canvas)
}
}
fun drawBorderBackground(canvas: Canvas) {
super.draw(canvas)
borderBackgroundDrawable?.apply {
setBounds(
paddingLeft,
paddingBottom,
viewWidth - paddingRight,
viewHeight - paddingBottom
)
draw(canvas)
}
}
var itemSelectBgDrawable: Drawable? = null
var itemDeselectBgDrawable: Drawable? = null
/**开启边框绘制后, [itemView]的背景也需要负责设置*/
open fun updateItemBackground(
tabLayout: DslTabLayout,
itemView: View,
index: Int,
select: Boolean
) {
if (!borderDrawItemBackground) {
return
}
if (select) {
val isFirst = index == 0
val isLast = index == tabLayout.dslSelector.visibleViewList.size - 1
val drawable = DslGradientDrawable().configDrawable {
gradientWidthOffset = borderBackgroundWidthOffset
gradientHeightOffset = borderBackgroundHeightOffset
gradientSolidColor =
borderItemBackgroundSolidColor ?: this@DslTabBorder.gradientStrokeColor
if (!tabLayout.itemEnableSelector) {
if (borderItemBackgroundSolidDisableColor != null) {
gradientSolidColor = borderItemBackgroundSolidDisableColor!!
}
}
gradientColors = borderItemBackgroundGradientColors
if ((isFirst && isLast) || borderKeepItemRadius) {
//只有一个child
gradientRadii = this@DslTabBorder.gradientRadii
} else if (isFirst) {
if (tabLayout.isHorizontal()) {
if (tabLayout.isLayoutRtl) {
gradientRadii = floatArrayOf(
0f,
0f,
this@DslTabBorder.gradientRadii[2],
this@DslTabBorder.gradientRadii[3],
this@DslTabBorder.gradientRadii[4],
this@DslTabBorder.gradientRadii[5],
0f,
0f
)
} else {
gradientRadii = floatArrayOf(
this@DslTabBorder.gradientRadii[0],
this@DslTabBorder.gradientRadii[1],
0f,
0f,
0f,
0f,
this@DslTabBorder.gradientRadii[6],
this@DslTabBorder.gradientRadii[7]
)
}
} else {
gradientRadii = floatArrayOf(
this@DslTabBorder.gradientRadii[0],
this@DslTabBorder.gradientRadii[1],
this@DslTabBorder.gradientRadii[2],
this@DslTabBorder.gradientRadii[3],
0f,
0f,
0f,
0f
)
}
} else if (isLast) {
if (tabLayout.isHorizontal()) {
if (tabLayout.isLayoutRtl) {
gradientRadii = floatArrayOf(
this@DslTabBorder.gradientRadii[0],
this@DslTabBorder.gradientRadii[1],
0f,
0f,
0f,
0f,
this@DslTabBorder.gradientRadii[6],
this@DslTabBorder.gradientRadii[7]
)
} else {
gradientRadii = floatArrayOf(
0f,
0f,
this@DslTabBorder.gradientRadii[2],
this@DslTabBorder.gradientRadii[3],
this@DslTabBorder.gradientRadii[4],
this@DslTabBorder.gradientRadii[5],
0f,
0f
)
}
} else {
gradientRadii = floatArrayOf(
0f,
0f,
0f,
0f,
this@DslTabBorder.gradientRadii[4],
this@DslTabBorder.gradientRadii[5],
this@DslTabBorder.gradientRadii[6],
this@DslTabBorder.gradientRadii[7]
)
}
}
}
itemSelectBgDrawable = drawable
ViewCompat.setBackground(itemView, itemSelectBgDrawable)
} else {
ViewCompat.setBackground(itemView, itemDeselectBgDrawable)
}
}
}

View File

@@ -0,0 +1,153 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.widget.LinearLayout
/**
* 垂直分割线
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/27
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabDivider : DslGradientDrawable() {
var dividerWidth = 2 * dpi
var dividerHeight = 2 * dpi
var dividerMarginLeft = 0
var dividerMarginRight = 0
var dividerMarginTop = 0
var dividerMarginBottom = 0
/**
* [LinearLayout.SHOW_DIVIDER_BEGINNING]
* [LinearLayout.SHOW_DIVIDER_MIDDLE]
* [LinearLayout.SHOW_DIVIDER_END]
* */
var dividerShowMode = LinearLayout.SHOW_DIVIDER_MIDDLE
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
super.initAttribute(context, attributeSet)
val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
dividerWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_width,
dividerWidth
)
dividerHeight = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_height,
dividerHeight
)
dividerMarginLeft = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_margin_left,
dividerMarginLeft
)
dividerMarginRight = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_margin_right,
dividerMarginRight
)
dividerMarginTop = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_margin_top,
dividerMarginTop
)
dividerMarginBottom = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_margin_bottom,
dividerMarginBottom
)
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_divider_solid_color)) {
gradientSolidColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_divider_solid_color,
gradientSolidColor
)
} else if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_stroke_color)) {
gradientSolidColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_border_stroke_color,
gradientSolidColor
)
} else {
gradientSolidColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_deselect_color,
gradientSolidColor
)
}
gradientStrokeColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_divider_stroke_color,
gradientStrokeColor
)
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_stroke_width,
0
)
val radiusSize =
typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_divider_radius_size,
2 * dpi
)
cornerRadius(radiusSize.toFloat())
originDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_divider_drawable)
dividerShowMode =
typedArray.getInt(R.styleable.DslTabLayout_tab_divider_show_mode, dividerShowMode)
typedArray.recycle()
if (originDrawable == null) {
//无自定义的drawable, 那么自绘.
updateOriginDrawable()
}
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
originDrawable?.apply {
bounds = this@DslTabDivider.bounds
draw(canvas)
}
}
val _tabLayout: DslTabLayout?
get() = if (callback is DslTabLayout) callback as DslTabLayout else null
/**
* [childIndex]位置前面是否需要分割线
* */
open fun haveBeforeDivider(childIndex: Int, childCount: Int): Boolean {
val tabLayout = _tabLayout
if (tabLayout != null && tabLayout.isHorizontal() && tabLayout.isLayoutRtl) {
if (childIndex == 0) {
return dividerShowMode and LinearLayout.SHOW_DIVIDER_END != 0
}
return dividerShowMode and LinearLayout.SHOW_DIVIDER_MIDDLE != 0
}
if (childIndex == 0) {
return dividerShowMode and LinearLayout.SHOW_DIVIDER_BEGINNING != 0
}
return dividerShowMode and LinearLayout.SHOW_DIVIDER_MIDDLE != 0
}
/**
* [childIndex]位置后面是否需要分割线
* */
open fun haveAfterDivider(childIndex: Int, childCount: Int): Boolean {
val tabLayout = _tabLayout
if (tabLayout != null && tabLayout.isHorizontal() && tabLayout.isLayoutRtl) {
if (childIndex == childCount - 1) {
return dividerShowMode and LinearLayout.SHOW_DIVIDER_BEGINNING != 0
}
}
if (childIndex == childCount - 1) {
return dividerShowMode and LinearLayout.SHOW_DIVIDER_END != 0
}
return false
}
}

View File

@@ -0,0 +1,118 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.ViewGroup
/**
*
* Email:angcyo@126.com
* @author angcyo
* @date 2021/05/19
* Copyright (c) 2020 ShenZhen Wayto Ltd. All rights reserved.
*/
open class DslTabHighlight(val tabLayout: DslTabLayout) : DslGradientDrawable() {
/**需要绘制的Drawable*/
var highlightDrawable: Drawable? = null
/**宽度测量模式*/
var highlightWidth = ViewGroup.LayoutParams.MATCH_PARENT
/**高度测量模式*/
var highlightHeight = ViewGroup.LayoutParams.MATCH_PARENT
/**宽度补偿*/
var highlightWidthOffset = 0
/**高度补偿*/
var highlightHeightOffset = 0
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
//super.initAttribute(context, attributeSet)
val typedArray =
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
highlightDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_highlight_drawable)
highlightWidth = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_highlight_width,
highlightWidth
)
highlightHeight = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_highlight_height,
highlightHeight
)
highlightWidthOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_highlight_width_offset,
highlightWidthOffset
)
highlightHeightOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_highlight_height_offset,
highlightHeightOffset
)
typedArray.recycle()
if (highlightDrawable == null && isValidConfig()) {
updateOriginDrawable()
}
}
override fun updateOriginDrawable(): GradientDrawable? {
val drawable = super.updateOriginDrawable()
highlightDrawable = originDrawable
return drawable
}
override fun draw(canvas: Canvas) {
//super.draw(canvas)
val itemView = tabLayout.currentItemView
if (itemView != null) {
val lp = itemView.layoutParams
if (lp is DslTabLayout.LayoutParams) {
lp.highlightDrawable ?: highlightDrawable
} else {
highlightDrawable
}?.apply {
val drawWidth: Int = when (highlightWidth) {
ViewGroup.LayoutParams.MATCH_PARENT -> itemView.measuredWidth
ViewGroup.LayoutParams.WRAP_CONTENT -> intrinsicWidth
else -> highlightWidth
} + highlightWidthOffset
val drawHeight: Int = when (highlightHeight) {
ViewGroup.LayoutParams.MATCH_PARENT -> itemView.measuredHeight
ViewGroup.LayoutParams.WRAP_CONTENT -> intrinsicHeight
else -> highlightHeight
} + highlightHeightOffset
val centerX: Int = itemView.left + (itemView.right - itemView.left) / 2
val centerY: Int = itemView.top + (itemView.bottom - itemView.top) / 2
setBounds(
centerX - drawWidth / 2,
centerY - drawHeight / 2,
centerX + drawWidth / 2,
centerY + drawHeight / 2
)
draw(canvas)
canvas.save()
if (tabLayout.isHorizontal()) {
canvas.translate(itemView.left.toFloat(), 0f)
} else {
canvas.translate(0f, itemView.top.toFloat())
}
itemView.draw(canvas)
canvas.restore()
}
}
}
}

View File

@@ -0,0 +1,931 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.withSave
import java.util.*
import kotlin.math.absoluteValue
import kotlin.math.max
/**
* 指示器
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/25
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabIndicator(val tabLayout: DslTabLayout) : DslGradientDrawable() {
companion object {
/**非颜色值*/
const val NO_COLOR = -2
//---style---
/**不绘制指示器*/
const val INDICATOR_STYLE_NONE = 0
/**指示器绘制在[itemView]的顶部*/
const val INDICATOR_STYLE_TOP = 0x1
/**指示器绘制在[itemView]的底部*/
const val INDICATOR_STYLE_BOTTOM = 0x2
/**默认样式,指示器绘制在[itemView]的中心*/
const val INDICATOR_STYLE_CENTER = 0x4
/**前景绘制,
* 默认是背景绘制, 指示器绘制[itemView]的背部, [itemView] 请不要设置background, 否则可能看不见*/
const val INDICATOR_STYLE_FOREGROUND = 0x1000
//---gravity---
/**指示器重力在开始的位置(横向左边, 纵向上边)*/
const val INDICATOR_GRAVITY_START = 0x1
/**指示器重力在结束的位置(横向右边, 纵向下边)*/
const val INDICATOR_GRAVITY_END = 0x2
/**指示器重力在中间*/
const val INDICATOR_GRAVITY_CENTER = 0x4
}
/**指示器绘制的样式*/
var indicatorStyle = INDICATOR_STYLE_NONE //初始化
/**[indicatorStyle]*/
val _indicatorDrawStyle: Int
get() = indicatorStyle.remove(INDICATOR_STYLE_FOREGROUND)
/**优先将指示器显示在[DslTabLayout]的什么位置
* [INDICATOR_GRAVITY_START] 开始的位置
* [INDICATOR_GRAVITY_END] 结束的位置
* [INDICATOR_GRAVITY_CENTER] 中间的位置*/
var indicatorGravity = INDICATOR_GRAVITY_CENTER
/**
* 指示器在流向下一个位置时, 是否采用[Flow]流线的方式改变宽度
* */
var indicatorEnableFlow: Boolean = false
/**指示器闪现效果, 从当前位置直接跨越到目标位置*/
var indicatorEnableFlash: Boolean = false
/**使用clip的方式绘制闪现效果*/
var indicatorEnableFlashClip: Boolean = true
/**当目标和当前的索引差值<=此值时, [Flow]效果才有效*/
var indicatorFlowStep: Int = 1
/**指示器绘制实体*/
var indicatorDrawable: Drawable? = null
set(value) {
field = tintDrawableColor(value, indicatorColor)
}
/**过滤[indicatorDrawable]的颜色*/
var indicatorColor: Int = NO_COLOR
set(value) {
field = value
indicatorDrawable = indicatorDrawable
}
/**
* 指示器的宽度
* WRAP_CONTENT: [childView]内容的宽度,
* MATCH_PARENT: [childView]的宽度
* 40dp: 固定值
* */
var indicatorWidth = 0 //初始化
/**宽度补偿*/
var indicatorWidthOffset = 0
/**
* 指示器的高度
* WRAP_CONTENT: [childView]内容的高度,
* MATCH_PARENT: [childView]的高度
* 40dp: 固定值
* */
var indicatorHeight = 0 //初始化
/**高度补偿*/
var indicatorHeightOffset = 0
/**XY轴方向补偿*/
var indicatorXOffset = 0
/**会根据[indicatorStyle]自动取负值*/
var indicatorYOffset = 0
/**
* 宽高[WRAP_CONTENT]时, 内容view的定位索引
* */
var indicatorContentIndex = -1
var indicatorContentId = View.NO_ID
/**切换时是否需要动画的支持*/
var indicatorAnim = true
/**在获取锚点view的宽高时, 是否需要忽略对应的padding属性*/
var ignoreChildPadding: Boolean = true
init {
callback = tabLayout
}
override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
val typedArray =
context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
indicatorDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_indicator_drawable)
indicatorColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_indicator_color, indicatorColor)
indicatorStyle = typedArray.getInt(
R.styleable.DslTabLayout_tab_indicator_style,
if (tabLayout.isHorizontal()) INDICATOR_STYLE_BOTTOM else INDICATOR_STYLE_TOP
)
indicatorGravity = typedArray.getInt(
R.styleable.DslTabLayout_tab_indicator_gravity,
indicatorGravity
)
//初始化指示器的高度和宽度
if (indicatorStyle.have(INDICATOR_STYLE_FOREGROUND)) {
//前景绘制
indicatorWidth = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_indicator_width,
if (tabLayout.isHorizontal()) ViewGroup.LayoutParams.MATCH_PARENT else 3 * dpi
)
indicatorHeight = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_indicator_height,
if (tabLayout.isHorizontal()) 3 * dpi else ViewGroup.LayoutParams.MATCH_PARENT
)
indicatorXOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_x_offset,
if (tabLayout.isHorizontal()) 0 else 2 * dpi
)
indicatorYOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_y_offset,
if (tabLayout.isHorizontal()) 2 * dpi else 0
)
} else {
//背景绘制样式
if (tabLayout.isHorizontal()) {
indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
} else {
indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
}
indicatorWidth = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_indicator_width,
indicatorWidth
)
indicatorHeight = typedArray.getLayoutDimension(
R.styleable.DslTabLayout_tab_indicator_height,
indicatorHeight
)
indicatorXOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_x_offset,
indicatorXOffset
)
indicatorYOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_y_offset,
indicatorYOffset
)
}
ignoreChildPadding = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_ignore_child_padding,
!indicatorStyle.have(INDICATOR_STYLE_CENTER)
)
indicatorFlowStep =
typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_flow_step, indicatorFlowStep)
indicatorEnableFlow = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_enable_flow,
indicatorEnableFlow
)
indicatorEnableFlash = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_enable_flash,
indicatorEnableFlash
)
indicatorEnableFlashClip = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_enable_flash_clip,
indicatorEnableFlashClip
)
indicatorWidthOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_width_offset,
indicatorWidthOffset
)
indicatorHeightOffset = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_height_offset,
indicatorHeightOffset
)
indicatorContentIndex = typedArray.getInt(
R.styleable.DslTabLayout_tab_indicator_content_index,
indicatorContentIndex
)
indicatorContentId = typedArray.getResourceId(
R.styleable.DslTabLayout_tab_indicator_content_id,
indicatorContentId
)
indicatorAnim = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_indicator_anim,
indicatorAnim
)
//代码构建Drawable
gradientShape =
typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_shape, gradientShape)
gradientSolidColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_indicator_solid_color,
gradientSolidColor
)
gradientStrokeColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_indicator_stroke_color,
gradientStrokeColor
)
gradientStrokeWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_stroke_width,
gradientStrokeWidth
)
gradientDashWidth = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_dash_width,
gradientDashWidth.toInt()
).toFloat()
gradientDashGap = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_indicator_dash_gap,
gradientDashGap.toInt()
).toFloat()
val gradientRadius =
typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_indicator_radius, 0)
if (gradientRadius > 0) {
Arrays.fill(gradientRadii, gradientRadius.toFloat())
} else {
typedArray.getString(R.styleable.DslTabLayout_tab_indicator_radii)?.let {
_fillRadii(gradientRadii, it)
}
}
val gradientColors =
typedArray.getString(R.styleable.DslTabLayout_tab_indicator_gradient_colors)
this.gradientColors = if (gradientColors.isNullOrEmpty()) {
val startColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_indicator_gradient_start_color,
Color.TRANSPARENT
)
val endColor = typedArray.getColor(
R.styleable.DslTabLayout_tab_indicator_gradient_end_color,
Color.TRANSPARENT
)
if (startColor != endColor) {
intArrayOf(startColor, endColor)
} else {
this.gradientColors
}
} else {
_fillColor(gradientColors) ?: this.gradientColors
}
//...end
typedArray.recycle()
if (indicatorDrawable == null && isValidConfig()) {
updateOriginDrawable()
}
}
override fun updateOriginDrawable(): GradientDrawable? {
val drawable = super.updateOriginDrawable()
indicatorDrawable = originDrawable
return drawable
}
open fun tintDrawableColor(drawable: Drawable?, color: Int): Drawable? {
if (drawable == null || color == NO_COLOR) {
return drawable
}
return drawable.tintDrawableColor(color)
}
/**指示器需要参考的目标控件*/
open fun indicatorContentView(childView: View): View? {
val lp = childView.layoutParams as DslTabLayout.LayoutParams
val contentId =
if (lp.indicatorContentId != View.NO_ID) lp.indicatorContentId else indicatorContentId
if (contentId != View.NO_ID) {
return childView.findViewById(contentId)
}
//如果child强制指定了index, 就用指定的.
val contentIndex =
if (lp.indicatorContentIndex >= 0) lp.indicatorContentIndex else indicatorContentIndex
return if (contentIndex >= 0 && childView is ViewGroup && contentIndex in 0 until childView.childCount) {
//有指定
val contentChildView = childView.getChildAt(contentIndex)
contentChildView
} else {
//没有指定
null
}
}
/**根据指定[index]索引, 获取目标[View]*/
open fun targetChildView(
index: Int,
onChildView: (childView: View, contentChildView: View?) -> Unit
) {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
onChildView(childView, indicatorContentView(childView))
}
}
open fun getChildTargetPaddingLeft(childView: View): Int =
if (ignoreChildPadding) childView.paddingLeft else 0
open fun getChildTargetPaddingRight(childView: View): Int =
if (ignoreChildPadding) childView.paddingRight else 0
open fun getChildTargetPaddingTop(childView: View): Int =
if (ignoreChildPadding) childView.paddingTop else 0
open fun getChildTargetPaddingBottom(childView: View): Int =
if (ignoreChildPadding) childView.paddingBottom else 0
open fun getChildTargetWidth(childView: View): Int =
if (ignoreChildPadding) childView.viewDrawWidth else childView.measuredWidth
open fun getChildTargetHeight(childView: View): Int =
if (ignoreChildPadding) childView.viewDrawHeight else childView.measuredHeight
/**
* [childview]对应的中心x坐标
* */
open fun getChildTargetX(index: Int, gravity: Int = indicatorGravity): Int {
var result = if (index > 0) tabLayout.maxWidth else 0
targetChildView(index) { childView, contentChildView ->
result = if (contentChildView == null) {
when (gravity) {
INDICATOR_GRAVITY_START -> childView.left
INDICATOR_GRAVITY_END -> childView.right
else -> childView.left + getChildTargetPaddingLeft(childView) + getChildTargetWidth(
childView
) / 2
}
} else {
when (gravity) {
INDICATOR_GRAVITY_START -> childView.left + contentChildView.left
INDICATOR_GRAVITY_END -> childView.left + contentChildView.right
else -> childView.left + contentChildView.left + getChildTargetPaddingLeft(
contentChildView
) + getChildTargetWidth(
contentChildView
) / 2
}
}
}
return result
}
open fun getChildTargetY(index: Int, gravity: Int = indicatorGravity): Int {
var result = if (index > 0) tabLayout.maxHeight else 0
targetChildView(index) { childView, contentChildView ->
result = if (contentChildView == null) {
when (gravity) {
INDICATOR_GRAVITY_START -> childView.top
INDICATOR_GRAVITY_END -> childView.bottom
else -> childView.top + getChildTargetPaddingTop(childView) + getChildTargetHeight(
childView
) / 2
}
} else {
when (gravity) {
INDICATOR_GRAVITY_START -> childView.top + contentChildView.top
INDICATOR_GRAVITY_END -> childView.top + childView.bottom
else -> childView.top + contentChildView.top + getChildTargetPaddingTop(
contentChildView
) + getChildTargetHeight(
contentChildView
) / 2
}
}
}
return result
}
open fun getIndicatorDrawWidth(index: Int): Int {
var result = indicatorWidth
when (indicatorWidth) {
ViewGroup.LayoutParams.WRAP_CONTENT -> {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
result = getChildTargetWidth(indicatorContentView(childView) ?: childView)
}
}
ViewGroup.LayoutParams.MATCH_PARENT -> {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
result = childView.measuredWidth
}
}
}
return result + indicatorWidthOffset
}
open fun getIndicatorDrawHeight(index: Int): Int {
var result = indicatorHeight
when (indicatorHeight) {
ViewGroup.LayoutParams.WRAP_CONTENT -> {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
result = getChildTargetHeight(indicatorContentView(childView) ?: childView)
}
}
ViewGroup.LayoutParams.MATCH_PARENT -> {
tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
result = childView.measuredHeight
}
}
}
return result + indicatorHeightOffset
}
override fun draw(canvas: Canvas) {
//super.draw(canvas)
if (!isVisible || _indicatorDrawStyle == INDICATOR_STYLE_NONE || indicatorDrawable == null) {
//不绘制
return
}
if (tabLayout.isHorizontal()) {
drawHorizontal(canvas)
} else {
drawVertical(canvas)
}
}
fun drawHorizontal(canvas: Canvas) {
val childSize = tabLayout.dslSelector.visibleViewList.size
var currentIndex = currentIndex
if (_targetIndex in 0 until childSize) {
currentIndex = max(0, currentIndex)
}
if (currentIndex in 0 until childSize) {
} else {
//无效的index
return
}
//"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
val drawTargetX = getChildTargetX(currentIndex)
val drawWidth = getIndicatorDrawWidth(currentIndex)
val drawHeight = getIndicatorDrawHeight(currentIndex)
val drawLeft = drawTargetX - drawWidth / 2 + indicatorXOffset
//动画过程中的left
var animLeft = drawLeft
//width
var animWidth = drawWidth
//动画执行过程中, 高度额外变大的值
var animExHeight = 0
//end value
val nextDrawTargetX = getChildTargetX(_targetIndex)
val nextDrawWidth = getIndicatorDrawWidth(_targetIndex)
val nextDrawLeft = nextDrawTargetX - nextDrawWidth / 2 + indicatorXOffset
var animEndWidth = nextDrawWidth
var animEndLeft = nextDrawLeft
if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
//动画过程参数计算变量
val animStartLeft = drawLeft
val animStartWidth = drawWidth
val animEndHeight = getIndicatorDrawHeight(_targetIndex)
if (indicatorEnableFlash) {
//闪现效果
animWidth = (animWidth * (1 - positionOffset)).toInt()
animEndWidth = (animEndWidth * positionOffset).toInt()
animLeft = drawTargetX - animWidth / 2 + indicatorXOffset
animEndLeft = nextDrawLeft
} else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
//激活了流动效果
val flowEndWidth: Int
if (_targetIndex > currentIndex) {
flowEndWidth = animEndLeft - animStartLeft + animEndWidth
//目标在右边
animLeft = if (positionOffset >= 0.5) {
(animStartLeft + (animEndLeft - animStartLeft) * (positionOffset - 0.5) / 0.5f).toInt()
} else {
animStartLeft
}
} else {
flowEndWidth = animStartLeft - animEndLeft + animStartWidth
//目标在左边
animLeft = if (positionOffset >= 0.5) {
animEndLeft
} else {
(animStartLeft - (animStartLeft - animEndLeft) * positionOffset / 0.5f).toInt()
}
}
animWidth = if (positionOffset >= 0.5) {
(flowEndWidth - (flowEndWidth - animEndWidth) * (positionOffset - 0.5) / 0.5f).toInt()
} else {
(animStartWidth + (flowEndWidth - animStartWidth) * positionOffset / 0.5f).toInt()
}
} else {
//默认平移效果
if (_targetIndex > currentIndex) {
//目标在右边
animLeft =
(animStartLeft + (animEndLeft - animStartLeft) * positionOffset).toInt()
} else {
//目标在左边
animLeft =
(animStartLeft - (animStartLeft - animEndLeft) * positionOffset).toInt()
}
//动画过程中的宽度
animWidth =
(animStartWidth + (animEndWidth - animStartWidth) * positionOffset).toInt()
}
animExHeight = ((animEndHeight - drawHeight) * positionOffset).toInt()
}
//前景
val drawTop = when (_indicatorDrawStyle) {
//底部绘制
INDICATOR_STYLE_BOTTOM -> viewHeight - drawHeight - indicatorYOffset
//顶部绘制
INDICATOR_STYLE_TOP -> 0 + indicatorYOffset
//居中绘制
else -> paddingTop + viewDrawHeight / 2 - drawHeight / 2 + indicatorYOffset -
animExHeight +
(tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
}
indicatorDrawable?.apply {
if (indicatorEnableFlash) {
//flash
if (indicatorEnableFlashClip) {
drawIndicatorClipHorizontal(
this,
canvas,
drawLeft,
drawTop,
drawLeft + drawWidth,
drawTop + drawHeight + animExHeight,
animWidth,
1 - positionOffset
)
} else {
drawIndicator(
this, canvas, animLeft,
drawTop,
animLeft + animWidth,
drawTop + drawHeight + animExHeight,
1 - positionOffset
)
}
if (_targetIndex in 0 until childSize) {
if (indicatorEnableFlashClip) {
drawIndicatorClipHorizontal(
this,
canvas,
nextDrawLeft,
drawTop,
nextDrawLeft + nextDrawWidth,
drawTop + drawHeight + animExHeight,
animEndWidth,
positionOffset
)
} else {
drawIndicator(
this, canvas, animEndLeft,
drawTop,
animEndLeft + animEndWidth,
drawTop + drawHeight + animExHeight,
positionOffset
)
}
}
} else {
//normal
drawIndicator(
this, canvas, animLeft,
drawTop,
animLeft + animWidth,
drawTop + drawHeight + animExHeight,
1 - positionOffset
)
}
}
}
fun drawIndicator(
indicator: Drawable,
canvas: Canvas,
l: Int,
t: Int,
r: Int,
b: Int,
offset: Float
) {
indicator.apply {
if (this is ITabIndicatorDraw) {
setBounds(l, t, r, b)
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
} else {
val width = r - l
val height = b - t
setBounds(0, 0, width, height)
canvas.withSave {
translate(l.toFloat(), t.toFloat())
draw(canvas)
}
}
}
}
fun drawIndicatorClipHorizontal(
indicator: Drawable,
canvas: Canvas,
l: Int,
t: Int,
r: Int,
b: Int,
endWidth: Int,
offset: Float
) {
indicator.apply {
canvas.save()
val dx = (r - l - endWidth) / 2
canvas.clipRect(l + dx, t, r - dx, b)
setBounds(l, t, r, b)
if (this is ITabIndicatorDraw) {
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
} else {
draw(canvas)
}
canvas.restore()
}
}
fun drawIndicatorClipVertical(
indicator: Drawable,
canvas: Canvas,
l: Int,
t: Int,
r: Int,
b: Int,
endHeight: Int,
offset: Float
) {
indicator.apply {
canvas.save()
val dy = (b - t - endHeight) / 2
canvas.clipRect(l, t + dy, r, b - dy)
setBounds(l, t, r, b)
if (this is ITabIndicatorDraw) {
onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
} else {
draw(canvas)
}
canvas.restore()
}
}
fun drawVertical(canvas: Canvas) {
val childSize = tabLayout.dslSelector.visibleViewList.size
var currentIndex = currentIndex
if (_targetIndex in 0 until childSize) {
currentIndex = max(0, currentIndex)
}
if (currentIndex in 0 until childSize) {
} else {
//无效的index
return
}
//"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
val drawTargetY = getChildTargetY(currentIndex)
val drawWidth = getIndicatorDrawWidth(currentIndex)
val drawHeight = getIndicatorDrawHeight(currentIndex)
val drawTop = drawTargetY - drawHeight / 2 + indicatorYOffset
//动画过程中的top
var animTop = drawTop
//height
var animHeight = drawHeight
//动画执行过程中, 宽度额外变大的值
var animExWidth = 0
//end value
val nextDrawTargetY = getChildTargetY(_targetIndex)
val nextDrawHeight = getIndicatorDrawHeight(_targetIndex)
val nextDrawTop = nextDrawTargetY - nextDrawHeight / 2 + indicatorYOffset
var animEndHeight = nextDrawHeight
var animEndTop = nextDrawTop
if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
//动画过程参数计算变量
val animStartTop = drawTop
val animStartHeight = drawHeight
val animEndWidth = getIndicatorDrawWidth(_targetIndex)
if (indicatorEnableFlash) {
//闪现效果
animHeight = (animHeight * (1 - positionOffset)).toInt()
animEndHeight = (animEndHeight * positionOffset).toInt()
animTop = drawTargetY - animHeight / 2 + indicatorXOffset
animEndTop = nextDrawTargetY - animEndHeight / 2 + indicatorXOffset
} else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
//激活了流动效果
val flowEndHeight: Int
if (_targetIndex > currentIndex) {
flowEndHeight = animEndTop - animStartTop + animEndHeight
//目标在下边
animTop = if (positionOffset >= 0.5) {
(animStartTop + (animEndTop - animStartTop) * (positionOffset - 0.5) / 0.5f).toInt()
} else {
animStartTop
}
} else {
flowEndHeight = animStartTop - animEndTop + animStartHeight
//目标在上边
animTop = if (positionOffset >= 0.5) {
animEndTop
} else {
(animStartTop - (animStartTop - animEndTop) * positionOffset / 0.5f).toInt()
}
}
animHeight = if (positionOffset >= 0.5) {
(flowEndHeight - (flowEndHeight - animEndHeight) * (positionOffset - 0.5) / 0.5f).toInt()
} else {
(animStartHeight + (flowEndHeight - animStartHeight) * positionOffset / 0.5f).toInt()
}
} else {
if (_targetIndex > currentIndex) {
//目标在下边
animTop = (animStartTop + (animEndTop - animStartTop) * positionOffset).toInt()
} else {
//目标在上边
animTop = (animStartTop - (animStartTop - animEndTop) * positionOffset).toInt()
}
//动画过程中的宽度
animHeight =
(animStartHeight + (animEndHeight - animStartHeight) * positionOffset).toInt()
}
animExWidth = ((animEndWidth - drawWidth) * positionOffset).toInt()
}
val drawLeft = when (_indicatorDrawStyle) {
INDICATOR_STYLE_BOTTOM -> {
//右边/底部绘制
viewWidth - drawWidth - indicatorXOffset
}
INDICATOR_STYLE_TOP -> {
//左边/顶部绘制
0 + indicatorXOffset
}
else -> {
//居中绘制
paddingLeft + indicatorXOffset + (viewDrawWidth / 2 - drawWidth / 2) -
(tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
}
}
indicatorDrawable?.apply {
//flash
if (indicatorEnableFlash) {
if (indicatorEnableFlashClip) {
drawIndicatorClipVertical(
this, canvas, drawLeft,
drawTop,
drawLeft + drawWidth + animExWidth,
drawTop + drawHeight,
animHeight,
1 - positionOffset
)
} else {
drawIndicator(
this, canvas, drawLeft,
animTop,
drawLeft + drawWidth + animExWidth,
animTop + animHeight,
1 - positionOffset
)
}
if (_targetIndex in 0 until childSize) {
if (indicatorEnableFlashClip) {
drawIndicatorClipVertical(
this, canvas, drawLeft,
nextDrawTop,
drawLeft + drawWidth + animExWidth,
nextDrawTop + nextDrawHeight,
animEndHeight,
positionOffset
)
} else {
drawIndicator(
this, canvas, drawLeft,
animEndTop,
drawLeft + drawWidth + animExWidth,
animEndTop + animEndHeight,
positionOffset
)
}
}
} else {
drawIndicator(
this, canvas, drawLeft,
animTop,
drawLeft + drawWidth + animExWidth,
animTop + animHeight,
1 - positionOffset
)
}
}
}
fun _childConvexHeight(index: Int): Int {
if (attachView is ViewGroup) {
((attachView as ViewGroup).getChildAt(index).layoutParams as? DslTabLayout.LayoutParams)?.apply {
return layoutConvexHeight
}
}
return 0
}
/**
* 距离[_targetIndex]的偏移比例.[0->1]的过程
* */
var positionOffset: Float = 0f
set(value) {
field = value
invalidateSelf()
}
/**当前绘制的index*/
var currentIndex: Int = -1
/**滚动目标的index*/
var _targetIndex = -1
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,526 @@
package com.angcyo.tablayout
import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.IdRes
import com.angcyo.tablayout.DslTabIndicator.Companion.NO_COLOR
import kotlin.math.max
import kotlin.math.min
/**
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/26
* Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
*/
open class DslTabLayoutConfig(val tabLayout: DslTabLayout) : DslSelectorConfig() {
/**是否开启文本颜色*/
var tabEnableTextColor = true
set(value) {
field = value
if (field) {
tabEnableIcoColor = true
}
}
/**是否开启颜色渐变效果*/
var tabEnableGradientColor = false
set(value) {
field = value
if (field) {
tabEnableIcoGradientColor = true
}
}
/**是否激活指示器的颜色渐变效果*/
var tabEnableIndicatorGradientColor = false
/**选中的文本颜色*/
var tabSelectColor: Int = Color.WHITE //Color.parseColor("#333333")
/**未选中的文本颜色*/
var tabDeselectColor: Int = Color.parseColor("#999999")
/**是否开启Bold, 文本加粗*/
var tabEnableTextBold = false
/**是否使用粗体字体的方式设置粗体, 否则使用[Paint.FAKE_BOLD_TEXT_FLAG]
* 需要先激活[tabEnableTextBold]*/
var tabUseTypefaceBold = false
/**是否开启图标颜色*/
var tabEnableIcoColor = true
/**是否开启图标颜色渐变效果*/
var tabEnableIcoGradientColor = false
/**选中的图标颜色*/
var tabIcoSelectColor: Int = NO_COLOR
get() {
return if (field == NO_COLOR) tabSelectColor else field
}
/**未选中的图标颜色*/
var tabIcoDeselectColor: Int = NO_COLOR
get() {
return if (field == NO_COLOR) tabDeselectColor else field
}
/**是否开启scale渐变效果*/
var tabEnableGradientScale = false
/**最小缩放的比例*/
var tabMinScale = 0.8f
/**最大缩放的比例*/
var tabMaxScale = 1.2f
/**是否开启字体大小渐变效果*/
var tabEnableGradientTextSize = true
/**tab中文本字体未选中时的字体大小, >0时激活*/
var tabTextMinSize = -1f
/**tab中文本字体选中时的字体大小, >0时激活*/
var tabTextMaxSize = -1f
/**渐变效果实现的回调*/
var tabGradientCallback = TabGradientCallback()
/**指定文本控件的id, 所有文本属性改变, 将会发生在这个控件上.
* 如果指定的控件不存在, 控件会降权至[ItemView]*/
@IdRes
var tabTextViewId: Int = View.NO_ID
/**指定图标控件的id*/
@IdRes
var tabIconViewId: Int = View.NO_ID
/**返回用于配置文本样式的控件*/
var onGetTextStyleView: (itemView: View, index: Int) -> TextView? = { itemView, _ ->
if (tabTextViewId == View.NO_ID) {
var tv: TextView? = if (itemView is TextView) itemView else null
if (tabLayout.tabIndicator.indicatorContentIndex != -1) {
itemView.getChildOrNull(tabLayout.tabIndicator.indicatorContentIndex)?.let {
if (it is TextView) {
tv = it
}
}
}
if (tabLayout.tabIndicator.indicatorContentId != View.NO_ID) {
itemView.findViewById<View>(tabLayout.tabIndicator.indicatorContentId)?.let {
if (it is TextView) {
tv = it
}
}
}
val lp = itemView.layoutParams
if (lp is DslTabLayout.LayoutParams) {
if (lp.indicatorContentIndex != -1 && itemView is ViewGroup) {
itemView.getChildOrNull(lp.indicatorContentIndex)?.let {
if (it is TextView) {
tv = it
}
}
}
if (lp.indicatorContentId != View.NO_ID) {
itemView.findViewById<View>(lp.indicatorContentId)?.let {
if (it is TextView) {
tv = it
}
}
}
if (lp.contentTextViewIndex != -1 && itemView is ViewGroup) {
itemView.getChildOrNull(lp.contentTextViewIndex)?.let {
if (it is TextView) {
tv = it
}
}
}
if (lp.contentTextViewId != View.NO_ID) {
itemView.findViewById<View>(lp.contentTextViewId)?.let {
if (it is TextView) {
tv = it
}
}
}
}
tv
} else {
itemView.findViewById(tabTextViewId)
}
}
/**返回用于配置ico样式的控件*/
var onGetIcoStyleView: (itemView: View, index: Int) -> View? = { itemView, _ ->
if (tabIconViewId == View.NO_ID) {
var iv: View? = itemView
if (tabLayout.tabIndicator.indicatorContentIndex != -1) {
itemView.getChildOrNull(tabLayout.tabIndicator.indicatorContentIndex)?.let {
iv = it
}
}
if (tabLayout.tabIndicator.indicatorContentId != View.NO_ID) {
itemView.findViewById<View>(tabLayout.tabIndicator.indicatorContentId)?.let {
iv = it
}
}
val lp = itemView.layoutParams
if (lp is DslTabLayout.LayoutParams) {
if (lp.indicatorContentIndex != -1 && itemView is ViewGroup) {
iv = itemView.getChildOrNull(lp.indicatorContentIndex)
}
if (lp.indicatorContentId != View.NO_ID) {
itemView.findViewById<View>(lp.indicatorContentId)?.let {
iv = it
}
}
if (lp.contentIconViewIndex != -1 && itemView is ViewGroup) {
iv = itemView.getChildOrNull(lp.contentIconViewIndex)
}
if (lp.contentIconViewId != View.NO_ID) {
itemView.findViewById<View>(lp.contentIconViewId)?.let {
iv = it
}
}
}
iv
} else {
itemView.findViewById(tabIconViewId)
}
}
/**获取渐变结束时,指示器的颜色.*/
var onGetGradientIndicatorColor: (fromIndex: Int, toIndex: Int, positionOffset: Float) -> Int =
{ fromIndex, toIndex, positionOffset ->
tabLayout.tabIndicator.indicatorColor
}
init {
onStyleItemView = { itemView, index, select ->
onUpdateItemStyle(itemView, index, select)
}
onSelectIndexChange = { fromIndex, selectIndexList, reselect, fromUser ->
val toIndex = selectIndexList.last()
tabLayout._viewPagerDelegate?.onSetCurrentItem(fromIndex, toIndex, reselect, fromUser)
}
}
/**xml属性读取*/
open fun initAttribute(context: Context, attributeSet: AttributeSet? = null) {
val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
tabSelectColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_select_color, tabSelectColor)
tabDeselectColor =
typedArray.getColor(
R.styleable.DslTabLayout_tab_deselect_color,
tabDeselectColor
)
tabIcoSelectColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_ico_select_color, NO_COLOR)
tabIcoDeselectColor =
typedArray.getColor(R.styleable.DslTabLayout_tab_ico_deselect_color, NO_COLOR)
tabEnableTextColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_text_color,
tabEnableTextColor
)
tabEnableIndicatorGradientColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_indicator_gradient_color,
tabEnableIndicatorGradientColor
)
tabEnableGradientColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_gradient_color,
tabEnableGradientColor
)
tabEnableIcoColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_ico_color,
tabEnableIcoColor
)
tabEnableIcoGradientColor = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_ico_gradient_color,
tabEnableIcoGradientColor
)
tabEnableTextBold = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_text_bold,
tabEnableTextBold
)
tabUseTypefaceBold = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_use_typeface_bold,
tabUseTypefaceBold
)
tabEnableGradientScale = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_gradient_scale,
tabEnableGradientScale
)
tabMinScale = typedArray.getFloat(R.styleable.DslTabLayout_tab_min_scale, tabMinScale)
tabMaxScale = typedArray.getFloat(R.styleable.DslTabLayout_tab_max_scale, tabMaxScale)
tabEnableGradientTextSize = typedArray.getBoolean(
R.styleable.DslTabLayout_tab_enable_gradient_text_size,
tabEnableGradientTextSize
)
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_text_min_size)) {
tabTextMinSize = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_text_min_size,
tabTextMinSize.toInt()
).toFloat()
}
if (typedArray.hasValue(R.styleable.DslTabLayout_tab_text_max_size)) {
tabTextMaxSize = typedArray.getDimensionPixelOffset(
R.styleable.DslTabLayout_tab_text_max_size,
tabTextMaxSize.toInt()
).toFloat()
}
tabTextViewId =
typedArray.getResourceId(R.styleable.DslTabLayout_tab_text_view_id, tabTextViewId)
tabIconViewId =
typedArray.getResourceId(R.styleable.DslTabLayout_tab_icon_view_id, tabIconViewId)
typedArray.recycle()
}
/**更新item的样式*/
open fun onUpdateItemStyle(itemView: View, index: Int, select: Boolean) {
//"$itemView\n$index\n$select".logw()
(onGetTextStyleView(itemView, index))?.apply {
//文本加粗
paint?.apply {
if (tabEnableTextBold && select) {
//设置粗体
if (tabUseTypefaceBold) {
typeface = Typeface.defaultFromStyle(Typeface.BOLD)
} else {
flags = flags or Paint.FAKE_BOLD_TEXT_FLAG
isFakeBoldText = true
}
} else {
//取消粗体
if (tabUseTypefaceBold) {
typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
} else {
flags = flags and Paint.FAKE_BOLD_TEXT_FLAG.inv()
isFakeBoldText = false
}
}
}
if (tabEnableTextColor) {
//文本颜色
setTextColor(if (select) tabSelectColor else tabDeselectColor)
}
if (tabTextMaxSize > 0 || tabTextMinSize > 0) {
//文本字体大小
val minTextSize = min(tabTextMinSize, tabTextMaxSize)
val maxTextSize = max(tabTextMinSize, tabTextMaxSize)
setTextSize(
TypedValue.COMPLEX_UNIT_PX,
if (select) maxTextSize else minTextSize
)
}
}
if (tabEnableIcoColor) {
onGetIcoStyleView(itemView, index)?.apply {
_updateIcoColor(this, if (select) tabIcoSelectColor else tabIcoDeselectColor)
}
}
if (tabEnableGradientScale) {
itemView.scaleX = if (select) tabMaxScale else tabMinScale
itemView.scaleY = if (select) tabMaxScale else tabMinScale
}
if (tabLayout.drawBorder) {
tabLayout.tabBorder?.updateItemBackground(tabLayout, itemView, index, select)
}
}
/**
* [DslTabLayout]滚动时回调.
* */
open fun onPageIndexScrolled(fromIndex: Int, toIndex: Int, positionOffset: Float) {
}
/**
* [onPageIndexScrolled]
* */
open fun onPageViewScrolled(fromView: View?, toView: View, positionOffset: Float) {
//"$fromView\n$toView\n$positionOffset".logi()
if (fromView != toView) {
val fromIndex = tabLayout.tabIndicator.currentIndex
val toIndex = tabLayout.tabIndicator._targetIndex
if (tabEnableIndicatorGradientColor) {
val startColor = onGetGradientIndicatorColor(fromIndex, fromIndex, 0f)
val endColor = onGetGradientIndicatorColor(fromIndex, toIndex, positionOffset)
tabLayout.tabIndicator.indicatorColor =
evaluateColor(positionOffset, startColor, endColor)
}
if (tabEnableGradientColor) {
//文本渐变
fromView?.apply {
_gradientColor(
onGetTextStyleView(this, fromIndex),
tabSelectColor,
tabDeselectColor,
positionOffset
)
}
_gradientColor(
onGetTextStyleView(toView, toIndex),
tabDeselectColor,
tabSelectColor,
positionOffset
)
}
if (tabEnableIcoGradientColor) {
//图标渐变
fromView?.apply {
_gradientIcoColor(
onGetIcoStyleView(this, fromIndex),
tabIcoSelectColor,
tabIcoDeselectColor,
positionOffset
)
}
_gradientIcoColor(
onGetIcoStyleView(toView, toIndex),
tabIcoDeselectColor,
tabIcoSelectColor,
positionOffset
)
}
if (tabEnableGradientScale) {
//scale渐变
_gradientScale(fromView, tabMaxScale, tabMinScale, positionOffset)
_gradientScale(toView, tabMinScale, tabMaxScale, positionOffset)
}
if (tabEnableGradientTextSize &&
tabTextMaxSize > 0 &&
tabTextMinSize > 0 &&
tabTextMinSize != tabTextMaxSize
) {
//文本字体大小渐变
_gradientTextSize(
fromView?.run { onGetTextStyleView(this, fromIndex) },
tabTextMaxSize,
tabTextMinSize,
positionOffset
)
_gradientTextSize(
onGetTextStyleView(toView, toIndex),
tabTextMinSize,
tabTextMaxSize,
positionOffset
)
if (toIndex == tabLayout.dslSelector.visibleViewList.lastIndex || toIndex == 0) {
tabLayout._scrollToTarget(toIndex, false)
}
}
}
}
open fun _gradientColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
tabGradientCallback.onGradientColor(view, startColor, endColor, percent)
}
open fun _gradientIcoColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
tabGradientCallback.onGradientIcoColor(view, startColor, endColor, percent)
}
open fun _gradientScale(view: View?, startScale: Float, endScale: Float, percent: Float) {
tabGradientCallback.onGradientScale(view, startScale, endScale, percent)
}
open fun _gradientTextSize(
view: TextView?,
startTextSize: Float,
endTextSize: Float,
percent: Float
) {
tabGradientCallback.onGradientTextSize(view, startTextSize, endTextSize, percent)
}
open fun _updateIcoColor(view: View?, color: Int) {
tabGradientCallback.onUpdateIcoColor(view, color)
}
}
open class TabGradientCallback {
open fun onGradientColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
(view as? TextView)?.apply {
setTextColor(evaluateColor(percent, startColor, endColor))
}
}
open fun onGradientIcoColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
onUpdateIcoColor(view, evaluateColor(percent, startColor, endColor))
}
open fun onUpdateIcoColor(view: View?, color: Int) {
view?.tintDrawableColor(color)
}
open fun onGradientScale(view: View?, startScale: Float, endScale: Float, percent: Float) {
view?.apply {
(startScale + (endScale - startScale) * percent).let {
scaleX = it
scaleY = it
}
}
}
open fun onGradientTextSize(
view: TextView?,
startTextSize: Float,
endTextSize: Float,
percent: Float
) {
view?.apply {
setTextSize(
TypedValue.COMPLEX_UNIT_PX,
(startTextSize + (endTextSize - startTextSize) * percent)
)
}
}
}

View File

@@ -0,0 +1,22 @@
package com.angcyo.tablayout
import android.graphics.Canvas
/**
* 用来实现[DslTabIndicator]的自绘
* Email:angcyo@126.com
* @author angcyo
* @date 2022/02/21
* Copyright (c) 2020 ShenZhen Wayto Ltd. All rights reserved.
*/
interface ITabIndicatorDraw {
/**绘制指示器
* [positionOffset] 页面偏移量*/
fun onDrawTabIndicator(
tabIndicator: DslTabIndicator,
canvas: Canvas,
positionOffset: Float
)
}

View File

@@ -0,0 +1,334 @@
package com.angcyo.tablayout
import android.app.Activity
import android.content.Context
import android.content.res.Resources
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Build
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.math.MathUtils
/**
*
* Email:angcyo@126.com
* @author angcyo
* @date 2019/11/23
*/
internal val dpi: Int
get() = dp.toInt()
internal val dp: Float
get() = Resources.getSystem().displayMetrics.density
internal val View.dpi: Int
get() = context.resources.displayMetrics.density.toInt()
internal val View.screenWidth: Int
get() = context.resources.displayMetrics.widthPixels
internal val View.screenHeight: Int
get() = context.resources.displayMetrics.heightPixels
internal val View.viewDrawWidth: Int
get() = measuredWidth - paddingLeft - paddingRight
internal val View.viewDrawHeight: Int
get() = measuredHeight - paddingTop - paddingBottom
/**Match_Parent*/
internal fun exactlyMeasure(size: Int): Int =
View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY)
internal fun exactlyMeasure(size: Float): Int = exactlyMeasure(size.toInt())
/**Wrap_Content*/
internal fun atmostMeasure(size: Int): Int =
View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.AT_MOST)
internal fun Int.have(value: Int): Boolean = if (this == 0 || value == 0) {
false
} else if (this == 0 && value == 0) {
true
} else {
((this > 0 && value > 0) || (this < 0 && value < 0)) && this and value == value
}
internal fun Int.remove(value: Int): Int = this and value.inv()
internal fun clamp(value: Float, min: Float, max: Float): Float {
if (value < min) {
return min
} else if (value > max) {
return max
}
return value
}
internal fun clamp(value: Int, min: Int, max: Int): Int {
if (value < min) {
return min
} else if (value > max) {
return max
}
return value
}
internal fun Any.logi() {
Log.i("DslTabLayout", "$this")
}
internal fun Any.logw() {
Log.w("DslTabLayout", "$this")
}
internal fun Any.loge() {
Log.e("DslTabLayout", "$this")
}
internal fun View.calcLayoutWidthHeight(
rLayoutWidth: String?, rLayoutHeight: String?,
parentWidth: Int, parentHeight: Int,
rLayoutWidthExclude: Int = 0, rLayoutHeightExclude: Int = 0
): IntArray {
val size = intArrayOf(-1, -1)
if (TextUtils.isEmpty(rLayoutWidth) && TextUtils.isEmpty(rLayoutHeight)) {
return size
}
if (!TextUtils.isEmpty(rLayoutWidth)) {
if (rLayoutWidth!!.contains("sw", true)) {
val ratio = rLayoutWidth.replace("sw", "", true).toFloatOrNull()
ratio?.let {
size[0] = (ratio * (screenWidth - rLayoutWidthExclude)).toInt()
}
} else if (rLayoutWidth!!.contains("pw", true)) {
val ratio = rLayoutWidth.replace("pw", "", true).toFloatOrNull()
ratio?.let {
size[0] = (ratio * (parentWidth - rLayoutWidthExclude)).toInt()
}
}
}
if (!TextUtils.isEmpty(rLayoutHeight)) {
if (rLayoutHeight!!.contains("sh", true)) {
val ratio = rLayoutHeight.replace("sh", "", true).toFloatOrNull()
ratio?.let {
size[1] = (ratio * (screenHeight - rLayoutHeightExclude)).toInt()
}
} else if (rLayoutHeight!!.contains("ph", true)) {
val ratio = rLayoutHeight.replace("ph", "", true).toFloatOrNull()
ratio?.let {
size[1] = (ratio * (parentHeight - rLayoutHeightExclude)).toInt()
}
}
}
return size
}
internal fun evaluateColor(fraction: Float /*0-1*/, startColor: Int, endColor: Int): Int {
val fr = MathUtils.clamp(fraction, 0f, 1f)
val startA = startColor shr 24 and 0xff
val startR = startColor shr 16 and 0xff
val startG = startColor shr 8 and 0xff
val startB = startColor and 0xff
val endA = endColor shr 24 and 0xff
val endR = endColor shr 16 and 0xff
val endG = endColor shr 8 and 0xff
val endB = endColor and 0xff
return startA + (fr * (endA - startA)).toInt() shl 24 or
(startR + (fr * (endR - startR)).toInt() shl 16) or
(startG + (fr * (endG - startG)).toInt() shl 8) or
startB + (fr * (endB - startB)).toInt()
}
internal fun Drawable?.tintDrawableColor(color: Int): Drawable? {
if (this == null) {
return this
}
val wrappedDrawable =
DrawableCompat.wrap(this).mutate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
DrawableCompat.setTint(wrappedDrawable, color)
} else {
wrappedDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
}
return wrappedDrawable
}
internal fun View?.tintDrawableColor(color: Int) {
when (this) {
is TextView -> {
val drawables = arrayOfNulls<Drawable?>(4)
compoundDrawables.forEachIndexed { index, drawable ->
drawables[index] = drawable?.tintDrawableColor(color)
}
setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3])
}
is ImageView -> {
setImageDrawable(drawable?.tintDrawableColor(color))
}
}
}
internal fun Paint?.textWidth(text: String?): Float {
if (TextUtils.isEmpty(text)) {
return 0f
}
return this?.run {
measureText(text)
} ?: 0f
}
internal fun Paint?.textHeight(): Float = this?.run { descent() - ascent() } ?: 0f
internal fun View.getChildOrNull(index: Int): View? {
return if (this is ViewGroup) {
return if (index in 0 until childCount) {
getChildAt(index)
} else {
null
}
} else {
this
}
}
/**获取[View]在指定[parent]中的矩形坐标*/
internal fun View.getLocationInParent(parentView: View? = null, result: Rect = Rect()): Rect {
val parent: View? = parentView ?: (parent as? View)
if (parent == null) {
getViewRect(result)
} else {
result.set(0, 0, 0, 0)
if (this != parent) {
fun doIt(view: View, parent: View, rect: Rect) {
val viewParent = view.parent
if (viewParent is View) {
rect.left += view.left
rect.top += view.top
if (viewParent != parent) {
doIt(viewParent, parent, rect)
}
}
}
doIt(this, parent, result)
}
result.right = result.left + this.measuredWidth
result.bottom = result.top + this.measuredHeight
}
return result
}
/**
* 获取View, 相对于手机屏幕的矩形
* */
internal fun View.getViewRect(result: Rect = Rect()): Rect {
var offsetX = 0
var offsetY = 0
//横屏, 并且显示了虚拟导航栏的时候. 需要左边偏移
//只计算一次
(context as? Activity)?.let {
it.window.decorView.getGlobalVisibleRect(result)
if (result.width() > result.height()) {
//横屏了
offsetX = navBarHeight(it)
}
}
return getViewRect(offsetX, offsetY, result)
}
/**
* 获取View, 相对于手机屏幕的矩形, 带皮阿尼一
* */
internal fun View.getViewRect(offsetX: Int, offsetY: Int, result: Rect = Rect()): Rect {
//可见位置的坐标, 超出屏幕的距离会被剃掉
//getGlobalVisibleRect(r)
val r2 = IntArray(2)
//val r3 = IntArray(2)
//相对于屏幕的坐标
getLocationOnScreen(r2)
//相对于窗口的坐标
//getLocationInWindow(r3)
val left = r2[0] + offsetX
val top = r2[1] + offsetY
result.set(left, top, left + measuredWidth, top + measuredHeight)
return result
}
/**
* 导航栏的高度(如果显示了)
*/
internal fun navBarHeight(context: Context): Int {
var result = 0
if (context is Activity) {
val decorRect = Rect()
val windowRect = Rect()
context.window.decorView.getGlobalVisibleRect(decorRect)
context.window.findViewById<View>(Window.ID_ANDROID_CONTENT)
.getGlobalVisibleRect(windowRect)
if (decorRect.width() > decorRect.height()) {
//横屏
result = decorRect.width() - windowRect.width()
} else {
//竖屏
result = decorRect.bottom - windowRect.bottom
}
}
return result
}
fun Collection<*>?.size() = this?.size ?: 0
/**判断2个列表中的数据是否改变过*/
internal fun <T> List<T>?.isChange(other: List<T>?): Boolean {
if (this.size() != other.size()) {
return true
}
this?.forEachIndexed { index, t ->
if (t != other?.getOrNull(index)) {
return true
}
}
return false
}
fun Int.isHorizontal() = this == LinearLayout.HORIZONTAL
fun Int.isVertical() = this == LinearLayout.VERTICAL
internal fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = true): View {
if (layoutId == -1) {
return this
}
val rootView = LayoutInflater.from(context).inflate(layoutId, this, false)
if (attachToRoot) {
addView(rootView)
}
return rootView
}

View File

@@ -0,0 +1,21 @@
package com.angcyo.tablayout
/**
* 不依赖ViewPager和ViewPager2
* Email:angcyo@126.com
* @author angcyo
* @date 2019/12/14
*/
interface ViewPagerDelegate {
companion object {
const val SCROLL_STATE_IDLE = 0
const val SCROLL_STATE_DRAGGING = 1
const val SCROLL_STATE_SETTLING = 2
}
/**获取当前页面索引*/
fun onGetCurrentItem(): Int
/**设置当前的页面*/
fun onSetCurrentItem(fromIndex: Int, toIndex: Int, reselect: Boolean, fromUser: Boolean)
}

View File

@@ -0,0 +1,299 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DslTabLayout">
<!--Item是否等宽-->
<attr name="tab_item_is_equ_width" format="boolean" />
<!--当子Item数量大于等于指定数量时,开启等宽,此属性优先级最高-->
<attr name="tab_item_equ_width_count" format="integer" />
<!--[~3] 小于等于3个 [3~] 大于等于3个 [3~5] 3<= <=5 -->
<attr name="tab_item_equ_width_count_range" format="string" />
<!--智能判断Item是否等宽, 如果所有子项, 未撑满tab时, 开启等宽模式.此属性会覆盖[tab_item_is_equ_width]-->
<attr name="tab_item_auto_equ_width" format="boolean" />
<!--默认选中的索引值-->
<attr name="tab_default_index" format="integer" />
<!--等宽模式下, 指定item的宽度. 不指定则平分-->
<attr name="tab_item_width" format="dimension" />
<!--在TabLayout wrap_content时, child match_parent时的高度-->
<attr name="tab_item_default_height" format="dimension" />
<!--是否绘制边框-->
<attr name="tab_draw_border" format="boolean" />
<!--是否绘制分割线-->
<attr name="tab_draw_divider" format="boolean" />
<!--是否绘制指示器-->
<attr name="tab_draw_indicator" format="boolean" />
<!--高凸模式下的背景drawable-->
<attr name="tab_convex_background" format="reference|color" />
<!--是否激活滑动选择模式-->
<attr name="tab_enable_selector_mode" format="boolean" />
<!--方向-->
<attr name="tab_orientation" format="enum">
<enum name="VERTICAL" value="1" />
<enum name="HORIZONTAL" value="0" />
</attr>
<attr name="tab_layout_scroll_anim" format="boolean" />
<attr name="tab_scroll_anim_duration" format="integer" />
<!--预览的布局id-->
<attr name="tab_preview_item_layout_id" format="reference" />
<!--预览的布局数量-->
<attr name="tab_preview_item_count" format="integer" />
<!--indicator 指示器相关属性-->
<!--强制指定指示器的Drawable-->
<attr name="tab_indicator_drawable" format="reference" />
<!--强制指定Drawable的颜色-->
<attr name="tab_indicator_color" format="color" />
<!--指示器的绘制类型, 可以使用[STYLE_TOP|STYLE_FOREGROUND]组合配置-->
<attr name="tab_indicator_style" format="flags">
<!--不绘制-->
<flag name="STYLE_NONE" value="0" />
<flag name="STYLE_TOP" value="0x1" />
<flag name="STYLE_BOTTOM" value="0x2" />
<flag name="STYLE_CENTER" value="0x4" />
<!--前景绘制-->
<flag name="STYLE_FOREGROUND" value="0x1000" />
</attr>
<!--指示器的重力-->
<attr name="tab_indicator_gravity" format="enum">
<!--指示器靠左显示-->
<enum name="GRAVITY_START" value="0x1" />
<!--指示器靠右显示-->
<enum name="GRAVITY_END" value="0x2" />
<!--指示器居中显示-->
<enum name="GRAVITY_CENTER" value="0x4" />
</attr>
<!--是否激活流式效果, ViewPager在滚动时, 指示器的宽度由小变大,再由大变小-->
<attr name="tab_indicator_enable_flow" format="boolean" />
<!--闪现效果-->
<attr name="tab_indicator_enable_flash" format="boolean" />
<!--闪现效果使用clip处理-->
<attr name="tab_indicator_enable_flash_clip" format="boolean" />
<!--tab child的索引相差多少值时, 才开启flow效果-->
<attr name="tab_indicator_flow_step" format="integer" />
<!--指示器的宽度-->
<attr name="tab_indicator_width" format="dimension|flags">
<flag name="WRAP_CONTENT" value="-2" />
<flag name="MATCH_PARENT" value="-1" />
</attr>
<!--宽度的补偿-->
<attr name="tab_indicator_width_offset" format="dimension" />
<!--同上-->
<attr name="tab_indicator_height" format="dimension|flags">
<flag name="WRAP_CONTENT" value="-2" />
<flag name="MATCH_PARENT" value="-1" />
</attr>
<!--同上-->
<attr name="tab_indicator_height_offset" format="dimension" />
<!--x轴的补偿-->
<attr name="tab_indicator_x_offset" format="dimension" />
<!--y轴的补偿, 会根据[tab_indicator_style]的类型, 自动取负值.-->
<attr name="tab_indicator_y_offset" format="dimension" />
<!--指示器child的锚点索引, 用来辅助定位计算指示器的宽度,高度,中点坐标-->
<attr name="tab_indicator_content_index" format="integer" />
<!--指示器child的锚点控件id, 用来辅助定位计算指示器的宽度,高度,中点坐标-->
<attr name="tab_indicator_content_id" format="reference" />
<!--切换指示器时, 是否需要动画的支持-->
<attr name="tab_indicator_anim" format="boolean" />
<!--请参考[GradientDrawable](https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable)-->
<attr name="tab_indicator_shape" format="enum">
<enum name="RECTANGLE" value="0" />
<enum name="OVAL" value="1" />
<enum name="LINE" value="2" />
<enum name="RING" value="3" />
</attr>
<attr name="tab_indicator_solid_color" format="color" />
<attr name="tab_indicator_stroke_color" format="color" />
<attr name="tab_indicator_stroke_width" format="dimension" />
<attr name="tab_indicator_dash_width" format="dimension" />
<attr name="tab_indicator_dash_gap" format="dimension" />
<attr name="tab_indicator_radius" format="dimension" />
<attr name="tab_indicator_radii" format="string" />
<attr name="tab_indicator_gradient_colors" format="string" />
<attr name="tab_indicator_gradient_start_color" format="color" />
<attr name="tab_indicator_gradient_end_color" format="color" />
<attr name="tab_indicator_ignore_child_padding" format="boolean" />
<!--end...-->
<!--TabLayoutConfig 相关属性-->
<!--item选中时 文本的颜色-->
<attr name="tab_select_color" format="color" />
<!--item未选中时 文本的颜色-->
<attr name="tab_deselect_color" format="color" />
<!--选中时 图标(Drawable)的颜色, 默认是文本的颜色-->
<attr name="tab_ico_select_color" format="color" />
<!--未选中时 图标(Drawable)的颜色, 默认是文本的颜色-->
<attr name="tab_ico_deselect_color" format="color" />
<!--是否激活自动设置文本的颜色-->
<attr name="tab_enable_text_color" format="boolean" />
<!--是否激活自动设置图标的颜色-->
<attr name="tab_enable_ico_color" format="boolean" />
<!--是否激活文本变粗-->
<attr name="tab_enable_text_bold" format="boolean" />
<!--是否使用字体的方式设置变粗效果, 需要先开启[tab_enable_text_bold]-->
<attr name="tab_use_typeface_bold" format="boolean" />
<!--是否激活文本颜色渐变-->
<attr name="tab_enable_gradient_color" format="boolean" />
<!--是否激活指示器的颜色渐变效果-->
<attr name="tab_enable_indicator_gradient_color" format="boolean" />
<!--是否激活图标颜色渐变-->
<attr name="tab_enable_ico_gradient_color" format="boolean" />
<!--是否激活缩放渐变-->
<attr name="tab_enable_gradient_scale" format="boolean" />
<!--缩放渐变的最小值-->
<attr name="tab_min_scale" format="float" />
<!--缩放渐变的最大值-->
<attr name="tab_max_scale" format="float" />
<!--是否激活文本大小渐变-->
<attr name="tab_enable_gradient_text_size" format="boolean" />
<!--文本字体大小最小值-->
<attr name="tab_text_min_size" format="dimension" />
<!--文本字体大小最大值-->
<attr name="tab_text_max_size" format="dimension" />
<!--指定文本控件的id, 所有文本属性改变, 将会发生在这个控件上, 如果指定的控件不存在, 控件会降权至[ItemView]-->
<attr name="tab_text_view_id" format="reference" />
<!--指定图标控件的id, 同上-->
<attr name="tab_icon_view_id" format="reference" />
<!--end...-->
<!--Divider 分割线相关属性-->
<!--强制指定分割线的Drawable-->
<attr name="tab_divider_drawable" format="reference" />
<!--参考[GradientDrawable](https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable)-->
<attr name="tab_divider_stroke_color" format="color" />
<attr name="tab_divider_solid_color" format="color" />
<attr name="tab_divider_stroke_width" format="dimension" />
<attr name="tab_divider_radius_size" format="dimension" />
<!--分割线margin距离-->
<attr name="tab_divider_margin_left" format="dimension" />
<attr name="tab_divider_margin_right" format="dimension" />
<attr name="tab_divider_margin_top" format="dimension" />
<attr name="tab_divider_margin_bottom" format="dimension" />
<!--分割线的宽度-->
<attr name="tab_divider_width" format="dimension" />
<attr name="tab_divider_height" format="dimension" />
<!--分割线显示的位置-->
<attr name="tab_divider_show_mode" format="flags">
<flag name="SHOW_DIVIDER_BEGINNING" value="1" />
<flag name="SHOW_DIVIDER_MIDDLE" value="2" />
<flag name="SHOW_DIVIDER_END" value="4" />
</attr>
<!--end...-->
<!--Border 边框相关属性-->
<!--强制指定边框的Drawable-->
<attr name="tab_border_drawable" format="reference" />
<!--参考[GradientDrawable](https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable)-->
<attr name="tab_border_stroke_color" format="color" />
<attr name="tab_border_solid_color" format="color" />
<attr name="tab_border_stroke_width" format="dimension" />
<attr name="tab_border_radius_size" format="dimension" />
<!--边框是否要负责绘制item的背景, 可以自动根据边框的圆角自动配置给item-->
<attr name="tab_border_draw_item_background" format="boolean" />
<!--高度补偿-->
<attr name="tab_border_item_background_height_offset" format="dimension" />
<!--宽度补偿-->
<attr name="tab_border_item_background_width_offset" format="dimension" />
<attr name="tab_border_keep_item_radius" format="boolean" />
<attr name="tab_border_item_background_solid_color" format="color" />
<attr name="tab_border_item_background_solid_disable_color" format="color" />
<attr name="tab_border_item_background_gradient_start_color" format="color" />
<attr name="tab_border_item_background_gradient_end_color" format="color" />
<!--end...-->
<!--Badge 角标相关属性-->
<!--是否绘制角标-->
<attr name="tab_draw_badge" format="boolean" />
<!--角标的背景填充颜色-->
<attr name="tab_badge_solid_color" format="color" />
<!--角标文本的颜色-->
<attr name="tab_badge_text_color" format="color" />
<!--圆点状态时的半径大小-->
<attr name="tab_badge_circle_radius" format="dimension" />
<!--角标的圆角半径-->
<attr name="tab_badge_radius" format="dimension" />
<!--角标重力-->
<attr name="tab_badge_gravity" format="flags">
<flag name="top" value="0x30" />
<flag name="bottom" value="0x50" />
<flag name="left" value="0x03" />
<flag name="right" value="0x05" />
<flag name="center_vertical" value="0x10" />
<flag name="center_horizontal" value="0x01" />
<flag name="center" value="0x11" />
</attr>
<!--x轴方向的偏移量, 会根据[Gravity]自动取负值-->
<attr name="tab_badge_offset_x" format="dimension" />
<!--同上-->
<attr name="tab_badge_offset_y" format="dimension" />
<!--参考[View]的padding属性-->
<attr name="tab_badge_padding_left" format="dimension" />
<attr name="tab_badge_padding_right" format="dimension" />
<attr name="tab_badge_padding_top" format="dimension" />
<attr name="tab_badge_padding_bottom" format="dimension" />
<!--角标的文本内容, 多用于xml预览-->
<attr name="tab_badge_text" format="string" />
<!--角标的文本字体大小-->
<attr name="tab_badge_text_size" format="dimension" />
<!--角标[tab_badge_gravity]定位锚点-->
<attr name="tab_badge_anchor_child_index" format="integer" />
<!--是否要忽略锚点view的padding-->
<attr name="tab_badge_ignore_child_padding" format="boolean" />
<!--角标圆形状态下的单独配置的偏移-->
<attr name="tab_badge_circle_offset_x" format="dimension" />
<!--角标圆形状态下的单独配置的偏移-->
<attr name="tab_badge_circle_offset_y" format="dimension" />
<attr name="tab_badge_stroke_color" format="color" />
<attr name="tab_badge_stroke_width" format="dimension" />
<attr name="tab_badge_min_width" format="dimension|flags">
<flag name="WRAP_HEIGHT" value="-1" />
<flag name="NONE" value="-2" />
</attr>
<attr name="tab_badge_min_height" format="dimension">
<flag name="NONE" value="-2" />
</attr>
<!--Highlight 突出相关属性-->
<attr name="tab_draw_highlight" format="boolean" />
<attr name="tab_highlight_drawable" format="reference" />
<attr name="tab_highlight_width_offset" format="dimension" />
<attr name="tab_highlight_height_offset" format="dimension" />
<!--突出的宽度-->
<attr name="tab_highlight_width" format="dimension|flags">
<flag name="WRAP_CONTENT" value="-2" />
<flag name="MATCH_PARENT" value="-1" />
</attr>
<attr name="tab_highlight_height" format="dimension|flags">
<flag name="WRAP_CONTENT" value="-2" />
<flag name="MATCH_PARENT" value="-1" />
</attr>
<!--end...-->
</declare-styleable>
<declare-styleable name="DslTabLayout_Layout">
<!--支持按比例设置宽度, sw屏幕宽度 pw父宽度, 0.5sw:屏幕宽度的0.5倍, 0.3pw:父宽度的0.3倍-->
<attr name="layout_tab_width" format="string" />
<!--同上 sh, ph-->
<attr name="layout_tab_height" format="string" />
<!--高凸模式, 需要高凸的高度-->
<attr name="layout_tab_convex_height" format="dimension" />
<!--单独指定child的锚点索引-->
<attr name="layout_tab_indicator_content_index" format="integer" />
<!--单独指定child的锚点控件的id-->
<attr name="layout_tab_indicator_content_id" format="reference" />
<!--android.widget.LinearLayout.LayoutParams.weight 剩余空间所占比例-->
<attr name="layout_tab_weight" format="float" />
<!--单独配置的drawable, 可以覆盖tab中的配置-->
<attr name="layout_highlight_drawable" format="reference" />
<attr name="layout_tab_text_view_index" format="integer" />
<attr name="layout_tab_icon_view_index" format="integer" />
<attr name="layout_tab_text_view_id" format="reference" />
<attr name="layout_tab_icon_view_id" format="reference" />
</declare-styleable>
</resources>

View File

@@ -1,92 +0,0 @@
{
"agcgw":{
"backurl":"connect-drcn.hispace.hicloud.com",
"url":"connect-drcn.dbankcloud.cn",
"websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com",
"websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn"
},
"agcgw_all":{
"CN":"connect-drcn.dbankcloud.cn",
"CN_back":"connect-drcn.hispace.hicloud.com",
"DE":"connect-dre.dbankcloud.cn",
"DE_back":"connect-dre.hispace.hicloud.com",
"RU":"connect-drru.hispace.dbankcloud.ru",
"RU_back":"connect-drru.hispace.dbankcloud.cn",
"SG":"connect-dra.dbankcloud.cn",
"SG_back":"connect-dra.hispace.hicloud.com"
},
"websocketgw_all":{
"CN":"connect-ws-drcn.hispace.dbankcloud.cn",
"CN_back":"connect-ws-drcn.hispace.dbankcloud.com",
"DE":"connect-ws-dre.hispace.dbankcloud.cn",
"DE_back":"connect-ws-dre.hispace.dbankcloud.com",
"RU":"connect-ws-drru.hispace.dbankcloud.ru",
"RU_back":"connect-ws-drru.hispace.dbankcloud.cn",
"SG":"connect-ws-dra.hispace.dbankcloud.cn",
"SG_back":"connect-ws-dra.hispace.dbankcloud.com"
},
"client":{
"cp_id":"30086000612391734",
"product_id":"99536292102564216",
"client_id":"964994320723627840",
"client_secret":"6D5FE29D85B967D3A66BDCD473641E4C7B5524F7F4935CA0EF4A842730C3402D",
"project_id":"99536292102564216",
"app_id":"106936673",
"api_key":"DAEDADYGta/0O4ZSdrnug52NgC67/w/RIyTq9A8LyAY0+mp6g6XeJDbxugpluFPLAhaqjaMs5c0PLnRx14UzWbPPADgi1EqihbWLoA==",
"package_name":"com.pdlive.shayu"
},
"oauth_client":{
"client_id":"106936673",
"client_type":1
},
"app_info":{
"app_id":"106936673",
"package_name":"com.pdlive.shayu"
},
"service":{
"analytics":{
"collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
"collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
"collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
"collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
"resource_id":"p1",
"channel_id":""
},
"search":{
"url":"https://search-drcn.cloud.huawei.com"
},
"cloudstorage":{
"storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia",
"storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru",
"storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru",
"storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu",
"storage_url_de":"https://ops-dre.agcstorage.link",
"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn",
"storage_url_sg":"https://ops-dra.agcstorage.link",
"storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn",
"storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn"
},
"ml":{
"mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn"
}
},
"region":"CN",
"configuration_version":"3.0",
"appInfos":[
{
"package_name":"com.pdlive.shayu",
"client":{
"app_id":"106936673"
},
"app_info":{
"package_name":"com.pdlive.shayu",
"app_id":"106936673"
},
"oauth_client":{
"client_type":1,
"client_id":"106936673"
}
}
]
}

View File

@@ -3,36 +3,11 @@ apply plugin: 'img-optimizer'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.alibaba.arouter'
android {
dexOptions {
jumboMode = true
}
/* applicationVariants.all { variant ->
variant.mergeAssetsProvider.configure {
doLast {
delete(fileTree(dir: outputDir, includes: [
'model/ai_bgseg_green.bundle',
'model/ai_face_processor.bundle',
'model/ai_face_processor_lite.bundle',
'model/ai_hairseg.bundle',
'model/ai_hand_processor.bundle',
'model/ai_human_processor.bundle',
'model/ai_human_processor_gpu.bundle',
'model/ai_human_processor_mb_fast.bundle',
'graphics/body_slim.bundle',
'graphics/controller_cpp.bundle',
'graphics/face_beautification.bundle',
'graphics/face_makeup.bundle',
'graphics/fuzzytoonfilter.bundle',
'graphics/fxaa.bundle',
'graphics/tongue.bundle'
]))
}
}
}*/
apply from: "../package_config.gradle"
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
android {
namespace "myname.pdlive.shayu"
compileSdk rootProject.ext.android.compileSdkVersion
packagingOptions {
pickFirst "lib/armeabi/libyuvutils.so"
pickFirst "lib/arm64-v8a/libyuvutils.so"
@@ -92,56 +67,150 @@ android {
exclude 'lib/armeabi-v7a/libmmlic.so'
exclude 'lib/armeabi-v7a/libMNN_CL.so'
exclude 'lib/armeabi-v7a/libMNN_Express.so'
//美颜
if (rootProject.ext.manifestPlaceholders.isPluginModel) {
exclude 'lib/armeabi-v7a/libCNamaSDK.so'
exclude 'lib/arm64-v8a/libCNamaSDK.so'
exclude 'lib/armeabi-v7a/libfuai.so'
exclude 'lib/arm64-v8a/libfuai.so'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_18
targetCompatibility JavaVersion.VERSION_18
}
buildFeatures {
buildConfig = true
}
applicationVariants.all { variant ->
println "清空build文件夹";
for (final def project in rootProject.getAllprojects()) {
def name = variant.name.replace('Debug', '').replace('Release', '').toLowerCase()
//delete project.buildDir
delete project.rootDir.absolutePath+File.separator+"app"+File.separator+name
//println project.buildDir
}
//delete project.rootDir.absolutePath + File.separator + "outputs"
String variantName = variant.name.capitalize()
def processManifestTask = project.tasks.getByName("process${variantName}Manifest")
processManifestTask.doLast { pm ->
String manifestPath = "build/intermediates/bundle_manifest/release/bundle-manifest/AndroidManifest.xml"
def isGooglePlay = rootProject.ext.manifestPlaceholders.isGooglePlay
// String manifestPath = "build/intermediates/bundle_manifest/google_onlineRelease/bundle-manifest/AndroidManifest.xml"
String manifestPath = "build/intermediates/merged_manifests/${variant.name}/process${variantName}Manifest/AndroidManifest.xml"
def isGooglePlay = !variant.name.contains("link")
println "variant = ${variant.name}"
println "谷歌版本:" + isGooglePlay
println "文件存在" + file(manifestPath).exists()
println "" + (isGooglePlay)
println "" + (file(manifestPath).exists() && isGooglePlay)
if (file(manifestPath).exists() && isGooglePlay) {
def manifestContent = file(manifestPath).getText()
println "移除权限"
manifestContent = manifestContent.replace('<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />', '')
manifestContent = manifestContent.replace('android.permission.REQUEST_INSTALL_PACKAGES', '')
file(manifestPath).write(manifestContent)
} else {
print "not Exists = " + manifestPath
}
manifestPath = "build/intermediates/merged_manifests/google_onlineRelease/processReleaseManifest/AndroidManifest.xml"
if (file(manifestPath).exists() && isGooglePlay) {
def manifestContent = file(manifestPath).getText()
println "移除权限2"
manifestContent = manifestContent.replace('<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />', '')
manifestContent = manifestContent.replace('android.permission.REQUEST_INSTALL_PACKAGES', '')
file(manifestPath).write(manifestContent)
}
}
variant.mergeAssetsProvider.configure {
doLast {
delete(fileTree(dir: outputDir, includes: [
'model/ai_bgseg_green.bundle',
'model/ai_face_processor.bundle',
//'model/ai_face_processor_lite.bundle',
'model/ai_hairseg.bundle',
'model/ai_hand_processor.bundle',
'model/ai_human_processor.bundle',
'model/ai_human_processor_gpu.bundle',
'model/ai_human_processor_mb_fast.bundle',
'graphics/body_slim.bundle',
'graphics/controller_cpp.bundle',
//'graphics/face_beautification.bundle',
'graphics/face_makeup.bundle',
'graphics/fuzzytoonfilter.bundle',
'graphics/fxaa.bundle',
'graphics/tongue.bundle',
//旧美颜
'model-all.zip',
'filterData.zip',
'KSYResource/*',
'Resources/*',
'Asset/*',
'image_effect_shaders/*',
'internal/*'
delete(fileTree(dir: outputDir, includes: ['model/ai_bgseg_green.bundle',
//'model/ai_face_processor.bundle',
//'model/ai_face_processor_lite.bundle',
'model/ai_hairseg.bundle',
'model/ai_hand_processor.bundle',
'model/ai_human_processor.bundle',
'model/ai_human_processor_gpu.bundle',
'model/ai_human_processor_mb_fast.bundle',
'graphics/body_slim.bundle',
'graphics/controller_cpp.bundle',
//'graphics/face_beautification.bundle',
'graphics/face_makeup.bundle',
'graphics/fuzzytoonfilter.bundle',
'graphics/fxaa.bundle',
'graphics/tongue.bundle',
//旧美颜
'model-all.zip',
'filterData.zip',
'KSYResource/*',
'Resources/*',
'Asset/*',
'image_effect_shaders/*',
'internal/*'
//美颜基础组件
]))
println "isPluginModel = " + rootProject.ext.manifestPlaceholders.isPluginModel
if (rootProject.ext.manifestPlaceholders.isPluginModel) {
delete(fileTree(dir: outputDir, includes: ['model/ai_face_processor.bundle',
'graphics/face_beautification.bundle']))
} else {
println "不删除bundle"
}
}
}
variant.assemble.doLast { vt ->
def channel = ''
def server = ''
if (variant.name.contains('huawei')) {
channel = "华为"
} else if (variant.name.contains('samsung')) {
channel = "三星"
} else if (variant.name.contains('google')) {
channel = "谷歌"
} else {
channel = "链接"
}
if (variant.name.contains('online')) {
server = '正式服'
} else {
server = '测试服'
}
def fileName = "[${new Date().format("yyyy-MM-dd HHmmss", TimeZone.getTimeZone("GMT+8"))}]PDLive-${defaultConfig.versionName}-${defaultConfig.versionCode}-${channel}-${server}-${variant.buildType.name}.apk"
variant.outputs.forEach { fe ->
copy {
from fe.outputFile
into file("${project.rootDir}\\outputs\\apk\\")
rename { fn ->
fileName
}
}
}
}
tasks.named("sign${variant.name.capitalize()}Bundle", com.android.build.gradle.internal.tasks.FinalizeBundleTask) {
File file = finalBundleFile.asFile.get()
def channel = ''
def server = ''
if (variant.name.startsWith('huawei')) {
channel = "华为"
} else if (variant.name.startsWith('samsung')) {
channel = "三星"
} else if (variant.name.startsWith('google')) {
channel = "谷歌"
} else {
channel = "链接"
}
if (variant.name.contains('online')) {
server = '正式服'
} else {
server = '测试服'
}
def fileName = "[${new Date().format("yyyy-MM-dd HHmmss", TimeZone.getTimeZone("GMT+8"))}]PDLive-${defaultConfig.versionName}-${defaultConfig.versionCode}-${channel}-${server}-${variant.buildType.name}.aab"
File finalFile = new File("${project.rootDir}\\outputs\\aab", fileName)
finalBundleFile.set(finalFile)
}
}
signingConfigs {
release {
@@ -181,24 +250,39 @@ android {
manifestPlaceholders = rootProject.ext.manifestPlaceholders
multiDexEnabled true
ndk {
// TODO: 谷歌商城需要兼容两个平台
abiFilters "armeabi-v7a", "arm64-v8a"
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().args.toString()
println("处理ndk 版本 = " + tskReqStr)
def isLink = tskReqStr.contains("Link")
if (isLink) {//移除32位so库可以有效降低包体大小等需要时再弄
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
//abiFilters "arm64-v8a", "x86_64"
println("打包ndk 链接")
} else {
abiFilters "armeabi-v7a", "arm64-v8a"
//abiFilters "arm64-v8a"
println("打包ndk其他")
}
}
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
buildTypes {
release {
minifyEnabled false
minifyEnabled true
shrinkResources true
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
minifyEnabled false
shrinkResources false
zipAlignEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
@@ -219,8 +303,8 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation platform('com.google.firebase:firebase-bom:30.5.0')
implementation 'com.google.firebase:firebase-crashlytics'
//implementation platform('com.google.firebase:firebase-bom:30.5.0')
//implementation 'com.google.firebase:firebase-crashlytics'
//直播
api project(':main')
@@ -233,18 +317,4 @@ dependencies {
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
}
// 链接包需要注释掉 否正无法更新 谷歌包需要打开
/*
project.afterEvaluate {
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.processResources.doFirst { pm->
String manifestPath = output.processResources.manifestFile;
def manifestContent = file(manifestPath).getText()
manifestContent = manifestContent.replace('android.permission.REQUEST_INSTALL_PACKAGES', '')
file(manifestPath).write(manifestContent)
}
}
}
}*/
}

251
app/proguard-rules.pro vendored
View File

@@ -12,17 +12,40 @@
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
# Uncomment this to preserve the groupLast number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
-keepattributes SourceFile,LineNumberTable
# If you keep the groupLast number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-renamesourcefileattribute SourceFile
-keep class com.yunbao.**{
public <methods>;
protected <methods>;
}
-keep class * implements com.yunbao.common.bean.BaseModel {
*;
}
-keep class com.yunbao.common.bean.** {
*;
}
-keep class com.yunbao.common.views.weight.VerticalViewPager$LayoutParams{
*;
}
-keep class android.**{
*;
}
-keep class **.R$* {
public static <fields>;
}
-keep class com.tencent.** { *; }
-keep class com.adjust.sdk.**{ *; }
-keep class com.google.android.gms.common.ConnectionResult {
int SUCCESS;
@@ -48,6 +71,13 @@
-keep class okhttp3.internal.**{*;}
-dontwarn okio.**
#okhttp
-dontwarn okhttp3.**
-keep class okhttp3.**{*;}
#okio
-dontwarn okio.**
-keep class okio.**{*;}
# Retrofit
@@ -55,9 +85,10 @@
-keep class retrofit2.** { *; }
-keepattributes Signature-keepattributes Exceptions
-keepattributes Signature-keepattributes,Exceptions
# RxJava RxAndroid
-dontwarn java.util.concurrent.Flow*
-dontwarn sun.misc.**
@@ -69,20 +100,220 @@ long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef* {
rx.internal.util.atomic.LinkedQueueNode producerNode;
rx.internal.util.atomic.LinkedQueueNode* producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef* {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
rx.internal.util.atomic.LinkedQueueNode* consumerNode;
}
# Gson
-keep class com.google.gson.stream.** { *; }
##---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
##---------------End: proguard configuration for Gson ----------
-keepattributes EnclosingMethod
#--------融云
-keepattributes Exceptions,InnerClasses
-keepattributes Signature
-keep class io.rong.** {*;}
-keep class cn.rongcloud.** {*;}
-keep class * implements io.rong.imlib.model.MessageContent {*;}
-dontwarn io.rong.push.**
-dontnote com.xiaomi.**
-dontnote com.google.android.gms.gcm.**
-dontnote io.rong.**
# 下方混淆使用了融云 IMKit 提供的 locationKit 位置插件时才需要配置,可参考高德官网的混淆方式:https://lbs.amap.com/api/android-sdk/guide/create-project/dev-attention
-keep class com.amap.api.maps.**{*;}
-keep class com.autonavi.**{*;}
-keep class com.amap.api.trace.**{*;}
-keep class com.amap.api.location.**{*;}
-keep class com.amap.api.fence.**{*;}
-keep class com.loc.**{*;}
-keep class com.autonavi.aps.amapapi.model.**{*;}
-keep class com.amap.api.services.**{*;}
-ignorewarnings
#--------科大讯飞
-keep class com.iflytek.**{*;}
-keepattributes Signature
#EvenBus
-keepattributes *Annotation*
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# If using AsyncExecutord, keep required constructor of default event used.
# Adjust the class name if a custom failure event type is used.
-keepclassmembers class org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
# Accessed via reflection, avoid renaming or removal
-keep class org.greenrobot.eventbus.android.AndroidComponentsImpl*
#--------ARouter
-keep public class com.alibaba.android.arouter.**{*;}
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep public class com.alibaba.android.arouter.facade.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
# 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现
-keep class * implements com.alibaba.android.arouter.facade.template.IProvider
# If single-type injection is used, that is, no interface is defined to implement IProvider, the following rules need to be added to protect the implementation
# -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
#----retrofit2
-keepattributes Signature, InnerClasses, EnclosingMethod
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
-keepattributes AnnotationDefault
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement*
-dontwarn javax.annotation.**
-dontwarn kotlin.Unit
-dontwarn retrofit2.KotlinExtensions*
-dontwarn retrofit2.KotlinExtensions$*
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
#-----glide
-keep public class com.bumptech.glide.** {*;}
-keep public class jp.co.cyberagent.** {*;}
-dontwarn jp.co.cyberagent.android.gpuimage.**
-printconfiguration tmp/full-r8-config.txt
#---美颜模块需要暴露出来的参数名
-keep class com.yunbao.faceunity.utils.FURenderer{
public static java.lang.String BUNDLE_AI_FACE;
}
-keep class com.yunbao.faceunity.utils.FaceUnityConfig{
public static java.lang.String BUNDLE_FACE_BEAUTIFICATION;
}
-keep class com.faceunity.wrapper.faceunity$LoadConfig*{
private static boolean sLoadedLibrary;
}
-keep class com.umeng.** {*;}
-keep class org.repackage.** {*;}
-keep class com.uyumao.** { *; }
-keepclassmembers class * {
public <init> (org.json.JSONObject);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#----svga
-keep class com.opensource.svgaplayer.**{
public <methods>;
public static <fields>;
}
# json序列化的混淆
-keep class tech.sud.mgp.hello.ui.scenes.ticket.model.** {*;}
-keep class tech.sud.mgp.hello.service.game.req.** {*;}
-keep class tech.sud.mgp.hello.service.game.resp.** {*;}
-keep class tech.sud.mgp.hello.service.login.req.** {*;}
-keep class tech.sud.mgp.hello.service.login.resp.** {*;}
-keep class tech.sud.mgp.hello.service.main.req.** {*;}
-keep class tech.sud.mgp.hello.service.main.resp.** {*;}
-keep class tech.sud.mgp.hello.service.main.config.** {*;}
-keep class tech.sud.mgp.hello.service.room.req.** {*;}
-keep class tech.sud.mgp.hello.service.room.resp.** {*;}
-keep class tech.sud.mgp.hello.service.room.model.** {*;}
-keep class tech.sud.mgp.hello.ui.main.home.model.** {*;}
-keep class tech.sud.mgp.hello.ui.scenes.base.model.** {*;}
-keep class tech.sud.mgp.hello.ui.scenes.common.cmd.** {*;}
-keep class tech.sud.mgp.hello.ui.scenes.custom.model.** {*;}
-keep class tech.sud.mgp.hello.ui.scenes.orderentertainment.model.** {*;}
-keep class tech.sud.mgp.hello.ui.main.settings.model.** {*;}
-keep class tech.sud.mgp.hello.ui.main.nft.model.** {*;}
-keep class tech.sud.mgp.hello.common.event.model.** {*;}
-keep class tech.sud.mgp.**{*;}
-keep class bitter.jnibridge.** { *; }
-keep class com.google.androidgamesdk.** { *; }
-keep class com.unity3d.** { *; }
-keep class do.do.do.** { *; }
-keep class do.if.do.** { *; }
-keep class for.do.** { *; }
-keep class if.do.do.do.** { *; }
-keep class org.fmod.** { *; }
-keep class tech.sud.** { *; }
-keep class tech.unity3d.** { *; }
-keep class com.yunbao.common.sud.** {*;}
-ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.huawei.hianalytics.**{*;}
-keep class com.huawei.updatesdk.**{*;}
-keep class com.huawei.hms.**{*;}
-keep class com.shayu.lib_google.**{*;}
-keep class com.shayu.lib_huawei.**{*;}
-keep class io.agora.**{*;}
-keep class com.qiniu.**{*;}
-keep class com.qiniu.**{public <init>();}
-ignorewarnings
-keep class com.samsung.android.sdk.**{*;}
-keep class com.samsung.**{*;}

View File

@@ -0,0 +1,71 @@
{
"project_info": {
"project_number": "822566078854",
"project_id": "pdlvenew",
"storage_bucket": "pdlvenew.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:822566078854:android:9cafc8bca8f63076bf8407",
"android_client_info": {
"package_name": "com.newpdlive.sy"
}
},
"oauth_client": [
{
"client_id": "822566078854-8c7698l64j66ijng9bq799o5qvbguhdo.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.newpdlive.sy",
"certificate_hash": "e059b937bfa49d58f40fddee4c7463e03e2aae47"
}
},
{
"client_id": "822566078854-9cej31ie42tgjeimdk691gmvkavrooa7.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.newpdlive.sy",
"certificate_hash": "15fc5e70cf238323bf7111c8c627803985478e87"
}
},
{
"client_id": "822566078854-c63gcmvkn2ctfct9eebuo0r4tiolloel.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.newpdlive.sy",
"certificate_hash": "b66dc8d21cfcf6c729577ddcf0c312b2a31ed872"
}
},
{
"client_id": "822566078854-jfpovcealtjkv6sf0338to2grv4e5i6k.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.newpdlive.sy",
"certificate_hash": "38cc19306c9facee36a9224e9a4070bc0be15c7d"
}
},
{
"client_id": "822566078854-lt8fjmii2f35anh46dquk0mk5qa0hi5f.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBVlPTRCNLnBNJNei5rHjEqok8CfbJLraI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "822566078854-lt8fjmii2f35anh46dquk0mk5qa0hi5f.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@@ -0,0 +1,71 @@
{
"project_info": {
"project_number": "822566078854",
"project_id": "pdlvenew",
"storage_bucket": "pdlvenew.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:822566078854:android:9cafc8bca8f63076bf8407",
"android_client_info": {
"package_name": "com.newpdlive.sy"
}
},
"oauth_client": [
{
"client_id": "822566078854-8c7698l64j66ijng9bq799o5qvbguhdo.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.newpdlive.sy",
"certificate_hash": "e059b937bfa49d58f40fddee4c7463e03e2aae47"
}
},
{
"client_id": "822566078854-9cej31ie42tgjeimdk691gmvkavrooa7.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.newpdlive.sy",
"certificate_hash": "15fc5e70cf238323bf7111c8c627803985478e87"
}
},
{
"client_id": "822566078854-c63gcmvkn2ctfct9eebuo0r4tiolloel.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.newpdlive.sy",
"certificate_hash": "b66dc8d21cfcf6c729577ddcf0c312b2a31ed872"
}
},
{
"client_id": "822566078854-jfpovcealtjkv6sf0338to2grv4e5i6k.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.newpdlive.sy",
"certificate_hash": "38cc19306c9facee36a9224e9a4070bc0be15c7d"
}
},
{
"client_id": "822566078854-lt8fjmii2f35anh46dquk0mk5qa0hi5f.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBVlPTRCNLnBNJNei5rHjEqok8CfbJLraI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "822566078854-lt8fjmii2f35anh46dquk0mk5qa0hi5f.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

Some files were not shown because too many files have changed in this diff Show More