├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.gradle
├── demo
├── .gitignore
├── build.gradle
├── demo.gif
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── jaredrummler
│ │ └── android
│ │ └── animatedsvgview
│ │ └── demo
│ │ ├── MainActivity.java
│ │ └── SVG.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── gradle.properties
├── gradle
├── maven-push.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── gradle.properties
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── jaredrummler
│ │ └── android
│ │ └── widget
│ │ ├── AnimatedSvgView.java
│ │ └── PathParser.java
│ └── res
│ └── values
│ └── attrs.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | ########### Specifies intentionally untracked files to ignore ###########
2 |
3 | ### Gradle
4 | .gradle/
5 | build/
6 |
7 | ### IntelliJ IDEA
8 | /.idea
9 | *.iml
10 | *.iws
11 | captures/
12 | .navigation/
13 | local.properties
14 | bin/
15 | gen/
16 | out/
17 | *.apk
18 | *.ap_
19 |
20 | ### Android
21 | *.jks
22 | *.dex
23 |
24 | ### Intellij
25 |
26 |
27 | ### Java
28 | *.class
29 | hs_err_pid*
30 |
31 | ### Windows
32 | Desktop.ini
33 | Thumbs.db
34 | ehthumbs.db
35 |
36 | ### OSX
37 | .DS_Store
38 |
39 | ### Linux
40 | *~
41 | .fuse_hidden*
42 | .directory
43 | .Trash-*
44 |
45 | ### Logs
46 | *.log
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | android:
4 | components:
5 | - tools # Tools
6 | - platform-tools # Platform tools
7 | - build-tools-28.0.3 # Build tools version
8 | - android-28 # Target SDK version
9 |
10 | jdk:
11 | - oraclejdk8
12 |
13 | script:
14 | - ./gradlew build
15 |
16 | branches:
17 | except:
18 | - gh-pages
19 |
20 | notifications:
21 | email: false
22 |
23 | sudo: false
24 |
25 | cache:
26 | directories:
27 | - $HOME/.gradle
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2016 Jared Rummler
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AnimatedSvgView
2 |
3 | [](https://maven-badges.herokuapp.com/maven-central/com.jaredrummler/animated-svg-view)
4 | [](LICENSE)
5 | [](https://android-arsenal.com/api?level=14)
6 | [](https://twitter.com/jaredrummler)
7 |
8 | Animated SVG Drawing for Android
9 |
10 | 
11 |
12 | # Usage
13 |
14 | Get SVG path data and add it to a string array:
15 |
16 | ```xml
17 |
18 | - M142.9,24.2C97.6,39.7,59,73.6,37.5,116.5c-7.5,14.8-12.9,30.5-16.2,46.8c-8.2,40.4-2.5,83.5,16.1,120.3 c12.1,24,29.5,45.4,50.5,62.1c19.9,15.8,43,27.6,67.6,34.1c31,8.3,64,8.1,95.2,1c28.2-6.5,54.9-20,76.2-39.6 c22.5-20.7,38.6-47.9,47.1-77.2c9.3-31.9,10.5-66,4.7-98.8c-58.3,0-116.7,0-175,0c0,24.2,0,48.4,0,72.6c33.8,0,67.6,0,101.4,0 c-3.9,23.2-17.7,44.4-37.2,57.5c-12.3,8.3-26.4,13.6-41,16.2c-14.6,2.5-29.8,2.8-44.4-0.1c-14.9-3-29-9.2-41.4-17.9 c-19.8-13.9-34.9-34.2-42.6-57.1c-7.9-23.3-8-49.2,0-72.4c5.6-16.4,14.8-31.5,27-43.9c15-15.4,34.5-26.4,55.6-30.9 c18-3.8,37-3.1,54.6,2.2c15,4.5,28.8,12.8,40.1,23.6c11.4-11.4,22.8-22.8,34.2-34.2c6-6.1,12.3-12,18.1-18.3 c-17.3-16-37.7-28.9-59.9-37.1C228.2,10.6,183.2,10.3,142.9,24.2z
19 | - M142.9,24.2c40.2-13.9,85.3-13.6,125.3,1.1c22.2,8.2,42.5,21,59.9,37.1c-5.8,6.3-12.1,12.2-18.1,18.3 c-11.4,11.4-22.8,22.8-34.2,34.2c-11.3-10.8-25.1-19-40.1-23.6c-17.6-5.3-36.6-6.1-54.6-2.2c-21,4.5-40.5,15.5-55.6,30.9 c-12.2,12.3-21.4,27.5-27,43.9c-20.3-15.8-40.6-31.5-61-47.3C59,73.6,97.6,39.7,142.9,24.2z
20 | - M21.4,163.2c3.3-16.2,8.7-32,16.2-46.8c20.3,15.8,40.6,31.5,61,47.3c-8,23.3-8,49.2,0,72.4 c-20.3,15.8-40.6,31.6-60.9,47.3C18.9,246.7,13.2,203.6,21.4,163.2z
21 | - M203.7,165.1c58.3,0,116.7,0,175,0c5.8,32.7,4.5,66.8-4.7,98.8c-8.5,29.3-24.6,56.5-47.1,77.2 c-19.7-15.3-39.4-30.6-59.1-45.9c19.5-13.1,33.3-34.3,37.2-57.5c-33.8,0-67.6,0-101.4,0C203.7,213.5,203.7,189.3,203.7,165.1z
22 | - M37.5,283.5c20.3-15.7,40.6-31.5,60.9-47.3c7.8,22.9,22.8,43.2,42.6,57.1c12.4,8.7,26.6,14.9,41.4,17.9 c14.6,3,29.7,2.6,44.4,0.1c14.6-2.6,28.7-7.9,41-16.2c19.7,15.3,39.4,30.6,59.1,45.9c-21.3,19.7-48,33.1-76.2,39.6 c-31.2,7.1-64.2,7.3-95.2-1c-24.6-6.5-47.7-18.2-67.6-34.1C67,328.9,49.6,307.5,37.5,283.5z
23 |
24 | ```
25 |
26 | Add the colors for each path in an integer-array:
27 |
28 | ```xml
29 | #EA4335
30 | #FBBC05
31 | #4285F4
32 | #34A853
33 |
34 |
35 | - @android:color/white
36 | - @color/google_red
37 | - @color/google_yellow
38 | - @color/google_blue
39 | - @color/google_green
40 |
41 | ```
42 |
43 | Add the view to your layout:
44 |
45 | ```xml
46 |
60 | ```
61 |
62 | Play the animation:
63 |
64 | ```java
65 | AnimatedSvgView svgView = (AnimatedSvgView) findViewById(R.id.animated_svg_view);
66 | svgView.start();
67 | ```
68 |
69 | You can also set SVG glyphs and colors dynamically (see the [demo](demo)).
70 |
71 | # Download
72 |
73 | Download [the latest AAR](https://repo1.maven.org/maven2/com/jaredrummler/animated-svg-view/1.0.6/animated-svg-view-1.0.6.aar) or grab via Gradle:
74 |
75 | ```groovy
76 | compile 'com.jaredrummler:animated-svg-view:1.0.6'
77 | ```
78 | or Maven:
79 | ```xml
80 |
81 | com.jaredrummler
82 | animated-svg-view
83 | 1.0.6
84 | aar
85 |
86 | ```
87 |
88 | # Acknowledgements
89 |
90 | [AnimatedMuzeiLogoView](https://github.com/romannurik/muzei/blob/master/main/src/main/java/com/google/android/apps/muzei/util/AnimatedMuzeiLogoView.java) by [Roman Nurik](https://github.com/romannurik)
91 |
92 | [oak-animatedsvgview](https://github.com/willowtreeapps/OAK/tree/master/oak-animatedsvgview) by [WillowTree, Inc.](https://github.com/willowtreeapps)
93 |
94 | # License
95 |
96 | Copyright (C) 2016, Jared Rummler
97 |
98 | Licensed under the Apache License, Version 2.0 (the "License");
99 | you may not use this file except in compliance with the License.
100 | You may obtain a copy of the License at
101 |
102 | http://www.apache.org/licenses/LICENSE-2.0
103 |
104 | Unless required by applicable law or agreed to in writing, software
105 | distributed under the License is distributed on an "AS IS" BASIS,
106 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
107 | See the License for the specific language governing permissions and
108 | limitations under the License.
109 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:3.2.1'
8 | classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.1.1"
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | task clean(type: Delete) {
20 | delete rootProject.buildDir
21 | }
22 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | buildToolsVersion '28.0.3'
6 |
7 | defaultConfig {
8 | applicationId "com.jaredrummler.android.animatedsvgview.demo"
9 | minSdkVersion 14
10 | targetSdkVersion 28
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation 'androidx.appcompat:appcompat:1.0.0'
24 | implementation project(':library')
25 | }
26 |
--------------------------------------------------------------------------------
/demo/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidSourceTools/AnimatedSvgView/a917c0a0d54f03e9cca4f2f8ffd404d35a98e71a/demo/demo.gif
--------------------------------------------------------------------------------
/demo/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 C:\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 |
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/jaredrummler/android/animatedsvgview/demo/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Jared Rummler
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.jaredrummler.android.animatedsvgview.demo;
19 |
20 | import android.os.Bundle;
21 | import androidx.appcompat.app.AppCompatActivity;
22 | import android.view.View;
23 | import com.jaredrummler.android.widget.AnimatedSvgView;
24 |
25 | public class MainActivity extends AppCompatActivity {
26 |
27 | /*package*/ AnimatedSvgView svgView;
28 | /*package*/ int index = -1;
29 |
30 | @Override protected void onCreate(Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 | setContentView(R.layout.activity_main);
33 |
34 | svgView = (AnimatedSvgView) findViewById(R.id.animated_svg_view);
35 |
36 | svgView.postDelayed(new Runnable() {
37 |
38 | @Override public void run() {
39 | svgView.start();
40 | }
41 | }, 500);
42 |
43 | svgView.setOnClickListener(new View.OnClickListener() {
44 |
45 | @Override public void onClick(View v) {
46 | if (svgView.getState() == AnimatedSvgView.STATE_FINISHED) {
47 | svgView.start();
48 | }
49 | }
50 | });
51 |
52 | svgView.setOnStateChangeListener(new AnimatedSvgView.OnStateChangeListener() {
53 |
54 | @Override public void onStateChange(@AnimatedSvgView.State int state) {
55 | if (state == AnimatedSvgView.STATE_TRACE_STARTED) {
56 | findViewById(R.id.btn_previous).setEnabled(false);
57 | findViewById(R.id.btn_next).setEnabled(false);
58 | } else if (state == AnimatedSvgView.STATE_FINISHED) {
59 | findViewById(R.id.btn_previous).setEnabled(index != -1);
60 | findViewById(R.id.btn_next).setEnabled(true);
61 | if (index == -1) index = 0; // first time
62 | }
63 | }
64 | });
65 | }
66 |
67 | public void onNext(View view) {
68 | if (++index >= SVG.values().length) index = 0;
69 | setSvg(SVG.values()[index]);
70 | }
71 |
72 | public void onPrevious(View view) {
73 | if (--index < 0) index = SVG.values().length - 1;
74 | setSvg(SVG.values()[index]);
75 | }
76 |
77 | private void setSvg(SVG svg) {
78 | svgView.setGlyphStrings(svg.glyphs);
79 | svgView.setFillColors(svg.colors);
80 | svgView.setViewportSize(svg.width, svg.height);
81 | svgView.setTraceResidueColor(0x32000000);
82 | svgView.setTraceColors(svg.colors);
83 | svgView.rebuildGlyphData();
84 | svgView.start();
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/jaredrummler/android/animatedsvgview/demo/SVG.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Jared Rummler
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.jaredrummler.android.animatedsvgview.demo;
19 |
20 | import android.graphics.Color;
21 |
22 | /**
23 | * Some SVGs to play with
24 | */
25 | public enum SVG {
26 | GOOGLE(
27 | new String[]{
28 | "M142.9,24.2c40.2-13.9,85.3-13.6,125.3,1.1c22.2,8.2,42.5,21,59.9,37.1c-5.8,6.3-12.1,12.2-18.1,18.3 c-11.4,11.4-22.8,22.8-34.2,34.2c-11.3-10.8-25.1-19-40.1-23.6c-17.6-5.3-36.6-6.1-54.6-2.2c-21,4.5-40.5,15.5-55.6,30.9 c-12.2,12.3-21.4,27.5-27,43.9c-20.3-15.8-40.6-31.5-61-47.3C59,73.6,97.6,39.7,142.9,24.2z",
29 | "M21.4,163.2c3.3-16.2,8.7-32,16.2-46.8c20.3,15.8,40.6,31.5,61,47.3c-8,23.3-8,49.2,0,72.4 c-20.3,15.8-40.6,31.6-60.9,47.3C18.9,246.7,13.2,203.6,21.4,163.2z",
30 | "M203.7,165.1c58.3,0,116.7,0,175,0c5.8,32.7,4.5,66.8-4.7,98.8c-8.5,29.3-24.6,56.5-47.1,77.2 c-19.7-15.3-39.4-30.6-59.1-45.9c19.5-13.1,33.3-34.3,37.2-57.5c-33.8,0-67.6,0-101.4,0C203.7,213.5,203.7,189.3,203.7,165.1z",
31 | "M37.5,283.5c20.3-15.7,40.6-31.5,60.9-47.3c7.8,22.9,22.8,43.2,42.6,57.1c12.4,8.7,26.6,14.9,41.4,17.9 c14.6,3,29.7,2.6,44.4,0.1c14.6-2.6,28.7-7.9,41-16.2c19.7,15.3,39.4,30.6,59.1,45.9c-21.3,19.7-48,33.1-76.2,39.6 c-31.2,7.1-64.2,7.3-95.2-1c-24.6-6.5-47.7-18.2-67.6-34.1C67,328.9,49.6,307.5,37.5,283.5z"
32 | },
33 | new int[]{
34 | 0xFFEA4335,
35 | 0xFFFBBC05,
36 | 0xFF4285F4,
37 | 0xFF34A853
38 | },
39 | 400, 400
40 | ),
41 | GITHUB(
42 | new String[]{
43 | "M256 70.7c-102.6 0-185.9 83.2-185.9 185.9 0 82.1 53.3 151.8 127.1 176.4 9.3 1.7 12.3-4 12.3-8.9V389.4c-51.7 11.3-62.5-21.9-62.5-21.9 -8.4-21.5-20.6-27.2-20.6-27.2 -16.9-11.5 1.3-11.3 1.3-11.3 18.7 1.3 28.5 19.2 28.5 19.2 16.6 28.4 43.5 20.2 54.1 15.4 1.7-12 6.5-20.2 11.8-24.9 -41.3-4.7-84.7-20.6-84.7-91.9 0-20.3 7.3-36.9 19.2-49.9 -1.9-4.7-8.3-23.6 1.8-49.2 0 0 15.6-5 51.1 19.1 14.8-4.1 30.7-6.2 46.5-6.3 15.8 0.1 31.7 2.1 46.6 6.3 35.5-24 51.1-19.1 51.1-19.1 10.1 25.6 3.8 44.5 1.8 49.2 11.9 13 19.1 29.6 19.1 49.9 0 71.4-43.5 87.1-84.9 91.7 6.7 5.8 12.8 17.1 12.8 34.4 0 24.9 0 44.9 0 51 0 4.9 3 10.7 12.4 8.9 73.8-24.6 127-94.3 127-176.4C441.9 153.9 358.6 70.7 256 70.7z"
44 | },
45 | new int[]{
46 | Color.BLACK
47 | },
48 | 512, 512
49 | ),
50 | TWITTER(
51 | new String[]{
52 | "m 1999.9999,192.4 c -73.58,32.64 -152.67,54.69 -235.66,64.61 84.7,-50.78 149.77,-131.19 180.41,-227.01 -79.29,47.03 -167.1,81.17 -260.57,99.57 C 1609.3399,49.82 1502.6999,0 1384.6799,0 c -226.6,0 -410.328,183.71 -410.328,410.31 0,32.16 3.628,63.48 10.625,93.51 -341.016,-17.11 -643.368,-180.47 -845.739,-428.72 -35.324,60.6 -55.5583,131.09 -55.5583,206.29 0,142.36 72.4373,267.95 182.5433,341.53 -67.262,-2.13 -130.535,-20.59 -185.8519,-51.32 -0.039,1.71 -0.039,3.42 -0.039,5.16 0,198.803 141.441,364.635 329.145,402.342 -34.426,9.375 -70.676,14.395 -108.098,14.395 -26.441,0 -52.145,-2.578 -77.203,-7.364 52.215,163.008 203.75,281.649 383.304,284.946 -140.429,110.062 -317.351,175.66 -509.5972,175.66 -33.1211,0 -65.7851,-1.949 -97.8828,-5.738 181.586,116.4176 397.27,184.359 628.988,184.359 754.732,0 1167.462,-625.238 1167.462,-1167.47 0,-17.79 -0.41,-35.48 -1.2,-53.08 80.1799,-57.86 149.7399,-130.12 204.7499,-212.41"
53 | },
54 | new int[]{
55 | 0xFF00ACED
56 | },
57 | 2000, 1625.36f
58 | ),
59 | JRUMMY_APPS(
60 | new String[]{
61 | "M457.9,91.1c0-0.8,0-1.7,0-2.5c-0.1-5.9-0.8-11.9-2-17.7c-5.4-25-20.2-41.4-45.7-46.4c-10.3-2-21.2-2.4-31.8-2.4c-82.8-0.2-165.7-0.1-248.5,0c-7.6,0-15.4,0.1-22.9,1.3c-33,5-53.5,27.2-54,62.7c0,0.6,0,1.1,0,1.7c0,56.6,0,113.2,0,169.8h0v0.4c0,34.6,0,69.3,0.1,103.9c0,5.3,0.5,10.7,1.5,15.8c6.8,31.9,34.9,51.6,67.3,47.2c6.1-0.8,12.1-2.4,17.9-4.2c3.4-1.1,6.5-3.7,5.8-7.8c-0.8-4.3-4.7-3.8-8.1-3.9c-10.6-0.6-21.4-0.5-31.8-2.2c-18.1-3-30.1-13.6-33.8-32.1c-1.5-7.5-2.7-15.1-2.8-22.7c-0.1-31.3-0.1-62.7-0.1-94v-0.4c0-43.2,0-86.5,0-129.7c0-10.6,0.1-21.2,0.5-31.8c0.1-3.5,0.3-7.1,0.5-10.6c0.3-5.2,1.2-10.2,2.7-14.9c0.4-1.2,0.8-2.3,1.3-3.5c2.7-6.4,7-12.1,13.2-16.8c9.6-7.1,20.9-9.6,32.5-9.7c22.6-0.3,45.2-0.1,67.8-0.1c54.1,0,108.2-0.1,162.4,0.1c17.3,0,34.7-0.2,51.9,1.2c22.7,1.8,38.7,19.4,39,41.4c0.4,29,0.9,57.9,0.9,86.9c0,29.2-0.2,58.4-0.3,87.6v0.4c-0.2,33.2-0.4,66.4-0.5,99.6c0,2.5,0,5,0,7.5c-0.1,24.5-18.1,43.7-42.5,43.9c-48.5,0.4-97,0.2-145.5,0.1c-6.9,0-11.2-3.3-12-10.2c-0.7-6.9,0-13.9-0.1-20.8c-0.3-24-0.6-48-1-71.9c-0.1-4.1-0.5-8.3-1.4-12.4c-1.9-9.4-7-14.8-16.5-15.5c-13.9-1-27.9-1.1-41.9-1c-6.1,0-9.7,3.6-10.2,9.7c-0.6,7.4-0.1,14.9-0.2,22.3c-0.3,31-0.5,61.9-0.9,92.9c-0.1,9.6-3.1,18.6-9.7,25.7c-15.7,16.9-35.3,23.2-58,18.6c-12.4-2.5-23.3-8.7-33.9-15.4c-2.2-1.4-4.5-3.4-6.6,0.1c0.1,0.4,0.2,0.8,0.4,1c5.1,5.5,10.3,10.9,15.3,16.5c24.9,28.2,55.4,42.8,93.5,37.3c28.5-4.1,49.9-18.7,62-45.4c2.3-5.1,5.5-8.9,10.5-10.7c5.1-1.8,10.5-3.6,15.9-3.6c47.6-0.6,95.3-0.5,142.9-1.3c15.4-0.3,29.3-5.8,40.3-17.3c12.2-12.7,16.4-28.6,16.4-45.4c0.1-34.8,0.2-69.6,0.2-104.3C458.1,202.3,458,146.7,457.9,91.1z M292.2,246.5c-5.3,4.1-11.1,7.4-17.3,11.4h-0.6c-0.2,0.1-0.4,0.2-0.6,0.4c3.8,5,7.4,9.8,11.2,14.4c18.4,22.4,34.6,46,45.9,72.9c4.9,11.5,10.7,22.6,16,33.9c2.3,4.9,6.1,8.4,11.5,8.4c14,0,27.9-0.3,41.9-1.3c8.5-0.6,13.9-6,15.5-14.6c0.8-4.1,1.2-8.3,1.2-12.4c0-33.9,0-67.8,0-101.7v-0.4c0-55.5,0-111,0-166.6c0-4-0.4-8-1.1-11.9c-1.2-6.6-4.9-11.5-11.8-12.6c-6.4-1-12.8-1.9-19.3-1.9c-79.3-0.1-158.7-0.1-238,0c-10.3,0-20.6,0.5-30.9,1C106,65.9,100,71.1,99.1,80c-0.7,6.8-0.7,13.6-1.2,20.4c-0.3,4.1,1.6,5,5.3,5c36,0.2,72,0.2,107.9,1.2c16.9,0.5,34,1.9,50.5,5.2c28.7,5.7,48.1,22.6,54.9,52c2.6,11.1,2.7,22.4,1.3,33.7C315.6,217.1,308.2,234.1,292.2,246.5z M170,217.5c0.4,9.3,3.3,13.7,11.9,14.4c11.7,0.9,23.6,1.1,35.3,0.1c11.5-1,19.4-8.1,21.4-19.8c1.2-7,1.8-14.3,1.2-21.3c-1.3-16.1-8.8-24.2-23.9-27c-2.4-0.5-5-0.8-7.4-0.8c-10.8,0-21.6,0.1-32.4,0.4c-3.8,0.1-6.2,2.5-6.2,6.5c-0.1,9.8,0,19.5,0,29.3h0.1v17.5C170,216.8,170,217.1,170,217.5z"
62 | },
63 | new int[]{
64 | 0xFF1D1D1D
65 | },
66 | 512, 512
67 | ),
68 | BUSYBOX_LOGO(
69 | new String[]{
70 | "M481,452.9c0,15.5-12.6,28.1-28.1,28.1H59.1C43.6,481,31,468.4,31,452.9V59.1C31,43.6,43.6,31,59.1,31h393.8c15.5,0,28.1,12.6,28.1,28.1V452.9z",
71 | "M256.4,397.8l-0.1-0.1l-119.9-71.4v-115l0,0l120.4,64.2l0,0l0.1,0l0,0V398l0,0l-0.1-0.1L256.4,397.8z",
72 | "M375.7,211.3l-118.8,64.3V398l118.8-71.9L375.7,211.3L375.7,211.3z",
73 | "M375.7,211.3v84.2L312.3,328l-55.5-52.4v0L375.7,211.3L375.7,211.3z",
74 | "M433.3,244.4L375.7,274l-64,32.9l-54.8-31.3v0l118.8-64.3l0,0L433.3,244.4z",
75 | "M255.2,147l120.5,64.3l0,0l54.5-31.3l-54.5-30.7l-63-35.4L255.2,147L255.2,147L255.2,147L255.2,147z",
76 | "M78.6,178.3l57.6-29.6l64-32.9l54.8,31.3v0l-118.8,64.3l0,0L78.6,178.3z",
77 | "M255.1,147l1.8,128.7l-0.1,0l-120.5-64.3L255.1,147z",
78 | "M256.9,275.7L256.9,275.7L255.1,147l0,0l0.1,0l120.5,64.3L256.9,275.7z",
79 | "M256.9,275.7V350l-91-48.6l9.6-6.1l23.8,13.4L256.9,275.7L256.9,275.7z",
80 | "M256.8,275.6L256.8,275.6l-0.1,122.3l0.2,0.1L256.8,275.6L256.8,275.6z",
81 | "M256.8,275.6l-120.5-64.3l0,0l-54.5,31.3l54.5,30.7l63,35.4L256.8,275.6L256.8,275.6L256.8,275.6L256.8,275.6z"
82 | },
83 | new int[]{
84 | 0xFF41A4C4,
85 | 0xFFD95545,
86 | 0xFFC54C3F,
87 | 0xFFAA4438,
88 | 0xFFF4F3EE,
89 | 0xFFF4F3EE,
90 | 0xFFF4F3EE,
91 | 0xFFD2D1CC,
92 | 0xFFDCDAD6,
93 | 0xFFC54C3F,
94 | 0xFFD95545,
95 | 0xFFF4F3EE
96 | },
97 | 512, 512
98 | );
99 |
100 | public final String[] glyphs;
101 | public final int[] colors;
102 | public final float width;
103 | public final float height;
104 |
105 | SVG(String[] glyphs, int[] colors, float width, float height) {
106 | this.glyphs = glyphs;
107 | this.colors = colors;
108 | this.width = width;
109 | this.height = height;
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
30 |
31 |
46 |
47 |
51 |
52 |
60 |
61 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidSourceTools/AnimatedSvgView/a917c0a0d54f03e9cca4f2f8ffd404d35a98e71a/demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidSourceTools/AnimatedSvgView/a917c0a0d54f03e9cca4f2f8ffd404d35a98e71a/demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidSourceTools/AnimatedSvgView/a917c0a0d54f03e9cca4f2f8ffd404d35a98e71a/demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidSourceTools/AnimatedSvgView/a917c0a0d54f03e9cca4f2f8ffd404d35a98e71a/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidSourceTools/AnimatedSvgView/a917c0a0d54f03e9cca4f2f8ffd404d35a98e71a/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
22 | 64dp
23 |
24 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 | #3F51B5
21 | #303F9F
22 | #FF4081
23 |
24 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 | 16dp
21 | 16dp
22 |
23 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 | AnimatedSvgView Demo
20 | Previous
21 | Next
22 |
23 | #EA4335
24 | #FBBC05
25 | #4285F4
26 | #34A853
27 |
28 |
29 | - M142.9,24.2C97.6,39.7,59,73.6,37.5,116.5c-7.5,14.8-12.9,30.5-16.2,46.8c-8.2,40.4-2.5,83.5,16.1,120.3 c12.1,24,29.5,45.4,50.5,62.1c19.9,15.8,43,27.6,67.6,34.1c31,8.3,64,8.1,95.2,1c28.2-6.5,54.9-20,76.2-39.6 c22.5-20.7,38.6-47.9,47.1-77.2c9.3-31.9,10.5-66,4.7-98.8c-58.3,0-116.7,0-175,0c0,24.2,0,48.4,0,72.6c33.8,0,67.6,0,101.4,0 c-3.9,23.2-17.7,44.4-37.2,57.5c-12.3,8.3-26.4,13.6-41,16.2c-14.6,2.5-29.8,2.8-44.4-0.1c-14.9-3-29-9.2-41.4-17.9 c-19.8-13.9-34.9-34.2-42.6-57.1c-7.9-23.3-8-49.2,0-72.4c5.6-16.4,14.8-31.5,27-43.9c15-15.4,34.5-26.4,55.6-30.9 c18-3.8,37-3.1,54.6,2.2c15,4.5,28.8,12.8,40.1,23.6c11.4-11.4,22.8-22.8,34.2-34.2c6-6.1,12.3-12,18.1-18.3 c-17.3-16-37.7-28.9-59.9-37.1C228.2,10.6,183.2,10.3,142.9,24.2z
30 | - M142.9,24.2c40.2-13.9,85.3-13.6,125.3,1.1c22.2,8.2,42.5,21,59.9,37.1c-5.8,6.3-12.1,12.2-18.1,18.3 c-11.4,11.4-22.8,22.8-34.2,34.2c-11.3-10.8-25.1-19-40.1-23.6c-17.6-5.3-36.6-6.1-54.6-2.2c-21,4.5-40.5,15.5-55.6,30.9 c-12.2,12.3-21.4,27.5-27,43.9c-20.3-15.8-40.6-31.5-61-47.3C59,73.6,97.6,39.7,142.9,24.2z
31 | - M21.4,163.2c3.3-16.2,8.7-32,16.2-46.8c20.3,15.8,40.6,31.5,61,47.3c-8,23.3-8,49.2,0,72.4 c-20.3,15.8-40.6,31.6-60.9,47.3C18.9,246.7,13.2,203.6,21.4,163.2z
32 | - M203.7,165.1c58.3,0,116.7,0,175,0c5.8,32.7,4.5,66.8-4.7,98.8c-8.5,29.3-24.6,56.5-47.1,77.2 c-19.7-15.3-39.4-30.6-59.1-45.9c19.5-13.1,33.3-34.3,37.2-57.5c-33.8,0-67.6,0-101.4,0C203.7,213.5,203.7,189.3,203.7,165.1z
33 | - M37.5,283.5c20.3-15.7,40.6-31.5,60.9-47.3c7.8,22.9,22.8,43.2,42.6,57.1c12.4,8.7,26.6,14.9,41.4,17.9 c14.6,3,29.7,2.6,44.4,0.1c14.6-2.6,28.7-7.9,41-16.2c19.7,15.3,39.4,30.6,59.1,45.9c-21.3,19.7-48,33.1-76.2,39.6 c-31.2,7.1-64.2,7.3-95.2-1c-24.6-6.5-47.7-18.2-67.6-34.1C67,328.9,49.6,307.5,37.5,283.5z
34 |
35 |
36 |
37 | - @android:color/white
38 | - @color/google_red
39 | - @color/google_yellow
40 | - @color/google_blue
41 | - @color/google_green
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | android.enableJetifier=true
2 | android.useAndroidX=true
--------------------------------------------------------------------------------
/gradle/maven-push.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Chris Banes
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'maven'
18 | apply plugin: 'signing'
19 |
20 | def isReleaseBuild() {
21 | return VERSION_NAME.contains("SNAPSHOT") == false
22 | }
23 |
24 | def getReleaseRepositoryUrl() {
25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
27 | }
28 |
29 | def getSnapshotRepositoryUrl() {
30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
31 | : "https://oss.sonatype.org/content/repositories/snapshots/"
32 | }
33 |
34 | def getRepositoryUsername() {
35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
36 | }
37 |
38 | def getRepositoryPassword() {
39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
40 | }
41 |
42 | afterEvaluate { project ->
43 | uploadArchives {
44 | repositories {
45 | mavenDeployer {
46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
47 |
48 | pom.groupId = GROUP
49 | pom.artifactId = POM_ARTIFACT_ID
50 | pom.version = VERSION_NAME
51 |
52 | repository(url: getReleaseRepositoryUrl()) {
53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
54 | }
55 | snapshotRepository(url: getSnapshotRepositoryUrl()) {
56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
57 | }
58 |
59 | pom.project {
60 | name POM_NAME
61 | packaging POM_PACKAGING
62 | description POM_DESCRIPTION
63 | url POM_URL
64 |
65 | scm {
66 | url POM_SCM_URL
67 | connection POM_SCM_CONNECTION
68 | developerConnection POM_SCM_DEV_CONNECTION
69 | }
70 |
71 | licenses {
72 | license {
73 | name POM_LICENCE_NAME
74 | url POM_LICENCE_URL
75 | distribution POM_LICENCE_DIST
76 | }
77 | }
78 |
79 | developers {
80 | developer {
81 | id POM_DEVELOPER_ID
82 | name POM_DEVELOPER_NAME
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | signing {
91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
92 | sign configurations.archives
93 | }
94 |
95 | task androidJavadocs(type: Javadoc) {
96 | source = android.sourceSets.main.java.srcDirs
97 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
98 | }
99 |
100 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
101 | classifier = 'javadoc'
102 | from androidJavadocs.destinationDir
103 | }
104 |
105 | task androidSourcesJar(type: Jar) {
106 | classifier = 'sources'
107 | from android.sourceSets.main.java.sourceFiles
108 | }
109 |
110 | artifacts {
111 | archives androidSourcesJar
112 | archives androidJavadocsJar
113 | }
114 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndroidSourceTools/AnimatedSvgView/a917c0a0d54f03e9cca4f2f8ffd404d35a98e71a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Oct 28 21:03:42 PDT 2018
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-4.6-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 28
5 | buildToolsVersion '28.0.3'
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 28
10 | }
11 | }
12 |
13 | dependencies {
14 | implementation 'androidx.core:core:1.0.0'
15 | }
16 |
17 | apply from: rootProject.file('gradle/maven-push.gradle')
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2016 Jared Rummler
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | #
17 |
18 | VERSION_NAME=1.0.6
19 | VERSION_CODE=106
20 | GROUP=com.jaredrummler
21 | ARTIFACT_ID=animated-svg-view
22 |
23 | POM_NAME=AnimatedSvgView
24 | POM_ARTIFACT_ID=animated-svg-view
25 | POM_PACKAGING=aar
26 |
27 | POM_DESCRIPTION=Animated SVG Drawing for Android
28 | POM_URL=https://github.com/jaredrummler/AnimatedSvgView
29 | POM_SCM_URL=https://github.com/jaredrummler/AnimatedSvgView
30 | POM_SCM_CONNECTION=scm:git@github.com:jaredrummler/AnimatedSvgView.git
31 | POM_SCM_DEV_CONNECTION=scm:git@github.com:jaredrummler/AnimatedSvgView.git
32 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
33 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
34 | POM_LICENCE_DIST=repo
35 | POM_DEVELOPER_ID=jaredrummler
36 | POM_DEVELOPER_NAME=Jared Rummler
37 |
38 | SNAPSHOT_REPOSITORY_URL=https://oss.sonatype.org/content/repositories/snapshots
39 | RELEASE_REPOSITORY_URL=https://oss.sonatype.org/service/local/staging/deploy/maven2
--------------------------------------------------------------------------------
/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 C:\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/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library/src/main/java/com/jaredrummler/android/widget/AnimatedSvgView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Jared Rummler
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.jaredrummler.android.widget;
19 |
20 | import android.annotation.SuppressLint;
21 | import android.content.Context;
22 | import android.content.res.TypedArray;
23 | import android.graphics.Canvas;
24 | import android.graphics.Color;
25 | import android.graphics.DashPathEffect;
26 | import android.graphics.Matrix;
27 | import android.graphics.Paint;
28 | import android.graphics.Path;
29 | import android.graphics.PathMeasure;
30 | import android.graphics.PointF;
31 | import android.graphics.RectF;
32 | import android.os.Build;
33 | import androidx.annotation.ColorInt;
34 | import androidx.annotation.IntDef;
35 | import androidx.annotation.NonNull;
36 | import androidx.core.view.ViewCompat;
37 | import android.util.AttributeSet;
38 | import android.util.Log;
39 | import android.util.TypedValue;
40 | import android.view.View;
41 | import android.view.animation.DecelerateInterpolator;
42 | import android.view.animation.Interpolator;
43 | import com.jaredrummler.android.animatedsvgview.R;
44 |
45 | /**
46 | * Animated SVG Drawing for Android
47 | */
48 | public class AnimatedSvgView extends View {
49 |
50 | /** The animation has been reset or hasn't started yet. */
51 | public static final int STATE_NOT_STARTED = 0;
52 | /** The SVG is being traced */
53 | public static final int STATE_TRACE_STARTED = 1;
54 | /** The SVG has been traced and is now being filled */
55 | public static final int STATE_FILL_STARTED = 2;
56 | /** The animation has finished */
57 | public static final int STATE_FINISHED = 3;
58 |
59 | private static final String TAG = "AnimatedSvgView";
60 |
61 | private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
62 |
63 | private static float constrain(float min, float max, float v) {
64 | return Math.max(min, Math.min(max, v));
65 | }
66 |
67 | private int mTraceTime = 2000;
68 | private int mTraceTimePerGlyph = 1000;
69 | private int mFillStart = 1200;
70 | private int mFillTime = 1000;
71 | private int[] mTraceResidueColors;
72 | private int[] mTraceColors;
73 | private float mViewportWidth;
74 | private float mViewportHeight;
75 | private PointF mViewport = new PointF(mViewportWidth, mViewportHeight);
76 | private float aspectRatioWidth = 1;
77 | private float aspectRatioHeight = 1;
78 |
79 | private Paint mFillPaint;
80 | private int[] mFillColors;
81 | private GlyphData[] mGlyphData;
82 | private String[] mGlyphStrings;
83 | private float mMarkerLength;
84 | private int mWidth;
85 | private int mHeight;
86 | private long mStartTime;
87 |
88 | private int mState = STATE_NOT_STARTED;
89 | private OnStateChangeListener mOnStateChangeListener;
90 |
91 | public AnimatedSvgView(Context context) {
92 | super(context);
93 | init(context, null);
94 | }
95 |
96 | public AnimatedSvgView(Context context, AttributeSet attrs) {
97 | super(context, attrs);
98 | init(context, attrs);
99 | }
100 |
101 | public AnimatedSvgView(Context context, AttributeSet attrs, int defStyle) {
102 | super(context, attrs, defStyle);
103 | init(context, attrs);
104 | }
105 |
106 | private void init(Context context, AttributeSet attrs) {
107 | mFillPaint = new Paint();
108 | mFillPaint.setAntiAlias(true);
109 | mFillPaint.setStyle(Paint.Style.FILL);
110 |
111 | mTraceColors = new int[1];
112 | mTraceColors[0] = Color.BLACK;
113 | mTraceResidueColors = new int[1];
114 | mTraceResidueColors[0] = 0x32000000;
115 |
116 | if (attrs != null) {
117 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AnimatedSvgView);
118 | mViewportWidth = a.getInt(R.styleable.AnimatedSvgView_animatedSvgImageSizeX, 512);
119 | aspectRatioWidth = a.getInt(R.styleable.AnimatedSvgView_animatedSvgImageSizeX, 512);
120 | mViewportHeight = a.getInt(R.styleable.AnimatedSvgView_animatedSvgImageSizeY, 512);
121 | aspectRatioHeight = a.getInt(R.styleable.AnimatedSvgView_animatedSvgImageSizeY, 512);
122 | mTraceTime = a.getInt(R.styleable.AnimatedSvgView_animatedSvgTraceTime, 2000);
123 | mTraceTimePerGlyph = a.getInt(R.styleable.AnimatedSvgView_animatedSvgTraceTimePerGlyph, 1000);
124 | mFillStart = a.getInt(R.styleable.AnimatedSvgView_animatedSvgFillStart, 1200);
125 | mFillTime = a.getInt(R.styleable.AnimatedSvgView_animatedSvgFillTime, 1000);
126 | int traceMarkerLength = a.getInt(R.styleable.AnimatedSvgView_animatedSvgTraceMarkerLength, 16);
127 | mMarkerLength =
128 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, traceMarkerLength, getResources().getDisplayMetrics());
129 | int glyphStringsId = a.getResourceId(R.styleable.AnimatedSvgView_animatedSvgGlyphStrings, 0);
130 | int traceResidueColorsId = a.getResourceId(R.styleable.AnimatedSvgView_animatedSvgTraceResidueColors, 0);
131 | int traceColorsId = a.getResourceId(R.styleable.AnimatedSvgView_animatedSvgTraceColors, 0);
132 | int fillColorsId = a.getResourceId(R.styleable.AnimatedSvgView_animatedSvgFillColors, 0);
133 |
134 | a.recycle();
135 |
136 | if (glyphStringsId != 0) {
137 | setGlyphStrings(getResources().getStringArray(glyphStringsId));
138 | setTraceResidueColor(Color.argb(50, 0, 0, 0));
139 | setTraceColor(Color.BLACK);
140 | }
141 | if (traceResidueColorsId != 0) {
142 | setTraceResidueColors(getResources().getIntArray(traceResidueColorsId));
143 | }
144 | if (traceColorsId != 0) {
145 | setTraceColors(getResources().getIntArray(traceColorsId));
146 | }
147 | if (fillColorsId != 0) {
148 | setFillColors(getResources().getIntArray(fillColorsId));
149 | }
150 |
151 | mViewport = new PointF(mViewportWidth, mViewportHeight);
152 | }
153 |
154 | // Note: using a software layer here is an optimization. This view works with hardware accelerated rendering but
155 | // every time a path is modified (when the dash path effect is modified), the graphics pipeline will rasterize
156 | // the path again in a new texture. Since we are dealing with dozens of paths, it is much more efficient to
157 | // rasterize the entire view into a single re-usable texture instead. Ideally this should be toggled using a
158 | // heuristic based on the number and or dimensions of paths to render.
159 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
160 | setLayerType(LAYER_TYPE_SOFTWARE, null);
161 | }
162 | }
163 |
164 | @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
165 | super.onSizeChanged(w, h, oldw, oldh);
166 | mWidth = w;
167 | mHeight = h;
168 | rebuildGlyphData();
169 | }
170 |
171 | @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
172 | int width = View.MeasureSpec.getSize(widthMeasureSpec);
173 | int height = View.MeasureSpec.getSize(heightMeasureSpec);
174 | int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
175 | int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
176 |
177 | if (height <= 0 && width <= 0 && heightMode == View.MeasureSpec.UNSPECIFIED &&
178 | widthMode == View.MeasureSpec.UNSPECIFIED) {
179 | width = 0;
180 | height = 0;
181 | } else if (height <= 0 && heightMode == View.MeasureSpec.UNSPECIFIED) {
182 | height = (int) (width * aspectRatioHeight / aspectRatioWidth);
183 | } else if (width <= 0 && widthMode == View.MeasureSpec.UNSPECIFIED) {
184 | width = (int) (height * aspectRatioWidth / aspectRatioHeight);
185 | } else if (width * aspectRatioHeight > aspectRatioWidth * height) {
186 | width = (int) (height * aspectRatioWidth / aspectRatioHeight);
187 | } else {
188 | height = (int) (width * aspectRatioHeight / aspectRatioWidth);
189 | }
190 |
191 | super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
192 | MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
193 | }
194 |
195 | @SuppressLint("DrawAllocation")
196 | @Override
197 | protected void onDraw(Canvas canvas) {
198 | super.onDraw(canvas);
199 | if (mState == STATE_NOT_STARTED || mGlyphData == null) {
200 | return;
201 | }
202 |
203 | long t = System.currentTimeMillis() - mStartTime;
204 |
205 | // Draw outlines (starts as traced)
206 | for (int i = 0; i < mGlyphData.length; i++) {
207 | float phase = constrain(0, 1,
208 | (t - (mTraceTime - mTraceTimePerGlyph) * i * 1f / mGlyphData.length) * 1f / mTraceTimePerGlyph);
209 | float distance = INTERPOLATOR.getInterpolation(phase) * mGlyphData[i].length;
210 | mGlyphData[i].paint.setColor(mTraceResidueColors[i]);
211 | mGlyphData[i].paint.setPathEffect(new DashPathEffect(
212 | new float[]{distance, mGlyphData[i].length}, 0));
213 | canvas.drawPath(mGlyphData[i].path, mGlyphData[i].paint);
214 |
215 | mGlyphData[i].paint.setColor(mTraceColors[i]);
216 | mGlyphData[i].paint.setPathEffect(new DashPathEffect(
217 | new float[]{0, distance, phase > 0 ? mMarkerLength : 0, mGlyphData[i].length}, 0));
218 | canvas.drawPath(mGlyphData[i].path, mGlyphData[i].paint);
219 | }
220 |
221 | if (t > mFillStart) {
222 | if (mState < STATE_FILL_STARTED) {
223 | changeState(STATE_FILL_STARTED);
224 | }
225 |
226 | // If after fill start, draw fill
227 | float phase = constrain(0, 1, (t - mFillStart) * 1f / mFillTime);
228 | for (int i = 0; i < mGlyphData.length; i++) {
229 | GlyphData glyphData = mGlyphData[i];
230 | int fillColor = mFillColors[i];
231 | int a = (int) (phase * ((float) Color.alpha(fillColor) / (float) 255) * 255);
232 | int r = Color.red(fillColor);
233 | int g = Color.green(fillColor);
234 | int b = Color.blue(fillColor);
235 | mFillPaint.setARGB(a, r, g, b);
236 | canvas.drawPath(glyphData.path, mFillPaint);
237 | }
238 | }
239 |
240 | if (t < mFillStart + mFillTime) {
241 | // draw next frame if animation isn't finished
242 | ViewCompat.postInvalidateOnAnimation(this);
243 | } else {
244 | changeState(STATE_FINISHED);
245 | }
246 | }
247 |
248 | /**
249 | * If you set the SVG data paths more than once using {@link #setGlyphStrings(String...)} you should call this method
250 | * before playing the animation.
251 | */
252 | @SuppressWarnings("SuspiciousNameCombination")
253 | public void rebuildGlyphData() {
254 |
255 | float X = mWidth / mViewport.x;
256 | float Y = mHeight / mViewport.y;
257 |
258 | Matrix scaleMatrix = new Matrix();
259 | RectF outerRect = new RectF(X, X, Y, Y);
260 | scaleMatrix.setScale(X, Y, outerRect.centerX(), outerRect.centerY());
261 |
262 | mGlyphData = new GlyphData[mGlyphStrings.length];
263 | for (int i = 0; i < mGlyphStrings.length; i++) {
264 | mGlyphData[i] = new GlyphData();
265 | try {
266 | mGlyphData[i].path = PathParser.createPathFromPathData(mGlyphStrings[i]);
267 | mGlyphData[i].path.transform(scaleMatrix);
268 | } catch (Exception e) {
269 | mGlyphData[i].path = new Path();
270 | Log.e(TAG, "Couldn't parse path", e);
271 | }
272 | PathMeasure pm = new PathMeasure(mGlyphData[i].path, true);
273 | while (true) {
274 | mGlyphData[i].length = Math.max(mGlyphData[i].length, pm.getLength());
275 | if (!pm.nextContour()) {
276 | break;
277 | }
278 | }
279 | mGlyphData[i].paint = new Paint();
280 | mGlyphData[i].paint.setStyle(Paint.Style.STROKE);
281 | mGlyphData[i].paint.setAntiAlias(true);
282 | mGlyphData[i].paint.setColor(Color.WHITE);
283 | mGlyphData[i].paint.setStrokeWidth(
284 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()));
285 | }
286 | }
287 |
288 | /**
289 | * Set the viewport width and height of the SVG. This can be found in the viewBox in the SVG. This is not the size
290 | * of the view.
291 | *
292 | * @param viewportWidth
293 | * the width
294 | * @param viewportHeight
295 | * the height
296 | */
297 | public void setViewportSize(float viewportWidth, float viewportHeight) {
298 | mViewportWidth = viewportWidth;
299 | mViewportHeight = viewportHeight;
300 | aspectRatioWidth = viewportWidth;
301 | aspectRatioHeight = viewportHeight;
302 | mViewport = new PointF(mViewportWidth, mViewportHeight);
303 | requestLayout();
304 | }
305 |
306 | /**
307 | * Set the SVG path data.
308 | *
309 | * @param glyphStrings
310 | * The path strings found in the SVG.
311 | */
312 | public void setGlyphStrings(@NonNull String... glyphStrings) {
313 | mGlyphStrings = glyphStrings;
314 | }
315 |
316 | /**
317 | * Set the colors used during tracing the SVG
318 | *
319 | * @param traceResidueColors
320 | * the colors. Should be the same length as the SVG paths.
321 | */
322 | public void setTraceResidueColors(@NonNull int[] traceResidueColors) {
323 | mTraceResidueColors = traceResidueColors;
324 | }
325 |
326 | /**
327 | * Set the colors used to trace the SVG.
328 | *
329 | * @param traceColors
330 | * The colors. Should be the same length as the SVG paths.
331 | */
332 | public void setTraceColors(@NonNull int[] traceColors) {
333 | mTraceColors = traceColors;
334 | }
335 |
336 | /**
337 | * Set the colors for the SVG. This corresponds with each data path.
338 | *
339 | * @param fillColors
340 | * The colors for each SVG data path.
341 | */
342 | public void setFillColors(@NonNull int[] fillColors) {
343 | mFillColors = fillColors;
344 | }
345 |
346 | /**
347 | * Set the color used for tracing. This will be applied to all data paths.
348 | *
349 | * @param color
350 | * The color
351 | */
352 | public void setTraceResidueColor(@ColorInt int color) {
353 | if (mGlyphStrings == null) {
354 | throw new RuntimeException("You need to set the glyphs first.");
355 | }
356 | int length = mGlyphStrings.length;
357 | int[] colors = new int[length];
358 | for (int i = 0; i < length; i++) {
359 | colors[i] = color;
360 | }
361 | setTraceResidueColors(colors);
362 | }
363 |
364 | /**
365 | * Set the color used for tracing. This will be applied to all data paths.
366 | *
367 | * @param color
368 | * The color
369 | */
370 | public void setTraceColor(@ColorInt int color) {
371 | if (mGlyphStrings == null) {
372 | throw new RuntimeException("You need to set the glyphs first.");
373 | }
374 | int length = mGlyphStrings.length;
375 | int[] colors = new int[length];
376 | for (int i = 0; i < length; i++) {
377 | colors[i] = color;
378 | }
379 | setTraceColors(colors);
380 | }
381 |
382 | /**
383 | * Set the color used for the icon. This will apply the color to all SVG data paths.
384 | *
385 | * @param color
386 | * The color
387 | */
388 | public void setFillColor(@ColorInt int color) {
389 | if (mGlyphStrings == null) {
390 | throw new RuntimeException("You need to set the glyphs first.");
391 | }
392 | int length = mGlyphStrings.length;
393 | int[] colors = new int[length];
394 | for (int i = 0; i < length; i++) {
395 | colors[i] = color;
396 | }
397 | setFillColors(colors);
398 | }
399 |
400 | /**
401 | * Set the animation trace time
402 | *
403 | * @param traceTime
404 | * time in milliseconds
405 | */
406 | public void setTraceTime(int traceTime) {
407 | mTraceTime = traceTime;
408 | }
409 |
410 | /**
411 | * Set the time used to trace each glyph
412 | *
413 | * @param traceTimePerGlyph
414 | * time in milliseconds
415 | */
416 | public void setTraceTimePerGlyph(int traceTimePerGlyph) {
417 | mTraceTimePerGlyph = traceTimePerGlyph;
418 | }
419 |
420 | /**
421 | * Set the time at which colors will start being filled after the tracing begins
422 | *
423 | * @param fillStart
424 | * time in milliseconds
425 | */
426 | public void setFillStart(int fillStart) {
427 | mFillStart = fillStart;
428 | }
429 |
430 | /**
431 | * Set the time it takes to fill colors
432 | *
433 | * @param fillTime
434 | * time in milliseconds
435 | */
436 | public void setFillTime(int fillTime) {
437 | mFillTime = fillTime;
438 | }
439 |
440 | /**
441 | * Start the animation
442 | */
443 | public void start() {
444 | mStartTime = System.currentTimeMillis();
445 | changeState(STATE_TRACE_STARTED);
446 | ViewCompat.postInvalidateOnAnimation(this);
447 | }
448 |
449 | /**
450 | * Reset the animation
451 | */
452 | public void reset() {
453 | mStartTime = 0;
454 | changeState(STATE_NOT_STARTED);
455 | ViewCompat.postInvalidateOnAnimation(this);
456 | }
457 |
458 | /**
459 | * Draw the SVG, skipping any animation.
460 | */
461 | public void setToFinishedFrame() {
462 | mStartTime = 1;
463 | changeState(STATE_FINISHED);
464 | ViewCompat.postInvalidateOnAnimation(this);
465 | }
466 |
467 | /**
468 | * Get the animation state.
469 | *
470 | * @return Either {{@link #STATE_NOT_STARTED},
471 | * {@link #STATE_TRACE_STARTED}},
472 | * {@link #STATE_FILL_STARTED} or
473 | * {@link #STATE_FINISHED}
474 | */
475 | @State public int getState() {
476 | return mState;
477 | }
478 |
479 | /**
480 | * Get notified about the animation states.
481 | *
482 | * @param onStateChangeListener
483 | * The {@link OnStateChangeListener}
484 | */
485 | public void setOnStateChangeListener(OnStateChangeListener onStateChangeListener) {
486 | mOnStateChangeListener = onStateChangeListener;
487 | }
488 |
489 | private void changeState(@State int state) {
490 | if (mState == state) {
491 | return;
492 | }
493 |
494 | mState = state;
495 | if (mOnStateChangeListener != null) {
496 | mOnStateChangeListener.onStateChange(state);
497 | }
498 | }
499 |
500 | /**
501 | * Callback for listening to animation state changes
502 | */
503 | public interface OnStateChangeListener {
504 |
505 | /**
506 | * Called when the animation state changes.
507 | *
508 | * @param state
509 | * The state of the animation.
510 | * Either {{@link #STATE_NOT_STARTED},
511 | * {@link #STATE_TRACE_STARTED}},
512 | * {@link #STATE_FILL_STARTED} or
513 | * {@link #STATE_FINISHED}
514 | */
515 | void onStateChange(@State int state);
516 | }
517 |
518 | @IntDef({STATE_NOT_STARTED, STATE_TRACE_STARTED, STATE_FILL_STARTED, STATE_FINISHED})
519 | public @interface State {
520 | }
521 |
522 | static final class GlyphData {
523 | Path path;
524 | Paint paint;
525 | float length;
526 |
527 | }
528 |
529 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/jaredrummler/android/widget/PathParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.jaredrummler.android.widget;
18 |
19 | import android.graphics.Path;
20 | import android.util.Log;
21 | import java.util.ArrayList;
22 |
23 | /**
24 | * This class is a duplicate from the PathParser.java of frameworks/base, with slight
25 | * update on incompatible API like copyOfRange().
26 | */
27 | final class PathParser {
28 |
29 | private static final String LOGTAG = "PathParser";
30 |
31 | // Copy from Arrays.copyOfRange() which is only available from API level 9.
32 |
33 | /**
34 | * Copies elements from {@code original} into a new array, from indexes start (inclusive) to
35 | * end (exclusive). The original order of elements is preserved.
36 | * If {@code end} is greater than {@code original.length}, the result is padded
37 | * with the value {@code 0.0f}.
38 | *
39 | * @param original
40 | * the original array
41 | * @param start
42 | * the start index, inclusive
43 | * @param end
44 | * the end index, exclusive
45 | * @return the new array
46 | * @throws ArrayIndexOutOfBoundsException
47 | * if {@code start < 0 || start > original.length}
48 | * @throws IllegalArgumentException
49 | * if {@code start > end}
50 | * @throws NullPointerException
51 | * if {@code original == null}
52 | */
53 | static float[] copyOfRange(float[] original, int start, int end) {
54 | if (start > end) {
55 | throw new IllegalArgumentException();
56 | }
57 | int originalLength = original.length;
58 | if (start < 0 || start > originalLength) {
59 | throw new ArrayIndexOutOfBoundsException();
60 | }
61 | int resultLength = end - start;
62 | int copyLength = Math.min(resultLength, originalLength - start);
63 | float[] result = new float[resultLength];
64 | System.arraycopy(original, start, result, 0, copyLength);
65 | return result;
66 | }
67 |
68 | /**
69 | * @param pathData
70 | * The string representing a path, the same as "d" string in svg file.
71 | * @return the generated Path object.
72 | */
73 | public static Path createPathFromPathData(String pathData) {
74 | Path path = new Path();
75 | PathDataNode[] nodes = createNodesFromPathData(pathData);
76 | if (nodes != null) {
77 | try {
78 | PathDataNode.nodesToPath(nodes, path);
79 | } catch (RuntimeException e) {
80 | throw new RuntimeException("Error in parsing " + pathData, e);
81 | }
82 | return path;
83 | }
84 | return null;
85 | }
86 |
87 | /**
88 | * @param pathData
89 | * The string representing a path, the same as "d" string in svg file.
90 | * @return an array of the PathDataNode.
91 | */
92 | public static PathDataNode[] createNodesFromPathData(String pathData) {
93 | if (pathData == null) {
94 | return null;
95 | }
96 | int start = 0;
97 | int end = 1;
98 |
99 | ArrayList list = new ArrayList();
100 | while (end < pathData.length()) {
101 | end = nextStart(pathData, end);
102 | String s = pathData.substring(start, end).trim();
103 | if (s.length() > 0) {
104 | float[] val = getFloats(s);
105 | addNode(list, s.charAt(0), val);
106 | }
107 |
108 | start = end;
109 | end++;
110 | }
111 | if ((end - start) == 1 && start < pathData.length()) {
112 | addNode(list, pathData.charAt(start), new float[0]);
113 | }
114 | return list.toArray(new PathDataNode[list.size()]);
115 | }
116 |
117 | /**
118 | * @param source
119 | * The array of PathDataNode to be duplicated.
120 | * @return a deep copy of the source
.
121 | */
122 | public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {
123 | if (source == null) {
124 | return null;
125 | }
126 | PathDataNode[] copy = new PathParser.PathDataNode[source.length];
127 | for (int i = 0; i < source.length; i++) {
128 | copy[i] = new PathDataNode(source[i]);
129 | }
130 | return copy;
131 | }
132 |
133 | /**
134 | * @param nodesFrom
135 | * The source path represented in an array of PathDataNode
136 | * @param nodesTo
137 | * The target path represented in an array of PathDataNode
138 | * @return whether the nodesFrom
can morph into nodesTo
139 | */
140 | public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
141 | if (nodesFrom == null || nodesTo == null) {
142 | return false;
143 | }
144 |
145 | if (nodesFrom.length != nodesTo.length) {
146 | return false;
147 | }
148 |
149 | for (int i = 0; i < nodesFrom.length; i++) {
150 | if (nodesFrom[i].mType != nodesTo[i].mType
151 | || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
152 | return false;
153 | }
154 | }
155 | return true;
156 | }
157 |
158 | /**
159 | * Update the target's data to match the source.
160 | * Before calling this, make sure canMorph(target, source) is true.
161 | *
162 | * @param target
163 | * The target path represented in an array of PathDataNode
164 | * @param source
165 | * The source path represented in an array of PathDataNode
166 | */
167 | public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
168 | for (int i = 0; i < source.length; i++) {
169 | target[i].mType = source[i].mType;
170 | for (int j = 0; j < source[i].mParams.length; j++) {
171 | target[i].mParams[j] = source[i].mParams[j];
172 | }
173 | }
174 | }
175 |
176 | private static int nextStart(String s, int end) {
177 | char c;
178 |
179 | while (end < s.length()) {
180 | c = s.charAt(end);
181 | // Note that 'e' or 'E' are not valid path commands, but could be
182 | // used for floating point numbers' scientific notation.
183 | // Therefore, when searching for next command, we should ignore 'e'
184 | // and 'E'.
185 | if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
186 | && c != 'e' && c != 'E') {
187 | return end;
188 | }
189 | end++;
190 | }
191 | return end;
192 | }
193 |
194 | private static void addNode(ArrayList list, char cmd, float[] val) {
195 | list.add(new PathDataNode(cmd, val));
196 | }
197 |
198 | private static class ExtractFloatResult {
199 | // We need to return the position of the next separator and whether the
200 | // next float starts with a '-' or a '.'.
201 | int mEndPosition;
202 | boolean mEndWithNegOrDot;
203 |
204 | ExtractFloatResult() {
205 | }
206 | }
207 |
208 | /**
209 | * Parse the floats in the string.
210 | * This is an optimized version of parseFloat(s.split(",|\\s"));
211 | *
212 | * @param s
213 | * the string containing a command and list of floats
214 | * @return array of floats
215 | */
216 | private static float[] getFloats(String s) {
217 | if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
218 | return new float[0];
219 | }
220 | try {
221 | float[] results = new float[s.length()];
222 | int count = 0;
223 | int startPosition = 1;
224 | int endPosition = 0;
225 |
226 | ExtractFloatResult result = new ExtractFloatResult();
227 | int totalLength = s.length();
228 |
229 | // The startPosition should always be the first character of the
230 | // current number, and endPosition is the character after the current
231 | // number.
232 | while (startPosition < totalLength) {
233 | extract(s, startPosition, result);
234 | endPosition = result.mEndPosition;
235 |
236 | if (startPosition < endPosition) {
237 | results[count++] = Float.parseFloat(
238 | s.substring(startPosition, endPosition));
239 | }
240 |
241 | if (result.mEndWithNegOrDot) {
242 | // Keep the '-' or '.' sign with next number.
243 | startPosition = endPosition;
244 | } else {
245 | startPosition = endPosition + 1;
246 | }
247 | }
248 | return copyOfRange(results, 0, count);
249 | } catch (NumberFormatException e) {
250 | throw new RuntimeException("error in parsing \"" + s + "\"", e);
251 | }
252 | }
253 |
254 | /**
255 | * Calculate the position of the next comma or space or negative sign
256 | *
257 | * @param s
258 | * the string to search
259 | * @param start
260 | * the position to start searching
261 | * @param result
262 | * the result of the extraction, including the position of the
263 | * the starting position of next number, whether it is ending with a '-'.
264 | */
265 | private static void extract(String s, int start, ExtractFloatResult result) {
266 | // Now looking for ' ', ',', '.' or '-' from the start.
267 | int currentIndex = start;
268 | boolean foundSeparator = false;
269 | result.mEndWithNegOrDot = false;
270 | boolean secondDot = false;
271 | boolean isExponential = false;
272 | for (; currentIndex < s.length(); currentIndex++) {
273 | boolean isPrevExponential = isExponential;
274 | isExponential = false;
275 | char currentChar = s.charAt(currentIndex);
276 | switch (currentChar) {
277 | case ' ':
278 | case ',':
279 | foundSeparator = true;
280 | break;
281 | case '-':
282 | // The negative sign following a 'e' or 'E' is not a separator.
283 | if (currentIndex != start && !isPrevExponential) {
284 | foundSeparator = true;
285 | result.mEndWithNegOrDot = true;
286 | }
287 | break;
288 | case '.':
289 | if (!secondDot) {
290 | secondDot = true;
291 | } else {
292 | // This is the second dot, and it is considered as a separator.
293 | foundSeparator = true;
294 | result.mEndWithNegOrDot = true;
295 | }
296 | break;
297 | case 'e':
298 | case 'E':
299 | isExponential = true;
300 | break;
301 | }
302 | if (foundSeparator) {
303 | break;
304 | }
305 | }
306 | // When there is nothing found, then we put the end position to the end
307 | // of the string.
308 | result.mEndPosition = currentIndex;
309 | }
310 |
311 | /**
312 | * Each PathDataNode represents one command in the "d" attribute of the svg
313 | * file.
314 | * An array of PathDataNode can represent the whole "d" attribute.
315 | */
316 | public static class PathDataNode {
317 |
318 | /**
319 | * @hide
320 | */
321 | public char mType;
322 |
323 | /**
324 | * @hide
325 | */
326 | public float[] mParams;
327 |
328 | PathDataNode(char type, float[] params) {
329 | this.mType = type;
330 | this.mParams = params;
331 | }
332 |
333 | PathDataNode(PathDataNode n) {
334 | mType = n.mType;
335 | mParams = copyOfRange(n.mParams, 0, n.mParams.length);
336 | }
337 |
338 | /**
339 | * Convert an array of PathDataNode to Path.
340 | *
341 | * @param node
342 | * The source array of PathDataNode.
343 | * @param path
344 | * The target Path object.
345 | */
346 | public static void nodesToPath(PathDataNode[] node, Path path) {
347 | float[] current = new float[6];
348 | char previousCommand = 'm';
349 | for (int i = 0; i < node.length; i++) {
350 | addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
351 | previousCommand = node[i].mType;
352 | }
353 | }
354 |
355 | /**
356 | * The current PathDataNode will be interpolated between the
357 | * nodeFrom
and nodeTo
according to the
358 | * fraction
.
359 | *
360 | * @param nodeFrom
361 | * The start value as a PathDataNode.
362 | * @param nodeTo
363 | * The end value as a PathDataNode
364 | * @param fraction
365 | * The fraction to interpolate.
366 | */
367 | public void interpolatePathDataNode(PathDataNode nodeFrom,
368 | PathDataNode nodeTo, float fraction) {
369 | for (int i = 0; i < nodeFrom.mParams.length; i++) {
370 | mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
371 | + nodeTo.mParams[i] * fraction;
372 | }
373 | }
374 |
375 | private static void addCommand(Path path, float[] current,
376 | char previousCmd, char cmd, float[] val) {
377 |
378 | int incr = 2;
379 | float currentX = current[0];
380 | float currentY = current[1];
381 | float ctrlPointX = current[2];
382 | float ctrlPointY = current[3];
383 | float currentSegmentStartX = current[4];
384 | float currentSegmentStartY = current[5];
385 | float reflectiveCtrlPointX;
386 | float reflectiveCtrlPointY;
387 |
388 | switch (cmd) {
389 | case 'z':
390 | case 'Z':
391 | path.close();
392 | // Path is closed here, but we need to move the pen to the
393 | // closed position. So we cache the segment's starting position,
394 | // and restore it here.
395 | currentX = currentSegmentStartX;
396 | currentY = currentSegmentStartY;
397 | ctrlPointX = currentSegmentStartX;
398 | ctrlPointY = currentSegmentStartY;
399 | path.moveTo(currentX, currentY);
400 | break;
401 | case 'm':
402 | case 'M':
403 | case 'l':
404 | case 'L':
405 | case 't':
406 | case 'T':
407 | incr = 2;
408 | break;
409 | case 'h':
410 | case 'H':
411 | case 'v':
412 | case 'V':
413 | incr = 1;
414 | break;
415 | case 'c':
416 | case 'C':
417 | incr = 6;
418 | break;
419 | case 's':
420 | case 'S':
421 | case 'q':
422 | case 'Q':
423 | incr = 4;
424 | break;
425 | case 'a':
426 | case 'A':
427 | incr = 7;
428 | break;
429 | }
430 |
431 | for (int k = 0; k < val.length; k += incr) {
432 | switch (cmd) {
433 | case 'm': // moveto - Start a new sub-path (relative)
434 | currentX += val[k + 0];
435 | currentY += val[k + 1];
436 | if (k > 0) {
437 | // According to the spec, if a moveto is followed by multiple
438 | // pairs of coordinates, the subsequent pairs are treated as
439 | // implicit lineto commands.
440 | path.rLineTo(val[k + 0], val[k + 1]);
441 | } else {
442 | path.rMoveTo(val[k + 0], val[k + 1]);
443 | currentSegmentStartX = currentX;
444 | currentSegmentStartY = currentY;
445 | }
446 | break;
447 | case 'M': // moveto - Start a new sub-path
448 | currentX = val[k + 0];
449 | currentY = val[k + 1];
450 | if (k > 0) {
451 | // According to the spec, if a moveto is followed by multiple
452 | // pairs of coordinates, the subsequent pairs are treated as
453 | // implicit lineto commands.
454 | path.lineTo(val[k + 0], val[k + 1]);
455 | } else {
456 | path.moveTo(val[k + 0], val[k + 1]);
457 | currentSegmentStartX = currentX;
458 | currentSegmentStartY = currentY;
459 | }
460 | break;
461 | case 'l': // lineto - Draw a line from the current point (relative)
462 | path.rLineTo(val[k + 0], val[k + 1]);
463 | currentX += val[k + 0];
464 | currentY += val[k + 1];
465 | break;
466 | case 'L': // lineto - Draw a line from the current point
467 | path.lineTo(val[k + 0], val[k + 1]);
468 | currentX = val[k + 0];
469 | currentY = val[k + 1];
470 | break;
471 | case 'h': // horizontal lineto - Draws a horizontal line (relative)
472 | path.rLineTo(val[k + 0], 0);
473 | currentX += val[k + 0];
474 | break;
475 | case 'H': // horizontal lineto - Draws a horizontal line
476 | path.lineTo(val[k + 0], currentY);
477 | currentX = val[k + 0];
478 | break;
479 | case 'v': // vertical lineto - Draws a vertical line from the current point (r)
480 | path.rLineTo(0, val[k + 0]);
481 | currentY += val[k + 0];
482 | break;
483 | case 'V': // vertical lineto - Draws a vertical line from the current point
484 | path.lineTo(currentX, val[k + 0]);
485 | currentY = val[k + 0];
486 | break;
487 | case 'c': // curveto - Draws a cubic Bézier curve (relative)
488 | path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
489 | val[k + 4], val[k + 5]);
490 |
491 | ctrlPointX = currentX + val[k + 2];
492 | ctrlPointY = currentY + val[k + 3];
493 | currentX += val[k + 4];
494 | currentY += val[k + 5];
495 |
496 | break;
497 | case 'C': // curveto - Draws a cubic Bézier curve
498 | path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
499 | val[k + 4], val[k + 5]);
500 | currentX = val[k + 4];
501 | currentY = val[k + 5];
502 | ctrlPointX = val[k + 2];
503 | ctrlPointY = val[k + 3];
504 | break;
505 | case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
506 | reflectiveCtrlPointX = 0;
507 | reflectiveCtrlPointY = 0;
508 | if (previousCmd == 'c' || previousCmd == 's'
509 | || previousCmd == 'C' || previousCmd == 'S') {
510 | reflectiveCtrlPointX = currentX - ctrlPointX;
511 | reflectiveCtrlPointY = currentY - ctrlPointY;
512 | }
513 | path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
514 | val[k + 0], val[k + 1],
515 | val[k + 2], val[k + 3]);
516 |
517 | ctrlPointX = currentX + val[k + 0];
518 | ctrlPointY = currentY + val[k + 1];
519 | currentX += val[k + 2];
520 | currentY += val[k + 3];
521 | break;
522 | case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
523 | reflectiveCtrlPointX = currentX;
524 | reflectiveCtrlPointY = currentY;
525 | if (previousCmd == 'c' || previousCmd == 's'
526 | || previousCmd == 'C' || previousCmd == 'S') {
527 | reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
528 | reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
529 | }
530 | path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
531 | val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
532 | ctrlPointX = val[k + 0];
533 | ctrlPointY = val[k + 1];
534 | currentX = val[k + 2];
535 | currentY = val[k + 3];
536 | break;
537 | case 'q': // Draws a quadratic Bézier (relative)
538 | path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
539 | ctrlPointX = currentX + val[k + 0];
540 | ctrlPointY = currentY + val[k + 1];
541 | currentX += val[k + 2];
542 | currentY += val[k + 3];
543 | break;
544 | case 'Q': // Draws a quadratic Bézier
545 | path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
546 | ctrlPointX = val[k + 0];
547 | ctrlPointY = val[k + 1];
548 | currentX = val[k + 2];
549 | currentY = val[k + 3];
550 | break;
551 | case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
552 | reflectiveCtrlPointX = 0;
553 | reflectiveCtrlPointY = 0;
554 | if (previousCmd == 'q' || previousCmd == 't'
555 | || previousCmd == 'Q' || previousCmd == 'T') {
556 | reflectiveCtrlPointX = currentX - ctrlPointX;
557 | reflectiveCtrlPointY = currentY - ctrlPointY;
558 | }
559 | path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
560 | val[k + 0], val[k + 1]);
561 | ctrlPointX = currentX + reflectiveCtrlPointX;
562 | ctrlPointY = currentY + reflectiveCtrlPointY;
563 | currentX += val[k + 0];
564 | currentY += val[k + 1];
565 | break;
566 | case 'T': // Draws a quadratic Bézier curve (reflective control point)
567 | reflectiveCtrlPointX = currentX;
568 | reflectiveCtrlPointY = currentY;
569 | if (previousCmd == 'q' || previousCmd == 't'
570 | || previousCmd == 'Q' || previousCmd == 'T') {
571 | reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
572 | reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
573 | }
574 | path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
575 | val[k + 0], val[k + 1]);
576 | ctrlPointX = reflectiveCtrlPointX;
577 | ctrlPointY = reflectiveCtrlPointY;
578 | currentX = val[k + 0];
579 | currentY = val[k + 1];
580 | break;
581 | case 'a': // Draws an elliptical arc
582 | // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
583 | drawArc(path,
584 | currentX,
585 | currentY,
586 | val[k + 5] + currentX,
587 | val[k + 6] + currentY,
588 | val[k + 0],
589 | val[k + 1],
590 | val[k + 2],
591 | val[k + 3] != 0,
592 | val[k + 4] != 0);
593 | currentX += val[k + 5];
594 | currentY += val[k + 6];
595 | ctrlPointX = currentX;
596 | ctrlPointY = currentY;
597 | break;
598 | case 'A': // Draws an elliptical arc
599 | drawArc(path,
600 | currentX,
601 | currentY,
602 | val[k + 5],
603 | val[k + 6],
604 | val[k + 0],
605 | val[k + 1],
606 | val[k + 2],
607 | val[k + 3] != 0,
608 | val[k + 4] != 0);
609 | currentX = val[k + 5];
610 | currentY = val[k + 6];
611 | ctrlPointX = currentX;
612 | ctrlPointY = currentY;
613 | break;
614 | }
615 | previousCmd = cmd;
616 | }
617 | current[0] = currentX;
618 | current[1] = currentY;
619 | current[2] = ctrlPointX;
620 | current[3] = ctrlPointY;
621 | current[4] = currentSegmentStartX;
622 | current[5] = currentSegmentStartY;
623 | }
624 |
625 | private static void drawArc(Path p,
626 | float x0,
627 | float y0,
628 | float x1,
629 | float y1,
630 | float a,
631 | float b,
632 | float theta,
633 | boolean isMoreThanHalf,
634 | boolean isPositiveArc) {
635 |
636 | /* Convert rotation angle from degrees to radians */
637 | double thetaD = Math.toRadians(theta);
638 | /* Pre-compute rotation matrix entries */
639 | double cosTheta = Math.cos(thetaD);
640 | double sinTheta = Math.sin(thetaD);
641 | /* Transform (x0, y0) and (x1, y1) into unit space */
642 | /* using (inverse) rotation, followed by (inverse) scale */
643 | double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
644 | double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
645 | double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
646 | double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
647 |
648 | /* Compute differences and averages */
649 | double dx = x0p - x1p;
650 | double dy = y0p - y1p;
651 | double xm = (x0p + x1p) / 2;
652 | double ym = (y0p + y1p) / 2;
653 | /* Solve for intersecting unit circles */
654 | double dsq = dx * dx + dy * dy;
655 | if (dsq == 0.0) {
656 | Log.w(LOGTAG, " Points are coincident");
657 | return; /* Points are coincident */
658 | }
659 | double disc = 1.0 / dsq - 1.0 / 4.0;
660 | if (disc < 0.0) {
661 | Log.w(LOGTAG, "Points are too far apart " + dsq);
662 | float adjust = (float) (Math.sqrt(dsq) / 1.99999);
663 | drawArc(p, x0, y0, x1, y1, a * adjust,
664 | b * adjust, theta, isMoreThanHalf, isPositiveArc);
665 | return; /* Points are too far apart */
666 | }
667 | double s = Math.sqrt(disc);
668 | double sdx = s * dx;
669 | double sdy = s * dy;
670 | double cx;
671 | double cy;
672 | if (isMoreThanHalf == isPositiveArc) {
673 | cx = xm - sdy;
674 | cy = ym + sdx;
675 | } else {
676 | cx = xm + sdy;
677 | cy = ym - sdx;
678 | }
679 |
680 | double eta0 = Math.atan2((y0p - cy), (x0p - cx));
681 |
682 | double eta1 = Math.atan2((y1p - cy), (x1p - cx));
683 |
684 | double sweep = (eta1 - eta0);
685 | if (isPositiveArc != (sweep >= 0)) {
686 | if (sweep > 0) {
687 | sweep -= 2 * Math.PI;
688 | } else {
689 | sweep += 2 * Math.PI;
690 | }
691 | }
692 |
693 | cx *= a;
694 | cy *= b;
695 | double tcx = cx;
696 | cx = cx * cosTheta - cy * sinTheta;
697 | cy = tcx * sinTheta + cy * cosTheta;
698 |
699 | arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
700 | }
701 |
702 | /**
703 | * Converts an arc to cubic Bezier segments and records them in p.
704 | *
705 | * @param p
706 | * The target for the cubic Bezier segments
707 | * @param cx
708 | * The x coordinate center of the ellipse
709 | * @param cy
710 | * The y coordinate center of the ellipse
711 | * @param a
712 | * The radius of the ellipse in the horizontal direction
713 | * @param b
714 | * The radius of the ellipse in the vertical direction
715 | * @param e1x
716 | * E(eta1) x coordinate of the starting point of the arc
717 | * @param e1y
718 | * E(eta2) y coordinate of the starting point of the arc
719 | * @param theta
720 | * The angle that the ellipse bounding rectangle makes with horizontal plane
721 | * @param start
722 | * The start angle of the arc on the ellipse
723 | * @param sweep
724 | * The angle (positive or negative) of the sweep of the arc on the ellipse
725 | */
726 | private static void arcToBezier(Path p,
727 | double cx,
728 | double cy,
729 | double a,
730 | double b,
731 | double e1x,
732 | double e1y,
733 | double theta,
734 | double start,
735 | double sweep) {
736 | // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
737 | // and http://www.spaceroots.org/documents/ellipse/node22.html
738 |
739 | // Maximum of 45 degrees per cubic Bezier segment
740 | int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
741 |
742 | double eta1 = start;
743 | double cosTheta = Math.cos(theta);
744 | double sinTheta = Math.sin(theta);
745 | double cosEta1 = Math.cos(eta1);
746 | double sinEta1 = Math.sin(eta1);
747 | double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
748 | double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
749 |
750 | double anglePerSegment = sweep / numSegments;
751 | for (int i = 0; i < numSegments; i++) {
752 | double eta2 = eta1 + anglePerSegment;
753 | double sinEta2 = Math.sin(eta2);
754 | double cosEta2 = Math.cos(eta2);
755 | double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
756 | double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
757 | double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
758 | double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
759 | double tanDiff2 = Math.tan((eta2 - eta1) / 2);
760 | double alpha =
761 | Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
762 | double q1x = e1x + alpha * ep1x;
763 | double q1y = e1y + alpha * ep1y;
764 | double q2x = e2x - alpha * ep2x;
765 | double q2y = e2y - alpha * ep2y;
766 |
767 | // Adding this no-op call to workaround a proguard related issue.
768 | p.rLineTo(0, 0);
769 |
770 | p.cubicTo((float) q1x,
771 | (float) q1y,
772 | (float) q2x,
773 | (float) q2y,
774 | (float) e2x,
775 | (float) e2y);
776 | eta1 = eta2;
777 | e1x = e2x;
778 | e1y = e2y;
779 | ep1x = ep2x;
780 | ep1y = ep2y;
781 | }
782 | }
783 | }
784 | }
785 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':demo', ':library'
2 |
--------------------------------------------------------------------------------