Compare commits
703 Commits
有聊v1.0
...
dev_基于6.8.
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d0b3da7ff | |||
| db05ff116e | |||
| bf8268712b | |||
|
|
d79dba045d | ||
|
|
0646c2c6a7 | ||
|
|
58ba80929b | ||
| d63cba7db1 | |||
| a454a97c58 | |||
|
|
0937186c93 | ||
| 11e0d62dde | |||
| eb90ae9e03 | |||
| ec5f32cf1e | |||
| ec5d4d77db | |||
| 0c74d81cc6 | |||
|
|
8e860b80d4 | ||
| 61b1101c3f | |||
| daf881f6a7 | |||
| 38f3b721e0 | |||
| 04ae7eeacc | |||
| d863c0af5a | |||
| 99c1037a15 | |||
|
|
3440b71229 | ||
| 50686957dc | |||
|
|
214e6f5d89 | ||
| 2d9e1cd685 | |||
| 60485deed5 | |||
| 7196ebd16e | |||
| 6eee7b9ede | |||
| e0405e9c13 | |||
| e8e0fc32f9 | |||
| 3d4ad99c99 | |||
| a25e22b142 | |||
| 3aeabfa32b | |||
| 47da21351e | |||
| 451a875526 | |||
| dc7b987eda | |||
| fe28d3508b | |||
|
|
cb87974320 | ||
|
|
dbd684a6e2 | ||
| abc37aa486 | |||
| 3349b2d7df | |||
| 97d636ddec | |||
| 3b8aedaa17 | |||
| 40ba4b8aa8 | |||
| f7db0b0768 | |||
| ed642f0137 | |||
| d1512bc256 | |||
| 9f90040168 | |||
| 9ae6fedd8d | |||
| 516a068c25 | |||
| 63b7a18c0b | |||
|
|
6af48002bb | ||
| 79c730c1b7 | |||
| c20a9804e9 | |||
|
|
96a6b05d03 | ||
| ba8090eec7 | |||
| e1fd4949b5 | |||
|
|
08c46a7684 | ||
|
|
3a12d848e1 | ||
|
|
cf245b0df8 | ||
| 1cde41f2d0 | |||
| 092d4cb914 | |||
|
|
0ea92c00a2 | ||
| 21ae621343 | |||
| 048c66736f | |||
| d3055d8fb2 | |||
|
|
b9164b6a08 | ||
|
|
67e5e4e02f | ||
|
|
2351618e5a | ||
|
|
b2e2ca7303 | ||
| 714e51b621 | |||
| efa4c25c4b | |||
|
|
854cad8ec6 | ||
| 46fba9429d | |||
| c67ed4b736 | |||
|
|
4a91aafb4b | ||
|
|
a0f3f246a6 | ||
|
|
2b20782def | ||
|
|
152848a04f | ||
|
|
fe827482c9 | ||
| 7fc1c2e712 | |||
| 7a6dfe5a3d | |||
|
|
96c45820b0 | ||
| ac18150503 | |||
|
|
22b208bcd9 | ||
|
|
484069dac7 | ||
| ade837e85c | |||
| 12a1f24101 | |||
| 250d4832a3 | |||
|
|
93db808f8b | ||
|
|
59f0fa4acb | ||
| 8fe6130c81 | |||
|
|
a11d7f07e9 | ||
|
|
90bc70ebf7 | ||
|
|
4618c0a4f1 | ||
| 9292e6f5c5 | |||
| 845b217c4d | |||
|
|
7edd75e223 | ||
|
|
15a52aaa62 | ||
|
|
6d6010d023 | ||
|
|
94156e2984 | ||
|
|
769f527565 | ||
|
|
eb9f615f70 | ||
|
|
3dcc801331 | ||
|
|
963fe2c110 | ||
| 9c38f40098 | |||
| 8cae89e7cd | |||
|
|
9dd3619049 | ||
|
|
96b80a460b | ||
|
|
a68bee94a9 | ||
|
|
40a2843696 | ||
| 2cefd50ac3 | |||
|
|
833b58d311 | ||
|
|
f14fb4612b | ||
| 3510a565f2 | |||
| e0669e98cf | |||
|
|
8f82c7c785 | ||
|
|
2eea1f1b75 | ||
| f1a1aae787 | |||
|
|
b68c7f1c46 | ||
|
|
dd7eb2326a | ||
| 3f1fe93f3d | |||
|
|
8c9cf4e3a8 | ||
| 0451f518d5 | |||
| a154f480f6 | |||
|
|
29badbf725 | ||
|
|
e89f22329f | ||
|
|
81e08d41af | ||
| d89b356395 | |||
| 4d2985456d | |||
| 48e3ff46ab | |||
|
|
6dd3e02c1d | ||
| d8e9cc2189 | |||
|
|
0310628ba3 | ||
| 2f54fdab12 | |||
|
|
a3228f48c2 | ||
|
|
35c83a255a | ||
|
|
0aeb2d32c2 | ||
|
|
313320473f | ||
|
|
2b86927b5b | ||
|
|
bf3ebfedbd | ||
|
|
1a2db91ceb | ||
|
|
9fa4924278 | ||
|
|
3318fa761c | ||
| a7421ba1a6 | |||
|
|
40a081caa6 | ||
|
|
1485173227 | ||
|
|
d5b60523a5 | ||
|
|
309d04653e | ||
|
|
45ae3b1624 | ||
|
|
e874c2ee16 | ||
|
|
002f0ad654 | ||
|
|
bb49472817 | ||
|
|
05e4ff89d7 | ||
| 020b5c1025 | |||
|
|
2cf10cc35a | ||
|
|
4c53c990e9 | ||
|
|
cd8b58a0ec | ||
| b41bd3b89b | |||
|
|
d66f098692 | ||
|
|
f131fcf546 | ||
|
|
eeb1cc008a | ||
| 6eba9558e1 | |||
|
|
0425b56106 | ||
|
|
c5de062171 | ||
|
|
f82dbccaa5 | ||
| 07d6b959d5 | |||
| 5ae55f87fa | |||
|
|
f59e5e527a | ||
| d70c3a8b27 | |||
|
|
c20147bf8f | ||
|
|
45743e3070 | ||
|
|
b8b43c1ef2 | ||
| 73847c46a3 | |||
|
|
b7c53a3b21 | ||
| c91947ce71 | |||
|
|
62e50ae310 | ||
|
|
5baea0f156 | ||
|
|
419e3a7ab5 | ||
|
|
66822139ba | ||
|
|
5a924d9fea | ||
| cb0772c9e3 | |||
|
|
9c9eb34756 | ||
|
|
1b57d8bc8f | ||
| edc738d80b | |||
| b5417f90b4 | |||
|
|
4bd1e4bc91 | ||
|
|
cb2729433e | ||
|
|
985e626cb5 | ||
|
|
efce2ad93c | ||
|
|
6132338c6c | ||
|
|
8c89fc1de1 | ||
|
|
a061637492 | ||
| 98095e07ee | |||
| 9764b3370e | |||
|
|
bbb5a12899 | ||
|
|
680780145c | ||
|
|
19c270ad22 | ||
|
|
ce9336737f | ||
| 0396e2d06d | |||
|
|
39cf76439f | ||
|
|
11c9a53028 | ||
|
|
c4d378daba | ||
|
|
efa698ebfa | ||
|
|
6496440e5d | ||
|
|
d3de01666c | ||
| 9621d8a6d8 | |||
| 07f97fefdb | |||
| 186b9d61ee | |||
| b7c0f5d3ec | |||
| 949c0ab759 | |||
|
|
278801b77a | ||
| 5f3ebb4235 | |||
| ab24348f72 | |||
| 022806d9da | |||
|
|
b0c5d44d1c | ||
|
|
d9b1149832 | ||
|
|
9711b1f8d4 | ||
|
|
0252963b2e | ||
|
|
ff8e75768e | ||
| c92e8dd8bc | |||
|
|
01dfb81a46 | ||
|
|
0afee3543e | ||
| 8d5990c25b | |||
|
|
d5f78cc83a | ||
|
|
cf48be7d15 | ||
| 225929c891 | |||
| bc63a3e601 | |||
| c33975fe67 | |||
| c033340aad | |||
| 24170d55e2 | |||
|
|
26a71ff825 | ||
| e36fbdc6b8 | |||
|
|
b6d0ea1fba | ||
|
|
cbf517bed6 | ||
|
|
dc0ae1124f | ||
|
|
3167ab0be8 | ||
|
|
0b218157d2 | ||
|
|
6a4ac35203 | ||
|
|
93dbee14b3 | ||
|
|
cb376aa83a | ||
| 92d1861d8f | |||
| 0ff0cb52f6 | |||
|
|
afef088555 | ||
|
|
73414f3349 | ||
|
|
abfd15f751 | ||
| 8d87dda691 | |||
| fcd13751c9 | |||
| e24805cf9b | |||
| 489a99e522 | |||
| 6a51b39134 | |||
| 86bbf6a7f3 | |||
|
|
95ded2f77c | ||
| 599e88acc6 | |||
|
|
d78055cdd9 | ||
| 2093306d36 | |||
|
|
b4adf4c77c | ||
| 8e2acc3417 | |||
| d28d0dd3d7 | |||
|
|
7d46ab8520 | ||
| 150bdba795 | |||
| 651fe12edd | |||
| d0c0f8f82e | |||
| 1f12187766 | |||
|
|
754f2a9294 | ||
|
|
9320d6c209 | ||
| 1c9493d7c8 | |||
| 34e4765e13 | |||
| d4d191fad6 | |||
|
|
8228c45a4d | ||
|
|
4147e3c5f5 | ||
|
|
583740e866 | ||
|
|
0db8eefbe6 | ||
|
|
3f68e2a3aa | ||
|
|
11ba2c45f8 | ||
|
|
320584769d | ||
|
|
5922f4c08b | ||
|
|
b29b0a1688 | ||
|
|
f88356cea2 | ||
|
|
2a67cf7228 | ||
|
|
a100ab1a4a | ||
|
|
69b45f1d21 | ||
|
|
479a743d29 | ||
|
|
382c53ab1f | ||
|
|
1c22f5fd48 | ||
|
|
09bef7afa6 | ||
|
|
88b96267ef | ||
|
|
7c006e8e03 | ||
|
|
f35b547c0d | ||
|
|
490c558e03 | ||
|
|
bbf5d159b4 | ||
|
|
e982994ecd | ||
|
|
a0294a8405 | ||
|
|
756cbb1ce1 | ||
| 9e5427bd35 | |||
|
|
f626ab03d4 | ||
|
|
0d7bdce4f0 | ||
|
|
cd3feebb94 | ||
|
|
3cca1a9e54 | ||
|
|
0b7b959151 | ||
| 1f1ac71bb0 | |||
|
|
ce7b6decc1 | ||
|
|
4a4a52aa61 | ||
|
|
123b9dd74a | ||
|
|
28dba93f17 | ||
|
|
285515345b | ||
|
|
f24744afe8 | ||
|
|
1e466ac13d | ||
|
|
d37b490088 | ||
|
|
a61bc8fbb8 | ||
| 9db23a32d1 | |||
|
|
5f1eadc47b | ||
|
|
de11b5123d | ||
|
|
5b0f5f03ab | ||
|
|
6095d2d04a | ||
|
|
3953e146a5 | ||
|
|
f04a2865af | ||
|
|
f94b3f27e3 | ||
|
|
61a966ea41 | ||
|
|
d0f2ac2c60 | ||
|
|
5dff64ec99 | ||
|
|
cad8236e5c | ||
|
|
faa401245f | ||
|
|
4fc021e866 | ||
|
|
f9a21d0357 | ||
|
|
d72a9a39c6 | ||
|
|
0317b339f8 | ||
|
|
4fabf3aba3 | ||
|
|
4693954a72 | ||
|
|
859810f1f6 | ||
|
|
53f389ddc8 | ||
|
|
ddc9bc344e | ||
|
|
cf34e73d32 | ||
|
|
1981fb553a | ||
|
|
3b6d586b58 | ||
|
|
50b6deaf01 | ||
|
|
bc8d916445 | ||
|
|
6870037683 | ||
|
|
8af7f907eb | ||
| 4d96bcc667 | |||
| f7845dde6f | |||
| ae9ef1f8a1 | |||
|
|
1023c0ab4d | ||
|
|
8184cf6c7f | ||
| df735af252 | |||
|
|
6f71a825b5 | ||
|
|
08e009e5f9 | ||
|
|
1a4a0632ee | ||
|
|
b3a02f8c2c | ||
| aa9bc7326c | |||
| 22769b981d | |||
|
|
85b4facfe8 | ||
| aef0435b93 | |||
| 9cc30186cd | |||
| f99641ed35 | |||
|
|
0552f56eb4 | ||
|
|
7ab4081f0b | ||
|
|
e7ef492015 | ||
| 1aa6cb8f55 | |||
| dc87fc7048 | |||
|
|
04b70d910a | ||
| a4ce44d9ba | |||
| 469e3e30ad | |||
|
|
7e7616ef29 | ||
|
|
dd64568fdf | ||
| 50fb801cf7 | |||
| e0200a9c7f | |||
| 5a32047c6d | |||
|
|
45fd56ba66 | ||
|
|
30b2884c46 | ||
| 0c07e8c2be | |||
|
|
508e9f1931 | ||
| 78d870ed89 | |||
|
|
c00afb1db0 | ||
|
|
dfca615735 | ||
|
|
a920b61bc4 | ||
|
|
3effb630c5 | ||
| 0e596e9be9 | |||
|
|
0a70cc027f | ||
| 47077a465a | |||
|
|
aa62dda844 | ||
|
|
004a5b2a53 | ||
|
|
64f9cee3af | ||
| 1eb43fb8cf | |||
|
|
f770370be0 | ||
| e185eb8612 | |||
| 299add0575 | |||
| feb08e1759 | |||
| 16755a9c8c | |||
|
|
d6e6164549 | ||
| 52c561fa08 | |||
| c2979b4cf7 | |||
|
|
0f70f10aed | ||
| 45f471055e | |||
|
|
c49c9c2f81 | ||
|
|
74b19de11c | ||
|
|
00b89aab69 | ||
|
|
6ff6cbbe6b | ||
|
|
65d38de73c | ||
|
|
ae6fdab7e8 | ||
|
|
3d1c4466e0 | ||
|
|
9963b37b6f | ||
| a8a1aa2e71 | |||
| 2857127285 | |||
|
|
3a1268aac7 | ||
|
|
5b813aff88 | ||
|
|
d9951b5a35 | ||
|
|
f928fce069 | ||
|
|
d365389ff8 | ||
| a4e98f3703 | |||
|
|
a307e6f864 | ||
|
|
b5c8f14994 | ||
|
|
34323fd983 | ||
|
|
1f7c6c8adc | ||
|
|
29c2334ee0 | ||
|
|
2aa50ecfc7 | ||
|
|
58b2d69601 | ||
|
|
e061e61088 | ||
|
|
24788d7e21 | ||
| 54693c0d37 | |||
|
|
27f136d6ca | ||
|
|
92d271ff93 | ||
|
|
0b15df41b8 | ||
|
|
f22f8fcb4b | ||
|
|
7627219e77 | ||
|
|
cf749d85c6 | ||
|
|
ac6eb21e06 | ||
|
|
5ca13b770c | ||
| 7795317c14 | |||
|
|
a036e5eff6 | ||
| c6fb323c68 | |||
|
|
926a1a3cb6 | ||
| d629ee3f9f | |||
| df0d9795ed | |||
| 1759757176 | |||
|
|
661c2fd334 | ||
| b175e42ee1 | |||
| 6cdc648ea9 | |||
|
|
85ccbcccae | ||
| 0a6b210a76 | |||
| 43d1abbae0 | |||
| 4a5e581978 | |||
| 8a7b65955c | |||
|
|
3445ee2a56 | ||
| cf2274195f | |||
|
|
aad607ace8 | ||
| 17506f6cbf | |||
|
|
418073f07e | ||
|
|
319295de57 | ||
|
|
dcf3f5c308 | ||
| 30bdf83724 | |||
| fbb801bf62 | |||
|
|
44601c74b4 | ||
| 2808e41288 | |||
| 05aea2448f | |||
| 34986d6897 | |||
| 924afce81e | |||
| 48dcfd4a04 | |||
|
|
163ab1c80d | ||
| e06f944def | |||
|
|
a5d546d1d1 | ||
| 1304ec6cde | |||
|
|
f46d14ce28 | ||
| e2005582fe | |||
| a56a5849fe | |||
|
|
1495bfee99 | ||
| 19eecd7022 | |||
|
|
d415efde35 | ||
| 61ee585ae9 | |||
|
|
7b103aaf7c | ||
|
|
9eba98f709 | ||
| dea3264577 | |||
|
|
42cfcce1dc | ||
|
|
f697e650a8 | ||
|
|
ac78268a85 | ||
|
|
9d543f2211 | ||
| d001712067 | |||
|
|
dc28a3d81e | ||
| 728dec9d97 | |||
|
|
f65c698602 | ||
|
|
215a2f8927 | ||
|
|
a21352302b | ||
|
|
0b3ebdfc30 | ||
|
|
f10657f89c | ||
|
|
0dc1009661 | ||
|
|
302b789970 | ||
|
|
bd9c8c2265 | ||
| 58ace7d613 | |||
|
|
c723e14182 | ||
|
|
3982afb0d3 | ||
|
|
280920d5f0 | ||
|
|
e25980cec8 | ||
|
|
e353638ec1 | ||
|
|
65a3a64d7d | ||
| b10312118b | |||
|
|
419b5d08b4 | ||
|
|
b146f93029 | ||
|
|
ef34a6cf4f | ||
|
|
213319d5d3 | ||
|
|
ddb4e98afd | ||
|
|
8dbc0dad7a | ||
| cdf3c4e411 | |||
|
|
446dc271d6 | ||
|
|
ecca9b7b8b | ||
|
|
331b55a5ab | ||
|
|
f8ac4617ed | ||
|
|
d0b7b065b2 | ||
| 53e6cb5a70 | |||
|
|
ad79190a61 | ||
|
|
5895fa8d71 | ||
|
|
48886374d5 | ||
|
|
32d0fe497d | ||
|
|
6347473d0c | ||
| 6df819f3b1 | |||
| 39fbe90e8b | |||
|
|
3396ca63d0 | ||
| 8c5ae3b61a | |||
|
|
484891cfe7 | ||
|
|
5c00e065d2 | ||
|
|
2857bb09a2 | ||
|
|
79c1c01003 | ||
| a2864187ed | |||
| ef76b2c7e4 | |||
|
|
05e7c4ffdd | ||
|
|
b4b1efe730 | ||
| 0c7ae4e4f7 | |||
|
|
b35d5d242e | ||
| b8264997f4 | |||
|
|
2ab1b31a94 | ||
|
|
9dba41c22f | ||
|
|
f1645cf72c | ||
|
|
cd35b9e40f | ||
|
|
33bce43737 | ||
|
|
1f8025599b | ||
|
|
7c44f09803 | ||
|
|
0b89ac21e8 | ||
|
|
238181deca | ||
|
|
71a7b47d42 | ||
|
|
59446d2518 | ||
|
|
8a8a7a8326 | ||
|
|
089b1881f1 | ||
|
|
7b1f3ddd79 | ||
|
|
56ae033523 | ||
|
|
f772a64507 | ||
|
|
8c75317ddc | ||
|
|
256c19a09e | ||
|
|
82c1d15ca5 | ||
|
|
aff5a178a0 | ||
|
|
04b1764f7b | ||
|
|
076f7130c8 | ||
|
|
b7842d5ac9 | ||
|
|
eb5aebda51 | ||
|
|
6990bd13ed | ||
|
|
c18af48fbb | ||
|
|
68e6f5df1b | ||
|
|
dc46ffc5b6 | ||
|
|
c58a5a9a55 | ||
|
|
e2ee742091 | ||
|
|
bcc86a899c | ||
|
|
b60031d2d0 | ||
|
|
54aa5fee67 | ||
|
|
816fd21fbc | ||
|
|
92e6b563e4 | ||
|
|
d7bc2615d6 | ||
|
|
21473b1d69 | ||
|
|
9bdee649d8 | ||
|
|
f1a8cd3a68 | ||
|
|
a39005b1a9 | ||
|
|
ec1197e5da | ||
|
|
45372f37b8 | ||
|
|
df93b9495a | ||
|
|
86d730cdf1 | ||
|
|
f356446c03 | ||
|
|
bf7bc6b786 | ||
|
|
6a3c15e3e7 | ||
|
|
07d3b190e2 | ||
|
|
27b316d39b | ||
|
|
f928fcd4bc | ||
|
|
adcafc7e16 | ||
|
|
b191dfb1be | ||
|
|
192b0cb417 | ||
|
|
a2435cbe95 | ||
|
|
8925d478da | ||
|
|
926de0463c | ||
|
|
993b09b853 | ||
|
|
e6c8495158 | ||
|
|
e15953dc64 | ||
|
|
4ff529ace9 | ||
|
|
530622c72c | ||
|
|
7f38739ddc | ||
|
|
bc549d52f6 | ||
|
|
14f0de093e | ||
|
|
51457738d2 | ||
|
|
d8a0427791 | ||
|
|
8abf92d42c | ||
|
|
fc2d700a31 | ||
|
|
867029fa3a | ||
|
|
52e199b901 | ||
|
|
c5d537fb4d | ||
|
|
e081f1e058 | ||
|
|
7bedf93dd5 | ||
|
|
000929fd02 | ||
|
|
3e5ba2ba19 | ||
|
|
a25ce2034f | ||
|
|
1625135d0c | ||
|
|
add3589683 | ||
|
|
45a5d6ad61 | ||
|
|
7b62409bf1 | ||
|
|
45ad8a9810 | ||
|
|
a788c347de | ||
|
|
c3d4cb8d62 | ||
|
|
916a1447f0 | ||
|
|
b2ee091c03 | ||
|
|
cf169e9a4e | ||
|
|
ea0ff70595 | ||
|
|
ce37918745 | ||
|
|
260b615d2b | ||
|
|
45566001a7 | ||
|
|
12482b6624 | ||
|
|
2d7684a5c8 | ||
|
|
c1d6b67322 | ||
|
|
69c0d5f28a | ||
|
|
d541a4d506 | ||
|
|
4b46c72f5a | ||
|
|
e191fb82a3 | ||
|
|
32ebfa719d | ||
|
|
c94f549531 | ||
|
|
6d98afa319 | ||
| 20ccb37ccf | |||
|
|
79fa6be313 | ||
|
|
210e755fed | ||
| a6faf12ff6 | |||
|
|
e2148c3f31 | ||
|
|
869ca75464 | ||
|
|
ce48a7dcf0 | ||
|
|
866024ae95 | ||
| b40abb9bc0 | |||
|
|
37217e529e | ||
|
|
eedbbb5304 | ||
| 4b3b47b65f | |||
| c015459cf1 | |||
| 326ced4646 | |||
| 18970c22cf | |||
| 00db8a00c7 | |||
|
|
0756dd9560 | ||
| f3195bbcf8 | |||
| 4e4b5f90bb | |||
| 3150489384 | |||
| 9ff382e35b | |||
| bcd8f7af52 | |||
| 3595b89672 | |||
| 6e8842d30d | |||
| 7b5cd9b797 | |||
| 9ec241f2a3 | |||
| a876018b0a | |||
| ea397af123 | |||
| a475e5dc2f | |||
| 54258c3f13 | |||
| d383a08013 | |||
|
|
fed6289f67 | ||
|
|
9b301eba25 | ||
| d1edf6bdd8 | |||
|
|
7ca8694aed | ||
|
|
406c67bf3a | ||
|
|
d643df72bc | ||
| a3dacf1f59 | |||
| 37605f46b7 | |||
|
|
a4895df622 | ||
| fc63cbbf50 | |||
| b7ba717cb9 | |||
| 0c5ef0befe | |||
|
|
84d4423069 | ||
|
|
0721ff4f37 | ||
| a5b0ee757c | |||
|
|
82fd35305a | ||
| e89e82483b | |||
| 57bda40a82 | |||
|
|
93aa8e55e5 | ||
| 2e907bc1ed | |||
| 8d984951c3 | |||
|
|
dd6600a89e | ||
|
|
233469c7e4 | ||
| 357ece2ebe | |||
|
|
72e9633045 | ||
|
|
7a31ca52da | ||
|
|
33930f3e32 | ||
| 8e19ddcc99 | |||
|
|
1a58311e26 | ||
| 9360e5438c | |||
| fa8d8a7421 | |||
|
|
bebf0820ca | ||
| e1f4e8dde4 | |||
| 2b59b1b4fe | |||
| cee1476ab1 | |||
| 20a6bae5ff | |||
| b6732adfd2 | |||
| bec031688e | |||
| 7f11195ede | |||
| d4ff9c3072 | |||
| 0a442e7df9 | |||
| 52f1a78e36 | |||
|
|
6bcaf2ba9c | ||
|
|
3de972d12c |
@@ -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' //底层库-标准版
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,10 +52,10 @@ public class FURenderer extends IFURenderer {
|
||||
|
||||
|
||||
/* 特效FURenderKit*/
|
||||
private FURenderKit mFURenderKit;
|
||||
public FURenderKit mFURenderKit;
|
||||
|
||||
/* AI道具*/
|
||||
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_HUMAN = "model" + File.separator + "ai_human_processor.bundle";
|
||||
|
||||
/* GL 线程 ID */
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#FF8D41</color>
|
||||
<color name="colorPrimaryDark">#FF8D41</color>
|
||||
<color name="colorAccent">#FF8D41</color>
|
||||
<color name="colorPrimary">#2ED0FF</color>
|
||||
<color name="colorPrimaryDark">#2ED0FF</color>
|
||||
<color name="colorAccent">#2ED0FF</color>
|
||||
|
||||
<!--fulive demo 主配色-->
|
||||
<color name="primary_background">#050F14</color>
|
||||
|
||||
@@ -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>
|
||||
|
||||
36
IAP6Helper/build.gradle
Normal file
36
IAP6Helper/build.gradle
Normal 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'
|
||||
|
||||
}
|
||||
BIN
IAP6Helper/libs/samsung-iap-6.1.1.aar
Normal file
BIN
IAP6Helper/libs/samsung-iap-6.1.1.aar
Normal file
Binary file not shown.
25
IAP6Helper/proguard-rules.pro
vendored
Normal file
25
IAP6Helper/proguard-rules.pro
vendored
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.samsung.android.iap;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
interface IAPServiceCallback {
|
||||
oneway void responseCallback(in Bundle bundle);
|
||||
}
|
||||
134
IAP6Helper/src/main/java/com/samsung/utils/SamsungUtil.java
Normal file
134
IAP6Helper/src/main/java/com/samsung/utils/SamsungUtil.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
24
IAP6Helper/src/main/res/values-zh-rCN/strings.xml
Normal file
24
IAP6Helper/src/main/res/values-zh-rCN/strings.xml
Normal 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>
|
||||
24
IAP6Helper/src/main/res/values-zh-rHK/strings.xml
Normal file
24
IAP6Helper/src/main/res/values-zh-rHK/strings.xml
Normal 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>
|
||||
24
IAP6Helper/src/main/res/values-zh-rTW/strings.xml
Normal file
24
IAP6Helper/src/main/res/values-zh-rTW/strings.xml
Normal 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>
|
||||
6
IAP6Helper/src/main/res/values/strings.xml
Normal file
6
IAP6Helper/src/main/res/values/strings.xml
Normal 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
2
SVGAlibrary/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
*.iml
|
||||
37
SVGAlibrary/build.gradle
Normal file
37
SVGAlibrary/build.gradle
Normal 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
17
SVGAlibrary/proguard-rules.pro
vendored
Normal 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 *;
|
||||
#}
|
||||
8
SVGAlibrary/src/main/AndroidManifest.xml
Normal file
8
SVGAlibrary/src/main/AndroidManifest.xml
Normal 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>
|
||||
@@ -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)
|
||||
}
|
||||
119
SVGAlibrary/src/main/java/com/opensource/svgaplayer/SVGACache.kt
Normal file
119
SVGAlibrary/src/main/java/com/opensource/svgaplayer/SVGACache.kt
Normal 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")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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]!!
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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?)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
16
SVGAlibrary/src/main/res/values/attrs.xml
Normal file
16
SVGAlibrary/src/main/res/values/attrs.xml
Normal 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>
|
||||
3
SVGAlibrary/src/main/res/values/strings.xml
Normal file
3
SVGAlibrary/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">SVGAPlayer</string>
|
||||
</resources>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -26,10 +25,10 @@
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<provider
|
||||
<!-- <provider
|
||||
android:name="com.facebook.FacebookContentProvider"
|
||||
android:authorities="com.facebook.app.FacebookContentProvider2011402032399020"
|
||||
android:exported="true" />
|
||||
android:exported="true" />-->
|
||||
<receiver
|
||||
android:name="com.yunbao.share.receiver.TwitterResultReceiver"
|
||||
android:exported="false">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -34,14 +34,14 @@ public class ShareBuilder {
|
||||
"/index.php?g=Appapi&m=home&a=share&uid=%s&user_id=%s&isGoogle=%s",
|
||||
anchorId,
|
||||
shareUid,
|
||||
CommonAppConfig.IS_GOOGLE_PLAY ? "1" : "0"
|
||||
) ;
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
18
Share/src/main/res/values-zh/strings.xml
Normal file
18
Share/src/main/res/values-zh/strings.xml
Normal 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">快來 HOSO觀看%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">快來 HOSO觀看直播,認識更多有趣的朋友吧!</string>
|
||||
<string name="dialog_share_copy">複製</string>
|
||||
</resources>
|
||||
@@ -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 HOSO and meet more interesting people!</string>
|
||||
<string name="dialog_invite_title">Invite Friends</string>
|
||||
<string name="dialog_invite_info">Come to HOSO to discover more and better live streams.</string>
|
||||
<string name="dialog_share_copy">Copy</string>
|
||||
</resources>
|
||||
1
TabLayout/.gitignore
vendored
Normal file
1
TabLayout/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
37
TabLayout/build.gradle
Normal file
37
TabLayout/build.gradle
Normal 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"
|
||||
0
TabLayout/consumer-rules.pro
Normal file
0
TabLayout/consumer-rules.pro
Normal file
21
TabLayout/proguard-rules.pro
vendored
Normal file
21
TabLayout/proguard-rules.pro
vendored
Normal 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
|
||||
2
TabLayout/src/main/AndroidManifest.xml
Normal file
2
TabLayout/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
/>
|
||||
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal file
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal 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="基类方法">
|
||||
}
|
||||
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal file
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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()!!
|
||||
}
|
||||
}
|
||||
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal file
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal 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
|
||||
}
|
||||
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal file
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal file
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal 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
|
||||
)
|
||||
279
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
Normal file
279
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
153
TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
Normal file
153
TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
Normal 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
|
||||
}
|
||||
}
|
||||
118
TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
Normal file
118
TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
931
TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
Normal file
931
TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
Normal 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
|
||||
}
|
||||
2042
TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
Normal file
2042
TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
}
|
||||
334
TabLayout/src/main/java/com/angcyo/tablayout/LibEx.kt
Normal file
334
TabLayout/src/main/java/com/angcyo/tablayout/LibEx.kt
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
299
TabLayout/src/main/res/values/attr_dsl_tab_layout.xml
Normal file
299
TabLayout/src/main/res/values/attr_dsl_tab_layout.xml
Normal 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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
230
app/build.gradle
230
app/build.gradle
@@ -3,39 +3,11 @@ apply plugin: 'img-optimizer'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'com.alibaba.arouter'
|
||||
apply from: "../package_config.gradle"
|
||||
|
||||
android {
|
||||
dexOptions {
|
||||
jumboMode = true
|
||||
}
|
||||
project.tasks.getByName("tasks").doFirst {
|
||||
|
||||
}
|
||||
/* 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'
|
||||
]))
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||
namespace "myname.pdlive.shayu"
|
||||
compileSdk rootProject.ext.android.compileSdkVersion
|
||||
packagingOptions {
|
||||
pickFirst "lib/armeabi/libyuvutils.so"
|
||||
pickFirst "lib/arm64-v8a/libyuvutils.so"
|
||||
@@ -97,93 +69,149 @@ android {
|
||||
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'
|
||||
}
|
||||
//谷歌包干掉美颜
|
||||
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()) {
|
||||
delete project.buildDir
|
||||
println project.buildDir
|
||||
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_lite.bundle',
|
||||
'graphics/face_beautification.bundle'
|
||||
]))
|
||||
delete(fileTree(dir: outputDir, includes: ['model/ai_face_processor.bundle',
|
||||
'graphics/face_beautification.bundle']))
|
||||
} else {
|
||||
println "不删除bundle"
|
||||
}
|
||||
}
|
||||
}
|
||||
variant.outputs.all {
|
||||
def isGoogle = "link"
|
||||
if (rootProject.ext.manifestPlaceholders.isGooglePlay) {
|
||||
isGoogle = "Google"
|
||||
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 = "链接"
|
||||
}
|
||||
def isPlugin = "all"
|
||||
if (rootProject.ext.manifestPlaceholders.isPluginModel) {
|
||||
isPlugin = "plugin"
|
||||
if (variant.name.contains('online')) {
|
||||
server = '正式服'
|
||||
} else {
|
||||
server = '测试服'
|
||||
}
|
||||
def isTest = "测试服"
|
||||
if (rootProject.ext.manifestPlaceholders.serverHost == "https://napi.yaoulive.com") {
|
||||
isTest = "正式服"
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
outputFileName = "[${new Date().format("yyyy-MM-dd HHmmss", TimeZone.getTimeZone("GMT+8"))}]PDLive-${defaultConfig.versionName}-${isGoogle}-${isPlugin}-${variant.buildType.name}-${isTest}.apk"
|
||||
|
||||
}
|
||||
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 {
|
||||
@@ -223,15 +251,25 @@ 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 "arm64-v8a"
|
||||
//abiFilters "arm64-v8a"
|
||||
println("打包ndk其他")
|
||||
}
|
||||
}
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments = [AROUTER_MODULE_NAME: project.getName()]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@@ -266,8 +304,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')
|
||||
@@ -280,18 +318,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
85
app/proguard-rules.pro
vendored
85
app/proguard-rules.pro
vendored
@@ -190,13 +190,18 @@ rx.internal.util.atomic.LinkedQueueNode* consumerNode;
|
||||
-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{*;}
|
||||
|
||||
# If you use the byType method to obtain Service, add the following rules to protect the interface:
|
||||
# 如果使用了 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
|
||||
|
||||
@@ -235,4 +240,80 @@ rx.internal.util.atomic.LinkedQueueNode* consumerNode;
|
||||
}
|
||||
-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.**{*;}
|
||||
71
app/src/google_online/google-services.json
Normal file
71
app/src/google_online/google-services.json
Normal 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.yhosolive.oy"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "822566078854-8c7698l64j66ijng9bq799o5qvbguhdo.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.yhosolive.oy",
|
||||
"certificate_hash": "e059b937bfa49d58f40fddee4c7463e03e2aae47"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "822566078854-9cej31ie42tgjeimdk691gmvkavrooa7.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.yhosolive.oy",
|
||||
"certificate_hash": "15fc5e70cf238323bf7111c8c627803985478e87"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "822566078854-c63gcmvkn2ctfct9eebuo0r4tiolloel.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.yhosolive.oy",
|
||||
"certificate_hash": "b66dc8d21cfcf6c729577ddcf0c312b2a31ed872"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "822566078854-jfpovcealtjkv6sf0338to2grv4e5i6k.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.yhosolive.oy",
|
||||
"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"
|
||||
}
|
||||
71
app/src/google_test/google-services.json
Normal file
71
app/src/google_test/google-services.json
Normal 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"
|
||||
}
|
||||
@@ -13,6 +13,14 @@
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "292494634914-8nuhhoeo061ki1jevbcsrl7dfdl6dlm0.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.pdlive.shayu",
|
||||
"certificate_hash": "38cc19306c9facee36a9224e9a4070bc0be15c7d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "292494634914-ctr3fptp5mkv2qqr4gkgjo9uluq2joqb.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user