From a08e52232210c838e258166ea0676192289a063b Mon Sep 17 00:00:00 2001 From: Thibault Guegan Date: Fri, 29 May 2015 18:29:29 +0000 Subject: [PATCH 1/9] Configure project for minSdk 14 and use VectorCompat --- elasticdownload/build.gradle | 11 ++++++----- example/build.gradle | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/elasticdownload/build.gradle b/elasticdownload/build.gradle index c74fec6..bd032a2 100644 --- a/elasticdownload/build.gradle +++ b/elasticdownload/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 21 - buildToolsVersion "21.1.2" + compileSdkVersion 22 + buildToolsVersion "22.0.1" defaultConfig { - minSdkVersion 21 - targetSdkVersion 21 + minSdkVersion 14 + targetSdkVersion 22 versionCode 1 versionName "1.0" } @@ -22,5 +22,6 @@ apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:21.0.3' + compile 'com.android.support:appcompat-v7:22.1.1' + compile 'com.wnafee:vector-compat:1.0.5' } diff --git a/example/build.gradle b/example/build.gradle index a657920..2a09c63 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 21 - buildToolsVersion "21.1.2" + compileSdkVersion 22 + buildToolsVersion "22.0.1" defaultConfig { applicationId "is.arontibo.sample" - minSdkVersion 21 - targetSdkVersion 21 + minSdkVersion 14 + targetSdkVersion 22 versionCode 1 versionName "1.0" } @@ -23,6 +23,6 @@ dependencies { compile project(':elasticdownload') compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:21.0.3' + compile 'com.android.support:appcompat-v7:22.1.1' compile 'com.jakewharton:butterknife:6.1.0' } From 300687246f003e8c2def5fda394e425816a1538e Mon Sep 17 00:00:00 2001 From: Thibault Guegan Date: Fri, 29 May 2015 18:31:54 +0000 Subject: [PATCH 2/9] Move over resources to common folder --- .../res/{anim-v21 => anim}/morph_arrow.xml | 0 .../res/{anim-v21 => anim}/morph_circle.xml | 0 .../{drawable-v21 => drawable}/avd_start.xml | 0 .../{drawable-v21 => drawable}/vd_start.xml | 0 .../src/main/res/values-v21/strings.xml | 42 ------------------- .../src/main/res/values/strings.xml | 40 ++++++++++++++++++ 6 files changed, 40 insertions(+), 42 deletions(-) rename elasticdownload/src/main/res/{anim-v21 => anim}/morph_arrow.xml (100%) rename elasticdownload/src/main/res/{anim-v21 => anim}/morph_circle.xml (100%) rename elasticdownload/src/main/res/{drawable-v21 => drawable}/avd_start.xml (100%) rename elasticdownload/src/main/res/{drawable-v21 => drawable}/vd_start.xml (100%) delete mode 100644 elasticdownload/src/main/res/values-v21/strings.xml diff --git a/elasticdownload/src/main/res/anim-v21/morph_arrow.xml b/elasticdownload/src/main/res/anim/morph_arrow.xml similarity index 100% rename from elasticdownload/src/main/res/anim-v21/morph_arrow.xml rename to elasticdownload/src/main/res/anim/morph_arrow.xml diff --git a/elasticdownload/src/main/res/anim-v21/morph_circle.xml b/elasticdownload/src/main/res/anim/morph_circle.xml similarity index 100% rename from elasticdownload/src/main/res/anim-v21/morph_circle.xml rename to elasticdownload/src/main/res/anim/morph_circle.xml diff --git a/elasticdownload/src/main/res/drawable-v21/avd_start.xml b/elasticdownload/src/main/res/drawable/avd_start.xml similarity index 100% rename from elasticdownload/src/main/res/drawable-v21/avd_start.xml rename to elasticdownload/src/main/res/drawable/avd_start.xml diff --git a/elasticdownload/src/main/res/drawable-v21/vd_start.xml b/elasticdownload/src/main/res/drawable/vd_start.xml similarity index 100% rename from elasticdownload/src/main/res/drawable-v21/vd_start.xml rename to elasticdownload/src/main/res/drawable/vd_start.xml diff --git a/elasticdownload/src/main/res/values-v21/strings.xml b/elasticdownload/src/main/res/values-v21/strings.xml deleted file mode 100644 index d22902f..0000000 --- a/elasticdownload/src/main/res/values-v21/strings.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - M 125 250 - C 137.5 83.34 362.5 83.34 375 250 - M 125 250 - C 137.5 416.66 362.5 416.66 375 250 - M 43 249 - C 125 249 125 249 249 249 - M 249 249 - C 375 249 375 249 450 249 - M 225 190 - L 275 190 - Q 280 190 280 195 - L 280 260 - Q 280 265 285 265 - L 300 265 - Q 310 265 305 270 - L 255 310 - Q 250 315 245 310 - L 195 270 - Q 190 265 200 265 - L 215 265 - Q 220 265 220 260 - L 220 195 - Q 220 190 225 190 - M 8 198 - L 71 198 - Q 73 198 73 202 - L 73 232 - Q 73 234 71 234 - L 52 234 - Q 50 234 50 236 - L 42 248 - Q 40 250 38 248 - L 29 234 - Q 29 234 27 234 - L 8 234 - Q 6 234 6 232 - L 6 202 - Q 6 198 8 198 - \ No newline at end of file diff --git a/elasticdownload/src/main/res/values/strings.xml b/elasticdownload/src/main/res/values/strings.xml index 925bf21..a3ca3a9 100644 --- a/elasticdownload/src/main/res/values/strings.xml +++ b/elasticdownload/src/main/res/values/strings.xml @@ -2,4 +2,44 @@ Library failed done + + + M 125 250 + C 137.5 83.34 362.5 83.34 375 250 + M 125 250 + C 137.5 416.66 362.5 416.66 375 250 + M 43 249 + C 125 249 125 249 249 249 + M 249 249 + C 375 249 375 249 450 249 + M 225 190 + L 275 190 + Q 280 190 280 195 + L 280 260 + Q 280 265 285 265 + L 300 265 + Q 310 265 305 270 + L 255 310 + Q 250 315 245 310 + L 195 270 + Q 190 265 200 265 + L 215 265 + Q 220 265 220 260 + L 220 195 + Q 220 190 225 190 + M 8 198 + L 71 198 + Q 73 198 73 202 + L 73 232 + Q 73 234 71 234 + L 52 234 + Q 50 234 50 236 + L 42 248 + Q 40 250 38 248 + L 29 234 + Q 29 234 27 234 + L 8 234 + Q 6 234 6 232 + L 6 202 + Q 6 198 8 198 From f7ed54ec40721ec3fa0194b5e1976051dc0a1792 Mon Sep 17 00:00:00 2001 From: Thibault Guegan Date: Fri, 29 May 2015 18:38:10 +0000 Subject: [PATCH 3/9] Revert "Move over resources to common folder" This reverts commit 300687246f003e8c2def5fda394e425816a1538e. --- .../res/{anim => anim-v21}/morph_arrow.xml | 0 .../res/{anim => anim-v21}/morph_circle.xml | 0 .../{drawable => drawable-v21}/avd_start.xml | 0 .../{drawable => drawable-v21}/vd_start.xml | 0 .../src/main/res/values-v21/strings.xml | 42 +++++++++++++++++++ .../src/main/res/values/strings.xml | 40 ------------------ 6 files changed, 42 insertions(+), 40 deletions(-) rename elasticdownload/src/main/res/{anim => anim-v21}/morph_arrow.xml (100%) rename elasticdownload/src/main/res/{anim => anim-v21}/morph_circle.xml (100%) rename elasticdownload/src/main/res/{drawable => drawable-v21}/avd_start.xml (100%) rename elasticdownload/src/main/res/{drawable => drawable-v21}/vd_start.xml (100%) create mode 100644 elasticdownload/src/main/res/values-v21/strings.xml diff --git a/elasticdownload/src/main/res/anim/morph_arrow.xml b/elasticdownload/src/main/res/anim-v21/morph_arrow.xml similarity index 100% rename from elasticdownload/src/main/res/anim/morph_arrow.xml rename to elasticdownload/src/main/res/anim-v21/morph_arrow.xml diff --git a/elasticdownload/src/main/res/anim/morph_circle.xml b/elasticdownload/src/main/res/anim-v21/morph_circle.xml similarity index 100% rename from elasticdownload/src/main/res/anim/morph_circle.xml rename to elasticdownload/src/main/res/anim-v21/morph_circle.xml diff --git a/elasticdownload/src/main/res/drawable/avd_start.xml b/elasticdownload/src/main/res/drawable-v21/avd_start.xml similarity index 100% rename from elasticdownload/src/main/res/drawable/avd_start.xml rename to elasticdownload/src/main/res/drawable-v21/avd_start.xml diff --git a/elasticdownload/src/main/res/drawable/vd_start.xml b/elasticdownload/src/main/res/drawable-v21/vd_start.xml similarity index 100% rename from elasticdownload/src/main/res/drawable/vd_start.xml rename to elasticdownload/src/main/res/drawable-v21/vd_start.xml diff --git a/elasticdownload/src/main/res/values-v21/strings.xml b/elasticdownload/src/main/res/values-v21/strings.xml new file mode 100644 index 0000000..d22902f --- /dev/null +++ b/elasticdownload/src/main/res/values-v21/strings.xml @@ -0,0 +1,42 @@ + + + + M 125 250 + C 137.5 83.34 362.5 83.34 375 250 + M 125 250 + C 137.5 416.66 362.5 416.66 375 250 + M 43 249 + C 125 249 125 249 249 249 + M 249 249 + C 375 249 375 249 450 249 + M 225 190 + L 275 190 + Q 280 190 280 195 + L 280 260 + Q 280 265 285 265 + L 300 265 + Q 310 265 305 270 + L 255 310 + Q 250 315 245 310 + L 195 270 + Q 190 265 200 265 + L 215 265 + Q 220 265 220 260 + L 220 195 + Q 220 190 225 190 + M 8 198 + L 71 198 + Q 73 198 73 202 + L 73 232 + Q 73 234 71 234 + L 52 234 + Q 50 234 50 236 + L 42 248 + Q 40 250 38 248 + L 29 234 + Q 29 234 27 234 + L 8 234 + Q 6 234 6 232 + L 6 202 + Q 6 198 8 198 + \ No newline at end of file diff --git a/elasticdownload/src/main/res/values/strings.xml b/elasticdownload/src/main/res/values/strings.xml index a3ca3a9..925bf21 100644 --- a/elasticdownload/src/main/res/values/strings.xml +++ b/elasticdownload/src/main/res/values/strings.xml @@ -2,44 +2,4 @@ Library failed done - - - M 125 250 - C 137.5 83.34 362.5 83.34 375 250 - M 125 250 - C 137.5 416.66 362.5 416.66 375 250 - M 43 249 - C 125 249 125 249 249 249 - M 249 249 - C 375 249 375 249 450 249 - M 225 190 - L 275 190 - Q 280 190 280 195 - L 280 260 - Q 280 265 285 265 - L 300 265 - Q 310 265 305 270 - L 255 310 - Q 250 315 245 310 - L 195 270 - Q 190 265 200 265 - L 215 265 - Q 220 265 220 260 - L 220 195 - Q 220 190 225 190 - M 8 198 - L 71 198 - Q 73 198 73 202 - L 73 232 - Q 73 234 71 234 - L 52 234 - Q 50 234 50 236 - L 42 248 - Q 40 250 38 248 - L 29 234 - Q 29 234 27 234 - L 8 234 - Q 6 234 6 232 - L 6 202 - Q 6 198 8 198 From 288c2341b07d0f55661de33ab33d6a59d707096d Mon Sep 17 00:00:00 2001 From: Thibault Guegan Date: Fri, 29 May 2015 19:48:15 +0000 Subject: [PATCH 4/9] Delete specific v21 folders --- .../java/is/arontibo/library/AVDWrapper.java | 3 +- .../java/is/arontibo/library/IntroView.java | 10 +++-- .../res/{anim-v21 => anim}/morph_arrow.xml | 0 .../res/{anim-v21 => anim}/morph_circle.xml | 0 .../src/main/res/drawable-v21/vd_start.xml | 22 ---------- .../{drawable-v21 => drawable}/avd_start.xml | 0 .../src/main/res/drawable/vd_start.xml | 33 +++++++++++++++ .../src/main/res/values-v21/strings.xml | 42 ------------------- .../src/main/res/values/strings.xml | 40 ++++++++++++++++++ 9 files changed, 80 insertions(+), 70 deletions(-) rename elasticdownload/src/main/res/{anim-v21 => anim}/morph_arrow.xml (100%) rename elasticdownload/src/main/res/{anim-v21 => anim}/morph_circle.xml (100%) delete mode 100644 elasticdownload/src/main/res/drawable-v21/vd_start.xml rename elasticdownload/src/main/res/{drawable-v21 => drawable}/avd_start.xml (100%) create mode 100644 elasticdownload/src/main/res/drawable/vd_start.xml delete mode 100644 elasticdownload/src/main/res/values-v21/strings.xml diff --git a/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java b/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java index 07c940e..4a5d654 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java +++ b/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java @@ -42,8 +42,7 @@ public void stop() { mDrawable.stop(); mHandler.removeCallbacks(mAnimationDoneRunnable); - if (mCallback != null) - mCallback.onAnimationStopped(); + if (mCallback != null) mCallback.onAnimationStopped(); } } diff --git a/elasticdownload/src/main/java/is/arontibo/library/IntroView.java b/elasticdownload/src/main/java/is/arontibo/library/IntroView.java index 9557e60..b7acb1a 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/IntroView.java +++ b/elasticdownload/src/main/java/is/arontibo/library/IntroView.java @@ -2,12 +2,14 @@ import android.content.Context; import android.graphics.drawable.Animatable; -import android.graphics.drawable.Drawable; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.widget.ImageView; +import com.wnafee.vector.compat.AnimatedVectorDrawable; +import com.wnafee.vector.compat.DrawableCompat; + /** * Created by thibaultguegan on 15/03/15. */ @@ -27,8 +29,8 @@ public interface EnterAnimationListener { public IntroView(Context context, AttributeSet attrs) { super(context, attrs); - - setImageResource(R.drawable.avd_start); + AnimatedVectorDrawable drawable = AnimatedVectorDrawable.getDrawable(context, R.drawable.avd_start); + setImageDrawable(drawable); } /** @@ -44,7 +46,7 @@ public void setListener(EnterAnimationListener listener) { */ public void startAnimation() { - Drawable drawable = getDrawable(); + DrawableCompat drawable = (DrawableCompat) getDrawable(); Animatable animatable = (Animatable) drawable; AVDWrapper.Callback callback = new AVDWrapper.Callback() { diff --git a/elasticdownload/src/main/res/anim-v21/morph_arrow.xml b/elasticdownload/src/main/res/anim/morph_arrow.xml similarity index 100% rename from elasticdownload/src/main/res/anim-v21/morph_arrow.xml rename to elasticdownload/src/main/res/anim/morph_arrow.xml diff --git a/elasticdownload/src/main/res/anim-v21/morph_circle.xml b/elasticdownload/src/main/res/anim/morph_circle.xml similarity index 100% rename from elasticdownload/src/main/res/anim-v21/morph_circle.xml rename to elasticdownload/src/main/res/anim/morph_circle.xml diff --git a/elasticdownload/src/main/res/drawable-v21/vd_start.xml b/elasticdownload/src/main/res/drawable-v21/vd_start.xml deleted file mode 100644 index 625868e..0000000 --- a/elasticdownload/src/main/res/drawable-v21/vd_start.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/elasticdownload/src/main/res/drawable-v21/avd_start.xml b/elasticdownload/src/main/res/drawable/avd_start.xml similarity index 100% rename from elasticdownload/src/main/res/drawable-v21/avd_start.xml rename to elasticdownload/src/main/res/drawable/avd_start.xml diff --git a/elasticdownload/src/main/res/drawable/vd_start.xml b/elasticdownload/src/main/res/drawable/vd_start.xml new file mode 100644 index 0000000..bf236c7 --- /dev/null +++ b/elasticdownload/src/main/res/drawable/vd_start.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/elasticdownload/src/main/res/values-v21/strings.xml b/elasticdownload/src/main/res/values-v21/strings.xml deleted file mode 100644 index d22902f..0000000 --- a/elasticdownload/src/main/res/values-v21/strings.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - M 125 250 - C 137.5 83.34 362.5 83.34 375 250 - M 125 250 - C 137.5 416.66 362.5 416.66 375 250 - M 43 249 - C 125 249 125 249 249 249 - M 249 249 - C 375 249 375 249 450 249 - M 225 190 - L 275 190 - Q 280 190 280 195 - L 280 260 - Q 280 265 285 265 - L 300 265 - Q 310 265 305 270 - L 255 310 - Q 250 315 245 310 - L 195 270 - Q 190 265 200 265 - L 215 265 - Q 220 265 220 260 - L 220 195 - Q 220 190 225 190 - M 8 198 - L 71 198 - Q 73 198 73 202 - L 73 232 - Q 73 234 71 234 - L 52 234 - Q 50 234 50 236 - L 42 248 - Q 40 250 38 248 - L 29 234 - Q 29 234 27 234 - L 8 234 - Q 6 234 6 232 - L 6 202 - Q 6 198 8 198 - \ No newline at end of file diff --git a/elasticdownload/src/main/res/values/strings.xml b/elasticdownload/src/main/res/values/strings.xml index 925bf21..a3ca3a9 100644 --- a/elasticdownload/src/main/res/values/strings.xml +++ b/elasticdownload/src/main/res/values/strings.xml @@ -2,4 +2,44 @@ Library failed done + + + M 125 250 + C 137.5 83.34 362.5 83.34 375 250 + M 125 250 + C 137.5 416.66 362.5 416.66 375 250 + M 43 249 + C 125 249 125 249 249 249 + M 249 249 + C 375 249 375 249 450 249 + M 225 190 + L 275 190 + Q 280 190 280 195 + L 280 260 + Q 280 265 285 265 + L 300 265 + Q 310 265 305 270 + L 255 310 + Q 250 315 245 310 + L 195 270 + Q 190 265 200 265 + L 215 265 + Q 220 265 220 260 + L 220 195 + Q 220 190 225 190 + M 8 198 + L 71 198 + Q 73 198 73 202 + L 73 232 + Q 73 234 71 234 + L 52 234 + Q 50 234 50 236 + L 42 248 + Q 40 250 38 248 + L 29 234 + Q 29 234 27 234 + L 8 234 + Q 6 234 6 232 + L 6 202 + Q 6 198 8 198 From bca83283c411a0758564cdbb08cabccd48d1fb8e Mon Sep 17 00:00:00 2001 From: Thibault Guegan Date: Sat, 30 May 2015 12:44:33 +0000 Subject: [PATCH 5/9] Back port for API 14 + a bit of code cleaning --- .../java/is/arontibo/library/IntroView.java | 15 +++++++++---- .../library/ProgressDownloadView.java | 1 - .../src/main/res/anim-v21/morph_arrow.xml | 12 ++++++++++ .../src/main/res/anim-v21/morph_circle.xml | 12 ++++++++++ .../src/main/res/anim/morph_arrow.xml | 7 +++--- .../src/main/res/anim/morph_circle.xml | 7 +++--- .../src/main/res/drawable-v21/avd_start.xml | 13 +++++++++++ .../src/main/res/drawable-v21/vd_start.xml | 22 +++++++++++++++++++ .../src/main/res/drawable/vd_start.xml | 10 --------- 9 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 elasticdownload/src/main/res/anim-v21/morph_arrow.xml create mode 100644 elasticdownload/src/main/res/anim-v21/morph_circle.xml create mode 100644 elasticdownload/src/main/res/drawable-v21/avd_start.xml create mode 100644 elasticdownload/src/main/res/drawable-v21/vd_start.xml diff --git a/elasticdownload/src/main/java/is/arontibo/library/IntroView.java b/elasticdownload/src/main/java/is/arontibo/library/IntroView.java index b7acb1a..98c819a 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/IntroView.java +++ b/elasticdownload/src/main/java/is/arontibo/library/IntroView.java @@ -2,13 +2,14 @@ import android.content.Context; import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.widget.ImageView; import com.wnafee.vector.compat.AnimatedVectorDrawable; -import com.wnafee.vector.compat.DrawableCompat; /** * Created by thibaultguegan on 15/03/15. @@ -29,8 +30,12 @@ public interface EnterAnimationListener { public IntroView(Context context, AttributeSet attrs) { super(context, attrs); - AnimatedVectorDrawable drawable = AnimatedVectorDrawable.getDrawable(context, R.drawable.avd_start); - setImageDrawable(drawable); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setImageResource(R.drawable.avd_start); + } else { + AnimatedVectorDrawable drawable = AnimatedVectorDrawable.getDrawable(context, R.drawable.avd_start); + setImageDrawable(drawable); + } } /** @@ -46,7 +51,8 @@ public void setListener(EnterAnimationListener listener) { */ public void startAnimation() { - DrawableCompat drawable = (DrawableCompat) getDrawable(); + + Drawable drawable = getDrawable(); Animatable animatable = (Animatable) drawable; AVDWrapper.Callback callback = new AVDWrapper.Callback() { @@ -65,4 +71,5 @@ public void onAnimationStopped() { AVDWrapper wrapper = new AVDWrapper(animatable, new Handler(), callback); wrapper.start(getContext().getResources().getInteger(R.integer.enter_animation_duration)); } + } diff --git a/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java b/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java index b4668dd..68cc0c1 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java +++ b/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java @@ -6,7 +6,6 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.CornerPathEffect; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; diff --git a/elasticdownload/src/main/res/anim-v21/morph_arrow.xml b/elasticdownload/src/main/res/anim-v21/morph_arrow.xml new file mode 100644 index 0000000..46208e4 --- /dev/null +++ b/elasticdownload/src/main/res/anim-v21/morph_arrow.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/elasticdownload/src/main/res/anim-v21/morph_circle.xml b/elasticdownload/src/main/res/anim-v21/morph_circle.xml new file mode 100644 index 0000000..ddfe36f --- /dev/null +++ b/elasticdownload/src/main/res/anim-v21/morph_circle.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/elasticdownload/src/main/res/anim/morph_arrow.xml b/elasticdownload/src/main/res/anim/morph_arrow.xml index 46208e4..6ded708 100644 --- a/elasticdownload/src/main/res/anim/morph_arrow.xml +++ b/elasticdownload/src/main/res/anim/morph_arrow.xml @@ -1,12 +1,13 @@ - + + app:vc_valueType="pathType" /> \ No newline at end of file diff --git a/elasticdownload/src/main/res/anim/morph_circle.xml b/elasticdownload/src/main/res/anim/morph_circle.xml index 1b05c6b..72f3006 100644 --- a/elasticdownload/src/main/res/anim/morph_circle.xml +++ b/elasticdownload/src/main/res/anim/morph_circle.xml @@ -1,12 +1,13 @@ - + + app:vc_valueType="pathType" /> \ No newline at end of file diff --git a/elasticdownload/src/main/res/drawable-v21/avd_start.xml b/elasticdownload/src/main/res/drawable-v21/avd_start.xml new file mode 100644 index 0000000..bba402b --- /dev/null +++ b/elasticdownload/src/main/res/drawable-v21/avd_start.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/elasticdownload/src/main/res/drawable-v21/vd_start.xml b/elasticdownload/src/main/res/drawable-v21/vd_start.xml new file mode 100644 index 0000000..9631e28 --- /dev/null +++ b/elasticdownload/src/main/res/drawable-v21/vd_start.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/elasticdownload/src/main/res/drawable/vd_start.xml b/elasticdownload/src/main/res/drawable/vd_start.xml index bf236c7..ff4a4f0 100644 --- a/elasticdownload/src/main/res/drawable/vd_start.xml +++ b/elasticdownload/src/main/res/drawable/vd_start.xml @@ -3,28 +3,18 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:width="500dp" android:height="500dp" - android:viewportWidth="500" - android:viewportHeight="500" app:vc_viewportHeight="500" app:vc_viewportWidth="500"> Date: Sat, 30 May 2015 16:38:20 +0000 Subject: [PATCH 6/9] Backport to API8 (modifications of the VectorCompat lib + add nineoldandroids to the library) --- elasticdownload/build.gradle | 5 +- .../arontibo/library/ElasticDownloadView.java | 7 +- .../java/is/arontibo/library/IntroView.java | 2 +- .../library/ProgressDownloadView.java | 7 +- .../VectorCompat/AnimatedVectorDrawable.java | 446 ++++++ .../library/VectorCompat/DrawableCompat.java | 115 ++ .../library/VectorCompat/Outline.java | 208 +++ .../VectorCompat/PathAnimatorInflater.java | 346 ++++ .../library/VectorCompat/PathParser.java | 635 ++++++++ .../library/VectorCompat/ResourcesCompat.java | 39 + .../library/VectorCompat/Tintable.java | 10 + .../library/VectorCompat/VectorDrawable.java | 1423 +++++++++++++++++ .../src/main/res/anim-v21/morph_arrow.xml | 18 +- .../src/main/res/anim-v21/morph_circle.xml | 18 +- .../src/main/res/anim/morph_arrow.xml | 21 +- .../src/main/res/anim/morph_circle.xml | 21 +- elasticdownload/src/main/res/values/attrs.xml | 187 +++ example/build.gradle | 2 +- example/src/main/res/layout/activity_main.xml | 2 +- 19 files changed, 3457 insertions(+), 55 deletions(-) create mode 100644 elasticdownload/src/main/java/is/arontibo/library/VectorCompat/AnimatedVectorDrawable.java create mode 100644 elasticdownload/src/main/java/is/arontibo/library/VectorCompat/DrawableCompat.java create mode 100644 elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Outline.java create mode 100644 elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathAnimatorInflater.java create mode 100644 elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathParser.java create mode 100644 elasticdownload/src/main/java/is/arontibo/library/VectorCompat/ResourcesCompat.java create mode 100644 elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Tintable.java create mode 100644 elasticdownload/src/main/java/is/arontibo/library/VectorCompat/VectorDrawable.java diff --git a/elasticdownload/build.gradle b/elasticdownload/build.gradle index bd032a2..bb70f48 100644 --- a/elasticdownload/build.gradle +++ b/elasticdownload/build.gradle @@ -5,7 +5,7 @@ android { buildToolsVersion "22.0.1" defaultConfig { - minSdkVersion 14 + minSdkVersion 8 targetSdkVersion 22 versionCode 1 versionName "1.0" @@ -23,5 +23,6 @@ apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.1.1' - compile 'com.wnafee:vector-compat:1.0.5' + compile 'com.nineoldandroids:library:2.4.0+' + //compile 'com.wnafee:vector-compat:1.0.5' } diff --git a/elasticdownload/src/main/java/is/arontibo/library/ElasticDownloadView.java b/elasticdownload/src/main/java/is/arontibo/library/ElasticDownloadView.java index 6b60f2a..e5aad42 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/ElasticDownloadView.java +++ b/elasticdownload/src/main/java/is/arontibo/library/ElasticDownloadView.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.res.TypedArray; +import android.os.Build; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.ViewTreeObserver; @@ -51,7 +52,11 @@ protected void onFinishInflate() { vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - mProgressDownloadView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + mProgressDownloadView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + mProgressDownloadView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } mIntroView.getLayoutParams().width = mProgressDownloadView.getWidth(); mIntroView.getLayoutParams().height = mProgressDownloadView.getHeight(); diff --git a/elasticdownload/src/main/java/is/arontibo/library/IntroView.java b/elasticdownload/src/main/java/is/arontibo/library/IntroView.java index 98c819a..aa904b6 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/IntroView.java +++ b/elasticdownload/src/main/java/is/arontibo/library/IntroView.java @@ -9,7 +9,7 @@ import android.util.Log; import android.widget.ImageView; -import com.wnafee.vector.compat.AnimatedVectorDrawable; +import is.arontibo.library.VectorCompat.AnimatedVectorDrawable; /** * Created by thibaultguegan on 15/03/15. diff --git a/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java b/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java index 68cc0c1..c73f088 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java +++ b/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java @@ -1,8 +1,5 @@ package is.arontibo.library; -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -18,6 +15,10 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.OvershootInterpolator; +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.animation.AnimatorSet; +import com.nineoldandroids.animation.ObjectAnimator; + /** * Created by thibaultguegan on 15/02/15. */ diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/AnimatedVectorDrawable.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/AnimatedVectorDrawable.java new file mode 100644 index 0000000..0d9d74c --- /dev/null +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/AnimatedVectorDrawable.java @@ -0,0 +1,446 @@ +package is.arontibo.library.VectorCompat; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.v4.util.ArrayMap; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.animation.AnimatorInflater; +import com.nineoldandroids.animation.AnimatorSet; +import com.nineoldandroids.animation.ValueAnimator; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +import is.arontibo.library.R; + +public class AnimatedVectorDrawable extends DrawableCompat implements Animatable, Tintable { + + private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName(); + + private static final String ANIMATED_VECTOR = "animated-vector"; + private static final String TARGET = "target"; + + private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; + + private AnimatedVectorDrawableState mAnimatedVectorState; + + private boolean mMutated; + + public AnimatedVectorDrawable() { + mAnimatedVectorState = new AnimatedVectorDrawableState(null); + } + + private AnimatedVectorDrawable(AnimatedVectorDrawableState state, Resources res, + Resources.Theme theme) { + mAnimatedVectorState = new AnimatedVectorDrawableState(state); + if (theme != null && canApplyTheme()) { + applyTheme(theme); + } + } + + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mAnimatedVectorState.mVectorDrawable.mutate(); + mMutated = true; + } + return this; + } + + @Override + public Drawable.ConstantState getConstantState() { + mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations(); + return mAnimatedVectorState; + } + + @Override + public int getChangingConfigurations() { + return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations; + } + + @Override + public void draw(Canvas canvas) { + mAnimatedVectorState.mVectorDrawable.draw(canvas); + if (isStarted()) { + invalidateSelf(); + } + } + + @Override + protected void onBoundsChange(Rect bounds) { + mAnimatedVectorState.mVectorDrawable.setBounds(bounds); + } + + @Override + protected boolean onStateChange(int[] state) { + return mAnimatedVectorState.mVectorDrawable.setState(state); + } + + @Override + protected boolean onLevelChange(int level) { + return mAnimatedVectorState.mVectorDrawable.setLevel(level); + } + + @Override + public int getAlpha() { + return mAnimatedVectorState.mVectorDrawable.getAlpha(); + } + + @Override + public void setAlpha(int alpha) { + mAnimatedVectorState.mVectorDrawable.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter); + } + + @Override + public void setTintList(ColorStateList tint) { + mAnimatedVectorState.mVectorDrawable.setTintList(tint); + } + + @Override + public void setHotspot(float x, float y) { + mAnimatedVectorState.mVectorDrawable.setHotspot(x, y); + } + + @Override + public void setHotspotBounds(int left, int top, int right, int bottom) { + mAnimatedVectorState.mVectorDrawable.setHotspotBounds(left, top, right, bottom); + } + + @Override + public void setTintMode(PorterDuff.Mode tintMode) { + mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart); + return super.setVisible(visible, restart); + } + + public void setLayoutDirection(int layoutDirection) { + mAnimatedVectorState.mVectorDrawable.setLayoutDirection(layoutDirection); + } + + @Override + public boolean isStateful() { + return mAnimatedVectorState.mVectorDrawable.isStateful(); + } + + @Override + public int getOpacity() { + return mAnimatedVectorState.mVectorDrawable.getOpacity(); + } + + @Override + public int getIntrinsicWidth() { + return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight(); + } + + @Override + public void getOutline(@NonNull Outline outline) { + mAnimatedVectorState.mVectorDrawable.getOutline(outline); + } + + public static AnimatedVectorDrawable getDrawable(Context c, int resId) { + return create(c, c.getResources(), resId); + } + + public static AnimatedVectorDrawable create(Context c, Resources resources, int rid) { + try { + final XmlPullParser parser = resources.getXml(rid); + final AttributeSet attrs = Xml.asAttributeSet(parser); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG && + type != XmlPullParser.END_DOCUMENT) { + // Empty loop + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } else if (!ANIMATED_VECTOR.equals(parser.getName())) { + throw new IllegalArgumentException("root node must start with: " + ANIMATED_VECTOR); + } + + final AnimatedVectorDrawable drawable = new AnimatedVectorDrawable(); + drawable.inflate(c, resources, parser, attrs, null); + + return drawable; + } catch (XmlPullParserException e) { + Log.e(LOGTAG, "parser error", e); + } catch (IOException e) { + Log.e(LOGTAG, "parser error", e); + } + return null; + } + + public void inflate(Context c, Resources res, XmlPullParser parser, AttributeSet attrs, Resources.Theme theme) + throws XmlPullParserException, IOException { + + int eventType = parser.getEventType(); + float pathErrorScale = 1; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + final String tagName = parser.getName(); + if (ANIMATED_VECTOR.equals(tagName)) { + final TypedArray a = obtainAttributes(res, theme, attrs, + R.styleable.AnimatedVectorDrawable); + int drawableRes = a.getResourceId( + R.styleable.AnimatedVectorDrawable_android_drawable, 0); + if (drawableRes != 0) { + VectorDrawable vectorDrawable = (VectorDrawable) VectorDrawable.create(res, drawableRes).mutate(); + vectorDrawable.setAllowCaching(false); + pathErrorScale = vectorDrawable.getPixelSize(); + mAnimatedVectorState.mVectorDrawable = vectorDrawable; + } + a.recycle(); + } else if (TARGET.equals(tagName)) { + final TypedArray a = obtainAttributes(res, theme, attrs, + R.styleable.AnimatedVectorDrawableTarget); + final String target = a.getString( + R.styleable.AnimatedVectorDrawableTarget_android_name); + + int id = a.getResourceId( + R.styleable.AnimatedVectorDrawableTarget_android_animation, 0); + if (id != 0) { + //path animators require separate handling + Animator objectAnimator; + if (isPath(target)) { + objectAnimator = getPathAnimator(c, res, theme, id, pathErrorScale); + } else { + objectAnimator = AnimatorInflater.loadAnimator(c, id); + } + setupAnimatorsForTarget(target, objectAnimator); + } + a.recycle(); + } + } + + eventType = parser.next(); + } + } + + public boolean isPath(String target) { + Object o = mAnimatedVectorState.mVectorDrawable.getTargetByName(target); + return (o instanceof VectorDrawable.VFullPath); + } + + Animator getPathAnimator(Context c, Resources res, Resources.Theme theme, int id, float pathErrorScale) { + return PathAnimatorInflater.loadAnimator(c, res, theme, id, pathErrorScale); + } + + @Override + public boolean canApplyTheme() { + return super.canApplyTheme() || mAnimatedVectorState != null + && mAnimatedVectorState.mVectorDrawable != null + && mAnimatedVectorState.mVectorDrawable.canApplyTheme(); + } + + @Override + public void applyTheme(Resources.Theme t) { + super.applyTheme(t); + + final VectorDrawable vectorDrawable = mAnimatedVectorState.mVectorDrawable; + if (vectorDrawable != null && vectorDrawable.canApplyTheme()) { + vectorDrawable.applyTheme(t); + } + } + + private static class AnimatedVectorDrawableState extends Drawable.ConstantState { + int mChangingConfigurations; + VectorDrawable mVectorDrawable; + ArrayList mAnimators; + ArrayMap mTargetNameMap; + + public AnimatedVectorDrawableState(AnimatedVectorDrawableState copy) { + if (copy != null) { + mChangingConfigurations = copy.mChangingConfigurations; + if (copy.mVectorDrawable != null) { + mVectorDrawable = (VectorDrawable) copy.mVectorDrawable.getConstantState().newDrawable(); + mVectorDrawable.mutate(); + mVectorDrawable.setAllowCaching(false); + mVectorDrawable.setBounds(copy.mVectorDrawable.getBounds()); + } + if (copy.mAnimators != null) { + final int numAnimators = copy.mAnimators.size(); + mAnimators = new ArrayList(numAnimators); + mTargetNameMap = new ArrayMap(numAnimators); + for (int i = 0; i < numAnimators; ++i) { + Animator anim = copy.mAnimators.get(i); + Animator animClone = anim.clone(); + String targetName = copy.mTargetNameMap.get(anim); + Object targetObject = mVectorDrawable.getTargetByName(targetName); + animClone.setTarget(targetObject); + mAnimators.add(animClone); + mTargetNameMap.put(animClone, targetName); + } + } + } else { + mVectorDrawable = new VectorDrawable(); + } + } + + @Override + public Drawable newDrawable() { + return new AnimatedVectorDrawable(this, null, null); + } + + @Override + public Drawable newDrawable(Resources res) { + return new AnimatedVectorDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Resources.Theme theme) { + return new AnimatedVectorDrawable(this, res, theme); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + } + + private void setupAnimatorsForTarget(String name, Animator animator) { + Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name); + animator.setTarget(target); + if (mAnimatedVectorState.mAnimators == null) { + mAnimatedVectorState.mAnimators = new ArrayList(); + mAnimatedVectorState.mTargetNameMap = new ArrayMap(); + } + mAnimatedVectorState.mAnimators.add(animator); + mAnimatedVectorState.mTargetNameMap.put(animator, name); + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.v(LOGTAG, "add animator for target " + name + " " + animator); + } + } + + @Override + public boolean isRunning() { + final ArrayList animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + if (animator.isRunning()) { + return true; + } + } + return false; + } + + private boolean isStarted() { + final ArrayList animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + if (animator.isStarted()) { + return true; + } + } + return false; + } + + @Override + public void start() { + final ArrayList animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + if (!animator.isStarted()) { + animator.start(); + } + } + invalidateSelf(); + } + + @Override + public void stop() { + final ArrayList animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + animator.end(); + } + } + + /** + * Reverses ongoing animations or starts pending animations in reverse. + *

+ * NOTE: Only works of all animations are ValueAnimators. + */ + public void reverse() { + final ArrayList animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + if (canReverse(animator)) { + reverse(animator); + } else { + Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); + } + } + } + + public boolean canReverse() { + final ArrayList animators = mAnimatedVectorState.mAnimators; + final int size = animators.size(); + for (int i = 0; i < size; i++) { + final Animator animator = animators.get(i); + if (!canReverse(animator)) { + return false; + } + } + return true; + } + + public static boolean canReverse(Animator a) { + if (a instanceof AnimatorSet) { + final ArrayList animators = ((AnimatorSet) a).getChildAnimations(); + for (Animator anim : animators) { + if (!canReverse(anim)) { + return false; + } + } + } else if (a instanceof ValueAnimator) { + return true; + } + + return false; + } + + private void reverse(Animator a) { + if (a instanceof AnimatorSet) { + final ArrayList animators = ((AnimatorSet) a).getChildAnimations(); + for (Animator anim : animators) { + reverse(anim); + } + } else if (a instanceof ValueAnimator) { + ((ValueAnimator) a).reverse(); + } + } + +} diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/DrawableCompat.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/DrawableCompat.java new file mode 100644 index 0000000..4c4ea82 --- /dev/null +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/DrawableCompat.java @@ -0,0 +1,115 @@ +package is.arontibo.library.VectorCompat; + +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.util.AttributeSet; + +public abstract class DrawableCompat extends Drawable { + int mLayoutDirection; + + public static abstract class ConstantStateCompat extends ConstantState { + + public boolean canApplyTheme() { + return false; + } + } + + public boolean canApplyTheme() { + return false; + } + + protected static TypedArray obtainAttributes(Resources res, Resources.Theme theme, AttributeSet set, int[] attrs) { + if (theme == null) { + return res.obtainAttributes(set, attrs); + } + return theme.obtainStyledAttributes(set, attrs, 0, 0); + } + + public void getOutline(@NonNull Outline outline) { + outline.setRect(getBounds()); + outline.setAlpha(0); + } + + /** + * Specifies the hotspot's location within the drawable. + * + * @param x The X coordinate of the center of the hotspot + * @param y The Y coordinate of the center of the hotspot + */ + public void setHotspot(float x, float y) { + } + + /** + * Sets the bounds to which the hotspot is constrained, if they should be + * different from the drawable bounds. + * + * @param left + * @param top + * @param right + * @param bottom + */ + public void setHotspotBounds(int left, int top, int right, int bottom) { + } + + public void getHotspotBounds(Rect outRect) { + outRect.set(getBounds()); + } + + public int getLayoutDirection() { + return mLayoutDirection; + } + + public void setLayoutDirection(int layoutDirection) { + if (getLayoutDirection() != layoutDirection) { + mLayoutDirection = layoutDirection; + } + } + + /** + * Ensures the tint filter is consistent with the current tint color and + * mode. + */ + PorterDuffColorFilter updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint, + PorterDuff.Mode tintMode) { + if (tint == null || tintMode == null) { + return null; + } + + final int color = tint.getColorForState(getState(), Color.TRANSPARENT); + tintFilter = new PorterDuffColorFilter(color, tintMode); + + return tintFilter; + } + + /** + * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode + * attribute's enum value. + * + * @hide + */ + public static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) { + switch (value) { + case 3: + return PorterDuff.Mode.SRC_OVER; + case 5: + return PorterDuff.Mode.SRC_IN; + case 9: + return PorterDuff.Mode.SRC_ATOP; + case 14: + return PorterDuff.Mode.MULTIPLY; + case 15: + return PorterDuff.Mode.SCREEN; + case 16: + return PorterDuff.Mode.ADD; + default: + return defaultMode; + } + } +} diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Outline.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Outline.java new file mode 100644 index 0000000..4e6ffc9 --- /dev/null +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Outline.java @@ -0,0 +1,208 @@ +package is.arontibo.library.VectorCompat; + +import android.graphics.Path; +import android.graphics.Rect; +import android.support.annotation.NonNull; + +/** + * Defines a simple shape, used for bounding graphical regions. + *

+ * Can be computed for a View, or computed by a Drawable, to drive the shape of + * shadows cast by a View, or to clip the contents of the View. + * + * @see android.view.ViewOutlineProvider + * @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider) + */ +public final class Outline { + /** @hide */ + public Path mPath; + + /** @hide */ + public Rect mRect; + /** @hide */ + public float mRadius; + /** @hide */ + public float mAlpha; + + /** + * Constructs an empty Outline. Call one of the setter methods to make + * the outline valid for use with a View. + */ + public Outline() {} + + /** + * Constructs an Outline with a copy of the data in src. + */ + public Outline(@NonNull Outline src) { + set(src); + } + + /** + * Sets the outline to be empty. + * + * @see #isEmpty() + */ + public void setEmpty() { + mPath = null; + mRect = null; + mRadius = 0; + } + + /** + * Returns whether the Outline is empty. + *

+ * Outlines are empty when constructed, or if {@link #setEmpty()} is called, + * until a setter method is called + * + * @see #setEmpty() + */ + public boolean isEmpty() { + return mRect == null && mPath == null; + } + + + /** + * Returns whether the outline can be used to clip a View. + *

+ * Currently, only Outlines that can be represented as a rectangle, circle, + * or round rect support clipping. + * + * @see {@link android.view.View#setClipToOutline(boolean)} + */ + public boolean canClip() { + return !isEmpty() && mRect != null; + } + + /** + * Sets the alpha represented by the Outline - the degree to which the + * producer is guaranteed to be opaque over the Outline's shape. + *

+ * An alpha value of 0.0f either represents completely + * transparent content, or content that isn't guaranteed to fill the shape + * it publishes. + *

+ * Content producing a fully opaque (alpha = 1.0f) outline is + * assumed by the drawing system to fully cover content beneath it, + * meaning content beneath may be optimized away. + */ + public void setAlpha(float alpha) { + mAlpha = alpha; + } + + /** + * Returns the alpha represented by the Outline. + */ + public float getAlpha() { + return mAlpha; + } + + /** + * Replace the contents of this Outline with the contents of src. + * + * @param src Source outline to copy from. + */ + public void set(@NonNull Outline src) { + if (src.mPath != null) { + if (mPath == null) { + mPath = new Path(); + } + mPath.set(src.mPath); + mRect = null; + } + if (src.mRect != null) { + if (mRect == null) { + mRect = new Rect(); + } + mRect.set(src.mRect); + } + mRadius = src.mRadius; + mAlpha = src.mAlpha; + } + + /** + * Sets the Outline to the rounded rect defined by the input rect, and + * corner radius. + */ + public void setRect(int left, int top, int right, int bottom) { + setRoundRect(left, top, right, bottom, 0.0f); + } + + /** + * Convenience for {@link #setRect(int, int, int, int)} + */ + public void setRect(@NonNull Rect rect) { + setRect(rect.left, rect.top, rect.right, rect.bottom); + } + + /** + * Sets the Outline to the rounded rect defined by the input rect, and corner radius. + *

+ * Passing a zero radius is equivalent to calling {@link #setRect(int, int, int, int)} + */ + public void setRoundRect(int left, int top, int right, int bottom, float radius) { + if (left >= right || top >= bottom) { + setEmpty(); + return; + } + + if (mRect == null) mRect = new Rect(); + mRect.set(left, top, right, bottom); + mRadius = radius; + mPath = null; + } + + /** + * Convenience for {@link #setRoundRect(int, int, int, int, float)} + */ + public void setRoundRect(@NonNull Rect rect, float radius) { + setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius); + } + +// /** +// * Sets the outline to the oval defined by input rect. +// */ +// public void setOval(int left, int top, int right, int bottom) { +// if (left >= right || top >= bottom) { +// setEmpty(); +// return; +// } +// +// if ((bottom - top) == (right - left)) { +// // represent circle as round rect, for efficiency, and to enable clipping +// setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f); +// return; +// } +// +// if (mPath == null) mPath = new Path(); +// mPath.reset(); +// mPath.addOval(left, top, right, bottom, Path.Direction.CW); +// mRect = null; +// } +// +// /** +// * Convenience for {@link #setOval(int, int, int, int)} +// */ +// public void setOval(@NonNull Rect rect) { +// setOval(rect.left, rect.top, rect.right, rect.bottom); +// } +// +// /** +// * Sets the Constructs an Outline from a +// * {@link android.graphics.Path#isConvex() convex path}. +// */ +// public void setConvexPath(@NonNull Path convexPath) { +// if (convexPath.isEmpty()) { +// setEmpty(); +// return; +// } +// +// if (!convexPath.isConvex()) { +// throw new IllegalArgumentException("path must be convex"); +// } +// if (mPath == null) mPath = new Path(); +// +// mPath.set(convexPath); +// mRect = null; +// mRadius = -1.0f; +// } +} diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathAnimatorInflater.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathAnimatorInflater.java new file mode 100644 index 0000000..bab5bf1 --- /dev/null +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathAnimatorInflater.java @@ -0,0 +1,346 @@ +package is.arontibo.library.VectorCompat; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; +import android.view.InflateException; +import android.view.animation.AnimationUtils; + +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.animation.AnimatorSet; +import com.nineoldandroids.animation.ObjectAnimator; +import com.nineoldandroids.animation.TypeEvaluator; +import com.nineoldandroids.animation.ValueAnimator; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +import is.arontibo.library.R; + +public class PathAnimatorInflater { + private static final String TAG = "PathAnimatorInflater"; + /** + * These flags are used when parsing PathAnimatorSet objects + */ + private static final int TOGETHER = 0; + private static final int SEQUENTIALLY = 1; + + private static final int VALUE_TYPE_PATH = 2; + + private static final boolean DBG_ANIMATOR_INFLATER = false; + + public static Animator loadAnimator(Context c, Resources resources, Resources.Theme theme, int id, + float pathErrorScale) throws Resources.NotFoundException { + + XmlResourceParser parser = null; + try { + parser = resources.getAnimation(id); + return createAnimatorFromXml(c, resources, theme, parser, pathErrorScale); + } catch (XmlPullParserException ex) { + Resources.NotFoundException rnf = + new Resources.NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + Resources.NotFoundException rnf = + new Resources.NotFoundException("Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } finally { + if (parser != null) parser.close(); + } + } + + private static Animator createAnimatorFromXml(Context c, Resources res, Resources.Theme theme, XmlPullParser parser, + float pixelSize) + throws XmlPullParserException, IOException { + return createAnimatorFromXml(c, res, theme, parser, Xml.asAttributeSet(parser), null, 0, + pixelSize); + } + + private static Animator createAnimatorFromXml(Context c, Resources res, Resources.Theme theme, XmlPullParser parser, + AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize) + throws XmlPullParserException, IOException { + + Animator anim = null; + ArrayList childAnims = null; + + // Make sure we are on a start tag. + int type; + int depth = parser.getDepth(); + + while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + if (name.equals("objectAnimator")) { + anim = loadObjectAnimator(c, res, theme, attrs, pixelSize); + } else if (name.equals("animator")) { + anim = loadAnimator(c, res, theme, attrs, null, pixelSize); + } else if (name.equals("set")) { + anim = new AnimatorSet(); + //TODO: don't care about 'set' attributes for now +// TypedArray a; +// if (theme != null) { +// a = theme.obtainStyledAttributes(attrs, AnimatorSet, 0, 0); +// } else { +// a = res.obtainAttributes(attrs, AnimatorSet); +// } +// int ordering = a.getInt(R.styleable.AnimatorSet_ordering, +// TOGETHER); + createAnimatorFromXml(c, res, theme, parser, attrs, (AnimatorSet) anim, TOGETHER, + pixelSize); +// a.recycle(); + } else { + throw new RuntimeException("Unknown animator name: " + parser.getName()); + } + + if (parent != null) { + if (childAnims == null) { + childAnims = new ArrayList(); + } + childAnims.add(anim); + } + } + if (parent != null && childAnims != null) { + Animator[] animsArray = new Animator[childAnims.size()]; + int index = 0; + for (Animator a : childAnims) { + animsArray[index++] = a; + } + if (sequenceOrdering == TOGETHER) { + parent.playTogether(animsArray); + } else { + parent.playSequentially(animsArray); + } + } + + return anim; + + } + + private static ObjectAnimator loadObjectAnimator(Context c, Resources res, Resources.Theme theme, AttributeSet attrs, + float pathErrorScale) throws Resources.NotFoundException { + ObjectAnimator anim = new ObjectAnimator(); + + loadAnimator(c, res, theme, attrs, anim, pathErrorScale); + + return anim; + } + + /** + * Creates a new animation whose parameters come from the specified context + * and attributes set. + * + * @param res The resources + * @param attrs The set of attributes holding the animation parameters + * @param anim Null if this is a ValueAnimator, otherwise this is an + * ObjectAnimator + */ + private static ValueAnimator loadAnimator(Context c, Resources res, Resources.Theme theme, + AttributeSet attrs, ValueAnimator anim, float pathErrorScale) + throws Resources.NotFoundException { + + TypedArray arrayAnimator = null; + TypedArray arrayObjectAnimator = null; + + if (theme != null) { + arrayAnimator = theme.obtainStyledAttributes(attrs, R.styleable.Animator, 0, 0); + } else { + arrayAnimator = res.obtainAttributes(attrs, R.styleable.Animator); + } + + // If anim is not null, then it is an object animator. + if (anim != null) { + if (theme != null) { + arrayObjectAnimator = theme.obtainStyledAttributes(attrs, + R.styleable.PropertyAnimator, 0, 0); + } else { + arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator); + } + } + + if (anim == null) { + anim = new ValueAnimator(); + } + + parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator); + + final int resId = + arrayAnimator.getResourceId(R.styleable.Animator_android_interpolator, 0); + if (resId > 0) { + anim.setInterpolator(AnimationUtils.loadInterpolator(c, resId)); + } + + arrayAnimator.recycle(); + if (arrayObjectAnimator != null) { + arrayObjectAnimator.recycle(); + } + + return anim; + } + + /** + * @param anim The animator, must not be null + * @param arrayAnimator Incoming typed array for Animator's attributes. + * @param arrayObjectAnimator Incoming typed array for Object Animator's + * attributes. + */ + private static void parseAnimatorFromTypeArray(ValueAnimator anim, + TypedArray arrayAnimator, TypedArray arrayObjectAnimator) { + long duration = arrayAnimator.getInt(R.styleable.Animator_android_duration, 300); + + long startDelay = arrayAnimator.getInt(R.styleable.Animator_android_startOffset, 0); + + int valueType = arrayAnimator.getInt(R.styleable.Animator_vc_valueType, 0); + + TypeEvaluator evaluator = null; + + // Must be a path animator by the time I reach here + if (valueType == VALUE_TYPE_PATH) { + evaluator = setupAnimatorForPath(anim, arrayAnimator); + } else { + throw new IllegalArgumentException("target is not a pathType target"); + } + + anim.setDuration(duration); + anim.setStartDelay(startDelay); + + if (arrayAnimator.hasValue(R.styleable.Animator_android_repeatCount)) { + anim.setRepeatCount( + arrayAnimator.getInt(R.styleable.Animator_android_repeatCount, 0)); + } + if (arrayAnimator.hasValue(R.styleable.Animator_android_repeatMode)) { + anim.setRepeatMode( + arrayAnimator.getInt(R.styleable.Animator_android_repeatMode, + ValueAnimator.RESTART)); + } + if (evaluator != null) { + anim.setEvaluator(evaluator); + } + + if (arrayObjectAnimator != null) { + setupObjectAnimator(anim, arrayObjectAnimator); + } + } + + /** + * Setup the Animator to achieve path morphing. + * + * @param anim The target Animator which will be updated. + * @param arrayAnimator TypedArray for the ValueAnimator. + * @return the PathDataEvaluator. + */ + private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim, + TypedArray arrayAnimator) { + TypeEvaluator evaluator = null; + String fromString = arrayAnimator.getString(R.styleable.Animator_vc_valueFrom); + String toString = arrayAnimator.getString(R.styleable.Animator_vc_valueTo); + PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString); + PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString); + + Log.d(TAG, String.format("retrieved fromString: %s", fromString)); + Log.d(TAG, String.format("retrieved toString: %s", toString)); + + if (nodesFrom != null) { + if (nodesTo != null) { + anim.setObjectValues(nodesFrom, nodesTo); + if (!PathParser.canMorph(nodesFrom, nodesTo)) { + throw new InflateException(arrayAnimator.getPositionDescription() + + " Can't morph from " + fromString + " to " + toString); + } + } else { + anim.setObjectValues((Object)nodesFrom); + } + evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom)); + } else if (nodesTo != null) { + anim.setObjectValues((Object)nodesTo); + evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo)); + } + + if (DBG_ANIMATOR_INFLATER && evaluator != null) { + Log.v(TAG, "create a new PathDataEvaluator here"); + } + + return evaluator; + } + + /** + * Setup ObjectAnimator's property or values from pathData. + * + * @param anim The target Animator which will be updated. + * @param arrayObjectAnimator TypedArray for the ObjectAnimator. + * + */ + private static void setupObjectAnimator(ValueAnimator anim, TypedArray arrayObjectAnimator) { + ObjectAnimator oa = (ObjectAnimator) anim; + String propertyName = + arrayObjectAnimator.getString(R.styleable.PropertyAnimator_vc_propertyName); + oa.setPropertyName(propertyName); + } + + /** + * PathDataEvaluator is used to interpolate between two paths which are + * represented in the same format but different control points' values. + * The path is represented as an array of PathDataNode here, which is + * fundamentally an array of floating point numbers. + */ + private static class PathDataEvaluator implements TypeEvaluator { + private PathParser.PathDataNode[] mNodeArray; + + /** + * Create a PathParser.PathDataNode[] that does not reuse the animated value. + * Care must be taken when using this option because on every evaluation + * a new PathParser.PathDataNode[] will be allocated. + */ + private PathDataEvaluator() {} + + /** + * Create a PathDataEvaluator that reuses nodeArray for every evaluate() call. + * Caution must be taken to ensure that the value returned from + * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or + * used across threads. The value will be modified on each evaluate() call. + * + * @param nodeArray The array to modify and return from evaluate. + */ + public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) { + mNodeArray = nodeArray; + } + + @Override + public PathParser.PathDataNode[] evaluate(float fraction, + PathParser.PathDataNode[] startPathData, + PathParser.PathDataNode[] endPathData) { + if (!PathParser.canMorph(startPathData, endPathData)) { + throw new IllegalArgumentException("Can't interpolate between" + + " two incompatible pathData"); + } + + if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) { + mNodeArray = PathParser.deepCopyNodes(startPathData); + } + + for (int i = 0; i < startPathData.length; i++) { + mNodeArray[i].interpolatePathDataNode(startPathData[i], + endPathData[i], fraction); + } + + return mNodeArray; + } + } + +} diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathParser.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathParser.java new file mode 100644 index 0000000..66005ac --- /dev/null +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathParser.java @@ -0,0 +1,635 @@ +package is.arontibo.library.VectorCompat; + +import android.graphics.Path; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; + +public class PathParser { + static final String LOGTAG = PathParser.class.getSimpleName(); + + /** + * @param pathData The string representing a path, the same as "d" string in svg file. + * @return the generated Path object. + */ + public static Path createPathFromPathData(String pathData) { + Path path = new Path(); + PathDataNode[] nodes = createNodesFromPathData(pathData); + if (nodes != null) { + PathDataNode.nodesToPath(nodes, path); + return path; + } + return null; + } + + /** + * @param pathData The string representing a path, the same as "d" string in svg file. + * @return an array of the PathDataNode. + */ + public static PathDataNode[] createNodesFromPathData(String pathData) { + if (pathData == null) { + return null; + } + int start = 0; + int end = 1; + + ArrayList list = new ArrayList(); + while (end < pathData.length()) { + end = nextStart(pathData, end); + String s = pathData.substring(start, end).trim(); + if (s.length() > 0) { + float[] val = getFloats(s); + addNode(list, s.charAt(0), val); + } + + start = end; + end++; + } + if ((end - start) == 1 && start < pathData.length()) { + addNode(list, pathData.charAt(start), new float[0]); + } + return list.toArray(new PathDataNode[list.size()]); + } + + /** + * @param source The array of PathDataNode to be duplicated. + * @return a deep copy of the source. + */ + public static PathDataNode[] deepCopyNodes(PathDataNode[] source) { + if (source == null) { + return null; + } + PathDataNode[] copy = new PathDataNode[source.length]; + for (int i = 0; i < source.length; i++) { + copy[i] = new PathDataNode(source[i]); + } + return copy; + } + + /** + * @param nodesFrom The source path represented in an array of PathDataNode + * @param nodesTo The target path represented in an array of PathDataNode + * @return whether the nodesFrom can morph into nodesTo + */ + public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) { + if (nodesFrom == null || nodesTo == null) { + return false; + } + + if (nodesFrom.length != nodesTo.length) { + return false; + } + + for (int i = 0; i < nodesFrom.length; i++) { + if (nodesFrom[i].mType != nodesTo[i].mType + || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) { + return false; + } + } + return true; + } + + /** + * Update the target's data to match the source. + * Before calling this, make sure canMorph(target, source) is true. + * + * @param target The target path represented in an array of PathDataNode + * @param source The source path represented in an array of PathDataNode + */ + public static void updateNodes(PathDataNode[] target, PathDataNode[] source) { + for (int i = 0; i < source.length; i++) { + target[i].mType = source[i].mType; + for (int j = 0; j < source[i].mParams.length; j++) { + target[i].mParams[j] = source[i].mParams[j]; + } + } + } + + private static int nextStart(String s, int end) { + char c; + + while (end < s.length()) { + c = s.charAt(end); + if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) { + return end; + } + end++; + } + return end; + } + + private static void addNode(ArrayList list, char cmd, float[] val) { + list.add(new PathDataNode(cmd, val)); + } + + private static class ExtractFloatResult { + // We need to return the position of the next separator and whether the + // next float starts with a '-'. + int mEndPosition; + boolean mEndWithNegSign; + } + + /** + * Parse the floats in the string. + * This is an optimized version of parseFloat(s.split(",|\\s")); + * + * @param s the string containing a command and list of floats + * @return array of floats + */ + private static float[] getFloats(String s) { + if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') { + return new float[0]; + } + try { + float[] results = new float[s.length()]; + int count = 0; + int startPosition = 1; + int endPosition = 0; + + ExtractFloatResult result = new ExtractFloatResult(); + int totalLength = s.length(); + + // The startPosition should always be the first character of the + // current number, and endPosition is the character after the current + // number. + while (startPosition < totalLength) { + extract(s, startPosition, result); + endPosition = result.mEndPosition; + + if (startPosition < endPosition) { + results[count++] = Float.parseFloat( + s.substring(startPosition, endPosition)); + } + + if (result.mEndWithNegSign) { + // Keep the '-' sign with next number. + startPosition = endPosition; + } else { + startPosition = endPosition + 1; + } + } + return Arrays.copyOf(results, count); + } catch (NumberFormatException e) { + Log.e(LOGTAG, "error in parsing \"" + s + "\""); + throw e; + } + } + + /** + * Calculate the position of the next comma or space or negative sign + * + * @param s the string to search + * @param start the position to start searching + * @param result the result of the extraction, including the position of the + * the starting position of next number, whether it is ending with a '-'. + */ + private static void extract(String s, int start, ExtractFloatResult result) { + // Now looking for ' ', ',' or '-' from the start. + int currentIndex = start; + boolean foundSeparator = false; + result.mEndWithNegSign = false; + for (; currentIndex < s.length(); currentIndex++) { + char currentChar = s.charAt(currentIndex); + switch (currentChar) { + case ' ': + case ',': + foundSeparator = true; + break; + case '-': + if (currentIndex != start) { + foundSeparator = true; + result.mEndWithNegSign = true; + } + break; + } + if (foundSeparator) { + break; + } + } + // When there is nothing found, then we put the end position to the end + // of the string. + result.mEndPosition = currentIndex; + } + + /** + * Each PathDataNode represents one command in the "d" attribute of the svg + * file. + * An array of PathDataNode can represent the whole "d" attribute. + */ + public static class PathDataNode { + private char mType; + private float[] mParams; + + private PathDataNode(char type, float[] params) { + mType = type; + mParams = params; + } + + private PathDataNode(PathDataNode n) { + mType = n.mType; + mParams = Arrays.copyOf(n.mParams, n.mParams.length); + } + + /** + * Convert an array of PathDataNode to Path. + * + * @param node The source array of PathDataNode. + * @param path The target Path object. + */ + public static void nodesToPath(PathDataNode[] node, Path path) { + float[] current = new float[4]; + char previousCommand = 'm'; + for (int i = 0; i < node.length; i++) { + addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); + previousCommand = node[i].mType; + } + } + + /** + * The current PathDataNode will be interpolated between the + * nodeFrom and nodeTo according to the + * fraction. + * + * @param nodeFrom The start value as a PathDataNode. + * @param nodeTo The end value as a PathDataNode + * @param fraction The fraction to interpolate. + */ + public void interpolatePathDataNode(PathDataNode nodeFrom, + PathDataNode nodeTo, float fraction) { + for (int i = 0; i < nodeFrom.mParams.length; i++) { + mParams[i] = nodeFrom.mParams[i] * (1 - fraction) + + nodeTo.mParams[i] * fraction; + } + } + + private static void addCommand(Path path, float[] current, + char previousCmd, char cmd, float[] val) { + + int incr = 2; + float currentX = current[0]; + float currentY = current[1]; + float ctrlPointX = current[2]; + float ctrlPointY = current[3]; + float reflectiveCtrlPointX; + float reflectiveCtrlPointY; + + switch (cmd) { + case 'z': + case 'Z': + path.close(); + return; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + incr = 2; + break; + case 'h': + case 'H': + case 'v': + case 'V': + incr = 1; + break; + case 'c': + case 'C': + incr = 6; + break; + case 's': + case 'S': + case 'q': + case 'Q': + incr = 4; + break; + case 'a': + case 'A': + incr = 7; + break; + } + for (int k = 0; k < val.length; k += incr) { + switch (cmd) { + case 'm': // moveto - Start a new sub-path (relative) + path.rMoveTo(val[k + 0], val[k + 1]); + currentX += val[k + 0]; + currentY += val[k + 1]; + break; + case 'M': // moveto - Start a new sub-path + path.moveTo(val[k + 0], val[k + 1]); + currentX = val[k + 0]; + currentY = val[k + 1]; + break; + case 'l': // lineto - Draw a line from the current point (relative) + path.rLineTo(val[k + 0], val[k + 1]); + currentX += val[k + 0]; + currentY += val[k + 1]; + break; + case 'L': // lineto - Draw a line from the current point + path.lineTo(val[k + 0], val[k + 1]); + currentX = val[k + 0]; + currentY = val[k + 1]; + break; + case 'z': // closepath - Close the current subpath + case 'Z': // closepath - Close the current subpath + path.close(); + break; + case 'h': // horizontal lineto - Draws a horizontal line (relative) + path.rLineTo(val[k + 0], 0); + currentX += val[k + 0]; + break; + case 'H': // horizontal lineto - Draws a horizontal line + path.lineTo(val[k + 0], currentY); + currentX = val[k + 0]; + break; + case 'v': // vertical lineto - Draws a vertical line from the current point (r) + path.rLineTo(0, val[k + 0]); + currentY += val[k + 0]; + break; + case 'V': // vertical lineto - Draws a vertical line from the current point + path.lineTo(currentX, val[k + 0]); + currentY = val[k + 0]; + break; + case 'c': // curveto - Draws a cubic Bézier curve (relative) + path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], + val[k + 4], val[k + 5]); + + ctrlPointX = currentX + val[k + 2]; + ctrlPointY = currentY + val[k + 3]; + currentX += val[k + 4]; + currentY += val[k + 5]; + + break; + case 'C': // curveto - Draws a cubic Bézier curve + path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], + val[k + 4], val[k + 5]); + currentX = val[k + 4]; + currentY = val[k + 5]; + ctrlPointX = val[k + 2]; + ctrlPointY = val[k + 3]; + break; + case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1], + val[k + 2], val[k + 3]); + + ctrlPointX = currentX + val[k + 0]; + ctrlPointY = currentY + val[k + 1]; + currentX += val[k + 2]; + currentY += val[k + 3]; + break; + case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + currentX = val[k + 2]; + currentY = val[k + 3]; + break; + case 'q': // Draws a quadratic Bézier (relative) + path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = currentX + val[k + 0]; + ctrlPointY = currentY + val[k + 1]; + currentX += val[k + 2]; + currentY += val[k + 3]; + break; + case 'Q': // Draws a quadratic Bézier + path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); + ctrlPointX = val[k + 0]; + ctrlPointY = val[k + 1]; + currentX = val[k + 2]; + currentY = val[k + 3]; + break; + case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1]); + ctrlPointX = currentX + reflectiveCtrlPointX; + ctrlPointY = currentY + reflectiveCtrlPointY; + currentX += val[k + 0]; + currentY += val[k + 1]; + break; + case 'T': // Draws a quadratic Bézier curve (reflective control point) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + val[k + 0], val[k + 1]); + ctrlPointX = reflectiveCtrlPointX; + ctrlPointY = reflectiveCtrlPointY; + currentX = val[k + 0]; + currentY = val[k + 1]; + break; + case 'a': // Draws an elliptical arc + // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) + drawArc(path, + currentX, + currentY, + val[k + 5] + currentX, + val[k + 6] + currentY, + val[k + 0], + val[k + 1], + val[k + 2], + val[k + 3] != 0, + val[k + 4] != 0); + currentX += val[k + 5]; + currentY += val[k + 6]; + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + case 'A': // Draws an elliptical arc + drawArc(path, + currentX, + currentY, + val[k + 5], + val[k + 6], + val[k + 0], + val[k + 1], + val[k + 2], + val[k + 3] != 0, + val[k + 4] != 0); + currentX = val[k + 5]; + currentY = val[k + 6]; + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + } + previousCmd = cmd; + } + current[0] = currentX; + current[1] = currentY; + current[2] = ctrlPointX; + current[3] = ctrlPointY; + } + + private static void drawArc(Path p, + float x0, + float y0, + float x1, + float y1, + float a, + float b, + float theta, + boolean isMoreThanHalf, + boolean isPositiveArc) { + + /* Convert rotation angle from degrees to radians */ + double thetaD = Math.toRadians(theta); + /* Pre-compute rotation matrix entries */ + double cosTheta = Math.cos(thetaD); + double sinTheta = Math.sin(thetaD); + /* Transform (x0, y0) and (x1, y1) into unit space */ + /* using (inverse) rotation, followed by (inverse) scale */ + double x0p = (x0 * cosTheta + y0 * sinTheta) / a; + double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; + double x1p = (x1 * cosTheta + y1 * sinTheta) / a; + double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; + + /* Compute differences and averages */ + double dx = x0p - x1p; + double dy = y0p - y1p; + double xm = (x0p + x1p) / 2; + double ym = (y0p + y1p) / 2; + /* Solve for intersecting unit circles */ + double dsq = dx * dx + dy * dy; + if (dsq == 0.0) { + Log.w(LOGTAG, " Points are coincident"); + return; /* Points are coincident */ + } + double disc = 1.0 / dsq - 1.0 / 4.0; + if (disc < 0.0) { + Log.w(LOGTAG, "Points are too far apart " + dsq); + float adjust = (float) (Math.sqrt(dsq) / 1.99999); + drawArc(p, x0, y0, x1, y1, a * adjust, + b * adjust, theta, isMoreThanHalf, isPositiveArc); + return; /* Points are too far apart */ + } + double s = Math.sqrt(disc); + double sdx = s * dx; + double sdy = s * dy; + double cx; + double cy; + if (isMoreThanHalf == isPositiveArc) { + cx = xm - sdy; + cy = ym + sdx; + } else { + cx = xm + sdy; + cy = ym - sdx; + } + + double eta0 = Math.atan2((y0p - cy), (x0p - cx)); + + double eta1 = Math.atan2((y1p - cy), (x1p - cx)); + + double sweep = (eta1 - eta0); + if (isPositiveArc != (sweep >= 0)) { + if (sweep > 0) { + sweep -= 2 * Math.PI; + } else { + sweep += 2 * Math.PI; + } + } + + cx *= a; + cy *= b; + double tcx = cx; + cx = cx * cosTheta - cy * sinTheta; + cy = tcx * sinTheta + cy * cosTheta; + + arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); + } + + /** + * Converts an arc to cubic Bezier segments and records them in p. + * + * @param p The target for the cubic Bezier segments + * @param cx The x coordinate center of the ellipse + * @param cy The y coordinate center of the ellipse + * @param a The radius of the ellipse in the horizontal direction + * @param b The radius of the ellipse in the vertical direction + * @param e1x E(eta1) x coordinate of the starting point of the arc + * @param e1y E(eta2) y coordinate of the starting point of the arc + * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane + * @param start The start angle of the arc on the ellipse + * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse + */ + private static void arcToBezier(Path p, + double cx, + double cy, + double a, + double b, + double e1x, + double e1y, + double theta, + double start, + double sweep) { + // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html + // and http://www.spaceroots.org/documents/ellipse/node22.html + + // Maximum of 45 degrees per cubic Bezier segment + int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI)); + + double eta1 = start; + double cosTheta = Math.cos(theta); + double sinTheta = Math.sin(theta); + double cosEta1 = Math.cos(eta1); + double sinEta1 = Math.sin(eta1); + double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); + double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); + + double anglePerSegment = sweep / numSegments; + for (int i = 0; i < numSegments; i++) { + double eta2 = eta1 + anglePerSegment; + double sinEta2 = Math.sin(eta2); + double cosEta2 = Math.cos(eta2); + double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); + double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); + double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; + double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; + double tanDiff2 = Math.tan((eta2 - eta1) / 2); + double alpha = + Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; + double q1x = e1x + alpha * ep1x; + double q1y = e1y + alpha * ep1y; + double q2x = e2x - alpha * ep2x; + double q2y = e2y - alpha * ep2y; + + p.cubicTo((float) q1x, + (float) q1y, + (float) q2x, + (float) q2y, + (float) e2x, + (float) e2y); + eta1 = eta2; + e1x = e2x; + e1y = e2y; + ep1x = ep2x; + ep1y = ep2y; + } + } + } +} diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/ResourcesCompat.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/ResourcesCompat.java new file mode 100644 index 0000000..ed24292 --- /dev/null +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/ResourcesCompat.java @@ -0,0 +1,39 @@ +package is.arontibo.library.VectorCompat; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Build; + +public class ResourcesCompat { + public static final boolean LOLLIPOP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + + @SuppressWarnings("deprecation") + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static Drawable getDrawable(Context c, int resId) { + Drawable d; + try { + if (LOLLIPOP) { + d = c.getResources().getDrawable(resId, c.getTheme()); + } else { + d = c.getResources().getDrawable(resId); + } + } catch (Resources.NotFoundException e) { + + try { + d = VectorDrawable.getDrawable(c, resId); + } catch (IllegalArgumentException e1) { + + //We're not a VectorDrawable, try AnimatedVectorDrawable + try { + d = AnimatedVectorDrawable.getDrawable(c, resId); + } catch (IllegalArgumentException e2) { + //Throw NotFoundException + throw e; + } + } + } + return d; + } +} diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Tintable.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Tintable.java new file mode 100644 index 0000000..af2fc6c --- /dev/null +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Tintable.java @@ -0,0 +1,10 @@ +package is.arontibo.library.VectorCompat; + +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; + +public interface Tintable { + public void setTintMode(PorterDuff.Mode tintMode); + + public void setTintList(ColorStateList tint); +} diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/VectorDrawable.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/VectorDrawable.java new file mode 100644 index 0000000..670a914 --- /dev/null +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/VectorDrawable.java @@ -0,0 +1,1423 @@ +package is.arontibo.library.VectorCompat; + + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.util.ArrayMap; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Stack; + +import is.arontibo.library.R; + +public class VectorDrawable extends DrawableCompat implements Tintable { + private static final String LOGTAG = VectorDrawable.class.getSimpleName(); + + static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN; + + private static final String SHAPE_CLIP_PATH = "clip-path"; + private static final String SHAPE_GROUP = "group"; + private static final String SHAPE_PATH = "path"; + private static final String SHAPE_VECTOR = "vector"; + + private static final int LINECAP_BUTT = 0; + private static final int LINECAP_ROUND = 1; + private static final int LINECAP_SQUARE = 2; + + private static final int LINEJOIN_MITER = 0; + private static final int LINEJOIN_ROUND = 1; + private static final int LINEJOIN_BEVEL = 2; + + private static final boolean DBG_VECTOR_DRAWABLE = false; + + private VectorDrawableState mVectorState; + + private PorterDuffColorFilter mTintFilter; + private ColorFilter mColorFilter; + + private boolean mMutated; + + // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise, + // caching the bitmap by default is allowed. + private boolean mAllowCaching = true; + + public VectorDrawable() { + mVectorState = new VectorDrawableState(); + } + + private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) { + if (theme != null && state.canApplyTheme()) { + // If we need to apply a theme, implicitly mutate. + mVectorState = new VectorDrawableState(state); + applyTheme(theme); + } else { + mVectorState = state; + } + + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + } + + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mVectorState = new VectorDrawableState(mVectorState); + mMutated = true; + } + return this; + } + + Object getTargetByName(String name) { + return mVectorState.mVPathRenderer.mVGTargetsMap.get(name); + } + + @Override + public Drawable.ConstantState getConstantState() { + mVectorState.mChangingConfigurations = getChangingConfigurations(); + return mVectorState; + } + + @Override + public void draw(Canvas canvas) { + final Rect bounds = getBounds(); + if (bounds.width() == 0 || bounds.height() == 0) { + // too small to draw + return; + } + + final int saveCount = canvas.save(); + final boolean needMirroring = needMirroring(); + + canvas.translate(bounds.left, bounds.top); + if (needMirroring) { + canvas.translate(bounds.width(), 0); + canvas.scale(-1.0f, 1.0f); + } + + // Color filters always override tint filters. + final ColorFilter colorFilter = mColorFilter == null ? mTintFilter : mColorFilter; + + if (!mAllowCaching) { + // AnimatedVectorDrawable + if (!mVectorState.hasTranslucentRoot()) { + mVectorState.mVPathRenderer.draw( + canvas, bounds.width(), bounds.height(), colorFilter); + } else { + mVectorState.createCachedBitmapIfNeeded(bounds); + mVectorState.updateCachedBitmap(bounds); + mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter); + } + } else { + // Static Vector Drawable case. + mVectorState.createCachedBitmapIfNeeded(bounds); + if (!mVectorState.canReuseCache()) { + mVectorState.updateCachedBitmap(bounds); + mVectorState.updateCacheStates(); + } + mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter); + } + + canvas.restoreToCount(saveCount); + } + + @Override + public int getAlpha() { + return mVectorState.mVPathRenderer.getRootAlpha(); + } + + @Override + public void setAlpha(int alpha) { + if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) { + mVectorState.mVPathRenderer.setRootAlpha(alpha); + invalidateSelf(); + } + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mColorFilter = colorFilter; + invalidateSelf(); + } + + @Override + public void setTintList(ColorStateList tint) { + final VectorDrawableState state = mVectorState; + if (state.mTint != tint) { + state.mTint = tint; + mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode); + invalidateSelf(); + } + } + + @Override + public void setTintMode(PorterDuff.Mode tintMode) { + final VectorDrawableState state = mVectorState; + if (state.mTintMode != tintMode) { + state.mTintMode = tintMode; + mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode); + invalidateSelf(); + } + } + + @Override + public boolean isStateful() { + return super.isStateful() || (mVectorState != null && mVectorState.mTint != null + && mVectorState.mTint.isStateful()); + } + + @Override + protected boolean onStateChange(int[] stateSet) { + final VectorDrawableState state = mVectorState; + if (state.mTint != null && state.mTintMode != null) { + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + invalidateSelf(); + return true; + } + return false; + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public int getIntrinsicWidth() { + return (int) mVectorState.mVPathRenderer.mBaseWidth; + } + + @Override + public int getIntrinsicHeight() { + return (int) mVectorState.mVPathRenderer.mBaseHeight; + } + + @Override + public boolean canApplyTheme() { + return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme(); + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final VectorDrawableState state = mVectorState; + if (state != null && state.mThemeAttrs != null) { + //TODO + final TypedArray a = null;//t.resolveAttributes(state.mThemeAttrs, R.styleable.VectorDrawable); + try { + state.mCacheDirty = true; + updateStateFromTypedArray(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + } + + final VPathRenderer path = state.mVPathRenderer; + if (path != null && path.canApplyTheme()) { + path.applyTheme(t); + } + } + + /** + * The size of a pixel when scaled from the intrinsic dimension to the viewport dimension. + * This is used to calculate the path animation accuracy. + */ + public float getPixelSize() { + if (mVectorState == null && mVectorState.mVPathRenderer == null || + mVectorState.mVPathRenderer.mBaseWidth == 0 || + mVectorState.mVPathRenderer.mBaseHeight == 0 || + mVectorState.mVPathRenderer.mViewportHeight == 0 || + mVectorState.mVPathRenderer.mViewportWidth == 0) { + return 1; // fall back to 1:1 pixel mapping. + } + float intrinsicWidth = mVectorState.mVPathRenderer.mBaseWidth; + float intrinsicHeight = mVectorState.mVPathRenderer.mBaseHeight; + float viewportWidth = mVectorState.mVPathRenderer.mViewportWidth; + float viewportHeight = mVectorState.mVPathRenderer.mViewportHeight; + float scaleX = viewportWidth / intrinsicWidth; + float scaleY = viewportHeight / intrinsicHeight; + return Math.min(scaleX, scaleY); + } + + public static VectorDrawable getDrawable(Context c, int resId) { + return create(c.getResources(), resId); + } + + public static VectorDrawable create(Resources resources, int rid) { + try { + final XmlPullParser parser = resources.getXml(rid); + final AttributeSet attrs = Xml.asAttributeSet(parser); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG && + type != XmlPullParser.END_DOCUMENT) { + // Empty loop + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } else if (!SHAPE_VECTOR.equals(parser.getName())) { + throw new IllegalArgumentException("root node must start with: " + SHAPE_VECTOR); + } + + final VectorDrawable drawable = new VectorDrawable(); + drawable.inflate(resources, parser, attrs, null); + + return drawable; + } catch (XmlPullParserException e) { + Log.e(LOGTAG, "parser error", e); + } catch (IOException e) { + Log.e(LOGTAG, "parser error", e); + } + return null; + } + + private static int applyAlpha(int color, float alpha) { + int alphaBytes = Color.alpha(color); + color &= 0x00FFFFFF; + color |= ((int) (alphaBytes * alpha)) << 24; + return color; + } + + @Override + public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + final VectorDrawableState state = mVectorState; + final VPathRenderer pathRenderer = new VPathRenderer(); + state.mVPathRenderer = pathRenderer; + + final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable); + updateStateFromTypedArray(a); + a.recycle(); + + state.mCacheDirty = true; + inflateInternal(res, parser, attrs, theme); + + mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); + } + + private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { + final VectorDrawableState state = mVectorState; + final VPathRenderer pathRenderer = state.mVPathRenderer; + + // Account for any configuration changes. + state.mChangingConfigurations |= getChangingConfigurations(a); + + // Extract the theme attributes, if any. + //TODO: will not support drawable theming yet (applies to tinting mainly) + //state.mThemeAttrs = a.extractThemeAttrs(); + + final int tintMode = a.getInt(R.styleable.VectorDrawable_vc_tintMode, -1); + if (tintMode != -1) { + state.mTintMode = parseTintMode(tintMode, PorterDuff.Mode.SRC_IN); + } + + final ColorStateList tint = a.getColorStateList(R.styleable.VectorDrawable_vc_tint); + if (tint != null) { + state.mTint = tint; + } + + state.mAutoMirrored = a.getBoolean( + R.styleable.VectorDrawable_vc_autoMirrored, state.mAutoMirrored); + + pathRenderer.mViewportWidth = a.getFloat( + R.styleable.VectorDrawable_vc_viewportWidth, pathRenderer.mViewportWidth); + pathRenderer.mViewportHeight = a.getFloat( + R.styleable.VectorDrawable_vc_viewportHeight, pathRenderer.mViewportHeight); + + if (pathRenderer.mViewportWidth <= 0) { + throw new XmlPullParserException(a.getPositionDescription() + + " tag requires viewportWidth > 0"); + } else if (pathRenderer.mViewportHeight <= 0) { + throw new XmlPullParserException(a.getPositionDescription() + + " tag requires viewportHeight > 0"); + } + + pathRenderer.mBaseWidth = a.getDimension( + R.styleable.VectorDrawable_android_width, pathRenderer.mBaseWidth); + pathRenderer.mBaseHeight = a.getDimension( + R.styleable.VectorDrawable_android_height, pathRenderer.mBaseHeight); + + if (pathRenderer.mBaseWidth <= 0) { + throw new XmlPullParserException(a.getPositionDescription() + + " tag requires width > 0"); + } else if (pathRenderer.mBaseHeight <= 0) { + throw new XmlPullParserException(a.getPositionDescription() + + " tag requires height > 0"); + } + + final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_android_alpha, + pathRenderer.getAlpha()); + pathRenderer.setAlpha(alphaInFloat); + + final String name = a.getString(R.styleable.VectorDrawable_android_name); + if (name != null) { + pathRenderer.mRootName = name; + pathRenderer.mVGTargetsMap.put(name, pathRenderer); + } + } + + private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + final VectorDrawableState state = mVectorState; + final VPathRenderer pathRenderer = state.mVPathRenderer; + boolean noPathTag = true; + + // Use a stack to help to build the group tree. + // The top of the stack is always the current group. + final Stack groupStack = new Stack(); + groupStack.push(pathRenderer.mRootGroup); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + final String tagName = parser.getName(); + final VGroup currentGroup = groupStack.peek(); + + if (SHAPE_PATH.equals(tagName)) { + final VFullPath path = new VFullPath(); + path.inflate(res, attrs, theme); + currentGroup.mChildren.add(path); + if (path.getPathName() != null) { + pathRenderer.mVGTargetsMap.put(path.getPathName(), path); + } + noPathTag = false; + state.mChangingConfigurations |= path.mChangingConfigurations; + } else if (SHAPE_CLIP_PATH.equals(tagName)) { + final VClipPath path = new VClipPath(); + path.inflate(res, attrs, theme); + currentGroup.mChildren.add(path); + if (path.getPathName() != null) { + pathRenderer.mVGTargetsMap.put(path.getPathName(), path); + } + state.mChangingConfigurations |= path.mChangingConfigurations; + } else if (SHAPE_GROUP.equals(tagName)) { + VGroup newChildGroup = new VGroup(); + newChildGroup.inflate(res, attrs, theme); + currentGroup.mChildren.add(newChildGroup); + groupStack.push(newChildGroup); + if (newChildGroup.getGroupName() != null) { + pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(), + newChildGroup); + } + state.mChangingConfigurations |= newChildGroup.mChangingConfigurations; + } + } else if (eventType == XmlPullParser.END_TAG) { + final String tagName = parser.getName(); + if (SHAPE_GROUP.equals(tagName)) { + groupStack.pop(); + } + } + eventType = parser.next(); + } + + // Print the tree out for debug. + if (DBG_VECTOR_DRAWABLE) { + printGroupTree(pathRenderer.mRootGroup, 0); + } + + if (noPathTag) { + final StringBuffer tag = new StringBuffer(); + + if (tag.length() > 0) { + tag.append(" or "); + } + tag.append(SHAPE_PATH); + + throw new XmlPullParserException("no " + tag + " defined"); + } + } + + public static int getChangingConfigurations(TypedArray a) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return a.getChangingConfigurations(); + } + return 0; + } + + private void printGroupTree(VGroup currentGroup, int level) { + String indent = ""; + for (int i = 0; i < level; i++) { + indent += " "; + } + // Print the current node + Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName() + + " rotation is " + currentGroup.mRotate); + Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); + // Then print all the children groups + for (int i = 0; i < currentGroup.mChildren.size(); i++) { + Object child = currentGroup.mChildren.get(i); + if (child instanceof VGroup) { + printGroupTree((VGroup) child, level + 1); + } + } + } + + @Override + public int getChangingConfigurations() { + return super.getChangingConfigurations() | mVectorState.mChangingConfigurations; + } + + void setAllowCaching(boolean allowCaching) { + mAllowCaching = allowCaching; + } + + private boolean needMirroring() { + return isAutoMirrored() && getLayoutDirection() == 1; // 1 is for LayoutDirection.RTL + } + + @Override + public void setAutoMirrored(boolean mirrored) { + if (mVectorState.mAutoMirrored != mirrored) { + mVectorState.mAutoMirrored = mirrored; + invalidateSelf(); + } + } + + @Override + public boolean isAutoMirrored() { + return mVectorState.mAutoMirrored; + } + + private static class VectorDrawableState extends ConstantStateCompat { + int[] mThemeAttrs; + int mChangingConfigurations; + VPathRenderer mVPathRenderer; + ColorStateList mTint = null; + PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; + boolean mAutoMirrored; + + Bitmap mCachedBitmap; + int[] mCachedThemeAttrs; + ColorStateList mCachedTint; + PorterDuff.Mode mCachedTintMode; + int mCachedRootAlpha; + boolean mCachedAutoMirrored; + boolean mCacheDirty; + + /** + * Temporary paint object used to draw cached bitmaps. + */ + Paint mTempPaint; + + // Deep copy for mutate() or implicitly mutate. + public VectorDrawableState(VectorDrawableState copy) { + if (copy != null) { + mThemeAttrs = copy.mThemeAttrs; + mChangingConfigurations = copy.mChangingConfigurations; + mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); + if (copy.mVPathRenderer.mFillPaint != null) { + mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint); + } + if (copy.mVPathRenderer.mStrokePaint != null) { + mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint); + } + mTint = copy.mTint; + mTintMode = copy.mTintMode; + mAutoMirrored = copy.mAutoMirrored; + } + } + + public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter) { + // The bitmap's size is the same as the bounds. + final Paint p = getPaint(filter); + canvas.drawBitmap(mCachedBitmap, 0, 0, p); + } + + public boolean hasTranslucentRoot() { + return mVPathRenderer.getRootAlpha() < 255; + } + + /** + * @return null when there is no need for alpha paint. + */ + public Paint getPaint(ColorFilter filter) { + if (!hasTranslucentRoot() && filter == null) { + return null; + } + + if (mTempPaint == null) { + mTempPaint = new Paint(); + mTempPaint.setFilterBitmap(true); + } + mTempPaint.setAlpha(mVPathRenderer.getRootAlpha()); + mTempPaint.setColorFilter(filter); + return mTempPaint; + } + + public void updateCachedBitmap(Rect bounds) { + mCachedBitmap.eraseColor(Color.TRANSPARENT); + Canvas tmpCanvas = new Canvas(mCachedBitmap); + mVPathRenderer.draw(tmpCanvas, bounds.width(), bounds.height(), null); + } + + public void createCachedBitmapIfNeeded(Rect bounds) { + if (mCachedBitmap == null || !canReuseBitmap(bounds.width(), + bounds.height())) { + mCachedBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), + Bitmap.Config.ARGB_8888); + mCacheDirty = true; + } + + } + + public boolean canReuseBitmap(int width, int height) { + if (width == mCachedBitmap.getWidth() + && height == mCachedBitmap.getHeight()) { + return true; + } + return false; + } + + public boolean canReuseCache() { + if (!mCacheDirty + && mCachedThemeAttrs == mThemeAttrs + && mCachedTint == mTint + && mCachedTintMode == mTintMode + && mCachedAutoMirrored == mAutoMirrored + && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) { + return true; + } + return false; + } + + public void updateCacheStates() { + // Use shallow copy here and shallow comparison in canReuseCache(), + // likely hit cache miss more, but practically not much difference. + mCachedThemeAttrs = mThemeAttrs; + mCachedTint = mTint; + mCachedTintMode = mTintMode; + mCachedRootAlpha = mVPathRenderer.getRootAlpha(); + mCachedAutoMirrored = mAutoMirrored; + mCacheDirty = false; + } + + @Override + public boolean canApplyTheme() { + return super.canApplyTheme() || mThemeAttrs != null + || (mVPathRenderer != null && mVPathRenderer.canApplyTheme()); + } + + public VectorDrawableState() { + mVPathRenderer = new VPathRenderer(); + } + + @Override + public Drawable newDrawable() { + return new VectorDrawable(this, null, null); + } + + @Override + public Drawable newDrawable(Resources res) { + return new VectorDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new VectorDrawable(this, res, theme); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + } + + private static class VPathRenderer { + /* Right now the internal data structure is organized as a tree. + * Each node can be a group node, or a path. + * A group node can have groups or paths as children, but a path node has + * no children. + * One example can be: + * Root Group + * / | \ + * Group Path Group + * / \ | + * Path Path Path + * + */ + // Variables that only used temporarily inside the draw() call, so there + // is no need for deep copying. + private final Path mPath; + private final Path mRenderPath; + private static final Matrix IDENTITY_MATRIX = new Matrix(); + private final Matrix mFinalPathMatrix = new Matrix(); + + private Paint mStrokePaint; + private Paint mFillPaint; + private PathMeasure mPathMeasure; + + ///////////////////////////////////////////////////// + // Variables below need to be copied (deep copy if applicable) for mutation. + private int mChangingConfigurations; + private final VGroup mRootGroup; + float mBaseWidth = 0; + float mBaseHeight = 0; + float mViewportWidth = 0; + float mViewportHeight = 0; + int mRootAlpha = 0xFF; + String mRootName = null; + + final ArrayMap mVGTargetsMap = new ArrayMap(); + + public VPathRenderer() { + mRootGroup = new VGroup(); + mPath = new Path(); + mRenderPath = new Path(); + } + + public void setRootAlpha(int alpha) { + mRootAlpha = alpha; + } + + public int getRootAlpha() { + return mRootAlpha; + } + + // setAlpha() and getAlpha() are used mostly for animation purpose, since + // Animator like to use alpha from 0 to 1. + public void setAlpha(float alpha) { + setRootAlpha((int) (alpha * 255)); + } + + @SuppressWarnings("unused") + public float getAlpha() { + return getRootAlpha() / 255.0f; + } + + public VPathRenderer(VPathRenderer copy) { + mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap); + mPath = new Path(copy.mPath); + mRenderPath = new Path(copy.mRenderPath); + mBaseWidth = copy.mBaseWidth; + mBaseHeight = copy.mBaseHeight; + mViewportWidth = copy.mViewportWidth; + mViewportHeight = copy.mViewportHeight; + mChangingConfigurations = copy.mChangingConfigurations; + mRootAlpha = copy.mRootAlpha; + mRootName = copy.mRootName; + if (copy.mRootName != null) { + mVGTargetsMap.put(copy.mRootName, this); + } + } + + public boolean canApplyTheme() { + // If one of the paths can apply theme, then return true; + return recursiveCanApplyTheme(mRootGroup); + } + + private boolean recursiveCanApplyTheme(VGroup currentGroup) { + // We can do a tree traverse here, if there is one path return true, + // then we return true for the whole tree. + final ArrayList children = currentGroup.mChildren; + + for (int i = 0; i < children.size(); i++) { + Object child = children.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + if (childGroup.canApplyTheme() + || recursiveCanApplyTheme(childGroup)) { + return true; + } + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + if (childPath.canApplyTheme()) { + return true; + } + } + } + return false; + } + + public void applyTheme(Theme t) { + // Apply theme to every path of the tree. + recursiveApplyTheme(mRootGroup, t); + } + + private void recursiveApplyTheme(VGroup currentGroup, Theme t) { + // We can do a tree traverse here, apply theme to all paths which + // can apply theme. + final ArrayList children = currentGroup.mChildren; + for (int i = 0; i < children.size(); i++) { + Object child = children.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + if (childGroup.canApplyTheme()) { + childGroup.applyTheme(t); + } + recursiveApplyTheme(childGroup, t); + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + if (childPath.canApplyTheme()) { + childPath.applyTheme(t); + } + } + } + } + + private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix, + Canvas canvas, int w, int h, ColorFilter filter) { + // Calculate current group's matrix by preConcat the parent's and + // and the current one on the top of the stack. + // Basically the Mfinal = Mviewport * M0 * M1 * M2; + // Mi the local matrix at level i of the group tree. + currentGroup.mStackedMatrix.set(currentMatrix); + + currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); + + // Draw the group tree in the same order as the XML file. + for (int i = 0; i < currentGroup.mChildren.size(); i++) { + Object child = currentGroup.mChildren.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + drawGroupTree(childGroup, currentGroup.mStackedMatrix, + canvas, w, h, filter); + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + drawPath(currentGroup, childPath, canvas, w, h, filter); + } + } + } + + public void draw(Canvas canvas, int w, int h, ColorFilter filter) { + // Travese the tree in pre-order to draw. + drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h, filter); + } + + private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h, + ColorFilter filter) { + final float scaleX = w / mViewportWidth; + final float scaleY = h / mViewportHeight; + final float minScale = Math.min(scaleX, scaleY); + + mFinalPathMatrix.set(vGroup.mStackedMatrix); + mFinalPathMatrix.postScale(scaleX, scaleY); + + vPath.toPath(mPath); + final Path path = mPath; + + mRenderPath.reset(); + + if (vPath.isClipPath()) { + mRenderPath.addPath(path, mFinalPathMatrix); + canvas.clipPath(mRenderPath, Region.Op.REPLACE); + } else { + VFullPath fullPath = (VFullPath) vPath; + if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) { + float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f; + float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f; + + if (mPathMeasure == null) { + mPathMeasure = new PathMeasure(); + } + mPathMeasure.setPath(mPath, false); + + float len = mPathMeasure.getLength(); + start = start * len; + end = end * len; + path.reset(); + if (start > end) { + mPathMeasure.getSegment(start, len, path, true); + mPathMeasure.getSegment(0f, end, path, true); + } else { + mPathMeasure.getSegment(start, end, path, true); + } + path.rLineTo(0, 0); // fix bug in measure + } + mRenderPath.addPath(path, mFinalPathMatrix); + + if (fullPath.mFillColor != Color.TRANSPARENT) { + if (mFillPaint == null) { + mFillPaint = new Paint(); + mFillPaint.setStyle(Paint.Style.FILL); + mFillPaint.setAntiAlias(true); + } + + final Paint fillPaint = mFillPaint; + fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha)); + fillPaint.setColorFilter(filter); + canvas.drawPath(mRenderPath, fillPaint); + } + + if (fullPath.mStrokeColor != Color.TRANSPARENT) { + if (mStrokePaint == null) { + mStrokePaint = new Paint(); + mStrokePaint.setStyle(Paint.Style.STROKE); + mStrokePaint.setAntiAlias(true); + } + + final Paint strokePaint = mStrokePaint; + if (fullPath.mStrokeLineJoin != null) { + strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin); + } + + if (fullPath.mStrokeLineCap != null) { + strokePaint.setStrokeCap(fullPath.mStrokeLineCap); + } + + strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit); + strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha)); + strokePaint.setColorFilter(filter); + strokePaint.setStrokeWidth(fullPath.mStrokeWidth * minScale); + canvas.drawPath(mRenderPath, strokePaint); + } + } + } + } + + private static class VGroup { + // mStackedMatrix is only used temporarily when drawing, it combines all + // the parents' local matrices with the current one. + private final Matrix mStackedMatrix = new Matrix(); + + ///////////////////////////////////////////////////// + // Variables below need to be copied (deep copy if applicable) for mutation. + final ArrayList mChildren = new ArrayList(); + + private float mRotate = 0; + private float mPivotX = 0; + private float mPivotY = 0; + private float mScaleX = 1; + private float mScaleY = 1; + private float mTranslateX = 0; + private float mTranslateY = 0; + + // mLocalMatrix is updated based on the update of transformation information, + // either parsed from the XML or by animation. + private final Matrix mLocalMatrix = new Matrix(); + private int mChangingConfigurations; + private int[] mThemeAttrs; + private String mGroupName = null; + + public VGroup(VGroup copy, ArrayMap targetsMap) { + mRotate = copy.mRotate; + mPivotX = copy.mPivotX; + mPivotY = copy.mPivotY; + mScaleX = copy.mScaleX; + mScaleY = copy.mScaleY; + mTranslateX = copy.mTranslateX; + mTranslateY = copy.mTranslateY; + mThemeAttrs = copy.mThemeAttrs; + mGroupName = copy.mGroupName; + mChangingConfigurations = copy.mChangingConfigurations; + if (mGroupName != null) { + targetsMap.put(mGroupName, this); + } + + mLocalMatrix.set(copy.mLocalMatrix); + + final ArrayList children = copy.mChildren; + for (int i = 0; i < children.size(); i++) { + Object copyChild = children.get(i); + if (copyChild instanceof VGroup) { + VGroup copyGroup = (VGroup) copyChild; + mChildren.add(new VGroup(copyGroup, targetsMap)); + } else { + VPath newPath = null; + if (copyChild instanceof VFullPath) { + newPath = new VFullPath((VFullPath) copyChild); + } else if (copyChild instanceof VClipPath) { + newPath = new VClipPath((VClipPath) copyChild); + } else { + throw new IllegalStateException("Unknown object in the tree!"); + } + mChildren.add(newPath); + if (newPath.mPathName != null) { + targetsMap.put(newPath.mPathName, newPath); + } + } + } + } + + public VGroup() { + } + + public String getGroupName() { + return mGroupName; + } + + public Matrix getLocalMatrix() { + return mLocalMatrix; + } + + public void inflate(Resources res, AttributeSet attrs, Theme theme) { + final TypedArray a = obtainAttributes(res, theme, attrs, + R.styleable.VectorDrawableGroup); + updateStateFromTypedArray(a); + a.recycle(); + } + + private void updateStateFromTypedArray(TypedArray a) { + // Account for any configuration changes. + mChangingConfigurations |= getChangingConfigurations(a); + + // Extract the theme attributes, if any. + //TODO +// mThemeAttrs = a.extractThemeAttrs(); + + mRotate = a.getFloat(R.styleable.VectorDrawableGroup_android_rotation, mRotate); + mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_android_pivotX, mPivotX); + mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_android_pivotY, mPivotY); + mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_android_scaleX, mScaleX); + mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_android_scaleY, mScaleY); + mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_vc_translateX, mTranslateX); + mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_vc_translateY, mTranslateY); + + final String groupName = a.getString(R.styleable.VectorDrawableGroup_android_name); + if (groupName != null) { + mGroupName = groupName; + } + + updateLocalMatrix(); + } + + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + public void applyTheme(Theme t) { + if (mThemeAttrs == null) { + return; + } + + //TODO: will not support drawable theming yet (applies to tinting mainly) +// final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawableGroup); +// updateStateFromTypedArray(a); +// a.recycle(); + } + + private void updateLocalMatrix() { + // The order we apply is the same as the + // RenderNode.cpp::applyViewPropertyTransforms(). + mLocalMatrix.reset(); + mLocalMatrix.postTranslate(-mPivotX, -mPivotY); + mLocalMatrix.postScale(mScaleX, mScaleY); + mLocalMatrix.postRotate(mRotate, 0, 0); + mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); + } + + /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ + @SuppressWarnings("unused") + public float getRotation() { + return mRotate; + } + + @SuppressWarnings("unused") + public void setRotation(float rotation) { + if (rotation != mRotate) { + mRotate = rotation; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getPivotX() { + return mPivotX; + } + + @SuppressWarnings("unused") + public void setPivotX(float pivotX) { + if (pivotX != mPivotX) { + mPivotX = pivotX; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getPivotY() { + return mPivotY; + } + + @SuppressWarnings("unused") + public void setPivotY(float pivotY) { + if (pivotY != mPivotY) { + mPivotY = pivotY; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getScaleX() { + return mScaleX; + } + + @SuppressWarnings("unused") + public void setScaleX(float scaleX) { + if (scaleX != mScaleX) { + mScaleX = scaleX; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getScaleY() { + return mScaleY; + } + + @SuppressWarnings("unused") + public void setScaleY(float scaleY) { + if (scaleY != mScaleY) { + mScaleY = scaleY; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getTranslateX() { + return mTranslateX; + } + + @SuppressWarnings("unused") + public void setTranslateX(float translateX) { + if (translateX != mTranslateX) { + mTranslateX = translateX; + updateLocalMatrix(); + } + } + + @SuppressWarnings("unused") + public float getTranslateY() { + return mTranslateY; + } + + @SuppressWarnings("unused") + public void setTranslateY(float translateY) { + if (translateY != mTranslateY) { + mTranslateY = translateY; + updateLocalMatrix(); + } + } + } + + /** + * Common Path information for clip path and normal path. + */ + private static class VPath { + protected PathParser.PathDataNode[] mNodes = null; + String mPathName; + int mChangingConfigurations; + + public VPath() { + // Empty constructor. + } + + public VPath(VPath copy) { + mPathName = copy.mPathName; + mChangingConfigurations = copy.mChangingConfigurations; + mNodes = PathParser.deepCopyNodes(copy.mNodes); + } + + public void toPath(Path path) { + path.reset(); + if (mNodes != null) { + PathParser.PathDataNode.nodesToPath(mNodes, path); + } + } + + public String getPathName() { + return mPathName; + } + + public boolean canApplyTheme() { + return false; + } + + public void applyTheme(Theme t) { + } + + public boolean isClipPath() { + return false; + } + + /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ + @SuppressWarnings("unused") + public PathParser.PathDataNode[] getPathData() { + return mNodes; + } + + @SuppressWarnings("unused") + public void setPathData(PathParser.PathDataNode[] nodes) { + if (!PathParser.canMorph(mNodes, nodes)) { + // This should not happen in the middle of animation. + mNodes = PathParser.deepCopyNodes(nodes); + } else { + PathParser.updateNodes(mNodes, nodes); + } + } + } + + /** + * Clip path, which only has name and pathData. + */ + private static class VClipPath extends VPath { + public VClipPath() { + // Empty constructor. + } + + public VClipPath(VClipPath copy) { + super(copy); + } + + public void inflate(Resources r, AttributeSet attrs, Theme theme) { + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.VectorDrawableClipPath); + updateStateFromTypedArray(a); + a.recycle(); + } + + private void updateStateFromTypedArray(TypedArray a) { + // Account for any configuration changes. + mChangingConfigurations |= getChangingConfigurations(a); + + final String pathName = a.getString(R.styleable.VectorDrawableClipPath_android_name); + if (pathName != null) { + mPathName = pathName; + } + + final String pathData = a.getString(R.styleable.VectorDrawableClipPath_vc_pathData); + if (pathData != null) { + mNodes = PathParser.createNodesFromPathData(pathData); + } + } + + @Override + public boolean isClipPath() { + return true; + } + } + + /** + * Normal path, which contains all the fill / paint information. + */ + protected static class VFullPath extends VPath { + ///////////////////////////////////////////////////// + // Variables below need to be copied (deep copy if applicable) for mutation. + private int[] mThemeAttrs; + + int mStrokeColor = Color.TRANSPARENT; + float mStrokeWidth = 0; + + int mFillColor = Color.TRANSPARENT; + float mStrokeAlpha = 1.0f; + int mFillRule; + float mFillAlpha = 1.0f; + float mTrimPathStart = 0; + float mTrimPathEnd = 1; + float mTrimPathOffset = 0; + + Paint.Cap mStrokeLineCap = Paint.Cap.BUTT; + Paint.Join mStrokeLineJoin = Paint.Join.MITER; + float mStrokeMiterlimit = 4; + + public VFullPath() { + // Empty constructor. + } + + public VFullPath(VFullPath copy) { + super(copy); + mThemeAttrs = copy.mThemeAttrs; + + mStrokeColor = copy.mStrokeColor; + mStrokeWidth = copy.mStrokeWidth; + mStrokeAlpha = copy.mStrokeAlpha; + mFillColor = copy.mFillColor; + mFillRule = copy.mFillRule; + mFillAlpha = copy.mFillAlpha; + mTrimPathStart = copy.mTrimPathStart; + mTrimPathEnd = copy.mTrimPathEnd; + mTrimPathOffset = copy.mTrimPathOffset; + + mStrokeLineCap = copy.mStrokeLineCap; + mStrokeLineJoin = copy.mStrokeLineJoin; + mStrokeMiterlimit = copy.mStrokeMiterlimit; + } + + private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) { + switch (id) { + case LINECAP_BUTT: + return Paint.Cap.BUTT; + case LINECAP_ROUND: + return Paint.Cap.ROUND; + case LINECAP_SQUARE: + return Paint.Cap.SQUARE; + default: + return defValue; + } + } + + private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) { + switch (id) { + case LINEJOIN_MITER: + return Paint.Join.MITER; + case LINEJOIN_ROUND: + return Paint.Join.ROUND; + case LINEJOIN_BEVEL: + return Paint.Join.BEVEL; + default: + return defValue; + } + } + + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + public void inflate(Resources r, AttributeSet attrs, Theme theme) { + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.VectorDrawablePath); + updateStateFromTypedArray(a); + a.recycle(); + } + + private void updateStateFromTypedArray(TypedArray a) { + // Account for any configuration changes. + mChangingConfigurations |= getChangingConfigurations(a); + + // Extract the theme attributes, if any. + //TODO: will not support drawable theming yet (applies to tinting mainly) + //mThemeAttrs = a.extractThemeAttrs(); + + final String pathName = a.getString(R.styleable.VectorDrawablePath_android_name); + if (pathName != null) { + mPathName = pathName; + } + + final String pathData = a.getString(R.styleable.VectorDrawablePath_vc_pathData); + if (pathData != null) { + mNodes = PathParser.createNodesFromPathData(pathData); + } + + mFillColor = a.getColor(R.styleable.VectorDrawablePath_vc_fillColor, mFillColor); + mFillAlpha = a.getFloat(R.styleable.VectorDrawablePath_vc_fillAlpha, mFillAlpha); + mStrokeLineCap = getStrokeLineCap(a.getInt(R.styleable.VectorDrawablePath_vc_strokeLineCap, -1), mStrokeLineCap); + mStrokeLineJoin = getStrokeLineJoin(a.getInt(R.styleable.VectorDrawablePath_vc_strokeLineJoin, -1), mStrokeLineJoin); + mStrokeMiterlimit = a.getFloat(R.styleable.VectorDrawablePath_vc_strokeMiterLimit, mStrokeMiterlimit); + mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_vc_strokeColor, mStrokeColor); + mStrokeAlpha = a.getFloat(R.styleable.VectorDrawablePath_vc_strokeAlpha, mStrokeAlpha); + mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_vc_strokeWidth, mStrokeWidth); + mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_vc_trimPathEnd, mTrimPathEnd); + mTrimPathOffset = a.getFloat(R.styleable.VectorDrawablePath_vc_trimPathOffset, mTrimPathOffset); + mTrimPathStart = a.getFloat(R.styleable.VectorDrawablePath_vc_trimPathStart, mTrimPathStart); + } + + @Override + public void applyTheme(Theme t) { + if (mThemeAttrs == null) { + return; + } + + //TODO: will not support drawable theming yet (applies to tinting mainly) + //final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawablePath); + //updateStateFromTypedArray(a); + //a.recycle(); + } + + /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ + @SuppressWarnings("unused") + int getStrokeColor() { + return mStrokeColor; + } + + @SuppressWarnings("unused") + void setStrokeColor(int strokeColor) { + mStrokeColor = strokeColor; + } + + @SuppressWarnings("unused") + float getStrokeWidth() { + return mStrokeWidth; + } + + @SuppressWarnings("unused") + void setStrokeWidth(float strokeWidth) { + mStrokeWidth = strokeWidth; + } + + @SuppressWarnings("unused") + float getStrokeAlpha() { + return mStrokeAlpha; + } + + @SuppressWarnings("unused") + void setStrokeAlpha(float strokeAlpha) { + mStrokeAlpha = strokeAlpha; + } + + @SuppressWarnings("unused") + int getFillColor() { + return mFillColor; + } + + @SuppressWarnings("unused") + void setFillColor(int fillColor) { + mFillColor = fillColor; + } + + @SuppressWarnings("unused") + float getFillAlpha() { + return mFillAlpha; + } + + @SuppressWarnings("unused") + void setFillAlpha(float fillAlpha) { + mFillAlpha = fillAlpha; + } + + @SuppressWarnings("unused") + float getTrimPathStart() { + return mTrimPathStart; + } + + @SuppressWarnings("unused") + void setTrimPathStart(float trimPathStart) { + mTrimPathStart = trimPathStart; + } + + @SuppressWarnings("unused") + float getTrimPathEnd() { + return mTrimPathEnd; + } + + @SuppressWarnings("unused") + void setTrimPathEnd(float trimPathEnd) { + mTrimPathEnd = trimPathEnd; + } + + @SuppressWarnings("unused") + float getTrimPathOffset() { + return mTrimPathOffset; + } + + @SuppressWarnings("unused") + void setTrimPathOffset(float trimPathOffset) { + mTrimPathOffset = trimPathOffset; + } + } +} diff --git a/elasticdownload/src/main/res/anim-v21/morph_arrow.xml b/elasticdownload/src/main/res/anim-v21/morph_arrow.xml index 46208e4..9a627cb 100644 --- a/elasticdownload/src/main/res/anim-v21/morph_arrow.xml +++ b/elasticdownload/src/main/res/anim-v21/morph_arrow.xml @@ -1,12 +1,8 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/elasticdownload/src/main/res/anim-v21/morph_circle.xml b/elasticdownload/src/main/res/anim-v21/morph_circle.xml index ddfe36f..8941169 100644 --- a/elasticdownload/src/main/res/anim-v21/morph_circle.xml +++ b/elasticdownload/src/main/res/anim-v21/morph_circle.xml @@ -1,12 +1,8 @@ - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/elasticdownload/src/main/res/anim/morph_arrow.xml b/elasticdownload/src/main/res/anim/morph_arrow.xml index 6ded708..ff61c2d 100644 --- a/elasticdownload/src/main/res/anim/morph_arrow.xml +++ b/elasticdownload/src/main/res/anim/morph_arrow.xml @@ -1,13 +1,10 @@ - - - - - \ No newline at end of file + + diff --git a/elasticdownload/src/main/res/anim/morph_circle.xml b/elasticdownload/src/main/res/anim/morph_circle.xml index 72f3006..65619d3 100644 --- a/elasticdownload/src/main/res/anim/morph_circle.xml +++ b/elasticdownload/src/main/res/anim/morph_circle.xml @@ -1,13 +1,10 @@ - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/elasticdownload/src/main/res/values/attrs.xml b/elasticdownload/src/main/res/values/attrs.xml index 8847112..130aed4 100644 --- a/elasticdownload/src/main/res/values/attrs.xml +++ b/elasticdownload/src/main/res/values/attrs.xml @@ -3,4 +3,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/build.gradle b/example/build.gradle index 2a09c63..22ce6a3 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId "is.arontibo.sample" - minSdkVersion 14 + minSdkVersion 8 targetSdkVersion 22 versionCode 1 versionName "1.0" diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml index 26db283..5ffd4c4 100644 --- a/example/src/main/res/layout/activity_main.xml +++ b/example/src/main/res/layout/activity_main.xml @@ -9,7 +9,7 @@ android:id="@+id/elastic_download_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - elastic:backgroundColor="@android:color/holo_blue_dark" + elastic:backgroundColor="#00796B" android:layout_centerInParent="true"/> From e33039b156ae7843565310bb09be0485296faada Mon Sep 17 00:00:00 2001 From: Thibault Guegan Date: Sat, 30 May 2015 18:00:25 +0000 Subject: [PATCH 7/9] Code styling --- .../java/is/arontibo/library/AVDWrapper.java | 1 + .../arontibo/library/ElasticDownloadView.java | 4 +- .../library/ProgressDownloadView.java | 82 +++++++++---------- .../VectorCompat/AnimatedVectorDrawable.java | 10 +-- .../library/VectorCompat/Outline.java | 49 +---------- .../VectorCompat/PathAnimatorInflater.java | 8 +- 6 files changed, 53 insertions(+), 101 deletions(-) diff --git a/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java b/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java index 4a5d654..89e7d58 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java +++ b/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java @@ -22,6 +22,7 @@ public void run() { public interface Callback { public void onAnimationDone(); + public void onAnimationStopped(); } diff --git a/elasticdownload/src/main/java/is/arontibo/library/ElasticDownloadView.java b/elasticdownload/src/main/java/is/arontibo/library/ElasticDownloadView.java index e5aad42..07e4086 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/ElasticDownloadView.java +++ b/elasticdownload/src/main/java/is/arontibo/library/ElasticDownloadView.java @@ -11,7 +11,7 @@ /** * Created by thibaultguegan on 15/03/15. */ -public class ElasticDownloadView extends FrameLayout implements IntroView.EnterAnimationListener{ +public class ElasticDownloadView extends FrameLayout implements IntroView.EnterAnimationListener { private static final String LOG_TAG = ElasticDownloadView.class.getSimpleName(); @@ -52,7 +52,7 @@ protected void onFinishInflate() { vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { mProgressDownloadView.getViewTreeObserver().removeGlobalOnLayoutListener(this); } else { mProgressDownloadView.getViewTreeObserver().removeOnGlobalLayoutListener(this); diff --git a/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java b/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java index c73f088..01c5830 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java +++ b/elasticdownload/src/main/java/is/arontibo/library/ProgressDownloadView.java @@ -48,22 +48,22 @@ private enum State { public ProgressDownloadView(Context context, AttributeSet attrs) { super(context, attrs); - mPadding = (int) (30*mDensity); - mBubbleWidth = (int) (45*mDensity); - mBubbleHeight = (int) (35*mDensity); + mPadding = (int) (30 * mDensity); + mBubbleWidth = (int) (45 * mDensity); + mBubbleHeight = (int) (35 * mDensity); setPadding(mPadding, 0, mPadding, 0); mPaintBlack = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintBlack.setStyle(Paint.Style.STROKE); - mPaintBlack.setStrokeWidth(5*mDensity); + mPaintBlack.setStrokeWidth(5 * mDensity); mPaintBlack.setColor(getResources().getColor(R.color.red_wood)); mPaintBlack.setStrokeCap(Paint.Cap.ROUND); //mPaintBlack.setPathEffect(new CornerPathEffect(5*mDensity)); mPaintWhite = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintWhite.setStyle(Paint.Style.STROKE); - mPaintWhite.setStrokeWidth(5*mDensity); + mPaintWhite.setStrokeWidth(5 * mDensity); mPaintWhite.setColor(Color.WHITE); mPaintWhite.setStrokeCap(Paint.Cap.ROUND); //mPaintWhite.setPathEffect(new CornerPathEffect(5*mDensity)); @@ -75,7 +75,7 @@ public ProgressDownloadView(Context context, AttributeSet attrs) { mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG); mPaintText.setColor(Color.BLACK); mPaintText.setStyle(Paint.Style.FILL); - mPaintText.setTextSize(12*mDensity); + mPaintText.setTextSize(12 * mDensity); } /** @@ -84,25 +84,25 @@ public ProgressDownloadView(Context context, AttributeSet attrs) { @Override protected void onDraw(Canvas canvas) { - if(mPathWhite != null && mPathBlack != null) { + if (mPathWhite != null && mPathBlack != null) { - float textX = Math.max(getPaddingLeft()-(int)(mBubbleWidth/4.0f), mProgress*mWidth/100-(int)(mBubbleWidth/4.0f)); - float textY = mHeight/2-mBubbleHeight/2 + calculateDeltaY(); + float textX = Math.max(getPaddingLeft() - (int) (mBubbleWidth / 4.0f), mProgress * mWidth / 100 - (int) (mBubbleWidth / 4.0f)); + float textY = mHeight / 2 - mBubbleHeight / 2 + calculateDeltaY(); switch (mState) { case STATE_WORKING: //save and restore prevent the rest of the canvas to not be rotated canvas.save(); - float speed = (getProgress() - mTarget)/20; - mBubbleAngle += speed*10; - if(mBubbleAngle > 20) { + float speed = (getProgress() - mTarget) / 20; + mBubbleAngle += speed * 10; + if (mBubbleAngle > 20) { mBubbleAngle = 20; } - if(mBubbleAngle < -20) { + if (mBubbleAngle < -20) { mBubbleAngle = -20; } - if(Math.abs(speed) < 1) { - mSpeedAngle -= mBubbleAngle/20; + if (Math.abs(speed) < 1) { + mSpeedAngle -= mBubbleAngle / 20; mSpeedAngle *= .9f; } mBubbleAngle += mSpeedAngle; @@ -118,14 +118,14 @@ protected void onDraw(Canvas canvas) { canvas.drawPath(mPathBubble, mPaintBubble); canvas.rotate(mFailAngle, bubbleAnchorX, textY - mBubbleHeight / 7); mPaintText.setColor(getResources().getColor(R.color.red_wine)); - textX = Math.max(getPaddingLeft()-(int)(mBubbleWidth/3.2f), mProgress*mWidth/100-(int)(mBubbleWidth/3.2f)); + textX = Math.max(getPaddingLeft() - (int) (mBubbleWidth / 3.2f), mProgress * mWidth / 100 - (int) (mBubbleWidth / 3.2f)); canvas.drawText(getResources().getString(R.string.failed), textX, textY, mPaintText); canvas.restore(); break; case STATE_SUCCESS: canvas.save(); mPaintText.setColor(getResources().getColor(R.color.green_grass)); - textX = Math.max(getPaddingLeft()-(int)(mBubbleWidth/3.2f), mProgress*mWidth/100-(int)(mBubbleWidth/3.2f)); + textX = Math.max(getPaddingLeft() - (int) (mBubbleWidth / 3.2f), mProgress * mWidth / 100 - (int) (mBubbleWidth / 3.2f)); Matrix flipMatrix = new Matrix(); flipMatrix.setScale(mFlipFactor, 1, bubbleAnchorX, bubbleAnchorY); canvas.concat(flipMatrix); @@ -158,20 +158,20 @@ protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) { private void makePathBlack() { - if(mPathBlack ==null) { + if (mPathBlack == null) { mPathBlack = new Path(); } - Path p = new Path(); - p.moveTo(Math.max(getPaddingLeft(), mProgress*mWidth/100), mHeight/2 + calculateDeltaY()); - p.lineTo(mWidth, mHeight/2); + Path p = new Path(); + p.moveTo(Math.max(getPaddingLeft(), mProgress * mWidth / 100), mHeight / 2 + calculateDeltaY()); + p.lineTo(mWidth, mHeight / 2); mPathBlack.set(p); } private void makePathWhite() { - if(mPathWhite == null) { + if (mPathWhite == null) { mPathWhite = new Path(); } @@ -184,51 +184,51 @@ private void makePathWhite() { private void makePathBubble() { - if(mPathBubble == null) { + if (mPathBubble == null) { mPathBubble = new Path(); } int width = mBubbleWidth; int height = mBubbleHeight; - int arrowWidth = width/3; + int arrowWidth = width / 3; //Rect r = new Rect(Math.max(getPaddingLeft()-width/2-arrowWidth/4, mProgress*mWidth/100-width/2-arrowWidth/4), mHeight/2-height + calculatedeltaY(), Math.max(getPaddingLeft()+width/2-arrowWidth/4, mProgress*mWidth/100+width/2-arrowWidth/4), mHeight/2+height-height + calculatedeltaY()); - Rect r = new Rect((int) (Math.max(getPaddingLeft()-width/2, mProgress*mWidth/100-width/2)), (int) (mHeight/2-height + calculateDeltaY()), (int) (Math.max(getPaddingLeft()+width/2, mProgress*mWidth/100+width/2)), (int) (mHeight/2+height-height + calculateDeltaY())); - int arrowHeight = (int) (arrowWidth/1.5f); + Rect r = new Rect((int) (Math.max(getPaddingLeft() - width / 2, mProgress * mWidth / 100 - width / 2)), (int) (mHeight / 2 - height + calculateDeltaY()), (int) (Math.max(getPaddingLeft() + width / 2, mProgress * mWidth / 100 + width / 2)), (int) (mHeight / 2 + height - height + calculateDeltaY())); + int arrowHeight = (int) (arrowWidth / 1.5f); int radius = 8; Path path = new Path(); //down arrow - path.moveTo(r.left + r.width()/2-arrowWidth/2, r.top + r.height()-arrowHeight); - bubbleAnchorX = r.left + r.width()/2; + path.moveTo(r.left + r.width() / 2 - arrowWidth / 2, r.top + r.height() - arrowHeight); + bubbleAnchorX = r.left + r.width() / 2; bubbleAnchorY = r.top + r.height(); path.lineTo(bubbleAnchorX, bubbleAnchorY); - path.lineTo(r.left + r.width()/2+arrowWidth/2, r.top + r.height()-arrowHeight); + path.lineTo(r.left + r.width() / 2 + arrowWidth / 2, r.top + r.height() - arrowHeight); //go to bottom-right - path.lineTo(r.left + r.width()-radius, r.top + r.height()-arrowHeight); + path.lineTo(r.left + r.width() - radius, r.top + r.height() - arrowHeight); //bottom-right arc - path.arcTo(new RectF(r.left + r.width()-2*radius, r.top + r.height()-arrowHeight-2*radius, r.left + r.width(), r.top + r.height()-arrowHeight), 90, -90); + path.arcTo(new RectF(r.left + r.width() - 2 * radius, r.top + r.height() - arrowHeight - 2 * radius, r.left + r.width(), r.top + r.height() - arrowHeight), 90, -90); //go to upper-right path.lineTo(r.left + r.width(), r.top + arrowHeight); //upper-right arc - path.arcTo(new RectF(r.left + r.width()-2*radius, r.top, r.right, r.top + 2*radius), 0, -90); + path.arcTo(new RectF(r.left + r.width() - 2 * radius, r.top, r.right, r.top + 2 * radius), 0, -90); //go to upper-left path.lineTo(r.left + radius, r.top); //upper-left arc - path.arcTo(new RectF(r.left, r.top, r.left + 2*radius, r.top + 2*radius), 270, -90); + path.arcTo(new RectF(r.left, r.top, r.left + 2 * radius, r.top + 2 * radius), 270, -90); //go to bottom-left - path.lineTo(r.left, r.top + r.height()-arrowHeight-radius); + path.lineTo(r.left, r.top + r.height() - arrowHeight - radius); //bottom-left arc - path.arcTo(new RectF(r.left, r.top + r.height()-arrowHeight-2*radius, r.left + 2*radius, r.top + r.height()-arrowHeight), 180, -90); + path.arcTo(new RectF(r.left, r.top + r.height() - arrowHeight - 2 * radius, r.left + 2 * radius, r.top + r.height() - arrowHeight), 180, -90); path.close(); @@ -241,15 +241,15 @@ private void makePathBubble() { private float calculateDeltaY() { int wireTension = 15; - if(mProgress <= 50) { - return (mProgress * mWidth/wireTension)/50 + Math.abs((mTarget-getProgress())/wireTension) + Math.abs(mBubbleAngle); + if (mProgress <= 50) { + return (mProgress * mWidth / wireTension) / 50 + Math.abs((mTarget - getProgress()) / wireTension) + Math.abs(mBubbleAngle); } else { - return ((100-mProgress) * mWidth/wireTension)/50 + Math.abs((mTarget-getProgress())/wireTension) + Math.abs(mBubbleAngle); + return ((100 - mProgress) * mWidth / wireTension) / 50 + Math.abs((mTarget - getProgress()) / wireTension) + Math.abs(mBubbleAngle); } } public void setPercentage(float newProgress) { - if(newProgress < 0 || newProgress > 100) + if (newProgress < 0 || newProgress > 100) throw new IllegalArgumentException("setPercentage not between 0 and 100"); mState = State.STATE_WORKING; @@ -257,7 +257,7 @@ public void setPercentage(float newProgress) { ObjectAnimator anim = ObjectAnimator.ofFloat(this, "progress", getProgress(), mTarget); anim.setInterpolator(new DecelerateInterpolator()); - anim.setDuration((long) (ANIMATION_DURATION_BASE+Math.abs(mTarget*10-getProgress()*10)/2)); + anim.setDuration((long) (ANIMATION_DURATION_BASE + Math.abs(mTarget * 10 - getProgress() * 10) / 2)); anim.start(); } @@ -300,7 +300,7 @@ public void drawFail() { anim.setInterpolator(new AccelerateInterpolator()); AnimatorSet set = new AnimatorSet(); - set.setDuration((long) (ANIMATION_DURATION_BASE/1.7f)); + set.setDuration((long) (ANIMATION_DURATION_BASE / 1.7f)); set.playTogether( failAnim, anim diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/AnimatedVectorDrawable.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/AnimatedVectorDrawable.java index 0d9d74c..98c9adb 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/AnimatedVectorDrawable.java +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/AnimatedVectorDrawable.java @@ -31,7 +31,7 @@ public class AnimatedVectorDrawable extends DrawableCompat implements Animatable, Tintable { - private static final String LOGTAG = AnimatedVectorDrawable.class.getSimpleName(); + private static final String LOG_TAG = AnimatedVectorDrawable.class.getSimpleName(); private static final String ANIMATED_VECTOR = "animated-vector"; private static final String TARGET = "target"; @@ -191,9 +191,9 @@ public static AnimatedVectorDrawable create(Context c, Resources resources, int return drawable; } catch (XmlPullParserException e) { - Log.e(LOGTAG, "parser error", e); + Log.e(LOG_TAG, "parser error", e); } catch (IOException e) { - Log.e(LOGTAG, "parser error", e); + Log.e(LOG_TAG, "parser error", e); } return null; } @@ -335,7 +335,7 @@ private void setupAnimatorsForTarget(String name, Animator animator) { mAnimatedVectorState.mAnimators.add(animator); mAnimatedVectorState.mTargetNameMap.put(animator, name); if (DBG_ANIMATION_VECTOR_DRAWABLE) { - Log.v(LOGTAG, "add animator for target " + name + " " + animator); + Log.v(LOG_TAG, "add animator for target " + name + " " + animator); } } @@ -400,7 +400,7 @@ public void reverse() { if (canReverse(animator)) { reverse(animator); } else { - Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()"); + Log.w(LOG_TAG, "AnimatedVectorDrawable can't reverse()"); } } } diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Outline.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Outline.java index 4e6ffc9..b1404df 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Outline.java +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/Outline.java @@ -14,9 +14,9 @@ * @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider) */ public final class Outline { + /** @hide */ public Path mPath; - /** @hide */ public Rect mRect; /** @hide */ @@ -158,51 +158,4 @@ public void setRoundRect(@NonNull Rect rect, float radius) { setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius); } -// /** -// * Sets the outline to the oval defined by input rect. -// */ -// public void setOval(int left, int top, int right, int bottom) { -// if (left >= right || top >= bottom) { -// setEmpty(); -// return; -// } -// -// if ((bottom - top) == (right - left)) { -// // represent circle as round rect, for efficiency, and to enable clipping -// setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f); -// return; -// } -// -// if (mPath == null) mPath = new Path(); -// mPath.reset(); -// mPath.addOval(left, top, right, bottom, Path.Direction.CW); -// mRect = null; -// } -// -// /** -// * Convenience for {@link #setOval(int, int, int, int)} -// */ -// public void setOval(@NonNull Rect rect) { -// setOval(rect.left, rect.top, rect.right, rect.bottom); -// } -// -// /** -// * Sets the Constructs an Outline from a -// * {@link android.graphics.Path#isConvex() convex path}. -// */ -// public void setConvexPath(@NonNull Path convexPath) { -// if (convexPath.isEmpty()) { -// setEmpty(); -// return; -// } -// -// if (!convexPath.isConvex()) { -// throw new IllegalArgumentException("path must be convex"); -// } -// if (mPath == null) mPath = new Path(); -// -// mPath.set(convexPath); -// mRect = null; -// mRadius = -1.0f; -// } } diff --git a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathAnimatorInflater.java b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathAnimatorInflater.java index bab5bf1..a137a3b 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathAnimatorInflater.java +++ b/elasticdownload/src/main/java/is/arontibo/library/VectorCompat/PathAnimatorInflater.java @@ -25,7 +25,8 @@ import is.arontibo.library.R; public class PathAnimatorInflater { - private static final String TAG = "PathAnimatorInflater"; + + private static final String LOG_TAG = "PathAnimatorInflater"; /** * These flags are used when parsing PathAnimatorSet objects */ @@ -253,9 +254,6 @@ private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim, PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString); PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString); - Log.d(TAG, String.format("retrieved fromString: %s", fromString)); - Log.d(TAG, String.format("retrieved toString: %s", toString)); - if (nodesFrom != null) { if (nodesTo != null) { anim.setObjectValues(nodesFrom, nodesTo); @@ -273,7 +271,7 @@ private static TypeEvaluator setupAnimatorForPath(ValueAnimator anim, } if (DBG_ANIMATOR_INFLATER && evaluator != null) { - Log.v(TAG, "create a new PathDataEvaluator here"); + Log.v(LOG_TAG, "create a new PathDataEvaluator here"); } return evaluator; From 3245a65ea974cad3bdb0d38393b7b6e0166ee527 Mon Sep 17 00:00:00 2001 From: Thibault Guegan Date: Sat, 30 May 2015 18:01:30 +0000 Subject: [PATCH 8/9] Fix illegal if statement --- .../src/main/java/is/arontibo/library/AVDWrapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java b/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java index 89e7d58..4aea63f 100644 --- a/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java +++ b/elasticdownload/src/main/java/is/arontibo/library/AVDWrapper.java @@ -15,8 +15,9 @@ public class AVDWrapper { @Override public void run() { - if (mCallback != null) + if (mCallback != null) { mCallback.onAnimationDone(); + } } }; From 9bc29465bb13cf6a84a68aa12e907d8b924528c6 Mon Sep 17 00:00:00 2001 From: Thibault Guegan Date: Sat, 30 May 2015 18:05:44 +0000 Subject: [PATCH 9/9] removed unused gradle dependency in ElasticDownload --- elasticdownload/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/elasticdownload/build.gradle b/elasticdownload/build.gradle index bb70f48..80c3dc4 100644 --- a/elasticdownload/build.gradle +++ b/elasticdownload/build.gradle @@ -24,5 +24,4 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.1.1' compile 'com.nineoldandroids:library:2.4.0+' - //compile 'com.wnafee:vector-compat:1.0.5' }