├── .gitignore ├── CHANGELOG.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── app ├── .gitignore ├── build.gradle ├── local.properties ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── ponycui_home │ │ └── svgaplayer │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── 750x80.svga │ ├── Castle.svga │ ├── EmptyState.svga │ ├── Goddess.svga │ ├── MerryChristmas.svga │ ├── Rocket.svga │ ├── alarm.svga │ ├── angel.svga │ ├── gradientBorder.svga │ ├── heartbeat.svga │ ├── jojo_audio.svga │ ├── matteBitmap.svga │ ├── matteBitmap_1.x.svga │ ├── matteRect.svga │ ├── mp3_to_long.svga │ ├── posche.svga │ ├── rose.svga │ └── rose_2.0.0.svga │ ├── java │ └── com │ │ └── example │ │ └── ponycui_home │ │ └── svgaplayer │ │ ├── AnimationFromAssetsActivity.java │ │ ├── AnimationFromClickActivity.java │ │ ├── AnimationFromLayoutActivity.java │ │ ├── AnimationFromNetworkActivity.java │ │ ├── AnimationWithDynamicImageActivity.java │ │ └── MainActivity.java │ └── res │ ├── layout │ ├── activity_main.xml │ └── activity_simple.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── network_security_config.xml ├── backer ├── alipay.jpg ├── donate.md ├── hire.md └── wechat.jpg ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── opensource │ │ └── svgaplayer │ │ └── ApplicationTest.kt │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── opensource │ │ └── svgaplayer │ │ ├── IClickAreaListener.kt │ │ ├── SVGACache.kt │ │ ├── SVGACallback.kt │ │ ├── SVGAClickAreaListener.kt │ │ ├── SVGADrawable.kt │ │ ├── SVGADynamicEntity.kt │ │ ├── SVGAImageView.kt │ │ ├── SVGAParser.kt │ │ ├── SVGAPlayer.kt │ │ ├── SVGASoundManager.kt │ │ ├── SVGAVideoEntity.kt │ │ ├── bitmap │ │ ├── BitmapSampleSizeCalculator.kt │ │ ├── SVGABitmapByteArrayDecoder.kt │ │ ├── SVGABitmapDecoder.kt │ │ └── SVGABitmapFileDecoder.kt │ │ ├── drawer │ │ ├── SGVADrawer.kt │ │ └── SVGACanvasDrawer.kt │ │ ├── entities │ │ ├── SVGAAudioEntity.kt │ │ ├── SVGAPathEntity.kt │ │ ├── SVGAVideoShapeEntity.kt │ │ ├── SVGAVideoSpriteEntity.kt │ │ └── SVGAVideoSpriteFrameEntity.kt │ │ ├── proto │ │ ├── AudioEntity.java │ │ ├── FrameEntity.java │ │ ├── Layout.java │ │ ├── MovieEntity.java │ │ ├── MovieParams.java │ │ ├── ShapeEntity.java │ │ ├── SpriteEntity.java │ │ └── Transform.java │ │ └── utils │ │ ├── Pools.kt │ │ ├── SVGAScaleInfo.kt │ │ ├── SVGAStructs.kt │ │ └── log │ │ ├── DefaultLogCat.kt │ │ ├── ILogger.kt │ │ ├── LogUtils.kt │ │ └── SVGALogger.kt │ └── res │ └── values │ ├── attrs.xml │ └── strings.xml ├── readme.md ├── readme.zh.md └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | .idea/ 4 | .DS_Store 5 | /build 6 | /captures 7 | *.iml 8 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Attention 2 | 3 | * Do not ask any usage question, issue board is a issue board, accept library bugs only. 4 | * If you are facing any usage problem, read the README again. 5 | 6 | ## Issue Template 7 | 8 | Issue Description(What's your problem) 9 | 10 | How To Reappear(How to reappear the issue) 11 | 12 | Any Attachment(Provide a sample about your issue) 13 | 14 | ------ 中文分割线 ------ 15 | 16 | ## 注意 17 | 18 | * 不要在 Issue 板块提问使用问题,Issue 板块只接受 Bug 反馈。 19 | * 如果遇到使用上的问题,仔细阅读 README。 20 | 21 | ## Issue 模板 22 | 23 | 请尽量使用英文提交 Issue 24 | 25 | 请确切回答:问题的描述、重现方式、附件(提供一个 Demo 以重现问题) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 YY Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 28 6 | defaultConfig { 7 | applicationId "com.example.ponycui_home.svgaplayer" 8 | minSdkVersion 14 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | packagingOptions { 20 | exclude 'META-INF/ASL2.0' 21 | exclude 'META-INF/LICENSE' 22 | exclude 'META-INF/NOTICE' 23 | exclude 'META-INF/NOTICE.txt' 24 | exclude 'META-INF/LICENSE.txt' 25 | exclude 'META-INF/MANIFEST.MF' 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(include: ['*.jar'], dir: 'libs') 35 | implementation 'com.android.support:appcompat-v7:28.0.0' 36 | implementation project(':library') 37 | implementation 'com.squareup.okhttp3:okhttp:4.10.0' 38 | } 39 | -------------------------------------------------------------------------------- /app/local.properties: -------------------------------------------------------------------------------- 1 | ## This file is automatically generated by Android Studio. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | # 7 | # Location of the SDK. This is only used by Gradle. 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | #Wed Jun 22 18:31:58 CST 2016 11 | sdk.dir=/usr/local/Cellar/android-sdk/24.4.1_1 12 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/PonyCui_Home/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/ponycui_home/svgaplayer/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.example.ponycui_home.svgaplayer; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/assets/750x80.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/750x80.svga -------------------------------------------------------------------------------- /app/src/main/assets/Castle.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/Castle.svga -------------------------------------------------------------------------------- /app/src/main/assets/EmptyState.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/EmptyState.svga -------------------------------------------------------------------------------- /app/src/main/assets/Goddess.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/Goddess.svga -------------------------------------------------------------------------------- /app/src/main/assets/MerryChristmas.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/MerryChristmas.svga -------------------------------------------------------------------------------- /app/src/main/assets/Rocket.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/Rocket.svga -------------------------------------------------------------------------------- /app/src/main/assets/alarm.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/alarm.svga -------------------------------------------------------------------------------- /app/src/main/assets/angel.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/angel.svga -------------------------------------------------------------------------------- /app/src/main/assets/gradientBorder.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/gradientBorder.svga -------------------------------------------------------------------------------- /app/src/main/assets/heartbeat.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/heartbeat.svga -------------------------------------------------------------------------------- /app/src/main/assets/jojo_audio.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/jojo_audio.svga -------------------------------------------------------------------------------- /app/src/main/assets/matteBitmap.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/matteBitmap.svga -------------------------------------------------------------------------------- /app/src/main/assets/matteBitmap_1.x.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/matteBitmap_1.x.svga -------------------------------------------------------------------------------- /app/src/main/assets/matteRect.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/matteRect.svga -------------------------------------------------------------------------------- /app/src/main/assets/mp3_to_long.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/mp3_to_long.svga -------------------------------------------------------------------------------- /app/src/main/assets/posche.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/posche.svga -------------------------------------------------------------------------------- /app/src/main/assets/rose.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/rose.svga -------------------------------------------------------------------------------- /app/src/main/assets/rose_2.0.0.svga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/assets/rose_2.0.0.svga -------------------------------------------------------------------------------- /app/src/main/java/com/example/ponycui_home/svgaplayer/AnimationFromAssetsActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ponycui_home.svgaplayer; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.util.Log; 8 | import android.view.View; 9 | 10 | import com.opensource.svgaplayer.SVGAImageView; 11 | import com.opensource.svgaplayer.SVGAParser; 12 | import com.opensource.svgaplayer.SVGASoundManager; 13 | import com.opensource.svgaplayer.SVGAVideoEntity; 14 | import com.opensource.svgaplayer.utils.log.SVGALogger; 15 | 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.io.File; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | public class AnimationFromAssetsActivity extends Activity { 23 | 24 | int currentIndex = 0; 25 | SVGAImageView animationView = null; 26 | 27 | @Override 28 | protected void onCreate(@Nullable Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | animationView = new SVGAImageView(this); 31 | animationView.setBackgroundColor(Color.BLACK); 32 | animationView.setOnClickListener(new View.OnClickListener() { 33 | @Override 34 | public void onClick(View view) { 35 | animationView.stepToFrame(currentIndex++, false); 36 | } 37 | }); 38 | SVGALogger.INSTANCE.setLogEnabled(true); 39 | SVGASoundManager.INSTANCE.init(); 40 | loadAnimation(); 41 | setContentView(animationView); 42 | } 43 | 44 | private void loadAnimation() { 45 | SVGAParser svgaParser = SVGAParser.Companion.shareParser(); 46 | // String name = this.randomSample(); 47 | //asset jojo_audio.svga cannot callback 48 | String name = "mp3_to_long.svga"; 49 | Log.d("SVGA", "## name " + name); 50 | svgaParser.setFrameSize(100, 100); 51 | svgaParser.decodeFromAssets(name, new SVGAParser.ParseCompletion() { 52 | @Override 53 | public void onComplete(@NotNull SVGAVideoEntity videoItem) { 54 | Log.e("zzzz", "onComplete: "); 55 | animationView.setVideoItem(videoItem); 56 | animationView.stepToFrame(0, true); 57 | } 58 | 59 | @Override 60 | public void onError() { 61 | Log.e("zzzz", "onComplete: "); 62 | } 63 | 64 | }, null); 65 | } 66 | 67 | private ArrayList samples = new ArrayList(); 68 | 69 | private String randomSample() { 70 | if (samples.size() == 0) { 71 | samples.add("750x80.svga"); 72 | samples.add("alarm.svga"); 73 | samples.add("angel.svga"); 74 | samples.add("Castle.svga"); 75 | samples.add("EmptyState.svga"); 76 | samples.add("Goddess.svga"); 77 | samples.add("gradientBorder.svga"); 78 | samples.add("heartbeat.svga"); 79 | samples.add("matteBitmap.svga"); 80 | samples.add("matteBitmap_1.x.svga"); 81 | samples.add("matteRect.svga"); 82 | samples.add("MerryChristmas.svga"); 83 | samples.add("posche.svga"); 84 | samples.add("Rocket.svga"); 85 | samples.add("rose.svga"); 86 | samples.add("rose_2.0.0.svga"); 87 | } 88 | return samples.get((int) Math.floor(Math.random() * samples.size())); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ponycui_home/svgaplayer/AnimationFromClickActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ponycui_home.svgaplayer; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.widget.Toast; 8 | 9 | import com.opensource.svgaplayer.SVGAClickAreaListener; 10 | import com.opensource.svgaplayer.SVGADrawable; 11 | import com.opensource.svgaplayer.SVGADynamicEntity; 12 | import com.opensource.svgaplayer.SVGAImageView; 13 | import com.opensource.svgaplayer.SVGAParser; 14 | import com.opensource.svgaplayer.SVGAVideoEntity; 15 | 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | 19 | /** 20 | * Created by miaojun on 2019/6/21. 21 | * mail:1290846731@qq.com 22 | */ 23 | public class AnimationFromClickActivity extends Activity { 24 | 25 | SVGAImageView animationView = null; 26 | 27 | @Override 28 | protected void onCreate(@Nullable Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | animationView = new SVGAImageView(this); 31 | animationView.setOnAnimKeyClickListener(new SVGAClickAreaListener() { 32 | @Override 33 | public void onClick(@NotNull String clickKey) { 34 | Toast.makeText(AnimationFromClickActivity.this,clickKey,Toast.LENGTH_SHORT).show(); 35 | } 36 | }); 37 | animationView.setBackgroundColor(Color.WHITE); 38 | loadAnimation(); 39 | setContentView(animationView); 40 | } 41 | 42 | private void loadAnimation() { 43 | SVGAParser.Companion.shareParser().decodeFromAssets("MerryChristmas.svga",new SVGAParser.ParseCompletion() { 44 | @Override 45 | public void onComplete(@NotNull SVGAVideoEntity videoItem) { 46 | SVGADynamicEntity dynamicEntity = new SVGADynamicEntity(); 47 | dynamicEntity.setClickArea("img_10"); 48 | SVGADrawable drawable = new SVGADrawable(videoItem, dynamicEntity); 49 | animationView.setImageDrawable(drawable); 50 | animationView.startAnimation(); 51 | } 52 | @Override 53 | public void onError() { 54 | 55 | } 56 | },null); 57 | } 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ponycui_home/svgaplayer/AnimationFromLayoutActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ponycui_home.svgaplayer; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | /** 7 | * Created by cuiminghui on 2017/3/30. 8 | * 将 svga 文件打包到 assets 文件夹中,然后使用 layout.xml 加载动画。 9 | */ 10 | 11 | public class AnimationFromLayoutActivity extends Activity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_simple); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ponycui_home/svgaplayer/AnimationFromNetworkActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ponycui_home.svgaplayer; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.util.Log; 8 | 9 | import com.opensource.svgaplayer.SVGAImageView; 10 | import com.opensource.svgaplayer.SVGAParser; 11 | import com.opensource.svgaplayer.SVGAVideoEntity; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.net.MalformedURLException; 16 | import java.net.URL; 17 | 18 | public class AnimationFromNetworkActivity extends Activity { 19 | 20 | SVGAImageView animationView = null; 21 | 22 | @Override 23 | protected void onCreate(@Nullable Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | animationView = new SVGAImageView(this); 26 | animationView.setBackgroundColor(Color.GRAY); 27 | setContentView(animationView); 28 | loadAnimation(); 29 | } 30 | 31 | private void loadAnimation() { 32 | try { // new URL needs try catch. 33 | SVGAParser svgaParser = SVGAParser.Companion.shareParser(); 34 | svgaParser.setFrameSize(100,100); 35 | svgaParser.decodeFromURL(new URL("https://github.com/yyued/SVGA-Samples/blob/master/posche.svga?raw=true"), new SVGAParser.ParseCompletion() { 36 | @Override 37 | public void onComplete(@NotNull SVGAVideoEntity videoItem) { 38 | Log.d("##","## FromNetworkActivity load onComplete"); 39 | animationView.setVideoItem(videoItem); 40 | animationView.startAnimation(); 41 | } 42 | @Override 43 | public void onError() { 44 | 45 | } 46 | 47 | 48 | },null); 49 | } catch (MalformedURLException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ponycui_home/svgaplayer/AnimationWithDynamicImageActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ponycui_home.svgaplayer; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | 8 | import com.opensource.svgaplayer.SVGADrawable; 9 | import com.opensource.svgaplayer.SVGADynamicEntity; 10 | import com.opensource.svgaplayer.SVGAImageView; 11 | import com.opensource.svgaplayer.SVGAParser; 12 | import com.opensource.svgaplayer.SVGAVideoEntity; 13 | 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.io.File; 17 | import java.net.MalformedURLException; 18 | import java.net.URL; 19 | 20 | public class AnimationWithDynamicImageActivity extends Activity { 21 | 22 | SVGAImageView animationView = null; 23 | 24 | @Override 25 | protected void onCreate(@Nullable Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | animationView = new SVGAImageView(this); 28 | animationView.setBackgroundColor(Color.GRAY); 29 | loadAnimation(); 30 | setContentView(animationView); 31 | } 32 | 33 | private void loadAnimation() { 34 | try { // new URL needs try catch. 35 | SVGAParser parser = new SVGAParser(this); 36 | parser.decodeFromURL(new URL("https://github.com/yyued/SVGA-Samples/blob/master/kingset.svga?raw=true"), new SVGAParser.ParseCompletion() { 37 | @Override 38 | public void onComplete(@NotNull SVGAVideoEntity videoItem) { 39 | SVGADynamicEntity dynamicEntity = new SVGADynamicEntity(); 40 | dynamicEntity.setDynamicImage("https://github.com/PonyCui/resources/blob/master/svga_replace_avatar.png?raw=true", "99"); // Here is the KEY implementation. 41 | SVGADrawable drawable = new SVGADrawable(videoItem, dynamicEntity); 42 | animationView.setImageDrawable(drawable); 43 | animationView.startAnimation(); 44 | } 45 | 46 | @Override 47 | public void onError() { 48 | 49 | } 50 | }, null); 51 | } catch (MalformedURLException e) { 52 | e.printStackTrace(); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ponycui_home/svgaplayer/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ponycui_home.svgaplayer; 2 | 3 | import android.content.Intent; 4 | import android.database.DataSetObserver; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.Gravity; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.LinearLayout; 13 | import android.widget.ListAdapter; 14 | import android.widget.ListView; 15 | import android.widget.TextView; 16 | 17 | import com.opensource.svgaplayer.SVGAParser; 18 | import com.opensource.svgaplayer.utils.log.SVGALogger; 19 | 20 | import java.util.ArrayList; 21 | 22 | class SampleItem { 23 | 24 | String title; 25 | Intent intent; 26 | 27 | public SampleItem(String title, Intent intent) { 28 | this.title = title; 29 | this.intent = intent; 30 | } 31 | 32 | } 33 | 34 | public class MainActivity extends AppCompatActivity { 35 | 36 | ListView listView; 37 | ArrayList items = new ArrayList(); 38 | 39 | @Override 40 | protected void onCreate(@Nullable Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | this.setupData(); 43 | this.setupListView(); 44 | this.setupSVGAParser(); 45 | this.setupLogger(); 46 | setContentView(listView); 47 | } 48 | 49 | void setupData() { 50 | this.items.add(new SampleItem("Animation From Assets", new Intent(this, AnimationFromAssetsActivity.class))); 51 | this.items.add(new SampleItem("Animation From Network", new Intent(this, AnimationFromNetworkActivity.class))); 52 | this.items.add(new SampleItem("Animation From Layout XML", new Intent(this, AnimationFromLayoutActivity.class))); 53 | this.items.add(new SampleItem("Animation With Dynamic Image", new Intent(this, AnimationWithDynamicImageActivity.class))); 54 | this.items.add(new SampleItem("Animation With Dynamic Click", new Intent(this, AnimationFromClickActivity.class))); 55 | } 56 | 57 | void setupListView() { 58 | this.listView = new ListView(this); 59 | this.listView.setAdapter(new ListAdapter() { 60 | @Override 61 | public boolean areAllItemsEnabled() { 62 | return false; 63 | } 64 | 65 | @Override 66 | public boolean isEnabled(int i) { 67 | return false; 68 | } 69 | 70 | @Override 71 | public void registerDataSetObserver(DataSetObserver dataSetObserver) { 72 | 73 | } 74 | 75 | @Override 76 | public void unregisterDataSetObserver(DataSetObserver dataSetObserver) { 77 | 78 | } 79 | 80 | @Override 81 | public int getCount() { 82 | return MainActivity.this.items.size(); 83 | } 84 | 85 | @Override 86 | public Object getItem(int i) { 87 | return null; 88 | } 89 | 90 | @Override 91 | public long getItemId(int i) { 92 | return i; 93 | } 94 | 95 | @Override 96 | public boolean hasStableIds() { 97 | return false; 98 | } 99 | 100 | @Override 101 | public View getView(final int i, View view, ViewGroup viewGroup) { 102 | LinearLayout linearLayout = new LinearLayout(MainActivity.this); 103 | TextView textView = new TextView(MainActivity.this); 104 | textView.setOnClickListener(new View.OnClickListener() { 105 | @Override 106 | public void onClick(View view) { 107 | MainActivity.this.startActivity(MainActivity.this.items.get(i).intent); 108 | } 109 | }); 110 | textView.setText(MainActivity.this.items.get(i).title); 111 | textView.setTextSize(24); 112 | textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL); 113 | linearLayout.addView(textView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) (55 * getResources().getDisplayMetrics().density))); 114 | return linearLayout; 115 | } 116 | 117 | @Override 118 | public int getItemViewType(int i) { 119 | return 1; 120 | } 121 | 122 | @Override 123 | public int getViewTypeCount() { 124 | return 1; 125 | } 126 | 127 | @Override 128 | public boolean isEmpty() { 129 | return false; 130 | } 131 | }); 132 | this.listView.setBackgroundColor(Color.WHITE); 133 | } 134 | 135 | void setupSVGAParser() { 136 | SVGAParser.Companion.shareParser().init(this); 137 | } 138 | 139 | private void setupLogger() { 140 | SVGALogger.INSTANCE.setLogEnabled(true); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_simple.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SVGAPlayer 3 | 4 | Hello world! 5 | Settings 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /backer/alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/backer/alipay.jpg -------------------------------------------------------------------------------- /backer/donate.md: -------------------------------------------------------------------------------- 1 | # Donate 2 | 3 | One-time donation via AliPay or WeChat. 4 | 5 | ![](./wechat.jpg) 6 | 7 | ![](./alipay.jpg) 8 | 9 | We also provide a way to thank your help, please contact `cuis@vip.qq.com`. -------------------------------------------------------------------------------- /backer/hire.md: -------------------------------------------------------------------------------- 1 | # Counselor Service 2 | 3 | The author PonyCui could help you resolve kinds of problem, including SVGA, cross-platform application development issue. 4 | 5 | If you have any usage question, please contact `cuis@vip.qq.com` for further resolution. 6 | 7 | --- 8 | 9 | PonyCui 可以帮助你解决包括 SVGA、跨平台应用开发等问题,如果遇到任何使用上的问题,可以联系 `cuis@vip.qq.com` 获得更好的方案。 -------------------------------------------------------------------------------- /backer/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/backer/wechat.jpg -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:4.0.0' 8 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20" 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svga/SVGAPlayer-Android/662d0dc11824e35a3f3fcd247f491efce2bf5ec5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jan 21 16:42:42 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | yes | $ANDROID_HOME/tools/bin/sdkmanager "build-tools;28.0.3" 3 | 4 | ############################################################################## 5 | ## 6 | ## Gradle start up script for UN*X 7 | ## 8 | ############################################################################## 9 | 10 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 11 | DEFAULT_JVM_OPTS="" 12 | 13 | APP_NAME="Gradle" 14 | APP_BASE_NAME=`basename "$0"` 15 | 16 | # Use the maximum available, or set MAX_FD != -1 to use that value. 17 | MAX_FD="maximum" 18 | 19 | warn ( ) { 20 | echo "$*" 21 | } 22 | 23 | die ( ) { 24 | echo 25 | echo "$*" 26 | echo 27 | exit 1 28 | } 29 | 30 | # OS specific support (must be 'true' or 'false'). 31 | cygwin=false 32 | msys=false 33 | darwin=false 34 | case "`uname`" in 35 | CYGWIN* ) 36 | cygwin=true 37 | ;; 38 | Darwin* ) 39 | darwin=true 40 | ;; 41 | MINGW* ) 42 | msys=true 43 | ;; 44 | esac 45 | 46 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 47 | if $cygwin ; then 48 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 49 | fi 50 | 51 | # Attempt to set APP_HOME 52 | # Resolve links: $0 may be a link 53 | PRG="$0" 54 | # Need this for relative symlinks. 55 | while [ -h "$PRG" ] ; do 56 | ls=`ls -ld "$PRG"` 57 | link=`expr "$ls" : '.*-> \(.*\)$'` 58 | if expr "$link" : '/.*' > /dev/null; then 59 | PRG="$link" 60 | else 61 | PRG=`dirname "$PRG"`"/$link" 62 | fi 63 | done 64 | SAVED="`pwd`" 65 | cd "`dirname \"$PRG\"`/" >&- 66 | APP_HOME="`pwd -P`" 67 | cd "$SAVED" >&- 68 | 69 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 70 | 71 | # Determine the Java command to use to start the JVM. 72 | if [ -n "$JAVA_HOME" ] ; then 73 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 74 | # IBM's JDK on AIX uses strange locations for the executables 75 | JAVACMD="$JAVA_HOME/jre/sh/java" 76 | else 77 | JAVACMD="$JAVA_HOME/bin/java" 78 | fi 79 | if [ ! -x "$JAVACMD" ] ; then 80 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 81 | 82 | Please set the JAVA_HOME variable in your environment to match the 83 | location of your Java installation." 84 | fi 85 | else 86 | JAVACMD="java" 87 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 88 | 89 | Please set the JAVA_HOME variable in your environment to match the 90 | location of your Java installation." 91 | fi 92 | 93 | # Increase the maximum file descriptors if we can. 94 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 95 | MAX_FD_LIMIT=`ulimit -H -n` 96 | if [ $? -eq 0 ] ; then 97 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 98 | MAX_FD="$MAX_FD_LIMIT" 99 | fi 100 | ulimit -n $MAX_FD 101 | if [ $? -ne 0 ] ; then 102 | warn "Could not set maximum file descriptor limit: $MAX_FD" 103 | fi 104 | else 105 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 106 | fi 107 | fi 108 | 109 | # For Darwin, add options to specify how the application appears in the dock 110 | if $darwin; then 111 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 112 | fi 113 | 114 | # For Cygwin, switch paths to Windows format before running java 115 | if $cygwin ; then 116 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 117 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 118 | 119 | # We build the pattern for arguments to be converted via cygpath 120 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 121 | SEP="" 122 | for dir in $ROOTDIRSRAW ; do 123 | ROOTDIRS="$ROOTDIRS$SEP$dir" 124 | SEP="|" 125 | done 126 | OURCYGPATTERN="(^($ROOTDIRS))" 127 | # Add a user-defined pattern to the cygpath arguments 128 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 129 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 130 | fi 131 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 132 | i=0 133 | for arg in "$@" ; do 134 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 135 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 136 | 137 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 138 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 139 | else 140 | eval `echo args$i`="\"$arg\"" 141 | fi 142 | i=$((i+1)) 143 | done 144 | case $i in 145 | (0) set -- ;; 146 | (1) set -- "$args0" ;; 147 | (2) set -- "$args0" "$args1" ;; 148 | (3) set -- "$args0" "$args1" "$args2" ;; 149 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 150 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 151 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 152 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 153 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 154 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 155 | esac 156 | fi 157 | 158 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 159 | function splitJvmOpts() { 160 | JVM_OPTS=("$@") 161 | } 162 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 163 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 164 | 165 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 166 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | 4 | 5 | android { 6 | compileSdkVersion 28 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 28 10 | } 11 | compileOptions { 12 | kotlinOptions.freeCompilerArgs += ['-module-name', "com.opensource.svgaplayer"] 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | packagingOptions { 25 | exclude 'META-INF/ASL2.0' 26 | exclude 'META-INF/LICENSE' 27 | exclude 'META-INF/NOTICE' 28 | exclude 'META-INF/NOTICE.txt' 29 | exclude 'META-INF/LICENSE.txt' 30 | exclude 'META-INF/MANIFEST.MF' 31 | } 32 | } 33 | 34 | 35 | dependencies { 36 | implementation 'com.squareup.wire:wire-runtime:4.4.1' 37 | } 38 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/PonyCui_Home/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/opensource/svgaplayer/ApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | import android.app.Application 4 | import android.test.ApplicationTestCase 5 | 6 | /** 7 | * [Testing Fundamentals](http://d.android.com/tools/testing/testing_android.html) 8 | */ 9 | class ApplicationTest : ApplicationTestCase(Application::class.java) -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/IClickAreaListener.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | /** 4 | * Created by miaojun on 2019/6/21. 5 | * mail:1290846731@qq.com 6 | */ 7 | interface IClickAreaListener{ 8 | fun onResponseArea(key : String,x0 : Int, y0 : Int, x1 : Int, y1 : Int) 9 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/SVGACache.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | import android.content.Context 4 | import com.opensource.svgaplayer.utils.log.LogUtils 5 | import java.io.File 6 | import java.net.URL 7 | import java.security.MessageDigest 8 | 9 | /** 10 | * SVGA 缓存管理 11 | */ 12 | object SVGACache { 13 | enum class Type { 14 | DEFAULT, 15 | FILE 16 | } 17 | 18 | private const val TAG = "SVGACache" 19 | private var type: Type = Type.DEFAULT 20 | private var cacheDir: String = "/" 21 | get() { 22 | if (field != "/") { 23 | val dir = File(field) 24 | if (!dir.exists()) { 25 | dir.mkdirs() 26 | } 27 | } 28 | return field 29 | } 30 | 31 | 32 | fun onCreate(context: Context?) { 33 | onCreate(context, Type.DEFAULT) 34 | } 35 | 36 | fun onCreate(context: Context?, type: Type) { 37 | if (isInitialized()) return 38 | context ?: return 39 | cacheDir = "${context.cacheDir.absolutePath}/svga/" 40 | File(cacheDir).takeIf { !it.exists() }?.mkdirs() 41 | this.type = type 42 | } 43 | 44 | /** 45 | * 清理缓存 46 | */ 47 | fun clearCache() { 48 | if (!isInitialized()) { 49 | LogUtils.error(TAG, "SVGACache is not init!") 50 | return 51 | } 52 | SVGAParser.threadPoolExecutor.execute { 53 | clearDir(cacheDir) 54 | LogUtils.info(TAG, "Clear svga cache done!") 55 | } 56 | } 57 | 58 | // 清除目录下的所有文件 59 | internal fun clearDir(path: String) { 60 | try { 61 | val dir = File(path) 62 | dir.takeIf { it.exists() }?.let { parentDir -> 63 | parentDir.listFiles()?.forEach { file -> 64 | if (!file.exists()) { 65 | return@forEach 66 | } 67 | if (file.isDirectory) { 68 | clearDir(file.absolutePath) 69 | } 70 | file.delete() 71 | } 72 | } 73 | } catch (e: Exception) { 74 | LogUtils.error(TAG, "Clear svga cache path: $path fail", e) 75 | } 76 | } 77 | 78 | fun isInitialized(): Boolean { 79 | return "/" != cacheDir && File(cacheDir).exists() 80 | } 81 | 82 | fun isDefaultCache(): Boolean = type == Type.DEFAULT 83 | 84 | fun isCached(cacheKey: String): Boolean { 85 | return if (isDefaultCache()) { 86 | buildCacheDir(cacheKey) 87 | } else { 88 | buildSvgaFile( 89 | cacheKey 90 | ) 91 | }.exists() 92 | } 93 | 94 | fun buildCacheKey(str: String): String { 95 | val messageDigest = MessageDigest.getInstance("MD5") 96 | messageDigest.update(str.toByteArray(charset("UTF-8"))) 97 | val digest = messageDigest.digest() 98 | var sb = "" 99 | for (b in digest) { 100 | sb += String.format("%02x", b) 101 | } 102 | return sb 103 | } 104 | 105 | fun buildCacheKey(url: URL): String = buildCacheKey(url.toString()) 106 | 107 | fun buildCacheDir(cacheKey: String): File { 108 | return File("$cacheDir$cacheKey/") 109 | } 110 | 111 | fun buildSvgaFile(cacheKey: String): File { 112 | return File("$cacheDir$cacheKey.svga") 113 | } 114 | 115 | fun buildAudioFile(audio: String): File { 116 | return File("$cacheDir$audio.mp3") 117 | } 118 | 119 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/SVGACallback.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | /** 4 | * Created by cuiminghui on 2017/3/30. 5 | */ 6 | interface SVGACallback { 7 | 8 | fun onPause() 9 | fun onFinished() 10 | fun onRepeat() 11 | fun onStep(frame: Int, percentage: Double) 12 | 13 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/SVGAClickAreaListener.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | /** 4 | * Created by miaojun on 2019/6/21. 5 | * mail:1290846731@qq.com 6 | */ 7 | interface SVGAClickAreaListener{ 8 | fun onClick(clickKey : String) 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/SVGADrawable.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.ColorFilter 5 | import android.graphics.PixelFormat 6 | import android.graphics.drawable.Drawable 7 | import android.widget.ImageView 8 | import com.opensource.svgaplayer.drawer.SVGACanvasDrawer 9 | 10 | class SVGADrawable(val videoItem: SVGAVideoEntity, val dynamicItem: SVGADynamicEntity): Drawable() { 11 | 12 | constructor(videoItem: SVGAVideoEntity): this(videoItem, SVGADynamicEntity()) 13 | 14 | var cleared = true 15 | internal set (value) { 16 | if (field == value) { 17 | return 18 | } 19 | field = value 20 | invalidateSelf() 21 | } 22 | 23 | var currentFrame = 0 24 | internal set (value) { 25 | if (field == value) { 26 | return 27 | } 28 | field = value 29 | invalidateSelf() 30 | } 31 | 32 | var scaleType: ImageView.ScaleType = ImageView.ScaleType.MATRIX 33 | 34 | private val drawer = SVGACanvasDrawer(videoItem, dynamicItem) 35 | 36 | override fun draw(canvas: Canvas?) { 37 | if (cleared) { 38 | return 39 | } 40 | canvas?.let { 41 | drawer.drawFrame(it,currentFrame, scaleType) 42 | } 43 | } 44 | 45 | override fun setAlpha(alpha: Int) { 46 | 47 | } 48 | 49 | override fun getOpacity(): Int { 50 | return PixelFormat.TRANSPARENT 51 | } 52 | 53 | override fun setColorFilter(colorFilter: ColorFilter?) { 54 | 55 | } 56 | 57 | fun resume() { 58 | videoItem.audioList.forEach { audio -> 59 | audio.playID?.let { 60 | if (SVGASoundManager.isInit()){ 61 | SVGASoundManager.resume(it) 62 | }else{ 63 | videoItem.soundPool?.resume(it) 64 | } 65 | } 66 | } 67 | } 68 | 69 | fun pause() { 70 | videoItem.audioList.forEach { audio -> 71 | audio.playID?.let { 72 | if (SVGASoundManager.isInit()){ 73 | SVGASoundManager.pause(it) 74 | }else{ 75 | videoItem.soundPool?.pause(it) 76 | } 77 | } 78 | } 79 | } 80 | 81 | fun stop() { 82 | videoItem.audioList.forEach { audio -> 83 | audio.playID?.let { 84 | if (SVGASoundManager.isInit()){ 85 | SVGASoundManager.stop(it) 86 | }else{ 87 | videoItem.soundPool?.stop(it) 88 | } 89 | } 90 | } 91 | } 92 | 93 | fun clear() { 94 | videoItem.audioList.forEach { audio -> 95 | audio.playID?.let { 96 | if (SVGASoundManager.isInit()){ 97 | SVGASoundManager.stop(it) 98 | }else{ 99 | videoItem.soundPool?.stop(it) 100 | } 101 | } 102 | audio.playID = null 103 | } 104 | videoItem.clear() 105 | } 106 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/SVGADynamicEntity.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.BitmapFactory 5 | import android.graphics.Canvas 6 | import android.text.BoringLayout 7 | import android.text.StaticLayout 8 | import android.text.TextPaint 9 | import java.net.HttpURLConnection 10 | import java.net.URL 11 | 12 | /** 13 | * Created by cuiminghui on 2017/3/30. 14 | */ 15 | class SVGADynamicEntity { 16 | 17 | internal var dynamicHidden: HashMap = hashMapOf() 18 | 19 | internal var dynamicImage: HashMap = hashMapOf() 20 | 21 | internal var dynamicText: HashMap = hashMapOf() 22 | 23 | internal var dynamicTextPaint: HashMap = hashMapOf() 24 | 25 | internal var dynamicStaticLayoutText: HashMap = hashMapOf() 26 | 27 | internal var dynamicBoringLayoutText: HashMap = hashMapOf() 28 | 29 | internal var dynamicDrawer: HashMap Boolean> = hashMapOf() 30 | 31 | //点击事件回调map 32 | internal var mClickMap : HashMap = hashMapOf() 33 | internal var dynamicIClickArea: HashMap = hashMapOf() 34 | 35 | internal var dynamicDrawerSized: HashMap Boolean> = hashMapOf() 36 | 37 | 38 | internal var isTextDirty = false 39 | 40 | fun setHidden(value: Boolean, forKey: String) { 41 | this.dynamicHidden.put(forKey, value) 42 | } 43 | 44 | fun setDynamicImage(bitmap: Bitmap, forKey: String) { 45 | this.dynamicImage.put(forKey, bitmap) 46 | } 47 | 48 | fun setDynamicImage(url: String, forKey: String) { 49 | val handler = android.os.Handler() 50 | SVGAParser.threadPoolExecutor.execute { 51 | (URL(url).openConnection() as? HttpURLConnection)?.let { 52 | try { 53 | it.connectTimeout = 20 * 1000 54 | it.requestMethod = "GET" 55 | it.connect() 56 | it.inputStream.use { stream -> 57 | BitmapFactory.decodeStream(stream)?.let { 58 | handler.post { setDynamicImage(it, forKey) } 59 | } 60 | } 61 | } catch (e: Exception) { 62 | e.printStackTrace() 63 | } finally { 64 | try { 65 | it.disconnect() 66 | } catch (disconnectException: Throwable) { 67 | // ignored here 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | fun setDynamicText(text: String, textPaint: TextPaint, forKey: String) { 75 | this.isTextDirty = true 76 | this.dynamicText.put(forKey, text) 77 | this.dynamicTextPaint.put(forKey, textPaint) 78 | } 79 | 80 | fun setDynamicText(layoutText: StaticLayout, forKey: String) { 81 | this.isTextDirty = true 82 | this.dynamicStaticLayoutText.put(forKey, layoutText) 83 | } 84 | 85 | fun setDynamicText(layoutText: BoringLayout, forKey: String) { 86 | this.isTextDirty = true 87 | BoringLayout.isBoring(layoutText.text,layoutText.paint)?.let { 88 | this.dynamicBoringLayoutText.put(forKey,layoutText) 89 | } 90 | } 91 | 92 | fun setDynamicDrawer(drawer: (canvas: Canvas, frameIndex: Int) -> Boolean, forKey: String) { 93 | this.dynamicDrawer.put(forKey, drawer) 94 | } 95 | 96 | fun setClickArea(clickKey: List) { 97 | for(itemKey in clickKey){ 98 | dynamicIClickArea.put(itemKey,object : IClickAreaListener { 99 | override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) { 100 | mClickMap.let { 101 | if(it.get(key) == null){ 102 | it.put(key, intArrayOf(x0,y0,x1,y1)) 103 | }else{ 104 | it.get(key)?.let { 105 | it[0] = x0 106 | it[1] = y0 107 | it[2] = x1 108 | it[3] = y1 109 | } 110 | } 111 | } 112 | } 113 | }) 114 | } 115 | } 116 | 117 | fun setClickArea(clickKey: String) { 118 | dynamicIClickArea.put(clickKey, object : IClickAreaListener { 119 | override fun onResponseArea(key: String, x0: Int, y0: Int, x1: Int, y1: Int) { 120 | mClickMap.let { 121 | if (it.get(key) == null) { 122 | it.put(key, intArrayOf(x0, y0, x1, y1)) 123 | } else { 124 | it.get(key)?.let { 125 | it[0] = x0 126 | it[1] = y0 127 | it[2] = x1 128 | it[3] = y1 129 | } 130 | } 131 | } 132 | } 133 | }) 134 | } 135 | 136 | fun setDynamicDrawerSized(drawer: (canvas: Canvas, frameIndex: Int, width: Int, height: Int) -> Boolean, forKey: String) { 137 | this.dynamicDrawerSized.put(forKey, drawer) 138 | } 139 | 140 | fun clearDynamicObjects() { 141 | this.isTextDirty = true 142 | this.dynamicHidden.clear() 143 | this.dynamicImage.clear() 144 | this.dynamicText.clear() 145 | this.dynamicTextPaint.clear() 146 | this.dynamicStaticLayoutText.clear() 147 | this.dynamicBoringLayoutText.clear() 148 | this.dynamicDrawer.clear() 149 | this.dynamicIClickArea.clear() 150 | this.mClickMap.clear() 151 | this.dynamicDrawerSized.clear() 152 | } 153 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/SVGAImageView.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | import android.animation.Animator 4 | import android.animation.ValueAnimator 5 | import android.annotation.SuppressLint 6 | import android.content.Context 7 | import android.os.Build 8 | import android.util.AttributeSet 9 | import android.view.MotionEvent 10 | import android.view.View 11 | import android.view.animation.LinearInterpolator 12 | import android.widget.ImageView 13 | import com.opensource.svgaplayer.utils.SVGARange 14 | import com.opensource.svgaplayer.utils.log.LogUtils 15 | import java.lang.ref.WeakReference 16 | import java.net.URL 17 | 18 | /** 19 | * Created by PonyCui on 2017/3/29. 20 | */ 21 | open class SVGAImageView @JvmOverloads constructor( 22 | context: Context, 23 | attrs: AttributeSet? = null, 24 | defStyleAttr: Int = 0 25 | ) : ImageView(context, attrs, defStyleAttr) { 26 | 27 | private val TAG = "SVGAImageView" 28 | 29 | enum class FillMode { 30 | Backward, 31 | Forward, 32 | Clear, 33 | } 34 | 35 | var isAnimating = false 36 | private set 37 | 38 | var loops = 0 39 | 40 | @Deprecated( 41 | "It is recommended to use clearAfterDetached, or manually call to SVGAVideoEntity#clear." + 42 | "If you just consider cleaning up the canvas after playing, you can use FillMode#Clear.", 43 | level = DeprecationLevel.WARNING 44 | ) 45 | var clearsAfterStop = false 46 | var clearsAfterDetached = false 47 | var fillMode: FillMode = FillMode.Forward 48 | var callback: SVGACallback? = null 49 | 50 | private var mAnimator: ValueAnimator? = null 51 | private var mItemClickAreaListener: SVGAClickAreaListener? = null 52 | private var mAntiAlias = true 53 | private var mAutoPlay = true 54 | private val mAnimatorListener = AnimatorListener(this) 55 | private val mAnimatorUpdateListener = AnimatorUpdateListener(this) 56 | private var mStartFrame = 0 57 | private var mEndFrame = 0 58 | 59 | init { 60 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { 61 | this.setLayerType(View.LAYER_TYPE_SOFTWARE, null) 62 | } 63 | attrs?.let { loadAttrs(it) } 64 | } 65 | 66 | private fun loadAttrs(attrs: AttributeSet) { 67 | val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.SVGAImageView, 0, 0) 68 | loops = typedArray.getInt(R.styleable.SVGAImageView_loopCount, 0) 69 | clearsAfterStop = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterStop, false) 70 | clearsAfterDetached = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterDetached, false) 71 | mAntiAlias = typedArray.getBoolean(R.styleable.SVGAImageView_antiAlias, true) 72 | mAutoPlay = typedArray.getBoolean(R.styleable.SVGAImageView_autoPlay, true) 73 | typedArray.getString(R.styleable.SVGAImageView_fillMode)?.let { 74 | when (it) { 75 | "0" -> { 76 | fillMode = FillMode.Backward 77 | } 78 | "1" -> { 79 | fillMode = FillMode.Forward 80 | } 81 | "2" -> { 82 | fillMode = FillMode.Clear 83 | } 84 | } 85 | } 86 | typedArray.getString(R.styleable.SVGAImageView_source)?.let { 87 | parserSource(it) 88 | } 89 | typedArray.recycle() 90 | } 91 | 92 | private fun parserSource(source: String) { 93 | val refImgView = WeakReference(this) 94 | val parser = SVGAParser(context) 95 | if (source.startsWith("http://") || source.startsWith("https://")) { 96 | parser.decodeFromURL(URL(source), createParseCompletion(refImgView)) 97 | } else { 98 | parser.decodeFromAssets(source, createParseCompletion(refImgView)) 99 | } 100 | } 101 | 102 | private fun createParseCompletion(ref: WeakReference): SVGAParser.ParseCompletion { 103 | return object : SVGAParser.ParseCompletion { 104 | override fun onComplete(videoItem: SVGAVideoEntity) { 105 | ref.get()?.startAnimation(videoItem) 106 | } 107 | 108 | override fun onError() {} 109 | } 110 | } 111 | 112 | private fun startAnimation(videoItem: SVGAVideoEntity) { 113 | this@SVGAImageView.post { 114 | videoItem.antiAlias = mAntiAlias 115 | setVideoItem(videoItem) 116 | getSVGADrawable()?.scaleType = scaleType 117 | if (mAutoPlay) { 118 | startAnimation() 119 | } 120 | } 121 | } 122 | 123 | fun startAnimation() { 124 | startAnimation(null, false) 125 | } 126 | 127 | fun startAnimation(range: SVGARange?, reverse: Boolean = false) { 128 | stopAnimation(false) 129 | play(range, reverse) 130 | } 131 | 132 | private fun play(range: SVGARange?, reverse: Boolean) { 133 | LogUtils.info(TAG, "================ start animation ================") 134 | val drawable = getSVGADrawable() ?: return 135 | setupDrawable() 136 | mStartFrame = Math.max(0, range?.location ?: 0) 137 | val videoItem = drawable.videoItem 138 | mEndFrame = Math.min(videoItem.frames - 1, ((range?.location ?: 0) + (range?.length ?: Int.MAX_VALUE) - 1)) 139 | val animator = ValueAnimator.ofInt(mStartFrame, mEndFrame) 140 | animator.interpolator = LinearInterpolator() 141 | animator.duration = ((mEndFrame - mStartFrame + 1) * (1000 / videoItem.FPS) / generateScale()).toLong() 142 | animator.repeatCount = if (loops <= 0) 99999 else loops - 1 143 | animator.addUpdateListener(mAnimatorUpdateListener) 144 | animator.addListener(mAnimatorListener) 145 | if (reverse) { 146 | animator.reverse() 147 | } else { 148 | animator.start() 149 | } 150 | mAnimator = animator 151 | } 152 | 153 | private fun setupDrawable() { 154 | val drawable = getSVGADrawable() ?: return 155 | drawable.cleared = false 156 | drawable.scaleType = scaleType 157 | } 158 | 159 | private fun getSVGADrawable(): SVGADrawable? { 160 | return drawable as? SVGADrawable 161 | } 162 | 163 | @Suppress("UNNECESSARY_SAFE_CALL") 164 | private fun generateScale(): Double { 165 | var scale = 1.0 166 | try { 167 | val animatorClass = Class.forName("android.animation.ValueAnimator") ?: return scale 168 | val getMethod = animatorClass.getDeclaredMethod("getDurationScale") ?: return scale 169 | scale = (getMethod.invoke(animatorClass) as Float).toDouble() 170 | if (scale == 0.0) { 171 | val setMethod = animatorClass.getDeclaredMethod("setDurationScale",Float::class.java) ?: return scale 172 | setMethod.isAccessible = true 173 | setMethod.invoke(animatorClass,1.0f) 174 | scale = 1.0 175 | LogUtils.info(TAG, 176 | "The animation duration scale has been reset to" + 177 | " 1.0x, because you closed it on developer options.") 178 | } 179 | } catch (ignore: Exception) { 180 | ignore.printStackTrace() 181 | } 182 | return scale 183 | } 184 | 185 | private fun onAnimatorUpdate(animator: ValueAnimator?) { 186 | val drawable = getSVGADrawable() ?: return 187 | drawable.currentFrame = animator?.animatedValue as Int 188 | val percentage = (drawable.currentFrame + 1).toDouble() / drawable.videoItem.frames.toDouble() 189 | callback?.onStep(drawable.currentFrame, percentage) 190 | } 191 | 192 | private fun onAnimationEnd(animation: Animator?) { 193 | isAnimating = false 194 | stopAnimation() 195 | val drawable = getSVGADrawable() 196 | if (drawable != null) { 197 | when (fillMode) { 198 | FillMode.Backward -> { 199 | drawable.currentFrame = mStartFrame 200 | } 201 | FillMode.Forward -> { 202 | drawable.currentFrame = mEndFrame 203 | } 204 | FillMode.Clear -> { 205 | drawable.cleared = true 206 | } 207 | } 208 | } 209 | callback?.onFinished() 210 | } 211 | 212 | fun clear() { 213 | getSVGADrawable()?.cleared = true 214 | getSVGADrawable()?.clear() 215 | // 清除对 drawable 的引用 216 | setImageDrawable(null) 217 | } 218 | 219 | fun pauseAnimation() { 220 | stopAnimation(false) 221 | callback?.onPause() 222 | } 223 | 224 | fun stopAnimation() { 225 | stopAnimation(clear = clearsAfterStop) 226 | } 227 | 228 | fun stopAnimation(clear: Boolean) { 229 | mAnimator?.cancel() 230 | mAnimator?.removeAllListeners() 231 | mAnimator?.removeAllUpdateListeners() 232 | getSVGADrawable()?.stop() 233 | getSVGADrawable()?.cleared = clear 234 | } 235 | 236 | fun setVideoItem(videoItem: SVGAVideoEntity?) { 237 | setVideoItem(videoItem, SVGADynamicEntity()) 238 | } 239 | 240 | fun setVideoItem(videoItem: SVGAVideoEntity?, dynamicItem: SVGADynamicEntity?) { 241 | if (videoItem == null) { 242 | setImageDrawable(null) 243 | } else { 244 | val drawable = SVGADrawable(videoItem, dynamicItem ?: SVGADynamicEntity()) 245 | drawable.cleared = true 246 | setImageDrawable(drawable) 247 | } 248 | } 249 | 250 | fun stepToFrame(frame: Int, andPlay: Boolean) { 251 | pauseAnimation() 252 | val drawable = getSVGADrawable() ?: return 253 | drawable.currentFrame = frame 254 | if (andPlay) { 255 | startAnimation() 256 | mAnimator?.let { 257 | it.currentPlayTime = (Math.max(0.0f, Math.min(1.0f, (frame.toFloat() / drawable.videoItem.frames.toFloat()))) * it.duration).toLong() 258 | } 259 | } 260 | } 261 | 262 | fun stepToPercentage(percentage: Double, andPlay: Boolean) { 263 | val drawable = drawable as? SVGADrawable ?: return 264 | var frame = (drawable.videoItem.frames * percentage).toInt() 265 | if (frame >= drawable.videoItem.frames && frame > 0) { 266 | frame = drawable.videoItem.frames - 1 267 | } 268 | stepToFrame(frame, andPlay) 269 | } 270 | 271 | fun setOnAnimKeyClickListener(clickListener : SVGAClickAreaListener){ 272 | mItemClickAreaListener = clickListener 273 | } 274 | 275 | @SuppressLint("ClickableViewAccessibility") 276 | override fun onTouchEvent(event: MotionEvent?): Boolean { 277 | if (event?.action != MotionEvent.ACTION_DOWN) { 278 | return super.onTouchEvent(event) 279 | } 280 | val drawable = getSVGADrawable() ?: return super.onTouchEvent(event) 281 | for ((key, value) in drawable.dynamicItem.mClickMap) { 282 | if (event.x >= value[0] && event.x <= value[2] && event.y >= value[1] && event.y <= value[3]) { 283 | mItemClickAreaListener?.let { 284 | it.onClick(key) 285 | return true 286 | } 287 | } 288 | } 289 | 290 | return super.onTouchEvent(event) 291 | } 292 | 293 | override fun onDetachedFromWindow() { 294 | super.onDetachedFromWindow() 295 | stopAnimation(clearsAfterDetached) 296 | if (clearsAfterDetached) { 297 | clear() 298 | } 299 | } 300 | 301 | private class AnimatorListener(view: SVGAImageView) : Animator.AnimatorListener { 302 | private val weakReference = WeakReference(view) 303 | 304 | override fun onAnimationRepeat(animation: Animator?) { 305 | weakReference.get()?.callback?.onRepeat() 306 | } 307 | 308 | override fun onAnimationEnd(animation: Animator?) { 309 | weakReference.get()?.onAnimationEnd(animation) 310 | } 311 | 312 | override fun onAnimationCancel(animation: Animator?) { 313 | weakReference.get()?.isAnimating = false 314 | } 315 | 316 | override fun onAnimationStart(animation: Animator?) { 317 | weakReference.get()?.isAnimating = true 318 | } 319 | } // end of AnimatorListener 320 | 321 | 322 | private class AnimatorUpdateListener(view: SVGAImageView) : ValueAnimator.AnimatorUpdateListener { 323 | private val weakReference = WeakReference(view) 324 | 325 | override fun onAnimationUpdate(animation: ValueAnimator?) { 326 | weakReference.get()?.onAnimatorUpdate(animation) 327 | } 328 | } // end of AnimatorUpdateListener 329 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/SVGAPlayer.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | 6 | /** 7 | * Created by cuiminghui on 2017/3/30. 8 | * @deprecated from 2.4.0 9 | */ 10 | @Deprecated("This class has been deprecated from 2.4.0. We don't recommend you to use it.") 11 | class SVGAPlayer: SVGAImageView { 12 | 13 | constructor(context: Context) : super(context) {} 14 | 15 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} 16 | 17 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} 18 | 19 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/SVGASoundManager.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | /** 4 | * @author Devin 5 | * 6 | * Created on 2/24/21. 7 | */ 8 | import android.media.AudioAttributes 9 | import android.media.AudioManager 10 | import android.media.SoundPool 11 | import android.os.Build 12 | import com.opensource.svgaplayer.utils.log.LogUtils 13 | import java.io.FileDescriptor 14 | 15 | /** 16 | * Author : llk 17 | * Time : 2020/10/24 18 | * Description : svga 音频加载管理类 19 | * 将 SoundPool 抽取到单例里边,规避 load 资源之后不回调 onLoadComplete 的问题。 20 | * 21 | * 需要对 SVGASoundManager 进行初始化 22 | * 23 | * 相关文章:Android SoundPool 崩溃问题研究 24 | * https://zhuanlan.zhihu.com/p/29985198 25 | */ 26 | object SVGASoundManager { 27 | 28 | private val TAG = SVGASoundManager::class.java.simpleName 29 | 30 | private var soundPool: SoundPool? = null 31 | 32 | private val soundCallBackMap: MutableMap = mutableMapOf() 33 | 34 | /** 35 | * 音量设置,范围在 [0, 1] 之间 36 | */ 37 | private var volume: Float = 1f 38 | 39 | /** 40 | * 音频回调 41 | */ 42 | internal interface SVGASoundCallBack { 43 | 44 | // 音量发生变化 45 | fun onVolumeChange(value: Float) 46 | 47 | // 音频加载完成 48 | fun onComplete() 49 | } 50 | 51 | fun init() { 52 | init(20) 53 | } 54 | 55 | fun init(maxStreams: Int) { 56 | LogUtils.debug(TAG, "**************** init **************** $maxStreams") 57 | if (soundPool != null) { 58 | return 59 | } 60 | soundPool = getSoundPool(maxStreams) 61 | soundPool?.setOnLoadCompleteListener { _, soundId, status -> 62 | LogUtils.debug(TAG, "SoundPool onLoadComplete soundId=$soundId status=$status") 63 | if (status == 0) { //加载该声音成功 64 | if (soundCallBackMap.containsKey(soundId)) { 65 | soundCallBackMap[soundId]?.onComplete() 66 | } 67 | } 68 | } 69 | } 70 | 71 | fun release() { 72 | LogUtils.debug(TAG, "**************** release ****************") 73 | if (soundCallBackMap.isNotEmpty()) { 74 | soundCallBackMap.clear() 75 | } 76 | } 77 | 78 | /** 79 | * 根据当前播放实体,设置音量 80 | * 81 | * @param volume 范围在 [0, 1] 82 | * @param entity 根据需要控制对应 entity 音量大小,若为空则控制所有正在播放的音频音量 83 | */ 84 | fun setVolume(volume: Float, entity: SVGAVideoEntity? = null) { 85 | if (!checkInit()) { 86 | return 87 | } 88 | 89 | if (volume < 0f || volume > 1f) { 90 | LogUtils.error(TAG, "The volume level is in the range of 0 to 1 ") 91 | return 92 | } 93 | 94 | if (entity == null) { 95 | this.volume = volume 96 | val iterator = soundCallBackMap.entries.iterator() 97 | while (iterator.hasNext()) { 98 | val e = iterator.next() 99 | e.value.onVolumeChange(volume) 100 | } 101 | return 102 | } 103 | 104 | val soundPool = soundPool ?: return 105 | 106 | entity.audioList.forEach { audio -> 107 | val streamId = audio.playID ?: return 108 | soundPool.setVolume(streamId, volume, volume) 109 | } 110 | } 111 | 112 | /** 113 | * 是否初始化 114 | * 如果没有初始化,就使用原来SvgaPlayer库的音频加载逻辑。 115 | * @return true 则已初始化, 否则为 false 116 | */ 117 | internal fun isInit(): Boolean { 118 | return soundPool != null 119 | } 120 | 121 | private fun checkInit(): Boolean { 122 | val isInit = isInit() 123 | if (!isInit) { 124 | LogUtils.error(TAG, "soundPool is null, you need call init() !!!") 125 | } 126 | return isInit 127 | } 128 | 129 | private fun getSoundPool(maxStreams: Int) = if (Build.VERSION.SDK_INT >= 21) { 130 | val attributes = AudioAttributes.Builder() 131 | .setUsage(AudioAttributes.USAGE_MEDIA) 132 | .build() 133 | SoundPool.Builder().setAudioAttributes(attributes) 134 | .setMaxStreams(maxStreams) 135 | .build() 136 | } else { 137 | SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0) 138 | } 139 | 140 | internal fun load(callBack: SVGASoundCallBack?, 141 | fd: FileDescriptor?, 142 | offset: Long, 143 | length: Long, 144 | priority: Int): Int { 145 | if (!checkInit()) return -1 146 | 147 | val soundId = soundPool!!.load(fd, offset, length, priority) 148 | 149 | LogUtils.debug(TAG, "load soundId=$soundId callBack=$callBack") 150 | 151 | if (callBack != null && !soundCallBackMap.containsKey(soundId)) { 152 | soundCallBackMap[soundId] = callBack 153 | } 154 | return soundId 155 | } 156 | 157 | internal fun unload(soundId: Int) { 158 | if (!checkInit()) return 159 | 160 | LogUtils.debug(TAG, "unload soundId=$soundId") 161 | 162 | soundPool!!.unload(soundId) 163 | 164 | soundCallBackMap.remove(soundId) 165 | } 166 | 167 | internal fun play(soundId: Int): Int { 168 | if (!checkInit()) return -1 169 | 170 | LogUtils.debug(TAG, "play soundId=$soundId") 171 | return soundPool!!.play(soundId, volume, volume, 1, 0, 1.0f) 172 | } 173 | 174 | internal fun stop(soundId: Int) { 175 | if (!checkInit()) return 176 | 177 | LogUtils.debug(TAG, "stop soundId=$soundId") 178 | soundPool!!.stop(soundId) 179 | } 180 | 181 | internal fun resume(soundId: Int) { 182 | if (!checkInit()) return 183 | 184 | LogUtils.debug(TAG, "stop soundId=$soundId") 185 | soundPool!!.resume(soundId) 186 | } 187 | 188 | internal fun pause(soundId: Int) { 189 | if (!checkInit()) return 190 | 191 | LogUtils.debug(TAG, "pause soundId=$soundId") 192 | soundPool!!.pause(soundId) 193 | } 194 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/SVGAVideoEntity.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer 2 | 3 | import android.graphics.Bitmap 4 | import android.media.AudioAttributes 5 | import android.media.AudioManager 6 | import android.media.SoundPool 7 | import android.os.Build 8 | import com.opensource.svgaplayer.bitmap.SVGABitmapByteArrayDecoder 9 | import com.opensource.svgaplayer.bitmap.SVGABitmapFileDecoder 10 | import com.opensource.svgaplayer.entities.SVGAAudioEntity 11 | import com.opensource.svgaplayer.entities.SVGAVideoSpriteEntity 12 | import com.opensource.svgaplayer.proto.AudioEntity 13 | import com.opensource.svgaplayer.proto.MovieEntity 14 | import com.opensource.svgaplayer.proto.MovieParams 15 | import com.opensource.svgaplayer.utils.SVGARect 16 | import com.opensource.svgaplayer.utils.log.LogUtils 17 | import org.json.JSONObject 18 | import java.io.File 19 | import java.io.FileInputStream 20 | import java.io.FileOutputStream 21 | import java.util.* 22 | import kotlin.collections.ArrayList 23 | 24 | /** 25 | * Created by PonyCui on 16/6/18. 26 | */ 27 | class SVGAVideoEntity { 28 | 29 | private val TAG = "SVGAVideoEntity" 30 | 31 | var antiAlias = true 32 | var movieItem: MovieEntity? = null 33 | 34 | var videoSize = SVGARect(0.0, 0.0, 0.0, 0.0) 35 | private set 36 | 37 | var FPS = 15 38 | private set 39 | 40 | var frames: Int = 0 41 | private set 42 | 43 | internal var spriteList: List = emptyList() 44 | internal var audioList: List = emptyList() 45 | internal var soundPool: SoundPool? = null 46 | private var soundCallback: SVGASoundManager.SVGASoundCallBack? = null 47 | internal var imageMap = HashMap() 48 | private var mCacheDir: File 49 | private var mFrameHeight = 0 50 | private var mFrameWidth = 0 51 | private var mPlayCallback: SVGAParser.PlayCallback?=null 52 | private lateinit var mCallback: () -> Unit 53 | 54 | constructor(json: JSONObject, cacheDir: File) : this(json, cacheDir, 0, 0) 55 | 56 | constructor(json: JSONObject, cacheDir: File, frameWidth: Int, frameHeight: Int) { 57 | mFrameWidth = frameWidth 58 | mFrameHeight = frameHeight 59 | mCacheDir = cacheDir 60 | val movieJsonObject = json.optJSONObject("movie") ?: return 61 | setupByJson(movieJsonObject) 62 | try { 63 | parserImages(json) 64 | } catch (e: Exception) { 65 | e.printStackTrace() 66 | } catch (e: OutOfMemoryError) { 67 | e.printStackTrace() 68 | } 69 | resetSprites(json) 70 | } 71 | 72 | private fun setupByJson(movieObject: JSONObject) { 73 | movieObject.optJSONObject("viewBox")?.let { viewBoxObject -> 74 | val width = viewBoxObject.optDouble("width", 0.0) 75 | val height = viewBoxObject.optDouble("height", 0.0) 76 | videoSize = SVGARect(0.0, 0.0, width, height) 77 | } 78 | FPS = movieObject.optInt("fps", 20) 79 | frames = movieObject.optInt("frames", 0) 80 | } 81 | 82 | constructor(entity: MovieEntity, cacheDir: File) : this(entity, cacheDir, 0, 0) 83 | 84 | constructor(entity: MovieEntity, cacheDir: File, frameWidth: Int, frameHeight: Int) { 85 | this.mFrameWidth = frameWidth 86 | this.mFrameHeight = frameHeight 87 | this.mCacheDir = cacheDir 88 | this.movieItem = entity 89 | entity.params?.let(this::setupByMovie) 90 | try { 91 | parserImages(entity) 92 | } catch (e: Exception) { 93 | e.printStackTrace() 94 | } catch (e: OutOfMemoryError) { 95 | e.printStackTrace() 96 | } 97 | resetSprites(entity) 98 | } 99 | 100 | private fun setupByMovie(movieParams: MovieParams) { 101 | val width = (movieParams.viewBoxWidth ?: 0.0f).toDouble() 102 | val height = (movieParams.viewBoxHeight ?: 0.0f).toDouble() 103 | videoSize = SVGARect(0.0, 0.0, width, height) 104 | FPS = movieParams.fps ?: 20 105 | frames = movieParams.frames ?: 0 106 | } 107 | 108 | internal fun prepare(callback: () -> Unit, playCallback: SVGAParser.PlayCallback?) { 109 | mCallback = callback 110 | mPlayCallback = playCallback 111 | if (movieItem == null) { 112 | mCallback() 113 | } else { 114 | setupAudios(movieItem!!) { 115 | mCallback() 116 | } 117 | } 118 | } 119 | 120 | private fun parserImages(json: JSONObject) { 121 | val imgJson = json.optJSONObject("images") ?: return 122 | imgJson.keys().forEach { imgKey -> 123 | val filePath = generateBitmapFilePath(imgJson[imgKey].toString(), imgKey) 124 | if (filePath.isEmpty()) { 125 | return 126 | } 127 | val bitmapKey = imgKey.replace(".matte", "") 128 | val bitmap = createBitmap(filePath) 129 | if (bitmap != null) { 130 | imageMap[bitmapKey] = bitmap 131 | } 132 | } 133 | } 134 | 135 | private fun generateBitmapFilePath(imgName: String, imgKey: String): String { 136 | val path = mCacheDir.absolutePath + "/" + imgName 137 | val path1 = "$path.png" 138 | val path2 = mCacheDir.absolutePath + "/" + imgKey + ".png" 139 | 140 | return when { 141 | File(path).exists() -> path 142 | File(path1).exists() -> path1 143 | File(path2).exists() -> path2 144 | else -> "" 145 | } 146 | } 147 | 148 | private fun createBitmap(filePath: String): Bitmap? { 149 | return SVGABitmapFileDecoder.decodeBitmapFrom(filePath, mFrameWidth, mFrameHeight) 150 | } 151 | 152 | private fun parserImages(obj: MovieEntity) { 153 | obj.images?.entries?.forEach { entry -> 154 | val byteArray = entry.value.toByteArray() 155 | if (byteArray.count() < 4) { 156 | return@forEach 157 | } 158 | val fileTag = byteArray.slice(IntRange(0, 3)) 159 | if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) { 160 | return@forEach 161 | } 162 | val filePath = generateBitmapFilePath(entry.value.utf8(), entry.key) 163 | createBitmap(byteArray, filePath)?.let { bitmap -> 164 | imageMap[entry.key] = bitmap 165 | } 166 | } 167 | } 168 | 169 | private fun createBitmap(byteArray: ByteArray, filePath: String): Bitmap? { 170 | val bitmap = SVGABitmapByteArrayDecoder.decodeBitmapFrom(byteArray, mFrameWidth, mFrameHeight) 171 | return bitmap ?: createBitmap(filePath) 172 | } 173 | 174 | private fun resetSprites(json: JSONObject) { 175 | val mutableList: MutableList = mutableListOf() 176 | json.optJSONArray("sprites")?.let { item -> 177 | for (i in 0 until item.length()) { 178 | item.optJSONObject(i)?.let { entryJson -> 179 | mutableList.add(SVGAVideoSpriteEntity(entryJson)) 180 | } 181 | } 182 | } 183 | spriteList = mutableList.toList() 184 | } 185 | 186 | private fun resetSprites(entity: MovieEntity) { 187 | spriteList = entity.sprites?.map { 188 | return@map SVGAVideoSpriteEntity(it) 189 | } ?: listOf() 190 | } 191 | 192 | private fun setupAudios(entity: MovieEntity, completionBlock: () -> Unit) { 193 | if (entity.audios == null || entity.audios.isEmpty()) { 194 | run(completionBlock) 195 | return 196 | } 197 | setupSoundPool(entity, completionBlock) 198 | val audiosFileMap = generateAudioFileMap(entity) 199 | //repair when audioEntity error can not callback 200 | //如果audiosFileMap为空 soundPool?.load 不会走 导致 setOnLoadCompleteListener 不会回调 导致外层prepare不回调卡住 201 | if (audiosFileMap.size == 0) { 202 | run(completionBlock) 203 | return 204 | } 205 | this.audioList = entity.audios.map { audio -> 206 | return@map createSvgaAudioEntity(audio, audiosFileMap) 207 | } 208 | } 209 | 210 | private fun createSvgaAudioEntity(audio: AudioEntity, audiosFileMap: HashMap): SVGAAudioEntity { 211 | val item = SVGAAudioEntity(audio) 212 | val startTime = (audio.startTime ?: 0).toDouble() 213 | val totalTime = (audio.totalTime ?: 0).toDouble() 214 | if (totalTime.toInt() == 0) { 215 | // 除数不能为 0 216 | return item 217 | } 218 | // 直接回调文件,后续播放都不走 219 | mPlayCallback?.let { 220 | val fileList: MutableList = ArrayList() 221 | audiosFileMap.forEach { entity -> 222 | fileList.add(entity.value) 223 | } 224 | it.onPlay(fileList) 225 | mCallback() 226 | return item 227 | } 228 | 229 | audiosFileMap[audio.audioKey]?.let { file -> 230 | FileInputStream(file).use { 231 | val length = it.available().toDouble() 232 | val offset = ((startTime / totalTime) * length).toLong() 233 | if (SVGASoundManager.isInit()) { 234 | item.soundID = SVGASoundManager.load(soundCallback, 235 | it.fd, 236 | offset, 237 | length.toLong(), 238 | 1) 239 | } else { 240 | item.soundID = soundPool?.load(it.fd, offset, length.toLong(), 1) 241 | } 242 | } 243 | } 244 | return item 245 | } 246 | 247 | private fun generateAudioFile(audioCache: File, value: ByteArray): File { 248 | audioCache.createNewFile() 249 | FileOutputStream(audioCache).write(value) 250 | return audioCache 251 | } 252 | 253 | private fun generateAudioFileMap(entity: MovieEntity): HashMap { 254 | val audiosDataMap = generateAudioMap(entity) 255 | val audiosFileMap = HashMap() 256 | if (audiosDataMap.count() > 0) { 257 | audiosDataMap.forEach { 258 | val audioCache = SVGACache.buildAudioFile(it.key) 259 | audiosFileMap[it.key] = 260 | audioCache.takeIf { file -> file.exists() } ?: generateAudioFile( 261 | audioCache, 262 | it.value 263 | ) 264 | } 265 | } 266 | return audiosFileMap 267 | } 268 | 269 | private fun generateAudioMap(entity: MovieEntity): HashMap { 270 | val audiosDataMap = HashMap() 271 | entity.images?.entries?.forEach { 272 | val imageKey = it.key 273 | val byteArray = it.value.toByteArray() 274 | if (byteArray.count() < 4) { 275 | return@forEach 276 | } 277 | val fileTag = byteArray.slice(IntRange(0, 3)) 278 | if (fileTag[0].toInt() == 73 && fileTag[1].toInt() == 68 && fileTag[2].toInt() == 51) { 279 | audiosDataMap[imageKey] = byteArray 280 | }else if(fileTag[0].toInt() == -1 && fileTag[1].toInt() == -5 && fileTag[2].toInt() == -108){ 281 | audiosDataMap[imageKey] = byteArray 282 | } 283 | } 284 | return audiosDataMap 285 | } 286 | 287 | private fun setupSoundPool(entity: MovieEntity, completionBlock: () -> Unit) { 288 | var soundLoaded = 0 289 | if (SVGASoundManager.isInit()) { 290 | soundCallback = object : SVGASoundManager.SVGASoundCallBack { 291 | override fun onVolumeChange(value: Float) { 292 | SVGASoundManager.setVolume(value, this@SVGAVideoEntity) 293 | } 294 | 295 | override fun onComplete() { 296 | soundLoaded++ 297 | if (soundLoaded >= entity.audios.count()) { 298 | completionBlock() 299 | } 300 | } 301 | } 302 | return 303 | } 304 | soundPool = generateSoundPool(entity) 305 | LogUtils.info("SVGAParser", "pool_start") 306 | soundPool?.setOnLoadCompleteListener { _, _, _ -> 307 | LogUtils.info("SVGAParser", "pool_complete") 308 | soundLoaded++ 309 | if (soundLoaded >= entity.audios.count()) { 310 | completionBlock() 311 | } 312 | } 313 | } 314 | 315 | private fun generateSoundPool(entity: MovieEntity): SoundPool? { 316 | return try { 317 | if (Build.VERSION.SDK_INT >= 21) { 318 | val attributes = AudioAttributes.Builder() 319 | .setUsage(AudioAttributes.USAGE_MEDIA) 320 | .build() 321 | SoundPool.Builder().setAudioAttributes(attributes) 322 | .setMaxStreams(12.coerceAtMost(entity.audios.count())) 323 | .build() 324 | } else { 325 | SoundPool(12.coerceAtMost(entity.audios.count()), AudioManager.STREAM_MUSIC, 0) 326 | } 327 | } catch (e: Exception) { 328 | LogUtils.error(TAG, e) 329 | null 330 | } 331 | } 332 | 333 | fun clear() { 334 | if (SVGASoundManager.isInit()) { 335 | this.audioList.forEach { 336 | it.soundID?.let { id -> SVGASoundManager.unload(id) } 337 | } 338 | soundCallback = null 339 | } 340 | soundPool?.release() 341 | soundPool = null 342 | audioList = emptyList() 343 | spriteList = emptyList() 344 | imageMap.clear() 345 | } 346 | } 347 | 348 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/bitmap/BitmapSampleSizeCalculator.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.bitmap 2 | 3 | import android.graphics.BitmapFactory 4 | 5 | /** 6 | * 7 | * Create by im_dsd 2020/7/7 17:59 8 | */ 9 | internal object BitmapSampleSizeCalculator { 10 | 11 | fun calculate(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { 12 | // Raw height and width of image 13 | val (height: Int, width: Int) = options.run { outHeight to outWidth } 14 | var inSampleSize = 1 15 | 16 | if (reqHeight <= 0 || reqWidth <= 0) { 17 | return inSampleSize 18 | } 19 | if (height > reqHeight || width > reqWidth) { 20 | 21 | val halfHeight: Int = height / 2 22 | val halfWidth: Int = width / 2 23 | 24 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 25 | // height and width larger than the requested height and width. 26 | while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { 27 | inSampleSize *= 2 28 | } 29 | } 30 | 31 | return inSampleSize 32 | } 33 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/bitmap/SVGABitmapByteArrayDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.bitmap 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.BitmapFactory 5 | 6 | /** 7 | * 通过字节码解码 Bitmap 8 | * 9 | * Create by im_dsd 2020/7/7 17:50 10 | */ 11 | internal object SVGABitmapByteArrayDecoder : SVGABitmapDecoder() { 12 | 13 | override fun onDecode(data: ByteArray, ops: BitmapFactory.Options): Bitmap? { 14 | return BitmapFactory.decodeByteArray(data, 0, data.count(), ops) 15 | } 16 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/bitmap/SVGABitmapDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.bitmap 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.BitmapFactory 5 | 6 | /** 7 | * Bitmap 解码器 8 | * 9 | * 需要加载的数据类型 10 | * 11 | * Create by im_dsd 2020/7/7 17:39 12 | */ 13 | internal abstract class SVGABitmapDecoder { 14 | 15 | fun decodeBitmapFrom(data: T, reqWidth: Int, reqHeight: Int): Bitmap? { 16 | return BitmapFactory.Options().run { 17 | // 如果期望的宽高是合法的, 则开启检测尺寸模式 18 | inJustDecodeBounds = (reqWidth > 0 && reqHeight > 0) 19 | inPreferredConfig = Bitmap.Config.RGB_565 20 | 21 | val bitmap = onDecode(data, this) 22 | if (!inJustDecodeBounds) { 23 | return bitmap 24 | } 25 | 26 | // Calculate inSampleSize 27 | inSampleSize = BitmapSampleSizeCalculator.calculate(this, reqWidth, reqHeight) 28 | // Decode bitmap with inSampleSize set 29 | inJustDecodeBounds = false 30 | onDecode(data, this) 31 | } 32 | } 33 | 34 | abstract fun onDecode(data: T, ops: BitmapFactory.Options): Bitmap? 35 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/bitmap/SVGABitmapFileDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.bitmap 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.BitmapFactory 5 | 6 | /** 7 | * 通过文件解码 Bitmap 8 | * 9 | * Create by im_dsd 2020/7/7 17:50 10 | */ 11 | internal object SVGABitmapFileDecoder : SVGABitmapDecoder() { 12 | 13 | override fun onDecode(data: String, ops: BitmapFactory.Options): Bitmap? { 14 | return BitmapFactory.decodeFile(data, ops) 15 | } 16 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/drawer/SGVADrawer.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.drawer 2 | 3 | import android.graphics.Canvas 4 | import android.widget.ImageView 5 | import com.opensource.svgaplayer.SVGAVideoEntity 6 | import com.opensource.svgaplayer.entities.SVGAVideoSpriteFrameEntity 7 | import com.opensource.svgaplayer.utils.Pools 8 | import com.opensource.svgaplayer.utils.SVGAScaleInfo 9 | import kotlin.math.max 10 | 11 | /** 12 | * Created by cuiminghui on 2017/3/29. 13 | */ 14 | 15 | open internal class SGVADrawer(val videoItem: SVGAVideoEntity) { 16 | 17 | val scaleInfo = SVGAScaleInfo() 18 | 19 | private val spritePool = Pools.SimplePool(max(1, videoItem.spriteList.size)) 20 | 21 | inner class SVGADrawerSprite(var _matteKey: String? = null, var _imageKey: String? = null, var _frameEntity: SVGAVideoSpriteFrameEntity? = null) { 22 | val matteKey get() = _matteKey 23 | val imageKey get() = _imageKey 24 | val frameEntity get() = _frameEntity!! 25 | } 26 | 27 | internal fun requestFrameSprites(frameIndex: Int): List { 28 | return videoItem.spriteList.mapNotNull { 29 | if (frameIndex >= 0 && frameIndex < it.frames.size) { 30 | it.imageKey?.let { imageKey -> 31 | if (!imageKey.endsWith(".matte") && it.frames[frameIndex].alpha <= 0.0) { 32 | return@mapNotNull null 33 | } 34 | return@mapNotNull (spritePool.acquire() ?: SVGADrawerSprite()).apply { 35 | _matteKey = it.matteKey 36 | _imageKey = it.imageKey 37 | _frameEntity = it.frames[frameIndex] 38 | } 39 | } 40 | } 41 | return@mapNotNull null 42 | } 43 | } 44 | 45 | internal fun releaseFrameSprites(sprites: List) { 46 | sprites.forEach { spritePool.release(it) } 47 | } 48 | 49 | open fun drawFrame(canvas : Canvas, frameIndex: Int, scaleType: ImageView.ScaleType) { 50 | scaleInfo.performScaleType(canvas.width.toFloat(),canvas.height.toFloat(), videoItem.videoSize.width.toFloat(), videoItem.videoSize.height.toFloat(), scaleType) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/entities/SVGAAudioEntity.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.entities 2 | 3 | import com.opensource.svgaplayer.proto.AudioEntity 4 | import java.io.FileInputStream 5 | 6 | internal class SVGAAudioEntity { 7 | 8 | val audioKey: String? 9 | val startFrame: Int 10 | val endFrame: Int 11 | val startTime: Int 12 | val totalTime: Int 13 | var soundID: Int? = null 14 | var playID: Int? = null 15 | 16 | constructor(audioItem: AudioEntity) { 17 | this.audioKey = audioItem.audioKey 18 | this.startFrame = audioItem.startFrame ?: 0 19 | this.endFrame = audioItem.endFrame ?: 0 20 | this.startTime = audioItem.startTime ?: 0 21 | this.totalTime = audioItem.totalTime ?: 0 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/entities/SVGAPathEntity.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.entities 2 | 3 | import android.graphics.Path 4 | import com.opensource.svgaplayer.utils.SVGAPoint 5 | import java.util.* 6 | 7 | private val VALID_METHODS: Set = setOf("M", "L", "H", "V", "C", "S", "Q", "R", "A", "Z", "m", "l", "h", "v", "c", "s", "q", "r", "a", "z") 8 | 9 | class SVGAPathEntity(originValue: String) { 10 | 11 | private val replacedValue: String = if (originValue.contains(",")) originValue.replace(",", " ") else originValue 12 | 13 | private var cachedPath: Path? = null 14 | 15 | fun buildPath(toPath: Path) { 16 | cachedPath?.let { 17 | toPath.set(it) 18 | return 19 | } 20 | val cachedPath = Path() 21 | val segments = StringTokenizer(this.replacedValue, "MLHVCSQRAZmlhvcsqraz", true) 22 | var currentMethod = "" 23 | while (segments.hasMoreTokens()) { 24 | val segment = segments.nextToken() 25 | if (segment.isEmpty()) { continue } 26 | if (VALID_METHODS.contains(segment)) { 27 | currentMethod = segment 28 | if (currentMethod == "Z" || currentMethod == "z") { operate(cachedPath, currentMethod, StringTokenizer("", "")) } 29 | } 30 | else { 31 | operate(cachedPath, currentMethod, StringTokenizer(segment, " ")) 32 | } 33 | } 34 | this.cachedPath = cachedPath 35 | toPath.set(cachedPath) 36 | } 37 | 38 | private fun operate(finalPath: Path, method: String, args: StringTokenizer) { 39 | var x0 = 0.0f 40 | var y0 = 0.0f 41 | var x1 = 0.0f 42 | var y1 = 0.0f 43 | var x2 = 0.0f 44 | var y2 = 0.0f 45 | try { 46 | var index = 0 47 | while (args.hasMoreTokens()) { 48 | val s = args.nextToken() 49 | if (s.isEmpty()) {continue} 50 | if (index == 0) { x0 = s.toFloat() } 51 | if (index == 1) { y0 = s.toFloat() } 52 | if (index == 2) { x1 = s.toFloat() } 53 | if (index == 3) { y1 = s.toFloat() } 54 | if (index == 4) { x2 = s.toFloat() } 55 | if (index == 5) { y2 = s.toFloat() } 56 | index++ 57 | } 58 | } catch (e: Exception) {} 59 | var currentPoint = SVGAPoint(0.0f, 0.0f, 0.0f) 60 | if (method == "M") { 61 | finalPath.moveTo(x0, y0) 62 | currentPoint = SVGAPoint(x0, y0, 0.0f) 63 | } else if (method == "m") { 64 | finalPath.rMoveTo(x0, y0) 65 | currentPoint = SVGAPoint(currentPoint.x + x0, currentPoint.y + y0, 0.0f) 66 | } 67 | if (method == "L") { 68 | finalPath.lineTo(x0, y0) 69 | } else if (method == "l") { 70 | finalPath.rLineTo(x0, y0) 71 | } 72 | if (method == "C") { 73 | finalPath.cubicTo(x0, y0, x1, y1, x2, y2) 74 | } else if (method == "c") { 75 | finalPath.rCubicTo(x0, y0, x1, y1, x2, y2) 76 | } 77 | if (method == "Q") { 78 | finalPath.quadTo(x0, y0, x1, y1) 79 | } else if (method == "q") { 80 | finalPath.rQuadTo(x0, y0, x1, y1) 81 | } 82 | if (method == "H") { 83 | finalPath.lineTo(x0, currentPoint.y) 84 | } else if (method == "h") { 85 | finalPath.rLineTo(x0, 0f) 86 | } 87 | if (method == "V") { 88 | finalPath.lineTo(currentPoint.x, x0) 89 | } else if (method == "v") { 90 | finalPath.rLineTo(0f, x0) 91 | } 92 | if (method == "Z") { 93 | finalPath.close() 94 | } 95 | else if (method == "z") { 96 | finalPath.close() 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/entities/SVGAVideoShapeEntity.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.entities 2 | 3 | import android.graphics.Color 4 | import android.graphics.Matrix 5 | import android.graphics.Path 6 | import android.graphics.RectF 7 | import com.opensource.svgaplayer.proto.ShapeEntity 8 | import org.json.JSONArray 9 | import org.json.JSONObject 10 | import java.util.* 11 | 12 | /** 13 | * Created by cuiminghui on 2017/2/22. 14 | */ 15 | 16 | val sharedPath = Path() 17 | 18 | internal class SVGAVideoShapeEntity { 19 | 20 | enum class Type { 21 | shape, 22 | rect, 23 | ellipse, 24 | keep 25 | } 26 | 27 | class Styles { 28 | 29 | var fill = 0x00000000 30 | internal set 31 | 32 | var stroke = 0x00000000 33 | internal set 34 | 35 | var strokeWidth = 0.0f 36 | internal set 37 | 38 | var lineCap = "butt" 39 | internal set 40 | 41 | var lineJoin = "miter" 42 | internal set 43 | 44 | var miterLimit = 0 45 | internal set 46 | 47 | var lineDash = FloatArray(0) 48 | internal set 49 | 50 | } 51 | 52 | var type = Type.shape 53 | private set 54 | 55 | var args: Map? = null 56 | private set 57 | 58 | var styles: Styles? = null 59 | private set 60 | 61 | var transform: Matrix? = null 62 | private set 63 | 64 | constructor(obj: JSONObject) { 65 | parseType(obj) 66 | parseArgs(obj) 67 | parseStyles(obj) 68 | parseTransform(obj) 69 | } 70 | 71 | constructor(obj: ShapeEntity) { 72 | parseType(obj) 73 | parseArgs(obj) 74 | parseStyles(obj) 75 | parseTransform(obj) 76 | } 77 | 78 | val isKeep: Boolean 79 | get() = type == Type.keep 80 | 81 | var shapePath: Path? = null 82 | 83 | private fun parseType(obj: JSONObject) { 84 | obj.optString("type")?.let { 85 | when { 86 | it.equals("shape", ignoreCase = true) -> type = Type.shape 87 | it.equals("rect", ignoreCase = true) -> type = Type.rect 88 | it.equals("ellipse", ignoreCase = true) -> type = Type.ellipse 89 | it.equals("keep", ignoreCase = true) -> type = Type.keep 90 | } 91 | } 92 | } 93 | 94 | private fun parseType(obj: ShapeEntity) { 95 | obj.type?.let { 96 | type = when (it) { 97 | ShapeEntity.ShapeType.SHAPE -> Type.shape 98 | ShapeEntity.ShapeType.RECT -> Type.rect 99 | ShapeEntity.ShapeType.ELLIPSE -> Type.ellipse 100 | ShapeEntity.ShapeType.KEEP -> Type.keep 101 | } 102 | } 103 | } 104 | 105 | private fun parseArgs(obj: JSONObject) { 106 | val args = HashMap() 107 | obj.optJSONObject("args")?.let { values -> 108 | values.keys().forEach { key -> 109 | values.get(key)?.let { 110 | args.put(key, it) 111 | } 112 | } 113 | this.args = args 114 | } 115 | } 116 | 117 | private fun parseArgs(obj: ShapeEntity) { 118 | val args = HashMap() 119 | obj.shape?.let { 120 | it.d?.let { args.put("d", it) } 121 | } 122 | obj.ellipse?.let { 123 | args.put("x", it.x ?: 0.0f) 124 | args.put("y", it.y ?: 0.0f) 125 | args.put("radiusX", it.radiusX ?: 0.0f) 126 | args.put("radiusY", it.radiusY ?: 0.0f) 127 | } 128 | obj.rect?.let { 129 | args.put("x", it.x ?: 0.0f) 130 | args.put("y", it.y ?: 0.0f) 131 | args.put("width", it.width ?: 0.0f) 132 | args.put("height", it.height ?: 0.0f) 133 | args.put("cornerRadius", it.cornerRadius ?: 0.0f) 134 | } 135 | this.args = args 136 | } 137 | 138 | // 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f] 139 | private fun checkValueRange(obj: JSONArray): Float { 140 | return if ( 141 | obj.optDouble(0) <= 1 && 142 | obj.optDouble(1) <= 1 && 143 | obj.optDouble(2) <= 1 144 | ) { 145 | 255f 146 | } else { 147 | 1f 148 | } 149 | } 150 | 151 | // 检查 alpha 的范围是否是 [0f, 1f],或者是 [0f, 255f] 152 | private fun checkAlphaValueRange(obj: JSONArray): Float { 153 | return if (obj.optDouble(3) <= 1) { 154 | 255f 155 | } else { 156 | 1f 157 | } 158 | } 159 | 160 | private fun parseStyles(obj: JSONObject) { 161 | obj.optJSONObject("styles")?.let { 162 | val styles = Styles() 163 | it.optJSONArray("fill")?.let { 164 | if (it.length() == 4) { 165 | val mulValue = checkValueRange(it) 166 | val alphaRangeValue = checkAlphaValueRange(it) 167 | styles.fill = Color.argb( 168 | (it.optDouble(3) * alphaRangeValue).toInt(), 169 | (it.optDouble(0) * mulValue).toInt(), 170 | (it.optDouble(1) * mulValue).toInt(), 171 | (it.optDouble(2) * mulValue).toInt() 172 | ) 173 | } 174 | } 175 | it.optJSONArray("stroke")?.let { 176 | if (it.length() == 4) { 177 | val mulValue = checkValueRange(it) 178 | val alphaRangeValue = checkAlphaValueRange(it) 179 | styles.stroke = Color.argb( 180 | (it.optDouble(3) * alphaRangeValue).toInt(), 181 | (it.optDouble(0) * mulValue).toInt(), 182 | (it.optDouble(1) * mulValue).toInt(), 183 | (it.optDouble(2) * mulValue).toInt() 184 | ) 185 | } 186 | } 187 | styles.strokeWidth = it.optDouble("strokeWidth", 0.0).toFloat() 188 | styles.lineCap = it.optString("lineCap", "butt") 189 | styles.lineJoin = it.optString("lineJoin", "miter") 190 | styles.miterLimit = it.optInt("miterLimit", 0) 191 | it.optJSONArray("lineDash")?.let { 192 | styles.lineDash = FloatArray(it.length()) 193 | for (i in 0 until it.length()) { 194 | styles.lineDash[i] = it.optDouble(i, 0.0).toFloat() 195 | } 196 | } 197 | this.styles = styles 198 | } 199 | } 200 | 201 | // 检查色域范围是否是 [0f, 1f],或者是 [0f, 255f] 202 | private fun checkValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float { 203 | return if ( 204 | (color.r ?: 0f) <= 1 && 205 | (color.g ?: 0f) <= 1 && 206 | (color.b ?: 0f) <= 1 207 | ) { 208 | 255f 209 | } else { 210 | 1f 211 | } 212 | } 213 | 214 | // 检查 alpha 范围是否是 [0f, 1f],有可能是 [0f, 255f] 215 | private fun checkAlphaValueRange(color: ShapeEntity.ShapeStyle.RGBAColor): Float { 216 | return if (color.a <= 1f) { 217 | 255f 218 | } else { 219 | 1f 220 | } 221 | } 222 | 223 | private fun parseStyles(obj: ShapeEntity) { 224 | obj.styles?.let { 225 | val styles = Styles() 226 | it.fill?.let { 227 | val mulValue = checkValueRange(it) 228 | val alphaRangeValue = checkAlphaValueRange(it) 229 | styles.fill = Color.argb( 230 | ((it.a ?: 0f) * alphaRangeValue).toInt(), 231 | ((it.r ?: 0f) * mulValue).toInt(), 232 | ((it.g ?: 0f) * mulValue).toInt(), 233 | ((it.b ?: 0f) * mulValue).toInt() 234 | ) 235 | } 236 | it.stroke?.let { 237 | val mulValue = checkValueRange(it) 238 | val alphaRangeValue = checkAlphaValueRange(it) 239 | styles.stroke = Color.argb( 240 | ((it.a ?: 0f) * alphaRangeValue).toInt(), 241 | ((it.r ?: 0f) * mulValue).toInt(), 242 | ((it.g ?: 0f) * mulValue).toInt(), 243 | ((it.b ?: 0f) * mulValue).toInt() 244 | ) 245 | 246 | } 247 | styles.strokeWidth = it.strokeWidth ?: 0.0f 248 | it.lineCap?.let { 249 | when (it) { 250 | ShapeEntity.ShapeStyle.LineCap.LineCap_BUTT -> styles.lineCap = "butt" 251 | ShapeEntity.ShapeStyle.LineCap.LineCap_ROUND -> styles.lineCap = "round" 252 | ShapeEntity.ShapeStyle.LineCap.LineCap_SQUARE -> styles.lineCap = "square" 253 | } 254 | } 255 | it.lineJoin?.let { 256 | when (it) { 257 | ShapeEntity.ShapeStyle.LineJoin.LineJoin_BEVEL -> styles.lineJoin = "bevel" 258 | ShapeEntity.ShapeStyle.LineJoin.LineJoin_MITER -> styles.lineJoin = "miter" 259 | ShapeEntity.ShapeStyle.LineJoin.LineJoin_ROUND -> styles.lineJoin = "round" 260 | } 261 | } 262 | styles.miterLimit = (it.miterLimit ?: 0.0f).toInt() 263 | styles.lineDash = kotlin.FloatArray(3) 264 | it.lineDashI?.let { styles.lineDash[0] = it } 265 | it.lineDashII?.let { styles.lineDash[1] = it } 266 | it.lineDashIII?.let { styles.lineDash[2] = it } 267 | this.styles = styles 268 | } 269 | } 270 | 271 | private fun parseTransform(obj: JSONObject) { 272 | obj.optJSONObject("transform")?.let { 273 | val transform = Matrix() 274 | val arr = FloatArray(9) 275 | val a = it.optDouble("a", 1.0) 276 | val b = it.optDouble("b", 0.0) 277 | val c = it.optDouble("c", 0.0) 278 | val d = it.optDouble("d", 1.0) 279 | val tx = it.optDouble("tx", 0.0) 280 | val ty = it.optDouble("ty", 0.0) 281 | arr[0] = a.toFloat() // a 282 | arr[1] = c.toFloat() // c 283 | arr[2] = tx.toFloat() // tx 284 | arr[3] = b.toFloat() // b 285 | arr[4] = d.toFloat() // d 286 | arr[5] = ty.toFloat() // ty 287 | arr[6] = 0.0.toFloat() 288 | arr[7] = 0.0.toFloat() 289 | arr[8] = 1.0.toFloat() 290 | transform.setValues(arr) 291 | this.transform = transform 292 | } 293 | } 294 | 295 | private fun parseTransform(obj: ShapeEntity) { 296 | obj.transform?.let { 297 | val transform = Matrix() 298 | val arr = FloatArray(9) 299 | val a = it.a ?: 1.0f 300 | val b = it.b ?: 0.0f 301 | val c = it.c ?: 0.0f 302 | val d = it.d ?: 1.0f 303 | val tx = it.tx ?: 0.0f 304 | val ty = it.ty ?: 0.0f 305 | arr[0] = a 306 | arr[1] = c 307 | arr[2] = tx 308 | arr[3] = b 309 | arr[4] = d 310 | arr[5] = ty 311 | arr[6] = 0.0f 312 | arr[7] = 0.0f 313 | arr[8] = 1.0f 314 | transform.setValues(arr) 315 | this.transform = transform 316 | } 317 | } 318 | 319 | 320 | fun buildPath() { 321 | if (this.shapePath != null) { 322 | return 323 | } 324 | sharedPath.reset() 325 | if (this.type == Type.shape) { 326 | (this.args?.get("d") as? String)?.let { 327 | SVGAPathEntity(it).buildPath(sharedPath) 328 | } 329 | } else if (this.type == Type.ellipse) { 330 | val xv = this.args?.get("x") as? Number ?: return 331 | val yv = this.args?.get("y") as? Number ?: return 332 | val rxv = this.args?.get("radiusX") as? Number ?: return 333 | val ryv = this.args?.get("radiusY") as? Number ?: return 334 | val x = xv.toFloat() 335 | val y = yv.toFloat() 336 | val rx = rxv.toFloat() 337 | val ry = ryv.toFloat() 338 | sharedPath.addOval(RectF(x - rx, y - ry, x + rx, y + ry), Path.Direction.CW) 339 | } else if (this.type == Type.rect) { 340 | val xv = this.args?.get("x") as? Number ?: return 341 | val yv = this.args?.get("y") as? Number ?: return 342 | val wv = this.args?.get("width") as? Number ?: return 343 | val hv = this.args?.get("height") as? Number ?: return 344 | val crv = this.args?.get("cornerRadius") as? Number ?: return 345 | val x = xv.toFloat() 346 | val y = yv.toFloat() 347 | val width = wv.toFloat() 348 | val height = hv.toFloat() 349 | val cornerRadius = crv.toFloat() 350 | sharedPath.addRoundRect(RectF(x, y, x + width, y + height), cornerRadius, cornerRadius, Path.Direction.CW) 351 | } 352 | this.shapePath = Path() 353 | this.shapePath?.set(sharedPath) 354 | } 355 | 356 | } 357 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/entities/SVGAVideoSpriteEntity.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.entities 2 | 3 | import com.opensource.svgaplayer.proto.SpriteEntity 4 | import org.json.JSONObject 5 | 6 | /** 7 | * Created by cuiminghui on 2016/10/17. 8 | */ 9 | internal class SVGAVideoSpriteEntity { 10 | 11 | val imageKey: String? 12 | 13 | val matteKey: String? 14 | 15 | val frames: List 16 | 17 | constructor(obj: JSONObject) { 18 | this.imageKey = obj.optString("imageKey") 19 | this.matteKey = obj.optString("matteKey") 20 | val mutableFrames: MutableList = mutableListOf() 21 | obj.optJSONArray("frames")?.let { 22 | for (i in 0 until it.length()) { 23 | it.optJSONObject(i)?.let { 24 | val frameItem = SVGAVideoSpriteFrameEntity(it) 25 | if (frameItem.shapes.isNotEmpty()) { 26 | frameItem.shapes.first().let { 27 | if (it.isKeep && mutableFrames.size > 0) { 28 | frameItem.shapes = mutableFrames.last().shapes 29 | } 30 | } 31 | } 32 | mutableFrames.add(frameItem) 33 | } 34 | } 35 | } 36 | frames = mutableFrames.toList() 37 | } 38 | 39 | constructor(obj: SpriteEntity) { 40 | this.imageKey = obj.imageKey 41 | this.matteKey = obj.matteKey 42 | var lastFrame: SVGAVideoSpriteFrameEntity? = null 43 | frames = obj.frames?.map { 44 | val frameItem = SVGAVideoSpriteFrameEntity(it) 45 | if (frameItem.shapes.isNotEmpty()) { 46 | frameItem.shapes.first().let { 47 | if (it.isKeep) { 48 | lastFrame?.let { 49 | frameItem.shapes = it.shapes 50 | } 51 | } 52 | } 53 | } 54 | lastFrame = frameItem 55 | return@map frameItem 56 | } ?: listOf() 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/entities/SVGAVideoSpriteFrameEntity.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.entities 2 | 3 | import android.graphics.Matrix 4 | import com.opensource.svgaplayer.proto.FrameEntity 5 | import com.opensource.svgaplayer.utils.SVGARect 6 | 7 | import org.json.JSONObject 8 | 9 | /** 10 | * Created by cuiminghui on 2016/10/17. 11 | */ 12 | internal class SVGAVideoSpriteFrameEntity { 13 | 14 | var alpha: Double 15 | var layout = SVGARect(0.0, 0.0, 0.0, 0.0) 16 | var transform = Matrix() 17 | var maskPath: SVGAPathEntity? = null 18 | var shapes: List = listOf() 19 | 20 | constructor(obj: JSONObject) { 21 | this.alpha = obj.optDouble("alpha", 0.0) 22 | obj.optJSONObject("layout")?.let { 23 | layout = SVGARect(it.optDouble("x", 0.0), it.optDouble("y", 0.0), it.optDouble("width", 0.0), it.optDouble("height", 0.0)) 24 | } 25 | obj.optJSONObject("transform")?.let { 26 | val arr = FloatArray(9) 27 | val a = it.optDouble("a", 1.0) 28 | val b = it.optDouble("b", 0.0) 29 | val c = it.optDouble("c", 0.0) 30 | val d = it.optDouble("d", 1.0) 31 | val tx = it.optDouble("tx", 0.0) 32 | val ty = it.optDouble("ty", 0.0) 33 | arr[0] = a.toFloat() 34 | arr[1] = c.toFloat() 35 | arr[2] = tx.toFloat() 36 | arr[3] = b.toFloat() 37 | arr[4] = d.toFloat() 38 | arr[5] = ty.toFloat() 39 | arr[6] = 0.0.toFloat() 40 | arr[7] = 0.0.toFloat() 41 | arr[8] = 1.0.toFloat() 42 | transform.setValues(arr) 43 | } 44 | obj.optString("clipPath")?.let { d -> 45 | if (d.isNotEmpty()) { 46 | maskPath = SVGAPathEntity(d) 47 | } 48 | } 49 | obj.optJSONArray("shapes")?.let { 50 | val mutableList: MutableList = mutableListOf() 51 | for (i in 0 until it.length()) { 52 | it.optJSONObject(i)?.let { 53 | mutableList.add(SVGAVideoShapeEntity(it)) 54 | } 55 | } 56 | shapes = mutableList.toList() 57 | } 58 | } 59 | 60 | constructor(obj: FrameEntity) { 61 | this.alpha = (obj.alpha ?: 0.0f).toDouble() 62 | obj.layout?.let { 63 | this.layout = SVGARect((it.x ?: 0.0f).toDouble(), (it.y 64 | ?: 0.0f).toDouble(), (it.width ?: 0.0f).toDouble(), (it.height 65 | ?: 0.0f).toDouble()) 66 | } 67 | obj.transform?.let { 68 | val arr = FloatArray(9) 69 | val a = it.a ?: 1.0f 70 | val b = it.b ?: 0.0f 71 | val c = it.c ?: 0.0f 72 | val d = it.d ?: 1.0f 73 | val tx = it.tx ?: 0.0f 74 | val ty = it.ty ?: 0.0f 75 | arr[0] = a 76 | arr[1] = c 77 | arr[2] = tx 78 | arr[3] = b 79 | arr[4] = d 80 | arr[5] = ty 81 | arr[6] = 0.0f 82 | arr[7] = 0.0f 83 | arr[8] = 1.0f 84 | transform.setValues(arr) 85 | } 86 | obj.clipPath?.takeIf { it.isNotEmpty() }?.let { 87 | maskPath = SVGAPathEntity(it) 88 | } 89 | this.shapes = obj.shapes.map { 90 | return@map SVGAVideoShapeEntity(it) 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/proto/AudioEntity.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: svga.proto at 19:1 3 | package com.opensource.svgaplayer.proto; 4 | 5 | import com.squareup.wire.FieldEncoding; 6 | import com.squareup.wire.Message; 7 | import com.squareup.wire.ProtoAdapter; 8 | import com.squareup.wire.ProtoReader; 9 | import com.squareup.wire.ProtoWriter; 10 | import com.squareup.wire.WireField; 11 | import com.squareup.wire.internal.Internal; 12 | import java.io.IOException; 13 | import java.lang.Integer; 14 | import java.lang.Object; 15 | import java.lang.Override; 16 | import java.lang.String; 17 | import java.lang.StringBuilder; 18 | import okio.ByteString; 19 | 20 | public final class AudioEntity extends Message { 21 | public static final ProtoAdapter ADAPTER = new ProtoAdapter_AudioEntity(); 22 | 23 | private static final long serialVersionUID = 0L; 24 | 25 | public static final String DEFAULT_AUDIOKEY = ""; 26 | 27 | public static final Integer DEFAULT_STARTFRAME = 0; 28 | 29 | public static final Integer DEFAULT_ENDFRAME = 0; 30 | 31 | public static final Integer DEFAULT_STARTTIME = 0; 32 | 33 | public static final Integer DEFAULT_TOTALTIME = 0; 34 | 35 | /** 36 | * 音频文件名 37 | */ 38 | @WireField( 39 | tag = 1, 40 | adapter = "com.squareup.wire.ProtoAdapter#STRING" 41 | ) 42 | public final String audioKey; 43 | 44 | /** 45 | * 音频播放起始帧 46 | */ 47 | @WireField( 48 | tag = 2, 49 | adapter = "com.squareup.wire.ProtoAdapter#INT32" 50 | ) 51 | public final Integer startFrame; 52 | 53 | /** 54 | * 音频播放结束帧 55 | */ 56 | @WireField( 57 | tag = 3, 58 | adapter = "com.squareup.wire.ProtoAdapter#INT32" 59 | ) 60 | public final Integer endFrame; 61 | 62 | /** 63 | * 音频播放起始时间(相对音频长度) 64 | */ 65 | @WireField( 66 | tag = 4, 67 | adapter = "com.squareup.wire.ProtoAdapter#INT32" 68 | ) 69 | public final Integer startTime; 70 | 71 | /** 72 | * 音频总长度 73 | */ 74 | @WireField( 75 | tag = 5, 76 | adapter = "com.squareup.wire.ProtoAdapter#INT32" 77 | ) 78 | public final Integer totalTime; 79 | 80 | public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime) { 81 | this(audioKey, startFrame, endFrame, startTime, totalTime, ByteString.EMPTY); 82 | } 83 | 84 | public AudioEntity(String audioKey, Integer startFrame, Integer endFrame, Integer startTime, Integer totalTime, ByteString unknownFields) { 85 | super(ADAPTER, unknownFields); 86 | this.audioKey = audioKey; 87 | this.startFrame = startFrame; 88 | this.endFrame = endFrame; 89 | this.startTime = startTime; 90 | this.totalTime = totalTime; 91 | } 92 | 93 | @Override 94 | public Builder newBuilder() { 95 | Builder builder = new Builder(); 96 | builder.audioKey = audioKey; 97 | builder.startFrame = startFrame; 98 | builder.endFrame = endFrame; 99 | builder.startTime = startTime; 100 | builder.totalTime = totalTime; 101 | builder.addUnknownFields(unknownFields()); 102 | return builder; 103 | } 104 | 105 | @Override 106 | public boolean equals(Object other) { 107 | if (other == this) return true; 108 | if (!(other instanceof AudioEntity)) return false; 109 | AudioEntity o = (AudioEntity) other; 110 | return unknownFields().equals(o.unknownFields()) 111 | && Internal.equals(audioKey, o.audioKey) 112 | && Internal.equals(startFrame, o.startFrame) 113 | && Internal.equals(endFrame, o.endFrame) 114 | && Internal.equals(startTime, o.startTime) 115 | && Internal.equals(totalTime, o.totalTime); 116 | } 117 | 118 | @Override 119 | public int hashCode() { 120 | int result = super.hashCode; 121 | if (result == 0) { 122 | result = unknownFields().hashCode(); 123 | result = result * 37 + (audioKey != null ? audioKey.hashCode() : 0); 124 | result = result * 37 + (startFrame != null ? startFrame.hashCode() : 0); 125 | result = result * 37 + (endFrame != null ? endFrame.hashCode() : 0); 126 | result = result * 37 + (startTime != null ? startTime.hashCode() : 0); 127 | result = result * 37 + (totalTime != null ? totalTime.hashCode() : 0); 128 | super.hashCode = result; 129 | } 130 | return result; 131 | } 132 | 133 | @Override 134 | public String toString() { 135 | StringBuilder builder = new StringBuilder(); 136 | if (audioKey != null) builder.append(", audioKey=").append(audioKey); 137 | if (startFrame != null) builder.append(", startFrame=").append(startFrame); 138 | if (endFrame != null) builder.append(", endFrame=").append(endFrame); 139 | if (startTime != null) builder.append(", startTime=").append(startTime); 140 | if (totalTime != null) builder.append(", totalTime=").append(totalTime); 141 | return builder.replace(0, 2, "AudioEntity{").append('}').toString(); 142 | } 143 | 144 | public static final class Builder extends Message.Builder { 145 | public String audioKey; 146 | 147 | public Integer startFrame; 148 | 149 | public Integer endFrame; 150 | 151 | public Integer startTime; 152 | 153 | public Integer totalTime; 154 | 155 | public Builder() { 156 | } 157 | 158 | /** 159 | * 音频文件名 160 | */ 161 | public Builder audioKey(String audioKey) { 162 | this.audioKey = audioKey; 163 | return this; 164 | } 165 | 166 | /** 167 | * 音频播放起始帧 168 | */ 169 | public Builder startFrame(Integer startFrame) { 170 | this.startFrame = startFrame; 171 | return this; 172 | } 173 | 174 | /** 175 | * 音频播放结束帧 176 | */ 177 | public Builder endFrame(Integer endFrame) { 178 | this.endFrame = endFrame; 179 | return this; 180 | } 181 | 182 | /** 183 | * 音频播放起始时间(相对音频长度) 184 | */ 185 | public Builder startTime(Integer startTime) { 186 | this.startTime = startTime; 187 | return this; 188 | } 189 | 190 | /** 191 | * 音频总长度 192 | */ 193 | public Builder totalTime(Integer totalTime) { 194 | this.totalTime = totalTime; 195 | return this; 196 | } 197 | 198 | @Override 199 | public AudioEntity build() { 200 | return new AudioEntity(audioKey, startFrame, endFrame, startTime, totalTime, super.buildUnknownFields()); 201 | } 202 | } 203 | 204 | private static final class ProtoAdapter_AudioEntity extends ProtoAdapter { 205 | ProtoAdapter_AudioEntity() { 206 | super(FieldEncoding.LENGTH_DELIMITED, AudioEntity.class); 207 | } 208 | 209 | @Override 210 | public int encodedSize(AudioEntity value) { 211 | return (value.audioKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.audioKey) : 0) 212 | + (value.startFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(2, value.startFrame) : 0) 213 | + (value.endFrame != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.endFrame) : 0) 214 | + (value.startTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.startTime) : 0) 215 | + (value.totalTime != null ? ProtoAdapter.INT32.encodedSizeWithTag(5, value.totalTime) : 0) 216 | + value.unknownFields().size(); 217 | } 218 | 219 | @Override 220 | public void encode(ProtoWriter writer, AudioEntity value) throws IOException { 221 | if (value.audioKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.audioKey); 222 | if (value.startFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 2, value.startFrame); 223 | if (value.endFrame != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.endFrame); 224 | if (value.startTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.startTime); 225 | if (value.totalTime != null) ProtoAdapter.INT32.encodeWithTag(writer, 5, value.totalTime); 226 | writer.writeBytes(value.unknownFields()); 227 | } 228 | 229 | @Override 230 | public AudioEntity decode(ProtoReader reader) throws IOException { 231 | Builder builder = new Builder(); 232 | long token = reader.beginMessage(); 233 | for (int tag; (tag = reader.nextTag()) != -1;) { 234 | switch (tag) { 235 | case 1: builder.audioKey(ProtoAdapter.STRING.decode(reader)); break; 236 | case 2: builder.startFrame(ProtoAdapter.INT32.decode(reader)); break; 237 | case 3: builder.endFrame(ProtoAdapter.INT32.decode(reader)); break; 238 | case 4: builder.startTime(ProtoAdapter.INT32.decode(reader)); break; 239 | case 5: builder.totalTime(ProtoAdapter.INT32.decode(reader)); break; 240 | default: { 241 | FieldEncoding fieldEncoding = reader.peekFieldEncoding(); 242 | Object value = fieldEncoding.rawProtoAdapter().decode(reader); 243 | builder.addUnknownField(tag, fieldEncoding, value); 244 | } 245 | } 246 | } 247 | reader.endMessage(token); 248 | return builder.build(); 249 | } 250 | 251 | @Override 252 | public AudioEntity redact(AudioEntity value) { 253 | Builder builder = value.newBuilder(); 254 | builder.clearUnknownFields(); 255 | return builder.build(); 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/proto/FrameEntity.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: svga.proto at 115:1 3 | package com.opensource.svgaplayer.proto; 4 | 5 | import com.squareup.wire.FieldEncoding; 6 | import com.squareup.wire.Message; 7 | import com.squareup.wire.ProtoAdapter; 8 | import com.squareup.wire.ProtoReader; 9 | import com.squareup.wire.ProtoWriter; 10 | import com.squareup.wire.WireField; 11 | import com.squareup.wire.internal.Internal; 12 | import java.io.IOException; 13 | import java.lang.Float; 14 | import java.lang.Object; 15 | import java.lang.Override; 16 | import java.lang.String; 17 | import java.lang.StringBuilder; 18 | import java.util.List; 19 | import okio.ByteString; 20 | 21 | public final class FrameEntity extends Message { 22 | public static final ProtoAdapter ADAPTER = new ProtoAdapter_FrameEntity(); 23 | 24 | private static final long serialVersionUID = 0L; 25 | 26 | public static final Float DEFAULT_ALPHA = 0.0f; 27 | 28 | public static final String DEFAULT_CLIPPATH = ""; 29 | 30 | /** 31 | * 透明度 32 | */ 33 | @WireField( 34 | tag = 1, 35 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 36 | ) 37 | public final Float alpha; 38 | 39 | /** 40 | * 初始约束大小 41 | */ 42 | @WireField( 43 | tag = 2, 44 | adapter = "com.opensource.svgaplayer.proto.Layout#ADAPTER" 45 | ) 46 | public final Layout layout; 47 | 48 | /** 49 | * 2D 变换矩阵 50 | */ 51 | @WireField( 52 | tag = 3, 53 | adapter = "com.opensource.svgaplayer.proto.Transform#ADAPTER" 54 | ) 55 | public final Transform transform; 56 | 57 | /** 58 | * 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。 59 | */ 60 | @WireField( 61 | tag = 4, 62 | adapter = "com.squareup.wire.ProtoAdapter#STRING" 63 | ) 64 | public final String clipPath; 65 | 66 | /** 67 | * 矢量元素列表 68 | */ 69 | @WireField( 70 | tag = 5, 71 | adapter = "com.opensource.svgaplayer.proto.ShapeEntity#ADAPTER", 72 | label = WireField.Label.REPEATED 73 | ) 74 | public final List shapes; 75 | 76 | public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List shapes) { 77 | this(alpha, layout, transform, clipPath, shapes, ByteString.EMPTY); 78 | } 79 | 80 | public FrameEntity(Float alpha, Layout layout, Transform transform, String clipPath, List shapes, ByteString unknownFields) { 81 | super(ADAPTER, unknownFields); 82 | this.alpha = alpha; 83 | this.layout = layout; 84 | this.transform = transform; 85 | this.clipPath = clipPath; 86 | this.shapes = Internal.immutableCopyOf("shapes", shapes); 87 | } 88 | 89 | @Override 90 | public Builder newBuilder() { 91 | Builder builder = new Builder(); 92 | builder.alpha = alpha; 93 | builder.layout = layout; 94 | builder.transform = transform; 95 | builder.clipPath = clipPath; 96 | builder.shapes = Internal.copyOf("shapes", shapes); 97 | builder.addUnknownFields(unknownFields()); 98 | return builder; 99 | } 100 | 101 | @Override 102 | public boolean equals(Object other) { 103 | if (other == this) return true; 104 | if (!(other instanceof FrameEntity)) return false; 105 | FrameEntity o = (FrameEntity) other; 106 | return unknownFields().equals(o.unknownFields()) 107 | && Internal.equals(alpha, o.alpha) 108 | && Internal.equals(layout, o.layout) 109 | && Internal.equals(transform, o.transform) 110 | && Internal.equals(clipPath, o.clipPath) 111 | && shapes.equals(o.shapes); 112 | } 113 | 114 | @Override 115 | public int hashCode() { 116 | int result = super.hashCode; 117 | if (result == 0) { 118 | result = unknownFields().hashCode(); 119 | result = result * 37 + (alpha != null ? alpha.hashCode() : 0); 120 | result = result * 37 + (layout != null ? layout.hashCode() : 0); 121 | result = result * 37 + (transform != null ? transform.hashCode() : 0); 122 | result = result * 37 + (clipPath != null ? clipPath.hashCode() : 0); 123 | result = result * 37 + shapes.hashCode(); 124 | super.hashCode = result; 125 | } 126 | return result; 127 | } 128 | 129 | @Override 130 | public String toString() { 131 | StringBuilder builder = new StringBuilder(); 132 | if (alpha != null) builder.append(", alpha=").append(alpha); 133 | if (layout != null) builder.append(", layout=").append(layout); 134 | if (transform != null) builder.append(", transform=").append(transform); 135 | if (clipPath != null) builder.append(", clipPath=").append(clipPath); 136 | if (!shapes.isEmpty()) builder.append(", shapes=").append(shapes); 137 | return builder.replace(0, 2, "FrameEntity{").append('}').toString(); 138 | } 139 | 140 | public static final class Builder extends Message.Builder { 141 | public Float alpha; 142 | 143 | public Layout layout; 144 | 145 | public Transform transform; 146 | 147 | public String clipPath; 148 | 149 | public List shapes; 150 | 151 | public Builder() { 152 | shapes = Internal.newMutableList(); 153 | } 154 | 155 | /** 156 | * 透明度 157 | */ 158 | public Builder alpha(Float alpha) { 159 | this.alpha = alpha; 160 | return this; 161 | } 162 | 163 | /** 164 | * 初始约束大小 165 | */ 166 | public Builder layout(Layout layout) { 167 | this.layout = layout; 168 | return this; 169 | } 170 | 171 | /** 172 | * 2D 变换矩阵 173 | */ 174 | public Builder transform(Transform transform) { 175 | this.transform = transform; 176 | return this; 177 | } 178 | 179 | /** 180 | * 遮罩路径,使用 SVG 标准 Path 绘制图案进行 Mask 遮罩。 181 | */ 182 | public Builder clipPath(String clipPath) { 183 | this.clipPath = clipPath; 184 | return this; 185 | } 186 | 187 | /** 188 | * 矢量元素列表 189 | */ 190 | public Builder shapes(List shapes) { 191 | Internal.checkElementsNotNull(shapes); 192 | this.shapes = shapes; 193 | return this; 194 | } 195 | 196 | @Override 197 | public FrameEntity build() { 198 | return new FrameEntity(alpha, layout, transform, clipPath, shapes, super.buildUnknownFields()); 199 | } 200 | } 201 | 202 | private static final class ProtoAdapter_FrameEntity extends ProtoAdapter { 203 | ProtoAdapter_FrameEntity() { 204 | super(FieldEncoding.LENGTH_DELIMITED, FrameEntity.class); 205 | } 206 | 207 | @Override 208 | public int encodedSize(FrameEntity value) { 209 | return (value.alpha != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.alpha) : 0) 210 | + (value.layout != null ? Layout.ADAPTER.encodedSizeWithTag(2, value.layout) : 0) 211 | + (value.transform != null ? Transform.ADAPTER.encodedSizeWithTag(3, value.transform) : 0) 212 | + (value.clipPath != null ? ProtoAdapter.STRING.encodedSizeWithTag(4, value.clipPath) : 0) 213 | + ShapeEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.shapes) 214 | + value.unknownFields().size(); 215 | } 216 | 217 | @Override 218 | public void encode(ProtoWriter writer, FrameEntity value) throws IOException { 219 | if (value.alpha != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.alpha); 220 | if (value.layout != null) Layout.ADAPTER.encodeWithTag(writer, 2, value.layout); 221 | if (value.transform != null) Transform.ADAPTER.encodeWithTag(writer, 3, value.transform); 222 | if (value.clipPath != null) ProtoAdapter.STRING.encodeWithTag(writer, 4, value.clipPath); 223 | ShapeEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.shapes); 224 | writer.writeBytes(value.unknownFields()); 225 | } 226 | 227 | @Override 228 | public FrameEntity decode(ProtoReader reader) throws IOException { 229 | Builder builder = new Builder(); 230 | long token = reader.beginMessage(); 231 | for (int tag; (tag = reader.nextTag()) != -1;) { 232 | switch (tag) { 233 | case 1: builder.alpha(ProtoAdapter.FLOAT.decode(reader)); break; 234 | case 2: builder.layout(Layout.ADAPTER.decode(reader)); break; 235 | case 3: builder.transform(Transform.ADAPTER.decode(reader)); break; 236 | case 4: builder.clipPath(ProtoAdapter.STRING.decode(reader)); break; 237 | case 5: builder.shapes.add(ShapeEntity.ADAPTER.decode(reader)); break; 238 | default: { 239 | FieldEncoding fieldEncoding = reader.peekFieldEncoding(); 240 | Object value = fieldEncoding.rawProtoAdapter().decode(reader); 241 | builder.addUnknownField(tag, fieldEncoding, value); 242 | } 243 | } 244 | } 245 | reader.endMessage(token); 246 | return builder.build(); 247 | } 248 | 249 | @Override 250 | public FrameEntity redact(FrameEntity value) { 251 | Builder builder = value.newBuilder(); 252 | if (builder.layout != null) builder.layout = Layout.ADAPTER.redact(builder.layout); 253 | if (builder.transform != null) builder.transform = Transform.ADAPTER.redact(builder.transform); 254 | Internal.redactElements(builder.shapes, ShapeEntity.ADAPTER); 255 | builder.clearUnknownFields(); 256 | return builder.build(); 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/proto/Layout.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: svga.proto at 27:1 3 | package com.opensource.svgaplayer.proto; 4 | 5 | import com.squareup.wire.FieldEncoding; 6 | import com.squareup.wire.Message; 7 | import com.squareup.wire.ProtoAdapter; 8 | import com.squareup.wire.ProtoReader; 9 | import com.squareup.wire.ProtoWriter; 10 | import com.squareup.wire.WireField; 11 | import com.squareup.wire.internal.Internal; 12 | import java.io.IOException; 13 | import java.lang.Float; 14 | import java.lang.Object; 15 | import java.lang.Override; 16 | import java.lang.String; 17 | import java.lang.StringBuilder; 18 | import okio.ByteString; 19 | 20 | public final class Layout extends Message { 21 | public static final ProtoAdapter ADAPTER = new ProtoAdapter_Layout(); 22 | 23 | private static final long serialVersionUID = 0L; 24 | 25 | public static final Float DEFAULT_X = 0.0f; 26 | 27 | public static final Float DEFAULT_Y = 0.0f; 28 | 29 | public static final Float DEFAULT_WIDTH = 0.0f; 30 | 31 | public static final Float DEFAULT_HEIGHT = 0.0f; 32 | 33 | @WireField( 34 | tag = 1, 35 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 36 | ) 37 | public final Float x; 38 | 39 | @WireField( 40 | tag = 2, 41 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 42 | ) 43 | public final Float y; 44 | 45 | @WireField( 46 | tag = 3, 47 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 48 | ) 49 | public final Float width; 50 | 51 | @WireField( 52 | tag = 4, 53 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 54 | ) 55 | public final Float height; 56 | 57 | public Layout(Float x, Float y, Float width, Float height) { 58 | this(x, y, width, height, ByteString.EMPTY); 59 | } 60 | 61 | public Layout(Float x, Float y, Float width, Float height, ByteString unknownFields) { 62 | super(ADAPTER, unknownFields); 63 | this.x = x; 64 | this.y = y; 65 | this.width = width; 66 | this.height = height; 67 | } 68 | 69 | @Override 70 | public Builder newBuilder() { 71 | Builder builder = new Builder(); 72 | builder.x = x; 73 | builder.y = y; 74 | builder.width = width; 75 | builder.height = height; 76 | builder.addUnknownFields(unknownFields()); 77 | return builder; 78 | } 79 | 80 | @Override 81 | public boolean equals(Object other) { 82 | if (other == this) return true; 83 | if (!(other instanceof Layout)) return false; 84 | Layout o = (Layout) other; 85 | return unknownFields().equals(o.unknownFields()) 86 | && Internal.equals(x, o.x) 87 | && Internal.equals(y, o.y) 88 | && Internal.equals(width, o.width) 89 | && Internal.equals(height, o.height); 90 | } 91 | 92 | @Override 93 | public int hashCode() { 94 | int result = super.hashCode; 95 | if (result == 0) { 96 | result = unknownFields().hashCode(); 97 | result = result * 37 + (x != null ? x.hashCode() : 0); 98 | result = result * 37 + (y != null ? y.hashCode() : 0); 99 | result = result * 37 + (width != null ? width.hashCode() : 0); 100 | result = result * 37 + (height != null ? height.hashCode() : 0); 101 | super.hashCode = result; 102 | } 103 | return result; 104 | } 105 | 106 | @Override 107 | public String toString() { 108 | StringBuilder builder = new StringBuilder(); 109 | if (x != null) builder.append(", x=").append(x); 110 | if (y != null) builder.append(", y=").append(y); 111 | if (width != null) builder.append(", width=").append(width); 112 | if (height != null) builder.append(", height=").append(height); 113 | return builder.replace(0, 2, "Layout{").append('}').toString(); 114 | } 115 | 116 | public static final class Builder extends Message.Builder { 117 | public Float x; 118 | 119 | public Float y; 120 | 121 | public Float width; 122 | 123 | public Float height; 124 | 125 | public Builder() { 126 | } 127 | 128 | public Builder x(Float x) { 129 | this.x = x; 130 | return this; 131 | } 132 | 133 | public Builder y(Float y) { 134 | this.y = y; 135 | return this; 136 | } 137 | 138 | public Builder width(Float width) { 139 | this.width = width; 140 | return this; 141 | } 142 | 143 | public Builder height(Float height) { 144 | this.height = height; 145 | return this; 146 | } 147 | 148 | @Override 149 | public Layout build() { 150 | return new Layout(x, y, width, height, super.buildUnknownFields()); 151 | } 152 | } 153 | 154 | private static final class ProtoAdapter_Layout extends ProtoAdapter { 155 | ProtoAdapter_Layout() { 156 | super(FieldEncoding.LENGTH_DELIMITED, Layout.class); 157 | } 158 | 159 | @Override 160 | public int encodedSize(Layout value) { 161 | return (value.x != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.x) : 0) 162 | + (value.y != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.y) : 0) 163 | + (value.width != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.width) : 0) 164 | + (value.height != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.height) : 0) 165 | + value.unknownFields().size(); 166 | } 167 | 168 | @Override 169 | public void encode(ProtoWriter writer, Layout value) throws IOException { 170 | if (value.x != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.x); 171 | if (value.y != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.y); 172 | if (value.width != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.width); 173 | if (value.height != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.height); 174 | writer.writeBytes(value.unknownFields()); 175 | } 176 | 177 | @Override 178 | public Layout decode(ProtoReader reader) throws IOException { 179 | Builder builder = new Builder(); 180 | long token = reader.beginMessage(); 181 | for (int tag; (tag = reader.nextTag()) != -1;) { 182 | switch (tag) { 183 | case 1: builder.x(ProtoAdapter.FLOAT.decode(reader)); break; 184 | case 2: builder.y(ProtoAdapter.FLOAT.decode(reader)); break; 185 | case 3: builder.width(ProtoAdapter.FLOAT.decode(reader)); break; 186 | case 4: builder.height(ProtoAdapter.FLOAT.decode(reader)); break; 187 | default: { 188 | FieldEncoding fieldEncoding = reader.peekFieldEncoding(); 189 | Object value = fieldEncoding.rawProtoAdapter().decode(reader); 190 | builder.addUnknownField(tag, fieldEncoding, value); 191 | } 192 | } 193 | } 194 | reader.endMessage(token); 195 | return builder.build(); 196 | } 197 | 198 | @Override 199 | public Layout redact(Layout value) { 200 | Builder builder = value.newBuilder(); 201 | builder.clearUnknownFields(); 202 | return builder.build(); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/proto/MovieEntity.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: svga.proto at 123:1 3 | package com.opensource.svgaplayer.proto; 4 | 5 | import com.squareup.wire.FieldEncoding; 6 | import com.squareup.wire.Message; 7 | import com.squareup.wire.ProtoAdapter; 8 | import com.squareup.wire.ProtoReader; 9 | import com.squareup.wire.ProtoWriter; 10 | import com.squareup.wire.WireField; 11 | import com.squareup.wire.internal.Internal; 12 | import java.io.IOException; 13 | import java.lang.Object; 14 | import java.lang.Override; 15 | import java.lang.String; 16 | import java.lang.StringBuilder; 17 | import java.util.List; 18 | import java.util.Map; 19 | import okio.ByteString; 20 | 21 | public final class MovieEntity extends Message { 22 | public static final ProtoAdapter ADAPTER = new ProtoAdapter_MovieEntity(); 23 | 24 | private static final long serialVersionUID = 0L; 25 | 26 | public static final String DEFAULT_VERSION = ""; 27 | 28 | /** 29 | * SVGA 格式版本号 30 | */ 31 | @WireField( 32 | tag = 1, 33 | adapter = "com.squareup.wire.ProtoAdapter#STRING" 34 | ) 35 | public final String version; 36 | 37 | /** 38 | * 动画参数 39 | */ 40 | @WireField( 41 | tag = 2, 42 | adapter = "com.opensource.svgaplayer.proto.MovieParams#ADAPTER" 43 | ) 44 | public final MovieParams params; 45 | 46 | /** 47 | * Key 是位图键名,Value 是位图文件名或二进制 PNG 数据。 48 | */ 49 | @WireField( 50 | tag = 3, 51 | keyAdapter = "com.squareup.wire.ProtoAdapter#STRING", 52 | adapter = "com.squareup.wire.ProtoAdapter#BYTES" 53 | ) 54 | public final Map images; 55 | 56 | /** 57 | * 元素列表 58 | */ 59 | @WireField( 60 | tag = 4, 61 | adapter = "com.opensource.svgaplayer.proto.SpriteEntity#ADAPTER", 62 | label = WireField.Label.REPEATED 63 | ) 64 | public final List sprites; 65 | 66 | /** 67 | * 音频列表 68 | */ 69 | @WireField( 70 | tag = 5, 71 | adapter = "com.opensource.svgaplayer.proto.AudioEntity#ADAPTER", 72 | label = WireField.Label.REPEATED 73 | ) 74 | public final List audios; 75 | 76 | public MovieEntity(String version, MovieParams params, Map images, List sprites, List audios) { 77 | this(version, params, images, sprites, audios, ByteString.EMPTY); 78 | } 79 | 80 | public MovieEntity(String version, MovieParams params, Map images, List sprites, List audios, ByteString unknownFields) { 81 | super(ADAPTER, unknownFields); 82 | this.version = version; 83 | this.params = params; 84 | this.images = Internal.immutableCopyOf("images", images); 85 | this.sprites = Internal.immutableCopyOf("sprites", sprites); 86 | this.audios = Internal.immutableCopyOf("audios", audios); 87 | } 88 | 89 | @Override 90 | public Builder newBuilder() { 91 | Builder builder = new Builder(); 92 | builder.version = version; 93 | builder.params = params; 94 | builder.images = Internal.copyOf("images", images); 95 | builder.sprites = Internal.copyOf("sprites", sprites); 96 | builder.audios = Internal.copyOf("audios", audios); 97 | builder.addUnknownFields(unknownFields()); 98 | return builder; 99 | } 100 | 101 | @Override 102 | public boolean equals(Object other) { 103 | if (other == this) return true; 104 | if (!(other instanceof MovieEntity)) return false; 105 | MovieEntity o = (MovieEntity) other; 106 | return unknownFields().equals(o.unknownFields()) 107 | && Internal.equals(version, o.version) 108 | && Internal.equals(params, o.params) 109 | && images.equals(o.images) 110 | && sprites.equals(o.sprites) 111 | && audios.equals(o.audios); 112 | } 113 | 114 | @Override 115 | public int hashCode() { 116 | int result = super.hashCode; 117 | if (result == 0) { 118 | result = unknownFields().hashCode(); 119 | result = result * 37 + (version != null ? version.hashCode() : 0); 120 | result = result * 37 + (params != null ? params.hashCode() : 0); 121 | result = result * 37 + images.hashCode(); 122 | result = result * 37 + sprites.hashCode(); 123 | result = result * 37 + audios.hashCode(); 124 | super.hashCode = result; 125 | } 126 | return result; 127 | } 128 | 129 | @Override 130 | public String toString() { 131 | StringBuilder builder = new StringBuilder(); 132 | if (version != null) builder.append(", version=").append(version); 133 | if (params != null) builder.append(", params=").append(params); 134 | if (!images.isEmpty()) builder.append(", images=").append(images); 135 | if (!sprites.isEmpty()) builder.append(", sprites=").append(sprites); 136 | if (!audios.isEmpty()) builder.append(", audios=").append(audios); 137 | return builder.replace(0, 2, "MovieEntity{").append('}').toString(); 138 | } 139 | 140 | public static final class Builder extends Message.Builder { 141 | public String version; 142 | 143 | public MovieParams params; 144 | 145 | public Map images; 146 | 147 | public List sprites; 148 | 149 | public List audios; 150 | 151 | public Builder() { 152 | images = Internal.newMutableMap(); 153 | sprites = Internal.newMutableList(); 154 | audios = Internal.newMutableList(); 155 | } 156 | 157 | /** 158 | * SVGA 格式版本号 159 | */ 160 | public Builder version(String version) { 161 | this.version = version; 162 | return this; 163 | } 164 | 165 | /** 166 | * 动画参数 167 | */ 168 | public Builder params(MovieParams params) { 169 | this.params = params; 170 | return this; 171 | } 172 | 173 | /** 174 | * Key 是位图键名,Value 是位图文件名或二进制 PNG 数据。 175 | */ 176 | public Builder images(Map images) { 177 | Internal.checkElementsNotNull(images); 178 | this.images = images; 179 | return this; 180 | } 181 | 182 | /** 183 | * 元素列表 184 | */ 185 | public Builder sprites(List sprites) { 186 | Internal.checkElementsNotNull(sprites); 187 | this.sprites = sprites; 188 | return this; 189 | } 190 | 191 | /** 192 | * 音频列表 193 | */ 194 | public Builder audios(List audios) { 195 | Internal.checkElementsNotNull(audios); 196 | this.audios = audios; 197 | return this; 198 | } 199 | 200 | @Override 201 | public MovieEntity build() { 202 | return new MovieEntity(version, params, images, sprites, audios, super.buildUnknownFields()); 203 | } 204 | } 205 | 206 | private static final class ProtoAdapter_MovieEntity extends ProtoAdapter { 207 | private final ProtoAdapter> images = ProtoAdapter.newMapAdapter(ProtoAdapter.STRING, ProtoAdapter.BYTES); 208 | 209 | ProtoAdapter_MovieEntity() { 210 | super(FieldEncoding.LENGTH_DELIMITED, MovieEntity.class); 211 | } 212 | 213 | @Override 214 | public int encodedSize(MovieEntity value) { 215 | return (value.version != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.version) : 0) 216 | + (value.params != null ? MovieParams.ADAPTER.encodedSizeWithTag(2, value.params) : 0) 217 | + images.encodedSizeWithTag(3, value.images) 218 | + SpriteEntity.ADAPTER.asRepeated().encodedSizeWithTag(4, value.sprites) 219 | + AudioEntity.ADAPTER.asRepeated().encodedSizeWithTag(5, value.audios) 220 | + value.unknownFields().size(); 221 | } 222 | 223 | @Override 224 | public void encode(ProtoWriter writer, MovieEntity value) throws IOException { 225 | if (value.version != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.version); 226 | if (value.params != null) MovieParams.ADAPTER.encodeWithTag(writer, 2, value.params); 227 | images.encodeWithTag(writer, 3, value.images); 228 | SpriteEntity.ADAPTER.asRepeated().encodeWithTag(writer, 4, value.sprites); 229 | AudioEntity.ADAPTER.asRepeated().encodeWithTag(writer, 5, value.audios); 230 | writer.writeBytes(value.unknownFields()); 231 | } 232 | 233 | @Override 234 | public MovieEntity decode(ProtoReader reader) throws IOException { 235 | Builder builder = new Builder(); 236 | long token = reader.beginMessage(); 237 | for (int tag; (tag = reader.nextTag()) != -1;) { 238 | switch (tag) { 239 | case 1: builder.version(ProtoAdapter.STRING.decode(reader)); break; 240 | case 2: builder.params(MovieParams.ADAPTER.decode(reader)); break; 241 | case 3: builder.images.putAll(images.decode(reader)); break; 242 | case 4: builder.sprites.add(SpriteEntity.ADAPTER.decode(reader)); break; 243 | case 5: builder.audios.add(AudioEntity.ADAPTER.decode(reader)); break; 244 | default: { 245 | FieldEncoding fieldEncoding = reader.peekFieldEncoding(); 246 | Object value = fieldEncoding.rawProtoAdapter().decode(reader); 247 | builder.addUnknownField(tag, fieldEncoding, value); 248 | } 249 | } 250 | } 251 | reader.endMessage(token); 252 | return builder.build(); 253 | } 254 | 255 | @Override 256 | public MovieEntity redact(MovieEntity value) { 257 | Builder builder = value.newBuilder(); 258 | if (builder.params != null) builder.params = MovieParams.ADAPTER.redact(builder.params); 259 | Internal.redactElements(builder.sprites, SpriteEntity.ADAPTER); 260 | Internal.redactElements(builder.audios, AudioEntity.ADAPTER); 261 | builder.clearUnknownFields(); 262 | return builder.build(); 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/proto/MovieParams.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: svga.proto at 6:1 3 | package com.opensource.svgaplayer.proto; 4 | 5 | import com.squareup.wire.FieldEncoding; 6 | import com.squareup.wire.Message; 7 | import com.squareup.wire.ProtoAdapter; 8 | import com.squareup.wire.ProtoReader; 9 | import com.squareup.wire.ProtoWriter; 10 | import com.squareup.wire.WireField; 11 | import com.squareup.wire.internal.Internal; 12 | import java.io.IOException; 13 | import java.lang.Float; 14 | import java.lang.Integer; 15 | import java.lang.Object; 16 | import java.lang.Override; 17 | import java.lang.String; 18 | import java.lang.StringBuilder; 19 | import okio.ByteString; 20 | 21 | public final class MovieParams extends Message { 22 | public static final ProtoAdapter ADAPTER = new ProtoAdapter_MovieParams(); 23 | 24 | private static final long serialVersionUID = 0L; 25 | 26 | public static final Float DEFAULT_VIEWBOXWIDTH = 0.0f; 27 | 28 | public static final Float DEFAULT_VIEWBOXHEIGHT = 0.0f; 29 | 30 | public static final Integer DEFAULT_FPS = 0; 31 | 32 | public static final Integer DEFAULT_FRAMES = 0; 33 | 34 | /** 35 | * 画布宽 36 | */ 37 | @WireField( 38 | tag = 1, 39 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 40 | ) 41 | public final Float viewBoxWidth; 42 | 43 | /** 44 | * 画布高 45 | */ 46 | @WireField( 47 | tag = 2, 48 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 49 | ) 50 | public final Float viewBoxHeight; 51 | 52 | /** 53 | * 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。 54 | */ 55 | @WireField( 56 | tag = 3, 57 | adapter = "com.squareup.wire.ProtoAdapter#INT32" 58 | ) 59 | public final Integer fps; 60 | 61 | /** 62 | * 动画总帧数 63 | */ 64 | @WireField( 65 | tag = 4, 66 | adapter = "com.squareup.wire.ProtoAdapter#INT32" 67 | ) 68 | public final Integer frames; 69 | 70 | public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames) { 71 | this(viewBoxWidth, viewBoxHeight, fps, frames, ByteString.EMPTY); 72 | } 73 | 74 | public MovieParams(Float viewBoxWidth, Float viewBoxHeight, Integer fps, Integer frames, ByteString unknownFields) { 75 | super(ADAPTER, unknownFields); 76 | this.viewBoxWidth = viewBoxWidth; 77 | this.viewBoxHeight = viewBoxHeight; 78 | this.fps = fps; 79 | this.frames = frames; 80 | } 81 | 82 | @Override 83 | public Builder newBuilder() { 84 | Builder builder = new Builder(); 85 | builder.viewBoxWidth = viewBoxWidth; 86 | builder.viewBoxHeight = viewBoxHeight; 87 | builder.fps = fps; 88 | builder.frames = frames; 89 | builder.addUnknownFields(unknownFields()); 90 | return builder; 91 | } 92 | 93 | @Override 94 | public boolean equals(Object other) { 95 | if (other == this) return true; 96 | if (!(other instanceof MovieParams)) return false; 97 | MovieParams o = (MovieParams) other; 98 | return unknownFields().equals(o.unknownFields()) 99 | && Internal.equals(viewBoxWidth, o.viewBoxWidth) 100 | && Internal.equals(viewBoxHeight, o.viewBoxHeight) 101 | && Internal.equals(fps, o.fps) 102 | && Internal.equals(frames, o.frames); 103 | } 104 | 105 | @Override 106 | public int hashCode() { 107 | int result = super.hashCode; 108 | if (result == 0) { 109 | result = unknownFields().hashCode(); 110 | result = result * 37 + (viewBoxWidth != null ? viewBoxWidth.hashCode() : 0); 111 | result = result * 37 + (viewBoxHeight != null ? viewBoxHeight.hashCode() : 0); 112 | result = result * 37 + (fps != null ? fps.hashCode() : 0); 113 | result = result * 37 + (frames != null ? frames.hashCode() : 0); 114 | super.hashCode = result; 115 | } 116 | return result; 117 | } 118 | 119 | @Override 120 | public String toString() { 121 | StringBuilder builder = new StringBuilder(); 122 | if (viewBoxWidth != null) builder.append(", viewBoxWidth=").append(viewBoxWidth); 123 | if (viewBoxHeight != null) builder.append(", viewBoxHeight=").append(viewBoxHeight); 124 | if (fps != null) builder.append(", fps=").append(fps); 125 | if (frames != null) builder.append(", frames=").append(frames); 126 | return builder.replace(0, 2, "MovieParams{").append('}').toString(); 127 | } 128 | 129 | public static final class Builder extends Message.Builder { 130 | public Float viewBoxWidth; 131 | 132 | public Float viewBoxHeight; 133 | 134 | public Integer fps; 135 | 136 | public Integer frames; 137 | 138 | public Builder() { 139 | } 140 | 141 | /** 142 | * 画布宽 143 | */ 144 | public Builder viewBoxWidth(Float viewBoxWidth) { 145 | this.viewBoxWidth = viewBoxWidth; 146 | return this; 147 | } 148 | 149 | /** 150 | * 画布高 151 | */ 152 | public Builder viewBoxHeight(Float viewBoxHeight) { 153 | this.viewBoxHeight = viewBoxHeight; 154 | return this; 155 | } 156 | 157 | /** 158 | * 动画每秒播放帧数,合法值是 [1, 2, 3, 5, 6, 10, 12, 15, 20, 30, 60] 中的任意一个。 159 | */ 160 | public Builder fps(Integer fps) { 161 | this.fps = fps; 162 | return this; 163 | } 164 | 165 | /** 166 | * 动画总帧数 167 | */ 168 | public Builder frames(Integer frames) { 169 | this.frames = frames; 170 | return this; 171 | } 172 | 173 | @Override 174 | public MovieParams build() { 175 | return new MovieParams(viewBoxWidth, viewBoxHeight, fps, frames, super.buildUnknownFields()); 176 | } 177 | } 178 | 179 | private static final class ProtoAdapter_MovieParams extends ProtoAdapter { 180 | ProtoAdapter_MovieParams() { 181 | super(FieldEncoding.LENGTH_DELIMITED, MovieParams.class); 182 | } 183 | 184 | @Override 185 | public int encodedSize(MovieParams value) { 186 | return (value.viewBoxWidth != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.viewBoxWidth) : 0) 187 | + (value.viewBoxHeight != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.viewBoxHeight) : 0) 188 | + (value.fps != null ? ProtoAdapter.INT32.encodedSizeWithTag(3, value.fps) : 0) 189 | + (value.frames != null ? ProtoAdapter.INT32.encodedSizeWithTag(4, value.frames) : 0) 190 | + value.unknownFields().size(); 191 | } 192 | 193 | @Override 194 | public void encode(ProtoWriter writer, MovieParams value) throws IOException { 195 | if (value.viewBoxWidth != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.viewBoxWidth); 196 | if (value.viewBoxHeight != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.viewBoxHeight); 197 | if (value.fps != null) ProtoAdapter.INT32.encodeWithTag(writer, 3, value.fps); 198 | if (value.frames != null) ProtoAdapter.INT32.encodeWithTag(writer, 4, value.frames); 199 | writer.writeBytes(value.unknownFields()); 200 | } 201 | 202 | @Override 203 | public MovieParams decode(ProtoReader reader) throws IOException { 204 | Builder builder = new Builder(); 205 | long token = reader.beginMessage(); 206 | for (int tag; (tag = reader.nextTag()) != -1;) { 207 | switch (tag) { 208 | case 1: builder.viewBoxWidth(ProtoAdapter.FLOAT.decode(reader)); break; 209 | case 2: builder.viewBoxHeight(ProtoAdapter.FLOAT.decode(reader)); break; 210 | case 3: builder.fps(ProtoAdapter.INT32.decode(reader)); break; 211 | case 4: builder.frames(ProtoAdapter.INT32.decode(reader)); break; 212 | default: { 213 | FieldEncoding fieldEncoding = reader.peekFieldEncoding(); 214 | Object value = fieldEncoding.rawProtoAdapter().decode(reader); 215 | builder.addUnknownField(tag, fieldEncoding, value); 216 | } 217 | } 218 | } 219 | reader.endMessage(token); 220 | return builder.build(); 221 | } 222 | 223 | @Override 224 | public MovieParams redact(MovieParams value) { 225 | Builder builder = value.newBuilder(); 226 | builder.clearUnknownFields(); 227 | return builder.build(); 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/proto/SpriteEntity.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: svga.proto at 13:1 3 | package com.opensource.svgaplayer.proto; 4 | 5 | import com.squareup.wire.FieldEncoding; 6 | import com.squareup.wire.Message; 7 | import com.squareup.wire.ProtoAdapter; 8 | import com.squareup.wire.ProtoReader; 9 | import com.squareup.wire.ProtoWriter; 10 | import com.squareup.wire.WireField; 11 | import com.squareup.wire.internal.Internal; 12 | import java.io.IOException; 13 | import java.lang.Object; 14 | import java.lang.Override; 15 | import java.lang.String; 16 | import java.lang.StringBuilder; 17 | import java.util.List; 18 | import okio.ByteString; 19 | 20 | public final class SpriteEntity extends Message { 21 | public static final ProtoAdapter ADAPTER = new ProtoAdapter_SpriteEntity(); 22 | 23 | private static final long serialVersionUID = 0L; 24 | 25 | public static final String DEFAULT_IMAGEKEY = ""; 26 | 27 | public static final String DEFAULT_MATTEKEY = ""; 28 | 29 | /** 30 | * 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。 31 | */ 32 | @WireField( 33 | tag = 1, 34 | adapter = "com.squareup.wire.ProtoAdapter#STRING" 35 | ) 36 | public final String imageKey; 37 | 38 | /** 39 | * 帧列表 40 | */ 41 | @WireField( 42 | tag = 2, 43 | adapter = "com.opensource.svgaplayer.proto.FrameEntity#ADAPTER", 44 | label = WireField.Label.REPEATED 45 | ) 46 | public final List frames; 47 | 48 | /** 49 | * 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey. 50 | */ 51 | @WireField( 52 | tag = 3, 53 | adapter = "com.squareup.wire.ProtoAdapter#STRING" 54 | ) 55 | public final String matteKey; 56 | 57 | public SpriteEntity(String imageKey, List frames, String matteKey) { 58 | this(imageKey, frames, matteKey, ByteString.EMPTY); 59 | } 60 | 61 | public SpriteEntity(String imageKey, List frames, String matteKey, ByteString unknownFields) { 62 | super(ADAPTER, unknownFields); 63 | this.imageKey = imageKey; 64 | this.frames = Internal.immutableCopyOf("frames", frames); 65 | this.matteKey = matteKey; 66 | } 67 | 68 | @Override 69 | public Builder newBuilder() { 70 | Builder builder = new Builder(); 71 | builder.imageKey = imageKey; 72 | builder.frames = Internal.copyOf("frames", frames); 73 | builder.matteKey = matteKey; 74 | builder.addUnknownFields(unknownFields()); 75 | return builder; 76 | } 77 | 78 | @Override 79 | public boolean equals(Object other) { 80 | if (other == this) return true; 81 | if (!(other instanceof SpriteEntity)) return false; 82 | SpriteEntity o = (SpriteEntity) other; 83 | return unknownFields().equals(o.unknownFields()) 84 | && Internal.equals(imageKey, o.imageKey) 85 | && frames.equals(o.frames) 86 | && Internal.equals(matteKey, o.matteKey); 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | int result = super.hashCode; 92 | if (result == 0) { 93 | result = unknownFields().hashCode(); 94 | result = result * 37 + (imageKey != null ? imageKey.hashCode() : 0); 95 | result = result * 37 + frames.hashCode(); 96 | result = result * 37 + (matteKey != null ? matteKey.hashCode() : 0); 97 | super.hashCode = result; 98 | } 99 | return result; 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | StringBuilder builder = new StringBuilder(); 105 | if (imageKey != null) builder.append(", imageKey=").append(imageKey); 106 | if (!frames.isEmpty()) builder.append(", frames=").append(frames); 107 | if (matteKey != null) builder.append(", matteKey=").append(matteKey); 108 | return builder.replace(0, 2, "SpriteEntity{").append('}').toString(); 109 | } 110 | 111 | public static final class Builder extends Message.Builder { 112 | public String imageKey; 113 | 114 | public List frames; 115 | 116 | public String matteKey; 117 | 118 | public Builder() { 119 | frames = Internal.newMutableList(); 120 | } 121 | 122 | /** 123 | * 元件所对应的位图键名, 如果 imageKey 含有 .vector 后缀,该 sprite 为矢量图层 含有 .matte 后缀,该 sprite 为遮罩图层。 124 | */ 125 | public Builder imageKey(String imageKey) { 126 | this.imageKey = imageKey; 127 | return this; 128 | } 129 | 130 | /** 131 | * 帧列表 132 | */ 133 | public Builder frames(List frames) { 134 | Internal.checkElementsNotNull(frames); 135 | this.frames = frames; 136 | return this; 137 | } 138 | 139 | /** 140 | * 被遮罩图层的 matteKey 对应的是其遮罩图层的 imageKey. 141 | */ 142 | public Builder matteKey(String matteKey) { 143 | this.matteKey = matteKey; 144 | return this; 145 | } 146 | 147 | @Override 148 | public SpriteEntity build() { 149 | return new SpriteEntity(imageKey, frames, matteKey, super.buildUnknownFields()); 150 | } 151 | } 152 | 153 | private static final class ProtoAdapter_SpriteEntity extends ProtoAdapter { 154 | ProtoAdapter_SpriteEntity() { 155 | super(FieldEncoding.LENGTH_DELIMITED, SpriteEntity.class); 156 | } 157 | 158 | @Override 159 | public int encodedSize(SpriteEntity value) { 160 | return (value.imageKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.imageKey) : 0) 161 | + FrameEntity.ADAPTER.asRepeated().encodedSizeWithTag(2, value.frames) 162 | + (value.matteKey != null ? ProtoAdapter.STRING.encodedSizeWithTag(3, value.matteKey) : 0) 163 | + value.unknownFields().size(); 164 | } 165 | 166 | @Override 167 | public void encode(ProtoWriter writer, SpriteEntity value) throws IOException { 168 | if (value.imageKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.imageKey); 169 | FrameEntity.ADAPTER.asRepeated().encodeWithTag(writer, 2, value.frames); 170 | if (value.matteKey != null) ProtoAdapter.STRING.encodeWithTag(writer, 3, value.matteKey); 171 | writer.writeBytes(value.unknownFields()); 172 | } 173 | 174 | @Override 175 | public SpriteEntity decode(ProtoReader reader) throws IOException { 176 | Builder builder = new Builder(); 177 | long token = reader.beginMessage(); 178 | for (int tag; (tag = reader.nextTag()) != -1;) { 179 | switch (tag) { 180 | case 1: builder.imageKey(ProtoAdapter.STRING.decode(reader)); break; 181 | case 2: builder.frames.add(FrameEntity.ADAPTER.decode(reader)); break; 182 | case 3: builder.matteKey(ProtoAdapter.STRING.decode(reader)); break; 183 | default: { 184 | FieldEncoding fieldEncoding = reader.peekFieldEncoding(); 185 | Object value = fieldEncoding.rawProtoAdapter().decode(reader); 186 | builder.addUnknownField(tag, fieldEncoding, value); 187 | } 188 | } 189 | } 190 | reader.endMessage(token); 191 | return builder.build(); 192 | } 193 | 194 | @Override 195 | public SpriteEntity redact(SpriteEntity value) { 196 | Builder builder = value.newBuilder(); 197 | Internal.redactElements(builder.frames, FrameEntity.ADAPTER); 198 | builder.clearUnknownFields(); 199 | return builder.build(); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/proto/Transform.java: -------------------------------------------------------------------------------- 1 | // Code generated by Wire protocol buffer compiler, do not edit. 2 | // Source file: svga.proto at 34:1 3 | package com.opensource.svgaplayer.proto; 4 | 5 | import com.squareup.wire.FieldEncoding; 6 | import com.squareup.wire.Message; 7 | import com.squareup.wire.ProtoAdapter; 8 | import com.squareup.wire.ProtoReader; 9 | import com.squareup.wire.ProtoWriter; 10 | import com.squareup.wire.WireField; 11 | import com.squareup.wire.internal.Internal; 12 | import java.io.IOException; 13 | import java.lang.Float; 14 | import java.lang.Object; 15 | import java.lang.Override; 16 | import java.lang.String; 17 | import java.lang.StringBuilder; 18 | import okio.ByteString; 19 | 20 | public final class Transform extends Message { 21 | public static final ProtoAdapter ADAPTER = new ProtoAdapter_Transform(); 22 | 23 | private static final long serialVersionUID = 0L; 24 | 25 | public static final Float DEFAULT_A = 0.0f; 26 | 27 | public static final Float DEFAULT_B = 0.0f; 28 | 29 | public static final Float DEFAULT_C = 0.0f; 30 | 31 | public static final Float DEFAULT_D = 0.0f; 32 | 33 | public static final Float DEFAULT_TX = 0.0f; 34 | 35 | public static final Float DEFAULT_TY = 0.0f; 36 | 37 | @WireField( 38 | tag = 1, 39 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 40 | ) 41 | public final Float a; 42 | 43 | @WireField( 44 | tag = 2, 45 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 46 | ) 47 | public final Float b; 48 | 49 | @WireField( 50 | tag = 3, 51 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 52 | ) 53 | public final Float c; 54 | 55 | @WireField( 56 | tag = 4, 57 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 58 | ) 59 | public final Float d; 60 | 61 | @WireField( 62 | tag = 5, 63 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 64 | ) 65 | public final Float tx; 66 | 67 | @WireField( 68 | tag = 6, 69 | adapter = "com.squareup.wire.ProtoAdapter#FLOAT" 70 | ) 71 | public final Float ty; 72 | 73 | public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty) { 74 | this(a, b, c, d, tx, ty, ByteString.EMPTY); 75 | } 76 | 77 | public Transform(Float a, Float b, Float c, Float d, Float tx, Float ty, ByteString unknownFields) { 78 | super(ADAPTER, unknownFields); 79 | this.a = a; 80 | this.b = b; 81 | this.c = c; 82 | this.d = d; 83 | this.tx = tx; 84 | this.ty = ty; 85 | } 86 | 87 | @Override 88 | public Builder newBuilder() { 89 | Builder builder = new Builder(); 90 | builder.a = a; 91 | builder.b = b; 92 | builder.c = c; 93 | builder.d = d; 94 | builder.tx = tx; 95 | builder.ty = ty; 96 | builder.addUnknownFields(unknownFields()); 97 | return builder; 98 | } 99 | 100 | @Override 101 | public boolean equals(Object other) { 102 | if (other == this) return true; 103 | if (!(other instanceof Transform)) return false; 104 | Transform o = (Transform) other; 105 | return unknownFields().equals(o.unknownFields()) 106 | && Internal.equals(a, o.a) 107 | && Internal.equals(b, o.b) 108 | && Internal.equals(c, o.c) 109 | && Internal.equals(d, o.d) 110 | && Internal.equals(tx, o.tx) 111 | && Internal.equals(ty, o.ty); 112 | } 113 | 114 | @Override 115 | public int hashCode() { 116 | int result = super.hashCode; 117 | if (result == 0) { 118 | result = unknownFields().hashCode(); 119 | result = result * 37 + (a != null ? a.hashCode() : 0); 120 | result = result * 37 + (b != null ? b.hashCode() : 0); 121 | result = result * 37 + (c != null ? c.hashCode() : 0); 122 | result = result * 37 + (d != null ? d.hashCode() : 0); 123 | result = result * 37 + (tx != null ? tx.hashCode() : 0); 124 | result = result * 37 + (ty != null ? ty.hashCode() : 0); 125 | super.hashCode = result; 126 | } 127 | return result; 128 | } 129 | 130 | @Override 131 | public String toString() { 132 | StringBuilder builder = new StringBuilder(); 133 | if (a != null) builder.append(", a=").append(a); 134 | if (b != null) builder.append(", b=").append(b); 135 | if (c != null) builder.append(", c=").append(c); 136 | if (d != null) builder.append(", d=").append(d); 137 | if (tx != null) builder.append(", tx=").append(tx); 138 | if (ty != null) builder.append(", ty=").append(ty); 139 | return builder.replace(0, 2, "Transform{").append('}').toString(); 140 | } 141 | 142 | public static final class Builder extends Message.Builder { 143 | public Float a; 144 | 145 | public Float b; 146 | 147 | public Float c; 148 | 149 | public Float d; 150 | 151 | public Float tx; 152 | 153 | public Float ty; 154 | 155 | public Builder() { 156 | } 157 | 158 | public Builder a(Float a) { 159 | this.a = a; 160 | return this; 161 | } 162 | 163 | public Builder b(Float b) { 164 | this.b = b; 165 | return this; 166 | } 167 | 168 | public Builder c(Float c) { 169 | this.c = c; 170 | return this; 171 | } 172 | 173 | public Builder d(Float d) { 174 | this.d = d; 175 | return this; 176 | } 177 | 178 | public Builder tx(Float tx) { 179 | this.tx = tx; 180 | return this; 181 | } 182 | 183 | public Builder ty(Float ty) { 184 | this.ty = ty; 185 | return this; 186 | } 187 | 188 | @Override 189 | public Transform build() { 190 | return new Transform(a, b, c, d, tx, ty, super.buildUnknownFields()); 191 | } 192 | } 193 | 194 | private static final class ProtoAdapter_Transform extends ProtoAdapter { 195 | ProtoAdapter_Transform() { 196 | super(FieldEncoding.LENGTH_DELIMITED, Transform.class); 197 | } 198 | 199 | @Override 200 | public int encodedSize(Transform value) { 201 | return (value.a != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.a) : 0) 202 | + (value.b != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.b) : 0) 203 | + (value.c != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.c) : 0) 204 | + (value.d != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.d) : 0) 205 | + (value.tx != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(5, value.tx) : 0) 206 | + (value.ty != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(6, value.ty) : 0) 207 | + value.unknownFields().size(); 208 | } 209 | 210 | @Override 211 | public void encode(ProtoWriter writer, Transform value) throws IOException { 212 | if (value.a != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.a); 213 | if (value.b != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.b); 214 | if (value.c != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.c); 215 | if (value.d != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.d); 216 | if (value.tx != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 5, value.tx); 217 | if (value.ty != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 6, value.ty); 218 | writer.writeBytes(value.unknownFields()); 219 | } 220 | 221 | @Override 222 | public Transform decode(ProtoReader reader) throws IOException { 223 | Builder builder = new Builder(); 224 | long token = reader.beginMessage(); 225 | for (int tag; (tag = reader.nextTag()) != -1;) { 226 | switch (tag) { 227 | case 1: builder.a(ProtoAdapter.FLOAT.decode(reader)); break; 228 | case 2: builder.b(ProtoAdapter.FLOAT.decode(reader)); break; 229 | case 3: builder.c(ProtoAdapter.FLOAT.decode(reader)); break; 230 | case 4: builder.d(ProtoAdapter.FLOAT.decode(reader)); break; 231 | case 5: builder.tx(ProtoAdapter.FLOAT.decode(reader)); break; 232 | case 6: builder.ty(ProtoAdapter.FLOAT.decode(reader)); break; 233 | default: { 234 | FieldEncoding fieldEncoding = reader.peekFieldEncoding(); 235 | Object value = fieldEncoding.rawProtoAdapter().decode(reader); 236 | builder.addUnknownField(tag, fieldEncoding, value); 237 | } 238 | } 239 | } 240 | reader.endMessage(token); 241 | return builder.build(); 242 | } 243 | 244 | @Override 245 | public Transform redact(Transform value) { 246 | Builder builder = value.newBuilder(); 247 | builder.clearUnknownFields(); 248 | return builder.build(); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/utils/Pools.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.utils 2 | 3 | /** 4 | * Helper class for creating pools of objects. An example use looks like this: 5 | *
  6 |  * public class MyPooledClass {
  7 |  *
  8 |  *     private static final SynchronizedPool sPool =
  9 |  *             new SynchronizedPool(10);
 10 |  *
 11 |  *     public static MyPooledClass obtain() {
 12 |  *         MyPooledClass instance = sPool.acquire();
 13 |  *         return (instance != null) ? instance : new MyPooledClass();
 14 |  *     }
 15 |  *
 16 |  *     public void recycle() {
 17 |  *          // Clear state if needed.
 18 |  *          sPool.release(this);
 19 |  *     }
 20 |  *
 21 |  *     . . .
 22 |  * }
 23 |  * 
24 | * 25 | */ 26 | class Pools private constructor() { 27 | 28 | /** 29 | * Interface for managing a pool of objects. 30 | * 31 | * @param The pooled type. 32 | */ 33 | interface Pool { 34 | /** 35 | * @return An instance from the pool if such, null otherwise. 36 | */ 37 | fun acquire(): T? 38 | 39 | /** 40 | * Release an instance to the pool. 41 | * 42 | * @param instance The instance to release. 43 | * @return Whether the instance was put in the pool. 44 | * 45 | * @throws IllegalStateException If the instance is already in the pool. 46 | */ 47 | fun release(instance: T): Boolean 48 | } 49 | 50 | /** 51 | * Simple (non-synchronized) pool of objects. 52 | * 53 | * @param maxPoolSize The max pool size. 54 | * 55 | * @throws IllegalArgumentException If the max pool size is less than zero. 56 | * 57 | * @param The pooled type. 58 | */ 59 | open class SimplePool(maxPoolSize: Int) : Pool { 60 | private val mPool: Array 61 | private var mPoolSize = 0 62 | 63 | init { 64 | require(maxPoolSize > 0) { "The max pool size must be > 0" } 65 | mPool = arrayOfNulls(maxPoolSize) 66 | } 67 | 68 | @Suppress("UNCHECKED_CAST") 69 | override fun acquire(): T? { 70 | if (mPoolSize > 0) { 71 | val lastPooledIndex = mPoolSize - 1 72 | val instance = mPool[lastPooledIndex] as T? 73 | mPool[lastPooledIndex] = null 74 | mPoolSize-- 75 | return instance 76 | } 77 | return null 78 | } 79 | 80 | override fun release(instance: T): Boolean { 81 | check(!isInPool(instance)) { "Already in the pool!" } 82 | if (mPoolSize < mPool.size) { 83 | mPool[mPoolSize] = instance 84 | mPoolSize++ 85 | return true 86 | } 87 | return false 88 | } 89 | 90 | private fun isInPool(instance: T): Boolean { 91 | for (i in 0 until mPoolSize) { 92 | if (mPool[i] === instance) { 93 | return true 94 | } 95 | } 96 | return false 97 | } 98 | 99 | } 100 | 101 | 102 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/utils/SVGAScaleInfo.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.utils 2 | 3 | import android.widget.ImageView 4 | 5 | /** 6 | * Created by ubt on 2018/1/19. 7 | */ 8 | class SVGAScaleInfo { 9 | 10 | var tranFx : Float = 0.0f 11 | var tranFy : Float = 0.0f 12 | var scaleFx : Float = 1.0f 13 | var scaleFy : Float = 1.0f 14 | var ratio = 1.0f 15 | var ratioX = false 16 | 17 | private fun resetVar(){ 18 | tranFx = 0.0f 19 | tranFy = 0.0f 20 | scaleFx = 1.0f 21 | scaleFy = 1.0f 22 | ratio = 1.0f 23 | ratioX = false 24 | } 25 | 26 | fun performScaleType(canvasWidth : Float, canvasHeight: Float, videoWidth : Float, videoHeight : Float, scaleType: ImageView.ScaleType) { 27 | if (canvasWidth == 0.0f || canvasHeight == 0.0f || videoWidth == 0.0f || videoHeight == 0.0f) { 28 | return 29 | } 30 | 31 | resetVar() 32 | val canW_vidW_f = (canvasWidth - videoWidth) / 2.0f 33 | val canH_vidH_f = (canvasHeight - videoHeight) / 2.0f 34 | 35 | val videoRatio = videoWidth / videoHeight 36 | val canvasRatio = canvasWidth / canvasHeight 37 | 38 | val canH_d_vidH = canvasHeight / videoHeight 39 | val canW_d_vidW = canvasWidth / videoWidth 40 | 41 | when (scaleType) { 42 | ImageView.ScaleType.CENTER -> { 43 | tranFx = canW_vidW_f 44 | tranFy = canH_vidH_f 45 | } 46 | ImageView.ScaleType.CENTER_CROP -> { 47 | if (videoRatio > canvasRatio) { 48 | ratio = canH_d_vidH 49 | ratioX = false 50 | scaleFx = canH_d_vidH 51 | scaleFy = canH_d_vidH 52 | tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f 53 | } 54 | else { 55 | ratio = canW_d_vidW 56 | ratioX = true 57 | scaleFx = canW_d_vidW 58 | scaleFy = canW_d_vidW 59 | tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f 60 | } 61 | } 62 | ImageView.ScaleType.CENTER_INSIDE -> { 63 | if (videoWidth < canvasWidth && videoHeight < canvasHeight) { 64 | tranFx = canW_vidW_f 65 | tranFy = canH_vidH_f 66 | } 67 | else { 68 | if (videoRatio > canvasRatio) { 69 | ratio = canW_d_vidW 70 | ratioX = true 71 | scaleFx = canW_d_vidW 72 | scaleFy = canW_d_vidW 73 | tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f 74 | 75 | } 76 | else { 77 | ratio = canH_d_vidH 78 | ratioX = false 79 | scaleFx = canH_d_vidH 80 | scaleFy = canH_d_vidH 81 | tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f 82 | } 83 | } 84 | } 85 | ImageView.ScaleType.FIT_CENTER -> { 86 | if (videoRatio > canvasRatio) { 87 | ratio = canW_d_vidW 88 | ratioX = true 89 | scaleFx = canW_d_vidW 90 | scaleFy = canW_d_vidW 91 | tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f 92 | } 93 | else { 94 | ratio = canH_d_vidH 95 | ratioX = false 96 | scaleFx = canH_d_vidH 97 | scaleFy = canH_d_vidH 98 | tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f 99 | } 100 | } 101 | ImageView.ScaleType.FIT_START -> { 102 | if (videoRatio > canvasRatio) { 103 | ratio = canW_d_vidW 104 | ratioX = true 105 | scaleFx = canW_d_vidW 106 | scaleFy = canW_d_vidW 107 | } 108 | else { 109 | ratio = canH_d_vidH 110 | ratioX = false 111 | scaleFx = canH_d_vidH 112 | scaleFy = canH_d_vidH 113 | } 114 | } 115 | ImageView.ScaleType.FIT_END -> { 116 | if (videoRatio > canvasRatio) { 117 | ratio = canW_d_vidW 118 | ratioX = true 119 | scaleFx = canW_d_vidW 120 | scaleFy = canW_d_vidW 121 | tranFy= canvasHeight - videoHeight * (canW_d_vidW) 122 | } 123 | else { 124 | ratio = canH_d_vidH 125 | ratioX = false 126 | scaleFx = canH_d_vidH 127 | scaleFy = canH_d_vidH 128 | tranFx = canvasWidth - videoWidth * (canH_d_vidH) 129 | } 130 | } 131 | ImageView.ScaleType.FIT_XY -> { 132 | ratio = Math.max(canW_d_vidW, canH_d_vidH) 133 | ratioX = canW_d_vidW > canH_d_vidH 134 | scaleFx = canW_d_vidW 135 | scaleFy = canH_d_vidH 136 | } 137 | else -> { 138 | ratio = canW_d_vidW 139 | ratioX = true 140 | scaleFx = canW_d_vidW 141 | scaleFy = canW_d_vidW 142 | } 143 | } 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/utils/SVGAStructs.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.utils 2 | 3 | /** 4 | * Created by cuiminghui on 2017/3/29. 5 | */ 6 | 7 | class SVGAPoint(val x: Float, val y: Float, val value: Float) 8 | 9 | class SVGARect(val x: Double, val y: Double, val width: Double, val height: Double) 10 | 11 | class SVGARange(val location: Int, val length: Int) -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/utils/log/DefaultLogCat.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.utils.log 2 | 3 | import android.util.Log 4 | 5 | /** 6 | * 内部默认 ILogger 接口实现 7 | */ 8 | class DefaultLogCat : ILogger { 9 | override fun verbose(tag: String, msg: String) { 10 | Log.v(tag, msg) 11 | } 12 | 13 | override fun info(tag: String, msg: String) { 14 | Log.i(tag, msg) 15 | } 16 | 17 | override fun debug(tag: String, msg: String) { 18 | Log.d(tag, msg) 19 | } 20 | 21 | override fun warn(tag: String, msg: String) { 22 | Log.w(tag, msg) 23 | } 24 | 25 | override fun error(tag: String, msg: String?, error: Throwable?) { 26 | Log.e(tag, msg, error) 27 | } 28 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/utils/log/ILogger.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.utils.log 2 | 3 | /** 4 | * log 外部接管接口 5 | **/ 6 | interface ILogger { 7 | fun verbose(tag: String, msg: String) 8 | fun info(tag: String, msg: String) 9 | fun debug(tag: String, msg: String) 10 | fun warn(tag: String, msg: String) 11 | fun error(tag: String, msg: String?, error: Throwable?) 12 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/utils/log/LogUtils.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.utils.log 2 | 3 | /** 4 | * 日志输出 5 | */ 6 | internal object LogUtils { 7 | private const val TAG = "SVGALog" 8 | 9 | fun verbose(tag: String = TAG, msg: String) { 10 | if (!SVGALogger.isLogEnabled()) { 11 | return 12 | } 13 | SVGALogger.getSVGALogger()?.verbose(tag, msg) 14 | } 15 | 16 | fun info(tag: String = TAG, msg: String) { 17 | if (!SVGALogger.isLogEnabled()) { 18 | return 19 | } 20 | SVGALogger.getSVGALogger()?.info(tag, msg) 21 | } 22 | 23 | fun debug(tag: String = TAG, msg: String) { 24 | if (!SVGALogger.isLogEnabled()) { 25 | return 26 | } 27 | SVGALogger.getSVGALogger()?.debug(tag, msg) 28 | } 29 | 30 | fun warn(tag: String = TAG, msg: String) { 31 | if (!SVGALogger.isLogEnabled()) { 32 | return 33 | } 34 | SVGALogger.getSVGALogger()?.warn(tag, msg) 35 | } 36 | 37 | fun error(tag: String = TAG, msg: String) { 38 | if (!SVGALogger.isLogEnabled()) { 39 | return 40 | } 41 | SVGALogger.getSVGALogger()?.error(tag, msg, null) 42 | } 43 | 44 | fun error(tag: String, error: Throwable) { 45 | if (!SVGALogger.isLogEnabled()) { 46 | return 47 | } 48 | SVGALogger.getSVGALogger()?.error(tag, error.message, error) 49 | } 50 | 51 | fun error(tag: String = TAG, msg: String, error: Throwable) { 52 | if (!SVGALogger.isLogEnabled()) { 53 | return 54 | } 55 | SVGALogger.getSVGALogger()?.error(tag, msg, error) 56 | } 57 | } -------------------------------------------------------------------------------- /library/src/main/java/com/opensource/svgaplayer/utils/log/SVGALogger.kt: -------------------------------------------------------------------------------- 1 | package com.opensource.svgaplayer.utils.log 2 | 3 | /** 4 | * SVGA logger 配置管理 5 | **/ 6 | object SVGALogger { 7 | 8 | private var mLogger: ILogger? = DefaultLogCat() 9 | private var isLogEnabled = false 10 | 11 | /** 12 | * log 接管注入 13 | */ 14 | fun injectSVGALoggerImp(logImp: ILogger): SVGALogger { 15 | mLogger = logImp 16 | return this 17 | } 18 | 19 | /** 20 | * 设置是否开启 log 21 | */ 22 | fun setLogEnabled(isEnabled: Boolean): SVGALogger { 23 | isLogEnabled = isEnabled 24 | return this 25 | } 26 | 27 | /** 28 | * 获取当前 ILogger 实现类 29 | */ 30 | fun getSVGALogger(): ILogger? { 31 | return mLogger 32 | } 33 | 34 | /** 35 | * 是否开启 log 36 | */ 37 | fun isLogEnabled(): Boolean { 38 | return isLogEnabled 39 | } 40 | } -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SVGAPlayer 3 | 4 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Archived 2 | 本仓库已经停止维护,你仍然继续阅读源码及创建分叉,但本仓库不会继续更新,也不会回答任何 issue。 3 | 4 | This repo has stopped maintenance, you can still continue to read the source code and create forks, but this repo will not continue to be updated, nor will it answer any issues. 5 | 6 | # SVGAPlayer 7 | 8 | [简体中文](./readme.zh.md) 9 | 10 | ## 支持本项目 11 | 12 | 1. 轻点 GitHub Star,让更多人看到该项目。 13 | 14 | ## Introduce 15 | 16 | SVGAPlayer is a light-weight animation renderer. You use [tools](http://svga.io/designer.html) to export `svga` file from `Adobe Animate CC` or `Adobe After Effects`, and then use SVGAPlayer to render animation on mobile application. 17 | 18 | `SVGAPlayer-Android` render animation natively via Android Canvas Library, brings you a high-performance, low-cost animation experience. 19 | 20 | If wonder more information, go to this [website](http://svga.io/). 21 | 22 | ## Usage 23 | 24 | Here introduce `SVGAPlayer-Android` usage. Wonder exporting usage? Click [here](http://svga.io/designer.html). 25 | 26 | ### Install Via Gradle 27 | 28 | We host aar file on JitPack, your need to add `JitPack.io` repo `build.gradle` 29 | 30 | ``` 31 | allprojects { 32 | repositories { 33 | ... 34 | maven { url 'https://jitpack.io' } 35 | } 36 | } 37 | ``` 38 | 39 | Then, add dependency to app `build.gradle`. 40 | 41 | ``` 42 | compile 'com.github.yyued:SVGAPlayer-Android:latest' 43 | ``` 44 | 45 | [![](https://jitpack.io/v/yyued/SVGAPlayer-Android.svg)](https://jitpack.io/#yyued/SVGAPlayer-Android) 46 | 47 | ### Static Parser Support 48 | Perser#shareParser should be init(context) in Application or other Activity. 49 | Otherwise it will report an error: 50 | `Log.e("SVGAParser", "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")` 51 | 52 | 53 | ### Matte Support 54 | Head on over to [Dynamic · Matte Layer](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-%C2%B7-Matte-Layer) 55 | 56 | ### Proguard-rules 57 | 58 | ``` 59 | -keep class com.squareup.wire.** { *; } 60 | -keep class com.opensource.svgaplayer.proto.** { *; } 61 | ``` 62 | 63 | ### Locate files 64 | 65 | SVGAPlayer could load svga file from Android `assets` directory or remote server. 66 | 67 | ### Using XML 68 | 69 | You may use `layout.xml` to add a `SVGAImageView`. 70 | 71 | ```xml 72 | 73 | 78 | 79 | 85 | 86 | 87 | ``` 88 | 89 | The following attributes is allowable: 90 | 91 | #### source: String 92 | 93 | The svga file path, provide a path relative to Android assets directory, or provide a http url. 94 | 95 | #### autoPlay: Boolean 96 | 97 | Defaults to `true`. 98 | 99 | After animation parsed, plays animation automatically. 100 | 101 | #### loopCount: Int 102 | 103 | Defaults to `0`. 104 | 105 | How many times should animation loops. `0` means Infinity Loop. 106 | 107 | #### ~~clearsAfterStop: Boolean~~ 108 | 109 | Defaults to `false`.When the animation is finished, whether to clear the canvas and the internal data of SVGAVideoEntity. 110 | It is no longer recommended. Developers can control resource release through clearAfterDetached, or manually control resource release through SVGAVideoEntity#clear 111 | 112 | #### clearsAfterDetached: Boolean 113 | 114 | Defaults to `false`.Clears canvas and the internal data of SVGAVideoEntity after SVGAImageView detached. 115 | 116 | #### fillMode: String 117 | 118 | Defaults to `Forward`. Could be `Forward`, `Backward`, `Clear`. 119 | 120 | `Forward` means animation will pause on last frame after finished. 121 | 122 | `Backward` means animation will pause on first frame after finished. 123 | 124 | `Clear` after the animation is played, all the canvas content is cleared, but it is only the canvas and does not involve the internal data of SVGAVideoEntity. 125 | 126 | ### Using code 127 | 128 | You may use code to add `SVGAImageView` either. 129 | 130 | #### Create a `SVGAImageView` instance. 131 | 132 | ```kotlin 133 | SVGAImageView imageView = new SVGAImageView(this); 134 | ``` 135 | 136 | #### Declare a static Parser instance. 137 | 138 | ```kotlin 139 | parser = SVGAParser.shareParser() 140 | ``` 141 | 142 | #### Init parser instance 143 | 144 | You should initialize the parser instance with context before usage. 145 | ``` 146 | SVGAParser.shareParser().init(this); 147 | ``` 148 | 149 | Otherwise it will report an error: 150 | `Log.e("SVGAParser", "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")` 151 | 152 | You can also create `SVGAParser` instance by yourself. 153 | 154 | #### Create a `SVGAParser` instance, parse from assets like this. 155 | 156 | ```kotlin 157 | parser = new SVGAParser(this); 158 | // The third parameter is a default parameter, which is null by default. If this method is set, the audio parsing and playback will not be processed internally. The audio File instance will be sent back to the developer through PlayCallback, and the developer will control the audio playback and playback. stop 159 | parser.decodeFromAssets("posche.svga", object : SVGAParser.ParseCompletion { 160 | // ... 161 | }, object : SVGAParser.PlayCallback { 162 | // The default is null, can not be set 163 | }) 164 | ``` 165 | 166 | #### Create a `SVGAParser` instance, parse from remote server like this. 167 | 168 | ```kotlin 169 | parser = new SVGAParser(this); 170 | // The third parameter is a default parameter, which is null by default. If this method is set, the audio parsing and playback will not be processed internally. The audio File instance will be sent back to the developer through PlayCallback, and the developer will control the audio playback and playback. stop 171 | parser.decodeFromURL(new URL("https://github.com/yyued/SVGA-Samples/blob/master/posche.svga?raw=true"), new SVGAParser.ParseCompletion() { 172 | // ... 173 | }, object : SVGAParser.PlayCallback { 174 | // The default is null, can not be set 175 | }) 176 | ``` 177 | 178 | #### Create a `SVGADrawable` instance then set to `SVGAImageView`, play it as you want. 179 | 180 | ```kotlin 181 | parser = new SVGAParser(this); 182 | parser.decodeFromURL(..., new SVGAParser.ParseCompletion() { 183 | @Override 184 | public void onComplete(@NotNull SVGAVideoEntity videoItem) { 185 | SVGADrawable drawable = new SVGADrawable(videoItem); 186 | imageView.setImageDrawable(drawable); 187 | imageView.startAnimation(); 188 | } 189 | @Override 190 | public void onError() { 191 | 192 | } 193 | }); 194 | ``` 195 | 196 | ### Cache 197 | 198 | `SVGAParser` will not manage any cache, you need to setup cacher by yourself. 199 | 200 | #### Setup HttpResponseCache 201 | 202 | `SVGAParser` depends on `URLConnection`, `URLConnection` uses `HttpResponseCache` to cache things. 203 | 204 | Add codes to `Application.java:onCreate` to setup cacher. 205 | 206 | ```kotlin 207 | val cacheDir = File(context.applicationContext.cacheDir, "http") 208 | HttpResponseCache.install(cacheDir, 1024 * 1024 * 128) 209 | ``` 210 | 211 | ### SVGALogger 212 | Updated the internal log output, which can be managed and controlled through SVGALogger. It is not activated by default. Developers can also implement the ILogger interface to capture and collect logs externally to facilitate troubleshooting 213 | Set whether the log is enabled through the `setLogEnabled` method 214 | Inject a custom ILogger implementation class through the `injectSVGALoggerImp` method 215 | 216 | 217 | ```kotlin 218 | 219 | // By default, SVGA will not output any log, so you need to manually set it to true 220 | SVGALogger.setLogEnabled(true) 221 | 222 | // If you want to collect the output log of SVGA, you can obtain it in the following way 223 | SVGALogger.injectSVGALoggerImp(object: ILogger { 224 | // Implement related interfaces to receive log 225 | }) 226 | ``` 227 | 228 | ### SVGASoundManager 229 | Added SVGASoundManager to control SVGA audio, you need to manually call the init method to initialize, otherwise follow the default audio loading logic. 230 | In addition, through SVGASoundManager#setVolume, you can control the volume of SVGA playback. The range is [0f, 1f]. By default, the volume of all SVGA playbacks is controlled. 231 | And this method can set a second default parameter: SVGAVideoEntity, which means that only the current SVGA volume is controlled, and the volume of other SVGAs remains unchanged. 232 | 233 | ```kotlin 234 | // Initialize the audio manager for easy management of audio playback 235 | // If it is not initialized, the audio will be loaded in the original way by default 236 | SVGASoundManager.init() 237 | 238 | // Release audio resources 239 | SVGASoundManager.release() 240 | 241 | /** 242 | * Set the volume level, entity is null by default 243 | * When entity is null, it controls the volume of all audio loaded through SVGASoundManager, which includes the currently playing audio and subsequent loaded audio 244 | * When entity is not null, only the SVGA audio volume of the instance is controlled, and the others are not affected 245 | * 246 | * @param volume The value range is [0f, 1f] 247 | * @param entity That is, the instance of SVGAParser callback 248 | */ 249 | SVGASoundManager.setVolume(volume, entity) 250 | ``` 251 | 252 | ## Features 253 | 254 | Here are many feature samples. 255 | 256 | * [Replace an element with Bitmap.](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Image) 257 | * [Add text above an element.](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Text) 258 | * [Add static layout text above an element.](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Text-Layout) 259 | * [Hides an element dynamicaly.](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Hidden) 260 | * [Use a custom drawer for element.](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Drawer) 261 | 262 | ## APIs 263 | 264 | Head on over to [https://github.com/yyued/SVGAPlayer-Android/wiki/APIs](https://github.com/yyued/SVGAPlayer-Android/wiki/APIs) 265 | 266 | ## CHANGELOG 267 | 268 | Head on over to [CHANGELOG](./CHANGELOG.md) 269 | 270 | ## Credits 271 | 272 | ### Contributors 273 | 274 | This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. 275 | 276 | 277 | 278 | ### Backers 279 | 280 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/SVGAPlayer-Android#backer)] 281 | 282 | 283 | 284 | ### Sponsors 285 | 286 | Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/SVGAPlayer-Android#sponsor)] 287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /readme.zh.md: -------------------------------------------------------------------------------- 1 | # SVGAPlayer 2 | 3 | ## 介绍 4 | 5 | `SVGAPlayer` 是一个轻量的动画渲染库。你可以使用[工具](http://svga.io/designer.html)从 `Adobe Animate CC` 或者 `Adobe After Effects` 中导出动画文件,然后使用 `SVGAPlayer` 在移动设备上渲染并播放。 6 | 7 | `SVGAPlayer-Android` 使用原生 Android Canvas 库渲染动画,为你提供高性能、低开销的动画体验。 8 | 9 | 如果你想要了解更多细节,请访问[官方网站](http://svga.io/)。 10 | 11 | ## 用法 12 | 13 | 我们在这里介绍 `SVGAPlayer-Android` 的用法。想要知道如何导出动画,点击[这里](http://svga.io/designer.html)。 14 | 15 | ### 使用 Gradle 安装 16 | 17 | 我们的 aar 包托管在 JitPack 上,你需要将 `JitPack.io` 仓库添加到工程 `build.gradle` 中。 18 | 19 | ``` 20 | allprojects { 21 | repositories { 22 | ... 23 | maven { url 'https://jitpack.io' } 24 | } 25 | } 26 | ``` 27 | 28 | 然后,在应用 `build.gradle` 中添加依赖。 29 | 30 | ``` 31 | compile 'com.github.yyued:SVGAPlayer-Android:latest' 32 | ``` 33 | 34 | [![](https://jitpack.io/v/yyued/SVGAPlayer-Android.svg)](https://jitpack.io/#yyued/SVGAPlayer-Android) 35 | 36 | ### Parser 单例支持 37 | SVGAParser 单例需要在使用之前初始化, 38 | 否则会上报错误信息: 39 | `Log.e("SVGAParser", "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")` 40 | 41 | 42 | ### 遮罩支持 43 | 请参阅此处 [Dynamic · Matte Layer](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-%C2%B7-Matte-Layer) 44 | 45 | ### 混淆规则 46 | 47 | ``` 48 | -keep class com.squareup.wire.** { *; } 49 | -keep class com.opensource.svgaplayer.proto.** { *; } 50 | ``` 51 | 52 | ### 放置 svga 文件 53 | 54 | SVGAPlayer 可以从本地 `assets` 目录,或者远端服务器上加载动画文件。 55 | 56 | ### 使用 XML 57 | 58 | 你可以使用 `layout.xml` 添加一个 `SVGAImageView`。 59 | 60 | ```xml 61 | 62 | 67 | 68 | 74 | 75 | 76 | ``` 77 | 78 | 在 XML 中,允许定义以下这些标签: 79 | 80 | #### source: String 81 | 用于表示 svga 文件的路径,提供一个在 `assets` 目录下的文件名,或者提供一个 http url 地址。 82 | 83 | #### autoPlay: Boolean 84 | 默认为 `true`,当动画加载完成后,自动播放。 85 | 86 | #### loopCount: Int 87 | 默认为 `0`,设置动画的循环次数,0 表示无限循环。 88 | 89 | #### ~~clearsAfterStop: Boolean~~ 90 | 默认为 `false`,当动画播放完成后,是否清空画布,以及 SVGAVideoEntity 内部数据。 91 | 不再推荐使用,开发者可以通过 clearAfterDetached 控制资源释放,或者手动通过 SVGAVideoEntity#clear 控制资源释放 92 | 93 | #### clearsAfterDetached: Boolean 94 | 默认为 `false`,当 SVGAImageView 触发 onDetachedFromWindow 方法时,是否清空画布。 95 | 96 | #### fillMode: String 97 | 98 | 默认为 `Forward`,可以是 `Forward`、 `Backward`、 `Clear`。 99 | 100 | `Forward` 表示动画结束后,将停留在最后一帧。 101 | 102 | `Backward` 表示动画结束后,将停留在第一帧。 103 | 104 | `Clear` 表示动画播放完后,清空所有画布内容,但仅仅是画布,不涉及 SVGAVideoEntity 内部数据。 105 | 106 | ### 使用代码 107 | 108 | 也可以使用代码添加 `SVGAImageView`。 109 | 110 | #### 创建一个 `SVGAImageView` 实例 111 | 112 | ```kotlin 113 | SVGAImageView imageView = new SVGAImageView(this); 114 | ``` 115 | 116 | #### 声明一个 `SVGAParser` 单例. 117 | 118 | ```kotlin 119 | parser = SVGAParser.shareParser() 120 | ``` 121 | 122 | #### 初始化 `SVGAParser` 单例 123 | 124 | 必须在使用 `SVGAParser` 单例前初始化, 125 | ``` 126 | SVGAParser.shareParser().init(this); 127 | ``` 128 | 129 | 否则会上报错误信息: 130 | `Log.e("SVGAParser", "在配置 SVGAParser context 前, 无法解析 SVGA 文件。")` 131 | 132 | 你也可以自行创建 `SVGAParser` 实例。 133 | 134 | #### 创建一个 `SVGAParser` 实例,加载 assets 中的动画。 135 | 136 | ```kotlin 137 | parser = new SVGAParser(this); 138 | // 第三个为可缺省参数,默认为 null,如果设置该方法,则内部不在处理音频的解析以及播放,会通过 PlayCallback 把音频 File 实例回传给开发者,有开发者自行控制音频的播放与停止。 139 | parser.decodeFromAssets("posche.svga", object : SVGAParser.ParseCompletion { 140 | // ... 141 | }, object : SVGAParser.PlayCallback { 142 | // The default is null, can not be set 143 | }) 144 | ``` 145 | 146 | #### 创建一个 `SVGAParser` 实例,加载远端服务器中的动画。 147 | 148 | ```kotlin 149 | parser = new SVGAParser(this); 150 | // 第三个为可缺省参数,默认为 null,如果设置该方法,则内部不在处理音频的解析以及播放,会通过 PlayCallback 把音频 File 实例回传给开发者,有开发者自行控制音频的播放与停止。 151 | parser.decodeFromURL(new URL("https://github.com/yyued/SVGA-Samples/blob/master/posche.svga?raw=true"), new SVGAParser.ParseCompletion() { 152 | // ... 153 | }, object : SVGAParser.PlayCallback { 154 | // The default is null, can not be set 155 | }) 156 | ``` 157 | 158 | #### 创建一个 `SVGADrawable` 实例,并赋值给 `SVGAImageView`,然后播放动画。 159 | 160 | ```kotlin 161 | parser = new SVGAParser(this); 162 | parser.decodeFromURL(..., new SVGAParser.ParseCompletion() { 163 | @Override 164 | public void onComplete(@NotNull SVGAVideoEntity videoItem) { 165 | SVGADrawable drawable = new SVGADrawable(videoItem); 166 | imageView.setImageDrawable(drawable); 167 | imageView.startAnimation(); 168 | } 169 | @Override 170 | public void onError() { 171 | 172 | } 173 | }); 174 | ``` 175 | 176 | ### 缓存 177 | 178 | `SVGAParser` 不会管理缓存,你需要自行实现缓存器。 179 | 180 | #### 设置 HttpResponseCache 181 | 182 | `SVGAParser` 依赖 `URLConnection`, `URLConnection` 使用 `HttpResponseCache` 处理缓存。 183 | 184 | 添加代码至 `Application.java:onCreate` 以设置缓存。 185 | 186 | ```kotlin 187 | val cacheDir = File(context.applicationContext.cacheDir, "http") 188 | HttpResponseCache.install(cacheDir, 1024 * 1024 * 128) 189 | ``` 190 | 191 | ### SVGALogger 192 | 更新了内部 log 输出,可通过 SVGALogger 去管理和控制,默认是未启用 log 输出,开发者们也可以实现 ILogger 接口,做到外部捕获收集 log,方便排查问题。 193 | 通过 `setLogEnabled` 方法设置日志是否开启。 194 | 通过 `injectSVGALoggerImp` 方法注入自定义 ILogger 实现类。 195 | 196 | ```kotlin 197 | 198 | // 默认情况下,SVGA 内部不会输出任何 log,所以需要手动设置为 true 199 | SVGALogger.setLogEnabled(true) 200 | 201 | // 如果希望收集 SVGA 内部输出的日志,则可通过下面方式获取 202 | SVGALogger.injectSVGALoggerImp(object: ILogger { 203 | // 实现相关接口进行接收 log 204 | }) 205 | ``` 206 | 207 | ### SVGASoundManager 208 | 新增 SVGASoundManager 控制 SVGA 音频,需要手动调用 init 方法进行初始化,否则按照默认的音频加载逻辑。 209 | 另外通过 SVGASoundManager#setVolume 可控制 SVGA 播放时的音量大小,范围值在 [0f, 1f],默认控制所有 SVGA 播放时的音量, 210 | 而且该方法可设置第二个可缺省参数:SVGAVideoEntity,表示仅控制当前 SVGA 的音量大小,其他 SVGA 的音量保持不变。 211 | 212 | ```kotlin 213 | // 初始化音频管理器,方便管理音频播放 214 | // 如果没有初始化,则默认按照原有方式加载音频 215 | SVGASoundManager.init() 216 | 217 | // 释放音频资源 218 | SVGASoundManager.release() 219 | 220 | /** 221 | * 设置音量大小,entity 默认为空 222 | * 当 entity 为空,则控制所有通过 SVGASoundManager 加载的音频音量大小,即包括当前正在播放的音频以及后续加载的音频 223 | * 当 entity 不为空,则仅控制该实例的 SVGA 音频音量大小,其他则不受影响 224 | * 225 | * @param volume 取值范围为 [0f, 1f] 226 | * @param entity 即 SVGAParser 回调回来的实例 227 | */ 228 | SVGASoundManager.setVolume(volume, entity) 229 | ``` 230 | 231 | 232 | ## 功能示例 233 | 234 | * [使用位图替换指定元素。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Image) 235 | * [在指定元素上绘制文本。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Text) 236 | * [在指定元素上绘制富文本。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Text-Layout) 237 | * [隐藏指定元素。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Hidden) 238 | * [在指定元素上自由绘制。](https://github.com/yyued/SVGAPlayer-Android/wiki/Dynamic-Drawer) 239 | 240 | ## APIs 241 | 242 | 请参阅此处 [https://github.com/yyued/SVGAPlayer-Android/wiki/APIs](https://github.com/yyued/SVGAPlayer-Android/wiki/APIs) 243 | 244 | ## CHANGELOG 245 | 246 | 请参阅此处 [CHANGELOG](./CHANGELOG.md) 247 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------